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 : }
|