LCOV - code coverage report
Current view: top level - server/patch - AutoMerger.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 77 96 80.2 %
Date: 2022-11-19 15:00:39 Functions: 8 13 61.5 %

          Line data    Source code
       1             : // Copyright (C) 2016 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.patch;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : 
      19             : import com.google.common.flogger.FluentLogger;
      20             : import com.google.gerrit.common.UsedAt;
      21             : import com.google.gerrit.entities.RefNames;
      22             : import com.google.gerrit.metrics.Counter1;
      23             : import com.google.gerrit.metrics.Description;
      24             : import com.google.gerrit.metrics.Field;
      25             : import com.google.gerrit.metrics.MetricMaker;
      26             : import com.google.gerrit.metrics.Timer1;
      27             : import com.google.gerrit.server.GerritPersonIdent;
      28             : import com.google.gerrit.server.config.GerritServerConfig;
      29             : import com.google.gerrit.server.git.InMemoryInserter;
      30             : import com.google.gerrit.server.git.MergeUtil;
      31             : import com.google.gerrit.server.logging.Metadata;
      32             : import com.google.gerrit.server.update.RepoView;
      33             : import com.google.inject.Inject;
      34             : import com.google.inject.Provider;
      35             : import com.google.inject.Singleton;
      36             : import java.io.IOException;
      37             : import java.util.Optional;
      38             : import org.eclipse.jgit.dircache.DirCache;
      39             : import org.eclipse.jgit.lib.CommitBuilder;
      40             : import org.eclipse.jgit.lib.Config;
      41             : import org.eclipse.jgit.lib.ObjectId;
      42             : import org.eclipse.jgit.lib.ObjectInserter;
      43             : import org.eclipse.jgit.lib.ObjectReader;
      44             : import org.eclipse.jgit.lib.PersonIdent;
      45             : import org.eclipse.jgit.lib.Ref;
      46             : import org.eclipse.jgit.lib.Repository;
      47             : import org.eclipse.jgit.merge.ResolveMerger;
      48             : import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
      49             : import org.eclipse.jgit.revwalk.RevCommit;
      50             : import org.eclipse.jgit.revwalk.RevObject;
      51             : import org.eclipse.jgit.revwalk.RevWalk;
      52             : import org.eclipse.jgit.transport.ReceiveCommand;
      53             : 
      54             : /**
      55             :  * Utility class for creating an auto-merge commit of a merge commit.
      56             :  *
      57             :  * <p>An auto-merge commit is the result of merging the 2 parents of a merge commit automatically.
      58             :  * If there are conflicts the auto-merge commit contains Git conflict markers that indicate these
      59             :  * conflicts.
      60             :  *
      61             :  * <p>Creating auto-merge commits for octopus merges (merge commits with more than 2 parents) is not
      62             :  * supported. In this case the auto-merge is created between the first 2 parent commits.
      63             :  *
      64             :  * <p>All created auto-merge commits are stored in the repository of their merge commit as {@code
      65             :  * refs/cache-automerge/} branches. These branches serve:
      66             :  *
      67             :  * <ul>
      68             :  *   <li>as a cache so that the each auto-merge gets computed only once
      69             :  *   <li>as base for merge commits on which users can comment
      70             :  * </ul>
      71             :  *
      72             :  * <p>The second point means that these commits are referenced from NoteDb. The consequence of this
      73             :  * is that these refs should never be deleted.
      74             :  */
      75             : @Singleton
      76             : public class AutoMerger {
      77         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      78             : 
      79             :   public static final String AUTO_MERGE_MSG_PREFIX = "Auto-merge of ";
      80             : 
      81             :   @UsedAt(UsedAt.Project.GOOGLE)
      82             :   public static boolean cacheAutomerge(Config cfg) {
      83         152 :     return cfg.getBoolean("change", null, "cacheAutomerge", true);
      84             :   }
      85             : 
      86          31 :   private enum OperationType {
      87          31 :     CACHE_LOAD,
      88          31 :     IN_MEMORY_WRITE,
      89          31 :     ON_DISK_WRITE
      90             :   }
      91             : 
      92             :   private final Counter1<OperationType> counter;
      93             :   private final Timer1<OperationType> latency;
      94             :   private final Provider<PersonIdent> gerritIdentProvider;
      95             :   private final boolean save;
      96             :   private final ThreeWayMergeStrategy configuredMergeStrategy;
      97             : 
      98             :   @Inject
      99             :   AutoMerger(
     100             :       MetricMaker metricMaker,
     101             :       @GerritServerConfig Config cfg,
     102         152 :       @GerritPersonIdent Provider<PersonIdent> gerritIdentProvider) {
     103         152 :     Field<OperationType> operationTypeField =
     104         152 :         Field.ofEnum(OperationType.class, "type", Metadata.Builder::operationName)
     105         152 :             .description("The type of the operation (CACHE_LOAD, IN_MEMORY_WRITE, ON_DISK_WRITE).")
     106         152 :             .build();
     107         152 :     this.counter =
     108         152 :         metricMaker.newCounter(
     109             :             "git/auto-merge/num_operations",
     110         152 :             new Description("AutoMerge computations").setRate().setUnit("auto merge computations"),
     111             :             operationTypeField);
     112         152 :     this.latency =
     113         152 :         metricMaker.newTimer(
     114             :             "git/auto-merge/latency",
     115             :             new Description("AutoMerge computation latency")
     116         152 :                 .setCumulative()
     117         152 :                 .setUnit("milliseconds"),
     118             :             operationTypeField);
     119         152 :     this.save = cacheAutomerge(cfg);
     120         152 :     this.gerritIdentProvider = gerritIdentProvider;
     121         152 :     this.configuredMergeStrategy = MergeUtil.getMergeStrategy(cfg);
     122         152 :   }
     123             : 
     124             :   /**
     125             :    * Reads or creates an auto-merge commit of the parents of the given merge commit.
     126             :    *
     127             :    * <p>The result is read from Git or computed in-memory and not written back to Git. This method
     128             :    * exists for backwards compatibility only. All new changes have their auto-merge commits written
     129             :    * transactionally when the change or patch set is created.
     130             :    *
     131             :    * @return auto-merge commit. Headers of the returned RevCommit are parsed.
     132             :    */
     133             :   public RevCommit lookupFromGitOrMergeInMemory(
     134             :       Repository repo,
     135             :       RevWalk rw,
     136             :       InMemoryInserter ins,
     137             :       RevCommit merge,
     138             :       ThreeWayMergeStrategy mergeStrategy)
     139             :       throws IOException {
     140           0 :     checkArgument(rw.getObjectReader().getCreatedFromInserter() == ins);
     141           0 :     Optional<RevCommit> existingCommit =
     142           0 :         lookupCommit(repo, rw, RefNames.refsCacheAutomerge(merge.name()));
     143           0 :     if (existingCommit.isPresent()) {
     144           0 :       counter.increment(OperationType.CACHE_LOAD);
     145           0 :       return existingCommit.get();
     146             :     }
     147           0 :     counter.increment(OperationType.IN_MEMORY_WRITE);
     148           0 :     logger.atInfo().log("Computing in-memory AutoMerge for %s", merge.name());
     149           0 :     try (Timer1.Context<OperationType> ignored = latency.start(OperationType.IN_MEMORY_WRITE)) {
     150           0 :       return rw.parseCommit(createAutoMergeCommit(repo.getConfig(), rw, ins, merge, mergeStrategy));
     151             :     }
     152             :   }
     153             : 
     154             :   /**
     155             :    * Creates an auto merge commit for the provided commit in case it is a merge commit. To be used
     156             :    * whenever Gerrit creates new patch sets.
     157             :    *
     158             :    * <p>Callers need to include the returned {@link ReceiveCommand} in their ref transaction.
     159             :    *
     160             :    * @return A {@link ReceiveCommand} wrapped in an {@link Optional} to be used in a {@link
     161             :    *     org.eclipse.jgit.lib.BatchRefUpdate}. {@link Optional#empty()} in case we don't need an
     162             :    *     auto merge commit.
     163             :    */
     164             :   public Optional<ReceiveCommand> createAutoMergeCommitIfNecessary(
     165             :       RepoView repoView, RevWalk rw, ObjectInserter ins, RevCommit maybeMergeCommit)
     166             :       throws IOException {
     167         103 :     if (maybeMergeCommit.getParentCount() != 2 || !save) {
     168         103 :       logger.atFine().log("AutoMerge not required");
     169         103 :       return Optional.empty();
     170             :     }
     171             : 
     172          30 :     String automergeRef = RefNames.refsCacheAutomerge(maybeMergeCommit.name());
     173          30 :     logger.atFine().log("AutoMerge ref=%s, mergeCommit=%s", automergeRef, maybeMergeCommit.name());
     174          30 :     if (repoView.getRef(automergeRef).isPresent()) {
     175           1 :       logger.atFine().log("AutoMerge alredy exists");
     176           1 :       return Optional.empty();
     177             :     }
     178             : 
     179          30 :     return Optional.of(
     180             :         new ReceiveCommand(
     181          30 :             ObjectId.zeroId(),
     182          30 :             createAutoMergeCommit(repoView, rw, ins, maybeMergeCommit),
     183             :             automergeRef));
     184             :   }
     185             : 
     186             :   /**
     187             :    * Creates an auto merge commit for the provided merge commit.
     188             :    *
     189             :    * <p>Callers are expected to ensure that the provided commit indeed has 2 parents.
     190             :    *
     191             :    * @return An auto-merge commit. Headers of the returned RevCommit are parsed.
     192             :    */
     193             :   ObjectId createAutoMergeCommit(
     194             :       RepoView repoView, RevWalk rw, ObjectInserter ins, RevCommit mergeCommit) throws IOException {
     195             :     ObjectId autoMerge;
     196          31 :     try (Timer1.Context<OperationType> ignored = latency.start(OperationType.ON_DISK_WRITE)) {
     197          31 :       autoMerge =
     198          31 :           createAutoMergeCommit(
     199          31 :               repoView.getConfig(), rw, ins, mergeCommit, configuredMergeStrategy);
     200             :     }
     201          31 :     counter.increment(OperationType.ON_DISK_WRITE);
     202          31 :     logger.atFine().log("Added %s AutoMerge ref update for commit", autoMerge.name());
     203          31 :     return autoMerge;
     204             :   }
     205             : 
     206             :   Optional<RevCommit> lookupCommit(Repository repo, RevWalk rw, String refName) throws IOException {
     207          31 :     Ref ref = repo.getRefDatabase().exactRef(refName);
     208          31 :     if (ref != null && ref.getObjectId() != null) {
     209          30 :       RevObject obj = rw.parseAny(ref.getObjectId());
     210          30 :       if (obj instanceof RevCommit) {
     211          30 :         return Optional.of((RevCommit) obj);
     212             :       }
     213             :     }
     214           1 :     return Optional.empty();
     215             :   }
     216             : 
     217             :   /**
     218             :    * Creates an auto-merge commit of the parents of the given merge commit.
     219             :    *
     220             :    * @return auto-merge commit. Headers of the returned RevCommit are parsed.
     221             :    */
     222             :   private ObjectId createAutoMergeCommit(
     223             :       Config repoConfig,
     224             :       RevWalk rw,
     225             :       ObjectInserter ins,
     226             :       RevCommit merge,
     227             :       ThreeWayMergeStrategy mergeStrategy)
     228             :       throws IOException {
     229          31 :     rw.parseHeaders(merge);
     230          31 :     ResolveMerger m = (ResolveMerger) mergeStrategy.newMerger(ins, repoConfig);
     231          31 :     DirCache dc = DirCache.newInCore();
     232          31 :     m.setDirCache(dc);
     233             :     // If we don't plan on saving results, use a fully in-memory inserter.
     234             :     // Using just a non-flushing wrapper is not sufficient, since in particular DfsInserter might
     235             :     // try to write to storage after exceeding an internal buffer size.
     236          31 :     m.setObjectInserter(ins instanceof InMemoryInserter ? new NonFlushingWrapper(ins) : ins);
     237             : 
     238          31 :     boolean couldMerge = m.merge(merge.getParents());
     239             : 
     240             :     ObjectId treeId;
     241          31 :     if (couldMerge) {
     242          25 :       treeId = m.getResultTreeId();
     243             :     } else {
     244          24 :       treeId =
     245          24 :           MergeUtil.mergeWithConflicts(
     246             :               rw,
     247             :               ins,
     248             :               dc,
     249             :               "HEAD",
     250          24 :               merge.getParent(0),
     251             :               "BRANCH",
     252          24 :               merge.getParent(1),
     253          24 :               m.getMergeResults());
     254             :     }
     255          31 :     logger.atFine().log("AutoMerge treeId=%s", treeId.name());
     256             : 
     257          31 :     rw.parseHeaders(merge);
     258             :     // For maximum stability, choose a single ident using the committer time of
     259             :     // the input commit, using the server name and timezone.
     260          31 :     PersonIdent ident =
     261             :         new PersonIdent(
     262          31 :             gerritIdentProvider.get(),
     263          31 :             merge.getCommitterIdent().getWhen(),
     264          31 :             gerritIdentProvider.get().getTimeZone());
     265          31 :     CommitBuilder cb = new CommitBuilder();
     266          31 :     cb.setAuthor(ident);
     267          31 :     cb.setCommitter(ident);
     268          31 :     cb.setTreeId(treeId);
     269          31 :     cb.setMessage(AUTO_MERGE_MSG_PREFIX + merge.name() + '\n');
     270          31 :     for (RevCommit p : merge.getParents()) {
     271          31 :       cb.addParentId(p);
     272             :     }
     273             : 
     274          31 :     ObjectId commitId = ins.insert(cb);
     275          31 :     logger.atFine().log("AutoMerge commitId=%s", commitId.name());
     276          31 :     ins.flush();
     277             : 
     278          31 :     if (ins instanceof InMemoryInserter) {
     279             :       // When using an InMemoryInserter we need to read back the values from that inserter because
     280             :       // they are not available.
     281           0 :       try (ObjectReader tmpReader = ins.newReader();
     282           0 :           RevWalk tmpRw = new RevWalk(tmpReader)) {
     283           0 :         return tmpRw.parseCommit(commitId);
     284             :       }
     285             :     }
     286             : 
     287          31 :     return rw.parseCommit(commitId);
     288             :   }
     289             : 
     290             :   private static class NonFlushingWrapper extends ObjectInserter.Filter {
     291             :     private final ObjectInserter ins;
     292             : 
     293           0 :     private NonFlushingWrapper(ObjectInserter ins) {
     294           0 :       this.ins = ins;
     295           0 :     }
     296             : 
     297             :     @Override
     298             :     protected ObjectInserter delegate() {
     299           0 :       return ins;
     300             :     }
     301             : 
     302             :     @Override
     303           0 :     public void flush() {}
     304             : 
     305             :     @Override
     306           0 :     public void close() {}
     307             :   }
     308             : }

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