LCOV - code coverage report
Current view: top level - server/query/change - ConflictsPredicate.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 72 105 68.6 %
Date: 2022-11-19 15:00:39 Functions: 12 16 75.0 %

          Line data    Source code
       1             : // Copyright (C) 2013 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.query.change;
      16             : 
      17             : import static com.google.common.base.MoreObjects.firstNonNull;
      18             : import static com.google.common.flogger.LazyArgs.lazy;
      19             : import static com.google.gerrit.server.project.ProjectCache.noSuchProject;
      20             : import static java.util.concurrent.TimeUnit.MINUTES;
      21             : 
      22             : import com.google.common.flogger.FluentLogger;
      23             : import com.google.common.util.concurrent.UncheckedExecutionException;
      24             : import com.google.gerrit.entities.BooleanProjectConfig;
      25             : import com.google.gerrit.entities.BranchNameKey;
      26             : import com.google.gerrit.entities.Change;
      27             : import com.google.gerrit.entities.Project;
      28             : import com.google.gerrit.entities.SubmitTypeRecord;
      29             : import com.google.gerrit.exceptions.StorageException;
      30             : import com.google.gerrit.index.query.PostFilterPredicate;
      31             : import com.google.gerrit.index.query.Predicate;
      32             : import com.google.gerrit.index.query.QueryParseException;
      33             : import com.google.gerrit.server.git.CodeReviewCommit;
      34             : import com.google.gerrit.server.project.NoSuchProjectException;
      35             : import com.google.gerrit.server.project.ProjectCache;
      36             : import com.google.gerrit.server.project.ProjectState;
      37             : import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
      38             : import com.google.gerrit.server.submit.SubmitDryRun;
      39             : import java.io.IOException;
      40             : import java.util.ArrayList;
      41             : import java.util.HashSet;
      42             : import java.util.List;
      43             : import java.util.Set;
      44             : import java.util.concurrent.Callable;
      45             : import java.util.concurrent.ExecutionException;
      46             : import org.eclipse.jgit.lib.ObjectId;
      47             : import org.eclipse.jgit.lib.Repository;
      48             : import org.eclipse.jgit.revwalk.RevCommit;
      49             : import org.eclipse.jgit.revwalk.RevWalk;
      50             : 
      51             : public class ConflictsPredicate {
      52           4 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      53             : 
      54             :   // UI code may depend on this string, so use caution when changing.
      55             :   protected static final String TOO_MANY_FILES = "too many files to find conflicts";
      56             : 
      57             :   private ConflictsPredicate() {}
      58             : 
      59             :   public static Predicate<ChangeData> create(Arguments args, String value, Change c)
      60             :       throws QueryParseException {
      61             :     ChangeData cd;
      62             :     List<String> files;
      63             :     try {
      64           4 :       cd = args.changeDataFactory.create(c);
      65           4 :       files = cd.currentFilePaths();
      66           0 :     } catch (StorageException e) {
      67           0 :       warnWithOccasionalStackTrace(
      68             :           e,
      69             :           "Error constructing conflicts predicates for change %s in %s",
      70           0 :           c.getId(),
      71           0 :           c.getProject());
      72           0 :       return ChangeIndexPredicate.none();
      73           4 :     }
      74             : 
      75           4 :     if (3 + files.size() > args.indexConfig.maxTerms()) {
      76             :       // Short-circuit with a nice error message if we exceed the index
      77             :       // backend's term limit. This assumes that "conflicts:foo" is the entire
      78             :       // query; if there are more terms in the input, we might not
      79             :       // short-circuit here, which will result in a more generic error message
      80             :       // later on in the query parsing.
      81           0 :       throw new QueryParseException(TOO_MANY_FILES);
      82             :     }
      83             : 
      84           4 :     List<Predicate<ChangeData>> filePredicates = new ArrayList<>(files.size());
      85           4 :     for (String file : files) {
      86           4 :       filePredicates.add(ChangePredicates.path(file));
      87           4 :     }
      88             : 
      89           4 :     List<Predicate<ChangeData>> and = new ArrayList<>(5);
      90           4 :     and.add(ChangePredicates.project(c.getProject()));
      91           4 :     and.add(ChangePredicates.ref(c.getDest().branch()));
      92           4 :     and.add(Predicate.not(ChangePredicates.idStr(c.getId())));
      93           4 :     and.add(Predicate.or(filePredicates));
      94             : 
      95           4 :     ChangeDataCache changeDataCache = new ChangeDataCache(cd, args.projectCache);
      96           4 :     and.add(new CheckConflict(value, args, c, changeDataCache));
      97           4 :     return Predicate.and(and);
      98             :   }
      99             : 
     100             :   private static final class CheckConflict extends PostFilterPredicate<ChangeData> {
     101             :     private final Arguments args;
     102             :     private final BranchNameKey dest;
     103             :     private final ChangeDataCache changeDataCache;
     104             : 
     105             :     CheckConflict(String value, Arguments args, Change c, ChangeDataCache changeDataCache) {
     106           4 :       super(ChangeQueryBuilder.FIELD_CONFLICTS, value);
     107           4 :       this.args = args;
     108           4 :       this.dest = c.getDest();
     109           4 :       this.changeDataCache = changeDataCache;
     110           4 :     }
     111             : 
     112             :     @Override
     113             :     public boolean match(ChangeData object) {
     114           4 :       Change.Id id = object.getId();
     115           4 :       Project.NameKey otherProject = null;
     116           4 :       ObjectId other = null;
     117             :       try {
     118           4 :         Change otherChange = object.change();
     119           4 :         if (otherChange == null || !otherChange.getDest().equals(dest)) {
     120           0 :           return false;
     121             :         }
     122           4 :         otherProject = otherChange.getProject();
     123             : 
     124           4 :         SubmitTypeRecord str = object.submitTypeRecord();
     125           4 :         if (!str.isOk()) {
     126           0 :           return false;
     127             :         }
     128             : 
     129             :         ProjectState projectState;
     130             :         try {
     131           4 :           projectState = changeDataCache.getProjectState();
     132           0 :         } catch (NoSuchProjectException e) {
     133           0 :           return false;
     134           4 :         }
     135             : 
     136           4 :         other = object.currentPatchSet().commitId();
     137           4 :         ConflictKey conflictsKey =
     138           4 :             ConflictKey.create(
     139           4 :                 changeDataCache.getTestAgainst(),
     140             :                 other,
     141             :                 str.type,
     142           4 :                 projectState.is(BooleanProjectConfig.USE_CONTENT_MERGE));
     143           4 :         return args.conflictsCache.get(conflictsKey, new Loader(object, changeDataCache, args));
     144           0 :       } catch (StorageException | ExecutionException | UncheckedExecutionException e) {
     145           0 :         ObjectId finalOther = other;
     146           0 :         warnWithOccasionalStackTrace(
     147             :             e,
     148             :             "Merge failure checking conflicts of change %s in %s (%s): %s",
     149             :             id,
     150           0 :             firstNonNull(otherProject, "unknown project"),
     151           0 :             lazy(() -> finalOther != null ? finalOther.name() : "unknown commit"),
     152           0 :             e.getMessage());
     153           0 :         return false;
     154             :       }
     155             :     }
     156             : 
     157             :     @Override
     158             :     public int getCost() {
     159           4 :       return 5;
     160             :     }
     161             :   }
     162             : 
     163             :   static class ChangeDataCache {
     164             :     private final ChangeData cd;
     165             :     private final ProjectCache projectCache;
     166             : 
     167             :     private ObjectId testAgainst;
     168             :     private ProjectState projectState;
     169             :     private Set<ObjectId> alreadyAccepted;
     170             : 
     171           4 :     ChangeDataCache(ChangeData cd, ProjectCache projectCache) {
     172           4 :       this.cd = cd;
     173           4 :       this.projectCache = projectCache;
     174           4 :     }
     175             : 
     176             :     ObjectId getTestAgainst() {
     177           4 :       if (testAgainst == null) {
     178           4 :         testAgainst = cd.currentPatchSet().commitId();
     179             :       }
     180           4 :       return testAgainst;
     181             :     }
     182             : 
     183             :     ProjectState getProjectState() throws NoSuchProjectException {
     184           4 :       if (projectState == null) {
     185           4 :         projectState = projectCache.get(cd.project()).orElseThrow(noSuchProject(cd.project()));
     186             :       }
     187           4 :       return projectState;
     188             :     }
     189             : 
     190             :     Set<ObjectId> getAlreadyAccepted(Repository repo) throws IOException {
     191           4 :       if (alreadyAccepted == null) {
     192           4 :         alreadyAccepted = SubmitDryRun.getAlreadyAccepted(repo);
     193             :       }
     194           4 :       return alreadyAccepted;
     195             :     }
     196             :   }
     197             : 
     198             :   private static void warnWithOccasionalStackTrace(Throwable cause, String format, Object... args) {
     199           0 :     logger.atWarning().logVarargs(format, args);
     200           0 :     logger
     201           0 :         .atWarning()
     202           0 :         .withCause(cause)
     203           0 :         .atMostEvery(1, MINUTES)
     204           0 :         .logVarargs("(Re-logging with stack trace) " + format, args);
     205           0 :   }
     206             : 
     207             :   private static class Loader implements Callable<Boolean> {
     208             :     private final ChangeData changeData;
     209             :     private final ConflictsPredicate.ChangeDataCache changeDataCache;
     210             :     private final ChangeQueryBuilder.Arguments args;
     211             : 
     212             :     private Loader(
     213             :         ChangeData changeData,
     214             :         ConflictsPredicate.ChangeDataCache changeDataCache,
     215           4 :         ChangeQueryBuilder.Arguments args) {
     216           4 :       this.changeData = changeData;
     217           4 :       this.changeDataCache = changeDataCache;
     218           4 :       this.args = args;
     219           4 :     }
     220             : 
     221             :     @Override
     222             :     public Boolean call() throws Exception {
     223           4 :       Change otherChange = changeData.change();
     224           4 :       ObjectId other = changeData.currentPatchSet().commitId();
     225           4 :       try (Repository repo = args.repoManager.openRepository(otherChange.getProject());
     226           4 :           CodeReviewCommit.CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
     227           4 :         return !args.submitDryRun.run(
     228             :             null,
     229           4 :             changeData.submitTypeRecord().type,
     230             :             repo,
     231             :             rw,
     232           4 :             otherChange.getDest(),
     233           4 :             changeDataCache.getTestAgainst(),
     234             :             other,
     235           4 :             getAlreadyAccepted(repo, rw));
     236           0 :       } catch (NoSuchProjectException | IOException e) {
     237           0 :         warnWithOccasionalStackTrace(
     238             :             e,
     239             :             "Failure when loading conflicts of change %s in %s (%s): %s",
     240           0 :             lazy(changeData::getId),
     241           0 :             lazy(() -> firstNonNull(otherChange.getProject(), "unknown project")),
     242           0 :             lazy(() -> other != null ? other.name() : "unknown commit"),
     243           0 :             e.getMessage());
     244           0 :         return false;
     245             :       }
     246             :     }
     247             : 
     248             :     private Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw) {
     249             :       try {
     250           4 :         Set<RevCommit> accepted = new HashSet<>();
     251           4 :         SubmitDryRun.addCommits(changeDataCache.getAlreadyAccepted(repo), rw, accepted);
     252           4 :         ObjectId tip = changeDataCache.getTestAgainst();
     253           4 :         if (tip != null) {
     254           4 :           accepted.add(rw.parseCommit(tip));
     255             :         }
     256           4 :         return accepted;
     257           0 :       } catch (StorageException | IOException e) {
     258           0 :         throw new StorageException("Failed to determine already accepted commits.", e);
     259             :       }
     260             :     }
     261             :   }
     262             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750