restapi: Add merge strategy to rebase API
RebaseInput already has the allow_conflicts part of the MergeInput
API, but doesn't currently allow for a merge strategy.
This change also allows for non-three way merges on rebase so that we
can also use one-sided merge strategies where desirable.
Release-Notes: Add merge strategy to rebase REST API
Change-Id: I891fd13bdb5571ac58d4ac1a8233971c9cf22729
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 0a913d8..6b5b934 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -8175,6 +8175,9 @@
patch set is inferred. +
Empty string is used for rebasing directly on top of the target branch,
which effectively breaks dependency towards a parent change.
+|`strategy` |optional|
+The strategy of the merge, can be `recursive`, `resolve`,
+`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|`allow_conflicts` |optional, defaults to false|
If `true`, the rebase also succeeds if there are conflicts. +
If there are conflicts the file contents of the rebased patch set contain
diff --git a/java/com/google/gerrit/extensions/api/changes/RebaseInput.java b/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
index a85bc73..07e65d0 100644
--- a/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
@@ -20,6 +20,13 @@
public String base;
/**
+ * {@code strategy} name of the merge strategy.
+ *
+ * @see org.eclipse.jgit.merge.MergeStrategy
+ */
+ public String strategy;
+
+ /**
* Whether the rebase should succeed if there are conflicts.
*
* <p>If there are conflicts the file contents of the rebased change contain git conflict markers
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
index ed87c76..540e438 100644
--- a/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -14,11 +14,13 @@
package com.google.gerrit.server.change;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static java.util.Objects.requireNonNull;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
@@ -62,6 +64,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.merge.MergeResult;
+import org.eclipse.jgit.merge.Merger;
import org.eclipse.jgit.merge.ResolveMerger;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -108,6 +111,7 @@
private boolean storeCopiedVotes = true;
private boolean matchAuthorToCommitterDate = false;
private ImmutableListMultimap<String, String> validationOptions = ImmutableListMultimap.of();
+ private String mergeStrategy;
private CodeReviewCommit rebasedCommit;
private PatchSet.Id rebasedPatchSetId;
@@ -264,6 +268,11 @@
return this;
}
+ public RebaseChangeOp setMergeStrategy(String strategy) {
+ this.mergeStrategy = strategy;
+ return this;
+ }
+
@Override
public void updateRepo(RepoContext ctx)
throws InvalidChangeOperationException, RestApiException, IOException, NoSuchChangeException,
@@ -430,9 +439,14 @@
throw new ResourceConflictException("Change is already up to date.");
}
- ThreeWayMerger merger =
- newMergeUtil().newThreeWayMerger(ctx.getInserter(), ctx.getRepoView().getConfig());
- merger.setBase(parentCommit);
+ MergeUtil mergeUtil = newMergeUtil();
+ String strategy =
+ firstNonNull(Strings.emptyToNull(mergeStrategy), mergeUtil.mergeStrategyName());
+
+ Merger merger = MergeUtil.newMerger(ctx.getInserter(), ctx.getRepoView().getConfig(), strategy);
+ if (merger instanceof ThreeWayMerger) {
+ ((ThreeWayMerger) merger).setBase(parentCommit);
+ }
DirCache dc = DirCache.newInCore();
if (allowConflicts && merger instanceof ResolveMerger) {
diff --git a/java/com/google/gerrit/server/change/RebaseUtil.java b/java/com/google/gerrit/server/change/RebaseUtil.java
index 56ab936..48b052f 100644
--- a/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -552,6 +552,7 @@
private RebaseChangeOp applyRebaseInputToOp(RebaseChangeOp op, RebaseInput input) {
return op.setForceContentMerge(true)
.setAllowConflicts(input.allowConflicts)
+ .setMergeStrategy(input.strategy)
.setValidationOptions(
ValidationOptionsUtil.getValidateOptionsAsMultimap(input.validationOptions))
.setFireRevisionCreated(true);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index 5ecb5a7..ade7dc6 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -663,6 +663,87 @@
assertThat(r1.getPatchSetId().get()).isEqualTo(3);
}
+ private void rebaseWithConflict_strategy(String strategy) throws Exception {
+ String patchSetSubject = "patch set change";
+ String patchSetContent = "patch set content";
+ String baseSubject = "base change";
+ String baseContent = "base content";
+ String expectedContent = strategy.equals("theirs") ? baseContent : patchSetContent;
+
+ PushOneCommit.Result r1 = createChange(baseSubject, PushOneCommit.FILE_NAME, baseContent);
+ gApi.changes()
+ .id(r1.getChangeId())
+ .revision(r1.getCommit().name())
+ .review(ReviewInput.approve());
+ gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).submit();
+
+ testRepo.reset("HEAD~1");
+ PushOneCommit push =
+ pushFactory.create(
+ admin.newIdent(),
+ testRepo,
+ patchSetSubject,
+ PushOneCommit.FILE_NAME,
+ patchSetContent);
+ PushOneCommit.Result r2 = push.to("refs/for/master");
+ r2.assertOkStatus();
+
+ String changeId = r2.getChangeId();
+ RevCommit patchSet = r2.getCommit();
+ RevCommit base = r1.getCommit();
+
+ TestWorkInProgressStateChangedListener wipStateChangedListener =
+ new TestWorkInProgressStateChangedListener();
+ try (ExtensionRegistry.Registration registration =
+ extensionRegistry.newRegistration().add(wipStateChangedListener)) {
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.strategy = strategy;
+
+ testMetricMaker.reset();
+ ChangeInfo changeInfo =
+ gApi.changes().id(changeId).revision(patchSet.name()).rebaseAsInfo(rebaseInput);
+ assertThat(changeInfo.containsGitConflicts).isNull();
+ assertThat(changeInfo.workInProgress).isNull();
+
+ // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+ assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false))
+ .isEqualTo(1);
+ }
+ assertThat(wipStateChangedListener.invoked).isFalse();
+ assertThat(wipStateChangedListener.wip).isNull();
+
+ // To get the revisions, we must retrieve the change with more change options.
+ ChangeInfo changeInfo =
+ gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
+ assertThat(changeInfo.revisions).hasSize(2);
+ assertThat(changeInfo.getCurrentRevision().commit.parents.get(0).commit)
+ .isEqualTo(base.name());
+
+ // Verify that the file content in the created patch set is correct.
+ BinaryResult bin =
+ gApi.changes().id(changeId).current().file(PushOneCommit.FILE_NAME).content();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bin.writeTo(os);
+ String fileContent = new String(os.toByteArray(), UTF_8);
+ assertThat(fileContent).isEqualTo(expectedContent);
+
+ // Verify the message that has been posted on the change.
+ List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
+ assertThat(messages).hasSize(2);
+ assertThat(Iterables.getLast(messages).message)
+ .isEqualTo("Patch Set 2: Patch Set 1 was rebased");
+ }
+
+ @Test
+ public void rebaseWithConflict_strategyAcceptTheirs() throws Exception {
+ rebaseWithConflict_strategy("theirs");
+ }
+
+ @Test
+ public void rebaseWithConflict_strategyAcceptOurs() throws Exception {
+ rebaseWithConflict_strategy("ours");
+ }
+
@Test
public void rebaseWithConflict_conflictsAllowed() throws Exception {
String patchSetSubject = "patch set change";