summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java220
1 files changed, 220 insertions, 0 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
new file mode 100644
index 0000000000..ce5308ff15
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
@@ -0,0 +1,220 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetAncestor;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CherryPick extends SubmitStrategy {
+ private final PatchSetInfoFactory patchSetInfoFactory;
+ private final GitReferenceUpdated gitRefUpdated;
+ private final Map<Change.Id, CodeReviewCommit> newCommits;
+
+ CherryPick(final SubmitStrategy.Arguments args,
+ final PatchSetInfoFactory patchSetInfoFactory,
+ final GitReferenceUpdated gitRefUpdated) {
+ super(args);
+
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.gitRefUpdated = gitRefUpdated;
+ this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
+ }
+
+ @Override
+ protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
+ final List<CodeReviewCommit> toMerge) throws MergeException {
+ CodeReviewCommit newMergeTip = mergeTip;
+ while (!toMerge.isEmpty()) {
+ final CodeReviewCommit n = toMerge.remove(0);
+
+ try {
+ if (newMergeTip == null) {
+ // The branch is unborn. Take a fast-forward resolution to
+ // create the branch.
+ //
+ newMergeTip = n;
+ n.statusCode = CommitMergeStatus.CLEAN_MERGE;
+
+ } else if (n.getParentCount() == 0) {
+ // Refuse to merge a root commit into an existing branch,
+ // we cannot obtain a delta for the cherry-pick to apply.
+ //
+ n.statusCode = CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT;
+
+ } else if (n.getParentCount() == 1) {
+ // If there is only one parent, a cherry-pick can be done by
+ // taking the delta relative to that one parent and redoing
+ // that on the current merge tip.
+ //
+
+ newMergeTip = writeCherryPickCommit(mergeTip, n);
+
+ if (newMergeTip != null) {
+ newCommits.put(newMergeTip.patchsetId.getParentKey(), newMergeTip);
+ } else {
+ n.statusCode = CommitMergeStatus.PATH_CONFLICT;
+ }
+
+ } else {
+ // There are multiple parents, so this is a merge commit. We
+ // don't want to cherry-pick the merge as clients can't easily
+ // rebase their history with that merge present and replaced
+ // by an equivalent merge with a different first parent. So
+ // instead behave as though MERGE_IF_NECESSARY was configured.
+ //
+ if (!args.mergeUtil.hasMissingDependencies(args.mergeSorter, n)) {
+ if (args.rw.isMergedInto(newMergeTip, n)) {
+ newMergeTip = n;
+ } else {
+ newMergeTip =
+ args.mergeUtil.mergeOneCommit(args.myIdent, args.repo,
+ args.rw, args.inserter, args.canMergeFlag,
+ args.destBranch, newMergeTip, n);
+ }
+ final PatchSetApproval submitApproval =
+ args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+ newMergeTip, args.alreadyAccepted);
+ setRefLogIdent(submitApproval);
+
+ } else {
+ // One or more dependencies were not met. The status was
+ // already marked on the commit so we have nothing further
+ // to perform at this time.
+ //
+ }
+ }
+
+ } catch (IOException e) {
+ throw new MergeException("Cannot merge " + n.name(), e);
+ } catch (OrmException e) {
+ throw new MergeException("Cannot merge " + n.name(), e);
+ }
+ }
+ return newMergeTip;
+ }
+
+ private CodeReviewCommit writeCherryPickCommit(final CodeReviewCommit mergeTip, final CodeReviewCommit n)
+ throws IOException, OrmException {
+
+ args.rw.parseBody(n);
+
+ final PatchSetApproval submitAudit =
+ args.mergeUtil.getSubmitter(n.change.currentPatchSetId());
+
+ PersonIdent cherryPickCommitterIdent = null;
+ if (submitAudit != null) {
+ cherryPickCommitterIdent =
+ args.identifiedUserFactory.create(submitAudit.getAccountId())
+ .newCommitterIdent(submitAudit.getGranted(),
+ args.myIdent.getTimeZone());
+ } else {
+ cherryPickCommitterIdent = args.myIdent;
+ }
+
+ final String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
+
+ final CodeReviewCommit newCommit =
+ args.mergeUtil.createCherryPickFromCommit(args.repo, args.inserter, mergeTip, n,
+ cherryPickCommitterIdent, cherryPickCmtMsg, args.rw);
+
+ if (newCommit == null) {
+ return null;
+ }
+
+ PatchSet.Id id =
+ ChangeUtil.nextPatchSetId(args.repo, n.change.currentPatchSetId());
+ final PatchSet ps = new PatchSet(id);
+ ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+ ps.setUploader(submitAudit.getAccountId());
+ ps.setRevision(new RevId(newCommit.getId().getName()));
+ insertAncestors(args.db, ps.getId(), newCommit);
+ args.db.patchSets().insert(Collections.singleton(ps));
+
+ n.change.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
+ args.db.changes().update(Collections.singletonList(n.change));
+
+ final List<PatchSetApproval> approvals = Lists.newArrayList();
+ for (PatchSetApproval a : args.mergeUtil.getApprovalsForCommit(n)) {
+ approvals.add(new PatchSetApproval(ps.getId(), a));
+ }
+ args.db.patchSetApprovals().insert(approvals);
+
+ final RefUpdate ru = args.repo.updateRef(ps.getRefName());
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(newCommit);
+ ru.disableRefLog();
+ if (ru.update(args.rw) != RefUpdate.Result.NEW) {
+ throw new IOException(String.format("Failed to create ref %s in %s: %s",
+ ps.getRefName(), n.change.getDest().getParentKey().get(),
+ ru.getResult()));
+ }
+
+ gitRefUpdated.fire(n.change.getProject(), ru);
+
+ newCommit.copyFrom(n);
+ newCommit.statusCode = CommitMergeStatus.CLEAN_PICK;
+ newCommits.put(newCommit.patchsetId.getParentKey(), newCommit);
+ setRefLogIdent(submitAudit);
+ return newCommit;
+ }
+
+ private static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
+ throws OrmException {
+ final int cnt = src.getParentCount();
+ List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
+ for (int p = 0; p < cnt; p++) {
+ PatchSetAncestor a;
+
+ a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
+ a.setAncestorRevision(new RevId(src.getParent(p).getId().name()));
+ toInsert.add(a);
+ }
+ db.patchSetAncestors().insert(toInsert);
+ }
+
+ @Override
+ public Map<Change.Id, CodeReviewCommit> getNewCommits() {
+ return newCommits;
+ }
+
+ @Override
+ public boolean dryRun(final CodeReviewCommit mergeTip,
+ final CodeReviewCommit toMerge) throws MergeException {
+ return args.mergeUtil.canCherryPick(args.mergeSorter, args.repo,
+ mergeTip, args.rw, toMerge);
+ }
+}