Merge "RebaseUtil: Support base revisions which are part of the dest branch"
diff --git a/java/com/google/gerrit/server/change/RebaseUtil.java b/java/com/google/gerrit/server/change/RebaseUtil.java
index 2a215c2..93fcbc6 100644
--- a/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -51,6 +51,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
@@ -344,7 +345,7 @@
}
}
- // Try parsing as SHA-1.
+ // Try parsing as SHA-1 based on the change-index.
Base ret = null;
for (ChangeData cd : queryProvider.get().byProjectCommit(rsrc.getProject(), base)) {
for (PatchSet ps : cd.patchSets()) {
@@ -369,8 +370,8 @@
/**
* Parse or find the commit onto which a patch set should be rebased.
*
- * <p>If a {@code rebaseInput.base} is provided, parse it. Otherwise, finds the latest patch set
- * of the change corresponding to this commit's parent, or the destination branch tip in the case
+ * <p>If a {@code rebaseInput.base} is provided, parse it. Otherwise, find the latest patch set of
+ * the change corresponding to this commit's parent, or the destination branch tip in the case
* where the parent's change is merged.
*
* @param git the repository.
@@ -411,11 +412,16 @@
throw new UnprocessableEntityException(
String.format("Base change not found: %s", inputBase), e);
}
- if (base == null) {
- throw new ResourceConflictException(
- "base revision is missing from the destination branch: " + inputBase);
+ if (base != null) {
+ return getLatestRevisionForBaseChange(rw, permissionBackend, rsrc, base);
}
- return getLatestRevisionForBaseChange(rw, permissionBackend, rsrc, base);
+ if (isBaseRevisionInDestBranch(rw, inputBase, git, change.getDest())) {
+ // The requested base is a valid commit in the dest branch, which is not associated with any
+ // Gerrit change.
+ return ObjectId.fromString(inputBase);
+ }
+ throw new ResourceConflictException(
+ "base revision is missing from the destination branch: " + inputBase);
}
private ObjectId getDestRefTip(Repository git, BranchNameKey destRefKey)
@@ -531,6 +537,18 @@
return baseId;
}
+ private boolean isBaseRevisionInDestBranch(
+ RevWalk rw, String expectedBaseSha1, Repository git, BranchNameKey destRefKey)
+ throws IOException, ResourceConflictException {
+ RevCommit potentialBaseCommit;
+ try {
+ potentialBaseCommit = rw.parseCommit(ObjectId.fromString(expectedBaseSha1));
+ } catch (InvalidObjectIdException | IOException e) {
+ return false;
+ }
+ return rw.isMergedInto(potentialBaseCommit, rw.parseCommit(getDestRefTip(git, destRefKey)));
+ }
+
public RebaseChangeOp getRebaseOp(
RevWalk rw,
RevisionResource revRsrc,
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index af57417..7f21eb6 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -840,6 +840,51 @@
}
@Test
+ public void rebaseChangeWithValidBaseCommit() throws Exception {
+ RevCommit desiredBase =
+ createNewCommitWithoutChangeId(/*branch=*/ "refs/heads/master", "file", "content");
+ PushOneCommit.Result child = createChange();
+ RebaseInput ri = new RebaseInput();
+
+ // rebase child onto desiredBase (referenced by commit)
+ ri.base = desiredBase.getName();
+ rebaseCallWithInput.call(child.getChangeId(), ri);
+
+ PatchSet ps2 = child.getPatchSet();
+ assertThat(ps2.id().get()).isEqualTo(2);
+ RevisionInfo childInfo =
+ get(child.getChangeId(), CURRENT_REVISION, CURRENT_COMMIT).getCurrentRevision();
+ assertThat(childInfo.commit.parents.get(0).commit).isEqualTo(desiredBase.name());
+ }
+
+ @Test
+ public void cannotRebaseChangeWithInvalidBaseCommit() throws Exception {
+ // Create another branch and push the desired parent commit to it.
+ String branchName = "foo";
+ BranchInput branchInput = new BranchInput();
+ branchInput.ref = branchName;
+ branchInput.revision = projectOperations.project(project).getHead("master").name();
+ gApi.projects().name(project.get()).branch(branchInput.ref).create(branchInput);
+ RevCommit desiredBase =
+ createNewCommitWithoutChangeId(/*branch=*/ "refs/heads/foo", "file", "content");
+ // Create the child commit on "master".
+ PushOneCommit.Result child = createChange();
+ RebaseInput ri = new RebaseInput();
+
+ // Try to rebase child onto desiredBase (referenced by commit)
+ ri.base = desiredBase.getName();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> rebaseCallWithInput.call(child.getChangeId(), ri));
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format("base revision is missing from the destination branch: %s", ri.base));
+ }
+
+ @Test
public void rebaseUpToDateChange() throws Exception {
PushOneCommit.Result r = createChange();
verifyChangeIsUpToDate(r);