summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/change/RebaseChangeOp.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/server/change/RebaseChangeOp.java')
-rw-r--r--java/com/google/gerrit/server/change/RebaseChangeOp.java291
1 files changed, 291 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
new file mode 100644
index 0000000000..1f216f0a71
--- /dev/null
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -0,0 +1,291 @@
+// Copyright (C) 2015 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.change;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.restapi.MergeConflictException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.RebaseUtil.Base;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.update.BatchUpdateOp;
+import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.update.Context;
+import com.google.gerrit.server.update.RepoContext;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class RebaseChangeOp implements BatchUpdateOp {
+ public interface Factory {
+ RebaseChangeOp create(ChangeNotes notes, PatchSet originalPatchSet, ObjectId baseCommitId);
+ }
+
+ private final PatchSetInserter.Factory patchSetInserterFactory;
+ private final MergeUtil.Factory mergeUtilFactory;
+ private final RebaseUtil rebaseUtil;
+ private final ChangeResource.Factory changeResourceFactory;
+
+ private final ChangeNotes notes;
+ private final PatchSet originalPatchSet;
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
+ private final ProjectCache projectCache;
+
+ private ObjectId baseCommitId;
+ private PersonIdent committerIdent;
+ private boolean fireRevisionCreated = true;
+ private boolean validate = true;
+ private boolean checkAddPatchSetPermission = true;
+ private boolean forceContentMerge;
+ private boolean copyApprovals = true;
+ private boolean detailedCommitMessage;
+ private boolean postMessage = true;
+ private boolean matchAuthorToCommitterDate = false;
+
+ private RevCommit rebasedCommit;
+ private PatchSet.Id rebasedPatchSetId;
+ private PatchSetInserter patchSetInserter;
+ private PatchSet rebasedPatchSet;
+
+ @Inject
+ RebaseChangeOp(
+ PatchSetInserter.Factory patchSetInserterFactory,
+ MergeUtil.Factory mergeUtilFactory,
+ RebaseUtil rebaseUtil,
+ ChangeResource.Factory changeResourceFactory,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ ProjectCache projectCache,
+ @Assisted ChangeNotes notes,
+ @Assisted PatchSet originalPatchSet,
+ @Assisted ObjectId baseCommitId) {
+ this.patchSetInserterFactory = patchSetInserterFactory;
+ this.mergeUtilFactory = mergeUtilFactory;
+ this.rebaseUtil = rebaseUtil;
+ this.changeResourceFactory = changeResourceFactory;
+ this.identifiedUserFactory = identifiedUserFactory;
+ this.projectCache = projectCache;
+ this.notes = notes;
+ this.originalPatchSet = originalPatchSet;
+ this.baseCommitId = baseCommitId;
+ }
+
+ public RebaseChangeOp setCommitterIdent(PersonIdent committerIdent) {
+ this.committerIdent = committerIdent;
+ return this;
+ }
+
+ public RebaseChangeOp setValidate(boolean validate) {
+ this.validate = validate;
+ return this;
+ }
+
+ public RebaseChangeOp setCheckAddPatchSetPermission(boolean checkAddPatchSetPermission) {
+ this.checkAddPatchSetPermission = checkAddPatchSetPermission;
+ return this;
+ }
+
+ public RebaseChangeOp setFireRevisionCreated(boolean fireRevisionCreated) {
+ this.fireRevisionCreated = fireRevisionCreated;
+ return this;
+ }
+
+ public RebaseChangeOp setForceContentMerge(boolean forceContentMerge) {
+ this.forceContentMerge = forceContentMerge;
+ return this;
+ }
+
+ public RebaseChangeOp setCopyApprovals(boolean copyApprovals) {
+ this.copyApprovals = copyApprovals;
+ return this;
+ }
+
+ public RebaseChangeOp setDetailedCommitMessage(boolean detailedCommitMessage) {
+ this.detailedCommitMessage = detailedCommitMessage;
+ return this;
+ }
+
+ public RebaseChangeOp setPostMessage(boolean postMessage) {
+ this.postMessage = postMessage;
+ return this;
+ }
+
+ public RebaseChangeOp setMatchAuthorToCommitterDate(boolean matchAuthorToCommitterDate) {
+ this.matchAuthorToCommitterDate = matchAuthorToCommitterDate;
+ return this;
+ }
+
+ @Override
+ public void updateRepo(RepoContext ctx)
+ throws MergeConflictException, InvalidChangeOperationException, RestApiException, IOException,
+ OrmException, NoSuchChangeException, PermissionBackendException {
+ // Ok that originalPatchSet was not read in a transaction, since we just
+ // need its revision.
+ RevId oldRev = originalPatchSet.getRevision();
+
+ RevWalk rw = ctx.getRevWalk();
+ RevCommit original = rw.parseCommit(ObjectId.fromString(oldRev.get()));
+ rw.parseBody(original);
+ RevCommit baseCommit = rw.parseCommit(baseCommitId);
+ CurrentUser changeOwner = identifiedUserFactory.create(notes.getChange().getOwner());
+
+ String newCommitMessage;
+ if (detailedCommitMessage) {
+ rw.parseBody(baseCommit);
+ newCommitMessage =
+ newMergeUtil()
+ .createCommitMessageOnSubmit(original, baseCommit, notes, originalPatchSet.getId());
+ } else {
+ newCommitMessage = original.getFullMessage();
+ }
+
+ rebasedCommit = rebaseCommit(ctx, original, baseCommit, newCommitMessage);
+ Base base =
+ rebaseUtil.parseBase(
+ new RevisionResource(
+ changeResourceFactory.create(notes, changeOwner), originalPatchSet),
+ baseCommitId.name());
+
+ rebasedPatchSetId =
+ ChangeUtil.nextPatchSetIdFromChangeRefsMap(
+ ctx.getRepoView().getRefs(originalPatchSet.getId().getParentKey().toRefPrefix()),
+ notes.getChange().currentPatchSetId());
+ patchSetInserter =
+ patchSetInserterFactory
+ .create(notes, rebasedPatchSetId, rebasedCommit)
+ .setDescription("Rebase")
+ .setNotify(NotifyHandling.NONE)
+ .setFireRevisionCreated(fireRevisionCreated)
+ .setCopyApprovals(copyApprovals)
+ .setCheckAddPatchSetPermission(checkAddPatchSetPermission)
+ .setValidate(validate);
+ if (postMessage) {
+ patchSetInserter.setMessage(
+ "Patch Set "
+ + rebasedPatchSetId.get()
+ + ": Patch Set "
+ + originalPatchSet.getId().get()
+ + " was rebased");
+ }
+
+ if (base != null) {
+ patchSetInserter.setGroups(base.patchSet().getGroups());
+ }
+ patchSetInserter.updateRepo(ctx);
+ }
+
+ @Override
+ public boolean updateChange(ChangeContext ctx)
+ throws ResourceConflictException, OrmException, IOException {
+ boolean ret = patchSetInserter.updateChange(ctx);
+ rebasedPatchSet = patchSetInserter.getPatchSet();
+ return ret;
+ }
+
+ @Override
+ public void postUpdate(Context ctx) throws OrmException {
+ patchSetInserter.postUpdate(ctx);
+ }
+
+ public RevCommit getRebasedCommit() {
+ checkState(rebasedCommit != null, "getRebasedCommit() only valid after updateRepo");
+ return rebasedCommit;
+ }
+
+ public PatchSet.Id getPatchSetId() {
+ checkState(rebasedPatchSetId != null, "getPatchSetId() only valid after updateRepo");
+ return rebasedPatchSetId;
+ }
+
+ public PatchSet getPatchSet() {
+ checkState(rebasedPatchSet != null, "getPatchSet() only valid after executing update");
+ return rebasedPatchSet;
+ }
+
+ private MergeUtil newMergeUtil() throws IOException {
+ ProjectState project = projectCache.checkedGet(notes.getProjectName());
+ return forceContentMerge
+ ? mergeUtilFactory.create(project, true)
+ : mergeUtilFactory.create(project);
+ }
+
+ /**
+ * Rebase a commit.
+ *
+ * @param ctx repo context.
+ * @param original the commit to rebase.
+ * @param base base to rebase against.
+ * @return the rebased commit.
+ * @throws MergeConflictException the rebase failed due to a merge conflict.
+ * @throws IOException the merge failed for another reason.
+ */
+ private RevCommit rebaseCommit(
+ RepoContext ctx, RevCommit original, ObjectId base, String commitMessage)
+ throws ResourceConflictException, IOException {
+ RevCommit parentCommit = original.getParent(0);
+
+ if (base.equals(parentCommit)) {
+ throw new ResourceConflictException("Change is already up to date.");
+ }
+
+ ThreeWayMerger merger =
+ newMergeUtil().newThreeWayMerger(ctx.getInserter(), ctx.getRepoView().getConfig());
+ merger.setBase(parentCommit);
+ boolean success = merger.merge(original, base);
+
+ if (!success || merger.getResultTreeId() == null) {
+ throw new MergeConflictException(
+ "The change could not be rebased due to a conflict during merge.");
+ }
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(merger.getResultTreeId());
+ cb.setParentId(base);
+ cb.setAuthor(original.getAuthorIdent());
+ cb.setMessage(commitMessage);
+ if (committerIdent != null) {
+ cb.setCommitter(committerIdent);
+ } else {
+ cb.setCommitter(ctx.getIdentifiedUser().newCommitterIdent(ctx.getWhen(), ctx.getTimeZone()));
+ }
+ if (matchAuthorToCommitterDate) {
+ cb.setAuthor(
+ new PersonIdent(
+ cb.getAuthor(), cb.getCommitter().getWhen(), cb.getCommitter().getTimeZone()));
+ }
+ ObjectId objectId = ctx.getInserter().insert(cb);
+ ctx.getInserter().flush();
+ return ctx.getRevWalk().parseCommit(objectId);
+ }
+}