Merge "patch:apply - fix base commit picking"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 2cb4ecf..0162c48 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2233,7 +2233,7 @@
link:#applypatchpatchset-input[ApplyPatchPatchSetInput] entity.
If a base commit is given, the patch is applied on top of it. Otherwise, the
-patch is applied on top of the target change's branch tip.
+patch is applied on top of the target change's original parent.
Applying the patch will fail if the destination change is closed, or in case of any conflicts.
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyPatch.java b/java/com/google/gerrit/server/restapi/change/ApplyPatch.java
index c6e1e5e..f368eb9 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyPatch.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyPatch.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.CHANGE_MODIFICATION;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
@@ -24,6 +25,7 @@
import com.google.gerrit.extensions.api.changes.ApplyPatchPatchSetInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.PreconditionFailedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -139,9 +141,21 @@
destChange.change().getStatus().name()));
}
- RevCommit baseCommit =
- CommitUtil.getBaseCommit(
- project.get(), queryProvider.get(), revWalk, destRef, input.base);
+ RevCommit baseCommit;
+ if (!Strings.isNullOrEmpty(input.base)) {
+ baseCommit =
+ CommitUtil.getBaseCommit(
+ project.get(), queryProvider.get(), revWalk, destRef, input.base);
+ } else {
+ RevCommit latestPatchset = revWalk.parseCommit(destChange.currentPatchSet().commitId());
+ if (latestPatchset.getParentCount() != 1) {
+ throw new BadRequestException(
+ String.format(
+ "Cannot parse base commit for a change with none or multiple parents. Change ID: %s.",
+ destChange.getId()));
+ }
+ baseCommit = revWalk.parseCommit(latestPatchset.getParent(0));
+ }
ObjectId treeId = ApplyPatchUtil.applyPatch(repo, oi, input.patch, baseCommit);
Instant now = TimeUtil.now();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java b/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
index a1b9e3a..b451656 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
@@ -17,6 +17,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_FILES;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -47,6 +50,7 @@
import com.google.inject.Inject;
import java.io.IOException;
import org.eclipse.jgit.api.errors.PatchApplyException;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.Base64;
import org.junit.Test;
@@ -167,7 +171,7 @@
ChangeInfo result = applyPatch(in);
BinaryResult resultPatch = gApi.changes().id(result.id).current().patch();
- assertThat(removeHeader(resultPatch)).isEqualTo(removeHeader(originalPatch));
+ assertThat(cleanPatch(resultPatch)).isEqualTo(cleanPatch(originalPatch));
}
@Test
@@ -189,52 +193,54 @@
ChangeInfo result = applyPatch(in);
BinaryResult resultPatch = gApi.changes().id(result.id).current().patch();
- assertThat(removeHeader(resultPatch)).isEqualTo(removeHeader(originalPatch));
+ assertThat(cleanPatch(resultPatch)).isEqualTo(cleanPatch(originalPatch));
}
@Test
public void applyGerritBasedPatchUsingRest_success() throws Exception {
String head = getHead(repo(), HEAD).name();
- createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
- PushOneCommit.Result baseCommit = createChange("Add file", ADDED_FILE_NAME, ADDED_FILE_CONTENT);
- baseCommit.assertOkStatus();
createBranchWithRevision(BranchNameKey.create(project, DESTINATION_BRANCH), head);
+ PushOneCommit.Result destChange = createChange("refs/for/" + DESTINATION_BRANCH);
+ createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
+ PushOneCommit.Result baseCommit =
+ createChange(testRepo, "branch", "Add file", ADDED_FILE_NAME, ADDED_FILE_CONTENT, "");
+ baseCommit.assertOkStatus();
RestResponse patchResp =
userRestSession.get("/changes/" + baseCommit.getChangeId() + "/revisions/current/patch");
patchResp.assertOK();
String originalPatch = new String(Base64.decode(patchResp.getEntityContent()), UTF_8);
ApplyPatchPatchSetInput in = buildInput(originalPatch);
- PushOneCommit.Result destChange = createChange();
RestResponse resp =
adminRestSession.post("/changes/" + destChange.getChangeId() + "/patch:apply", in);
resp.assertOK();
BinaryResult resultPatch = gApi.changes().id(destChange.getChangeId()).current().patch();
- assertThat(removeHeader(resultPatch)).isEqualTo(removeHeader(originalPatch));
+ assertThat(cleanPatch(resultPatch)).isEqualTo(cleanPatch(originalPatch));
}
@Test
public void applyGerritBasedPatchUsingRestWithEncodedPatch_success() throws Exception {
String head = getHead(repo(), HEAD).name();
- createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
- PushOneCommit.Result baseCommit = createChange("Add file", ADDED_FILE_NAME, ADDED_FILE_CONTENT);
- baseCommit.assertOkStatus();
createBranchWithRevision(BranchNameKey.create(project, DESTINATION_BRANCH), head);
+ PushOneCommit.Result destChange = createChange("refs/for/" + DESTINATION_BRANCH);
+ createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
+ PushOneCommit.Result baseCommit =
+ createChange(testRepo, "branch", "Add file", ADDED_FILE_NAME, ADDED_FILE_CONTENT, "");
+ baseCommit.assertOkStatus();
RestResponse patchResp =
userRestSession.get("/changes/" + baseCommit.getChangeId() + "/revisions/current/patch");
patchResp.assertOK();
String originalEncodedPatch = patchResp.getEntityContent();
String originalDecodedPatch = new String(Base64.decode(patchResp.getEntityContent()), UTF_8);
ApplyPatchPatchSetInput in = buildInput(originalEncodedPatch);
- PushOneCommit.Result destChange = createChange();
RestResponse resp =
adminRestSession.post("/changes/" + destChange.getChangeId() + "/patch:apply", in);
resp.assertOK();
BinaryResult resultPatch = gApi.changes().id(destChange.getChangeId()).current().patch();
- assertThat(removeHeader(resultPatch)).isEqualTo(removeHeader(originalDecodedPatch));
+ assertThat(cleanPatch(resultPatch)).isEqualTo(cleanPatch(originalDecodedPatch));
}
@Test
@@ -395,6 +401,45 @@
GitPersonSubject.assertThat(person).email().isEqualTo(admin.email());
}
+ @Test
+ public void applyPatchWithExplicitBase_OverrideParentId() throws Exception {
+ PushOneCommit.Result inputParent = createChange("Input parent", "file1", "content");
+ PushOneCommit.Result parent = createChange("Parent Change", "file2", "content");
+ parent.assertOkStatus();
+ PushOneCommit.Result dest = createChange("Destination Change", "file3", "content");
+ ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
+ in.base = inputParent.getCommit().name();
+
+ gApi.changes().id(dest.getChangeId()).applyPatch(in);
+
+ ChangeInfo result = get(dest.getChangeId(), CURRENT_REVISION, CURRENT_COMMIT);
+ assertThat(result.revisions.get(result.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(inputParent.getCommit().name());
+
+ BinaryResult resultPatch = gApi.changes().id(dest.getChangeId()).current().patch();
+ assertThat(cleanPatch(resultPatch)).isEqualTo(ADDED_FILE_DIFF.trim());
+ }
+
+ @Test
+ public void applyPatchWithNoExplicitBase_overwritesLatestPatch() throws Exception {
+ PushOneCommit.Result dest = createChange("Destination Change", "ps1.txt", "ps1 content");
+ RevCommit originalParentCommit = dest.getCommit().getParent(0);
+ ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
+
+ gApi.changes().id(dest.getChangeId()).applyPatch(in);
+
+ ChangeInfo result = get(dest.getChangeId(), CURRENT_REVISION, CURRENT_COMMIT, CURRENT_FILES);
+ assertThat(result.revisions.get(result.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(originalParentCommit.name());
+ assertThat(result.revisions.get(result.currentRevision).files.keySet())
+ .containsExactly(ADDED_FILE_NAME);
+ assertDiffForNewFile(
+ fetchDiffForFile(result, ADDED_FILE_NAME),
+ result.currentRevision,
+ ADDED_FILE_NAME,
+ ADDED_FILE_CONTENT);
+ }
+
private void initDestBranch() throws Exception {
String head = getHead(repo(), HEAD).name();
createBranchWithRevision(BranchNameKey.create(project, ApplyPatchIT.DESTINATION_BRANCH), head);
@@ -425,11 +470,19 @@
return gApi.changes().id(result.id).current().file(fileName).diff();
}
- private String removeHeader(BinaryResult bin) throws IOException {
- return removeHeader(bin.asString());
+ private String cleanPatch(BinaryResult bin) throws IOException {
+ return cleanPatch(bin.asString());
}
- private String removeHeader(String s) {
- return s.substring(s.lastIndexOf("\ndiff --git"), s.length() - 1);
+ private String cleanPatch(String s) {
+ return s
+ // Remove the header
+ .substring(s.lastIndexOf("\ndiff --git"), s.length() - 1)
+ // Remove "index NN..NN lines
+ .replaceAll("(?m)^index.*", "")
+ // Remove empty lines
+ .replaceAll("\n+", "\n")
+ // Trim
+ .trim();
}
}