LCOV - code coverage report
Current view: top level - server/submit - CherryPick.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 75 94 79.8 %
Date: 2022-11-19 15:00:39 Functions: 8 11 72.7 %

          Line data    Source code
       1             : // Copyright (C) 2012 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.submit;
      16             : 
      17             : import static com.google.gerrit.server.submit.CommitMergeStatus.EMPTY_COMMIT;
      18             : import static com.google.gerrit.server.submit.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
      19             : import static java.util.Objects.requireNonNull;
      20             : 
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.gerrit.common.Nullable;
      23             : import com.google.gerrit.entities.BooleanProjectConfig;
      24             : import com.google.gerrit.entities.PatchSet;
      25             : import com.google.gerrit.entities.PatchSetInfo;
      26             : import com.google.gerrit.extensions.restapi.MergeConflictException;
      27             : import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
      28             : import com.google.gerrit.server.ChangeUtil;
      29             : import com.google.gerrit.server.git.CodeReviewCommit;
      30             : import com.google.gerrit.server.git.MergeTip;
      31             : import com.google.gerrit.server.project.NoSuchChangeException;
      32             : import com.google.gerrit.server.update.ChangeContext;
      33             : import com.google.gerrit.server.update.RepoContext;
      34             : import java.io.IOException;
      35             : import java.util.Collection;
      36             : import java.util.List;
      37             : import org.eclipse.jgit.lib.ObjectId;
      38             : import org.eclipse.jgit.lib.PersonIdent;
      39             : import org.eclipse.jgit.revwalk.RevCommit;
      40             : 
      41             : public class CherryPick extends SubmitStrategy {
      42             : 
      43             :   CherryPick(SubmitStrategy.Arguments args) {
      44           7 :     super(args);
      45           7 :   }
      46             : 
      47             :   @Override
      48             :   public ImmutableList<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
      49           7 :     List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
      50           7 :     ImmutableList.Builder<SubmitStrategyOp> ops =
      51           7 :         ImmutableList.builderWithExpectedSize(sorted.size());
      52           7 :     boolean first = true;
      53           7 :     while (!sorted.isEmpty()) {
      54           7 :       CodeReviewCommit n = sorted.remove(0);
      55           7 :       if (first && args.mergeTip.getInitialTip() == null) {
      56           2 :         ops.add(new FastForwardOp(args, n));
      57           7 :       } else if (n.getParentCount() == 0) {
      58           0 :         ops.add(new CherryPickRootOp(n));
      59           7 :       } else if (n.getParentCount() == 1) {
      60           7 :         ops.add(new CherryPickOneOp(n));
      61             :       } else {
      62           1 :         ops.add(new CherryPickMultipleParentsOp(n));
      63             :       }
      64           7 :       first = false;
      65           7 :     }
      66           7 :     return ops.build();
      67             :   }
      68             : 
      69             :   private class CherryPickRootOp extends SubmitStrategyOp {
      70           0 :     private CherryPickRootOp(CodeReviewCommit toMerge) {
      71           0 :       super(CherryPick.this.args, toMerge);
      72           0 :     }
      73             : 
      74             :     @Override
      75             :     public void updateRepoImpl(RepoContext ctx) {
      76             :       // Refuse to merge a root commit into an existing branch, we cannot obtain
      77             :       // a delta for the cherry-pick to apply.
      78           0 :       toMerge.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
      79           0 :     }
      80             :   }
      81             : 
      82             :   private class CherryPickOneOp extends SubmitStrategyOp {
      83             :     private PatchSet.Id psId;
      84             :     private CodeReviewCommit newCommit;
      85             :     private PatchSetInfo patchSetInfo;
      86             : 
      87           7 :     private CherryPickOneOp(CodeReviewCommit toMerge) {
      88           7 :       super(CherryPick.this.args, toMerge);
      89           7 :     }
      90             : 
      91             :     @Override
      92             :     protected void updateRepoImpl(RepoContext ctx)
      93             :         throws IntegrationConflictException, IOException, MethodNotAllowedException {
      94             :       // If there is only one parent, a cherry-pick can be done by taking the
      95             :       // delta relative to that one parent and redoing that on the current merge
      96             :       // tip.
      97           7 :       args.rw.parseBody(toMerge);
      98           7 :       psId =
      99           7 :           ChangeUtil.nextPatchSetIdFromChangeRefs(
     100           7 :               ctx.getRepoView().getRefs(getId().toRefPrefix()).keySet(),
     101           7 :               toMerge.change().currentPatchSetId());
     102           7 :       RevCommit mergeTip = args.mergeTip.getCurrentTip();
     103           7 :       args.rw.parseBody(mergeTip);
     104           7 :       String cherryPickCmtMsg = args.mergeUtil.createCommitMessageOnSubmit(toMerge, mergeTip);
     105             : 
     106           7 :       PersonIdent committer = ctx.newCommitterIdent(args.caller);
     107             :       try {
     108           7 :         newCommit =
     109           6 :             args.mergeUtil.createCherryPickFromCommit(
     110           7 :                 ctx.getInserter(),
     111           7 :                 ctx.getRepoView().getConfig(),
     112           7 :                 args.mergeTip.getCurrentTip(),
     113             :                 toMerge,
     114             :                 committer,
     115             :                 cherryPickCmtMsg,
     116             :                 args.rw,
     117             :                 0,
     118             :                 false,
     119             :                 false);
     120           1 :       } catch (MergeConflictException mce) {
     121             :         // Keep going in the case of a single merge failure; the goal is to
     122             :         // cherry-pick as many commits as possible.
     123           1 :         toMerge.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
     124           1 :         return;
     125           2 :       } catch (MergeIdenticalTreeException mie) {
     126           2 :         if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)) {
     127           1 :           toMerge.setStatusCode(EMPTY_COMMIT);
     128           1 :           return;
     129             :         }
     130           2 :         toMerge.setStatusCode(SKIPPED_IDENTICAL_TREE);
     131           2 :         return;
     132           6 :       }
     133             :       // Initial copy doesn't have new patch set ID since change hasn't been
     134             :       // updated yet.
     135           6 :       newCommit = amendGitlink(newCommit);
     136           6 :       newCommit.copyFrom(toMerge);
     137           6 :       newCommit.setPatchsetId(psId);
     138           6 :       newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
     139           6 :       args.mergeTip.moveTipTo(newCommit, newCommit);
     140           6 :       args.commitStatus.put(newCommit);
     141             : 
     142           6 :       ctx.addRefUpdate(ObjectId.zeroId(), newCommit, psId.toRefName());
     143           6 :       patchSetInfo = args.patchSetInfoFactory.get(ctx.getRevWalk(), newCommit, psId);
     144           6 :     }
     145             : 
     146             :     @Nullable
     147             :     @Override
     148             :     public PatchSet updateChangeImpl(ChangeContext ctx) throws NoSuchChangeException, IOException {
     149           7 :       if (newCommit == null && toMerge.getStatusCode() == SKIPPED_IDENTICAL_TREE) {
     150           2 :         return null;
     151             :       }
     152           6 :       requireNonNull(
     153             :           newCommit,
     154             :           () ->
     155           0 :               String.format(
     156             :                   "no new commit produced by CherryPick of %s, expected to fail fast",
     157           0 :                   toMerge.change().getId()));
     158           6 :       PatchSet prevPs = args.psUtil.current(ctx.getNotes());
     159           6 :       PatchSet newPs =
     160           6 :           args.psUtil.insert(
     161           6 :               ctx.getRevWalk(),
     162           6 :               ctx.getUpdate(psId),
     163             :               psId,
     164             :               newCommit,
     165           6 :               prevPs != null ? prevPs.groups() : ImmutableList.of(),
     166             :               null,
     167             :               null);
     168           6 :       ctx.getChange().setCurrentPatchSet(patchSetInfo);
     169             : 
     170             :       // Don't copy approvals, as this is already taken care of by
     171             :       // SubmitStrategyOp.
     172             : 
     173           6 :       newCommit.setNotes(ctx.getNotes());
     174           6 :       return newPs;
     175             :     }
     176             :   }
     177             : 
     178             :   private class CherryPickMultipleParentsOp extends SubmitStrategyOp {
     179           1 :     private CherryPickMultipleParentsOp(CodeReviewCommit toMerge) {
     180           1 :       super(CherryPick.this.args, toMerge);
     181           1 :     }
     182             : 
     183             :     @Override
     184             :     public void updateRepoImpl(RepoContext ctx) throws IntegrationConflictException, IOException {
     185           1 :       if (args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)) {
     186             :         // One or more dependencies were not met. The status was already marked
     187             :         // on the commit so we have nothing further to perform at this time.
     188           0 :         return;
     189             :       }
     190             :       // There are multiple parents, so this is a merge commit. We don't want
     191             :       // to cherry-pick the merge as clients can't easily rebase their history
     192             :       // with that merge present and replaced by an equivalent merge with a
     193             :       // different first parent. So instead behave as though MERGE_IF_NECESSARY
     194             :       // was configured.
     195           1 :       MergeTip mergeTip = args.mergeTip;
     196           1 :       if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)
     197           1 :           && !args.subscriptionGraph.hasSubscription(args.destBranch)) {
     198           1 :         mergeTip.moveTipTo(toMerge, toMerge);
     199             :       } else {
     200           0 :         PersonIdent myIdent = ctx.newPersonIdent(args.serverIdent);
     201           0 :         CodeReviewCommit result =
     202           0 :             args.mergeUtil.mergeOneCommit(
     203             :                 myIdent,
     204             :                 myIdent,
     205             :                 args.rw,
     206           0 :                 ctx.getInserter(),
     207           0 :                 ctx.getRepoView().getConfig(),
     208             :                 args.destBranch,
     209           0 :                 mergeTip.getCurrentTip(),
     210             :                 toMerge);
     211           0 :         result = amendGitlink(result);
     212           0 :         mergeTip.moveTipTo(result, toMerge);
     213           0 :         args.mergeUtil.markCleanMerges(
     214           0 :             args.rw, args.canMergeFlag, mergeTip.getCurrentTip(), args.alreadyAccepted);
     215             :       }
     216           1 :     }
     217             :   }
     218             : 
     219             :   static boolean dryRun(
     220             :       SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) {
     221           3 :     return args.mergeUtil.canCherryPick(args.mergeSorter, args.repo, mergeTip, args.rw, toMerge);
     222             :   }
     223             : }

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