blob: e9fac73cdb1f38b7ca3eb1842ee071076c16af37 [file] [log] [blame]
// Copyright (C) 2022 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.acceptance.api.change;
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.server.patch.DiffUtil.normalizePatchForComparison;
import static com.google.gerrit.server.patch.DiffUtil.removePatchHeader;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.api.changes.ApplyPatchInput;
import com.google.gerrit.extensions.api.changes.ApplyPatchPatchSetInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.testing.GitPersonSubject;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.Base64;
import org.junit.Test;
public class ApplyPatchIT extends AbstractDaemonTest {
private static final String DESTINATION_BRANCH = "destBranch";
private static final String ADDED_FILE_NAME = "a_new_file.txt";
private static final String ADDED_FILE_CONTENT = "First added line\nSecond added line";
private static final String ADDED_FILE_DIFF =
"diff --git a/a_new_file.txt b/a_new_file.txt\n"
+ "new file mode 100644\n"
+ "--- /dev/null\n"
+ "+++ b/a_new_file.txt\n"
+ "@@ -0,0 +1,2 @@\n"
+ "+First added line\n"
+ "+Second added line\n"
+ "\\ No newline at end of file\n";
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private ChangeOperations changeOperations;
@Inject private AccountOperations accountOperations;
@Test
public void applyAddedFilePatch_success() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
ChangeInfo result = applyPatch(in);
DiffInfo diff = fetchDiffForFile(result, ADDED_FILE_NAME);
assertDiffForNewFile(diff, result.currentRevision, ADDED_FILE_NAME, ADDED_FILE_CONTENT);
}
@Test
public void applyAddedFilePatchAfterUpdatingPreferredEmail() throws Exception {
String emailOne = "email1@example.com";
Account.Id testUser = accountOperations.newAccount().preferredEmail(emailOne).create();
// Create change
Change.Id change = changeOperations.newChange().project(project).owner(testUser).create();
// Change preferred email for the user
String emailTwo = "email2@example.com";
accountOperations.account(testUser).forUpdate().preferredEmail(emailTwo).update();
requestScopeOperations.setApiUser(testUser);
// Apply patch
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.responseFormatOptions = ImmutableList.of(CURRENT_REVISION, CURRENT_COMMIT);
ChangeInfo result = gApi.changes().id(change.get()).applyPatch(in);
assertThat(result.getCurrentRevision().commit.committer.email).isEqualTo(emailOne);
}
private static final String MODIFIED_FILE_NAME = "modified_file.txt";
private static final String MODIFIED_FILE_ORIGINAL_CONTENT =
"First original line\nSecond original line";
private static final String MODIFIED_FILE_NEW_CONTENT = "Modified line\n";
private static final String MODIFIED_FILE_DIFF =
"diff --git a/modified_file.txt b/modified_file.txt\n"
+ "--- a/modified_file.txt\n"
+ "+++ b/modified_file.txt\n"
+ "@@ -1,2 +1 @@\n"
+ "-First original line\n"
+ "-Second original line\n"
+ "+Modified line\n";
@Test
public void applyPatchWithoutProvidingPatch_badRequest() throws Exception {
initBaseWithFile(MODIFIED_FILE_NAME, MODIFIED_FILE_ORIGINAL_CONTENT);
Throwable error = assertThrows(BadRequestException.class, () -> applyPatch(buildInput(null)));
assertThat(error).hasMessageThat().isEqualTo("patch required");
}
@Test
public void applyModifiedFilePatch_success() throws Exception {
initBaseWithFile(MODIFIED_FILE_NAME, MODIFIED_FILE_ORIGINAL_CONTENT);
ApplyPatchPatchSetInput in = buildInput(MODIFIED_FILE_DIFF);
ChangeInfo result = applyPatch(in);
DiffInfo diff = fetchDiffForFile(result, MODIFIED_FILE_NAME);
assertDiffForFullyModifiedFile(
diff,
result.currentRevision,
MODIFIED_FILE_NAME,
MODIFIED_FILE_ORIGINAL_CONTENT,
MODIFIED_FILE_NEW_CONTENT);
}
@Test
public void applyDeletedFilePatch_success() throws Exception {
final String deletedFileName = "deleted_file.txt";
final String deletedFileOriginalContent = "content to be deleted.\n";
final String deletedFileDiff =
"diff --git a/deleted_file.txt b/deleted_file.txt\n"
+ "--- a/deleted_file.txt\n"
+ "+++ /dev/null\n";
initBaseWithFile(deletedFileName, deletedFileOriginalContent);
ApplyPatchPatchSetInput in = buildInput(deletedFileDiff);
ChangeInfo result = applyPatch(in);
DiffInfo diff = fetchDiffForFile(result, deletedFileName);
assertDiffForDeletedFile(diff, deletedFileName, deletedFileOriginalContent);
}
@Test
public void applyRenamedFilePatch_success() throws Exception {
final String renamedFileOriginalName = "renamed_file_origin.txt";
final String renamedFileNewName = "renamed_file_new.txt";
final String renamedFileDiff =
"diff --git a/renamed_file_origin.txt b/renamed_file_new.txt\n"
+ "rename from renamed_file_origin.txt\n"
+ "rename to renamed_file_new.txt\n"
+ "--- a/renamed_file_origin.txt\n"
+ "+++ b/renamed_file_new.txt\n"
+ "@@ -1,2 +1 @@\n"
+ "-First original line\n"
+ "-Second original line\n"
+ "+Modified line\n";
initBaseWithFile(renamedFileOriginalName, MODIFIED_FILE_ORIGINAL_CONTENT);
ApplyPatchPatchSetInput in = buildInput(renamedFileDiff);
ChangeInfo result = applyPatch(in);
DiffInfo originalFileDiff = fetchDiffForFile(result, renamedFileOriginalName);
assertDiffForDeletedFile(
originalFileDiff, renamedFileOriginalName, MODIFIED_FILE_ORIGINAL_CONTENT);
DiffInfo newFileDiff = fetchDiffForFile(result, renamedFileNewName);
assertDiffForNewFile(
newFileDiff, result.currentRevision, renamedFileNewName, MODIFIED_FILE_NEW_CONTENT);
}
@Test
public void applyValidTraditionalPatch_success() throws Exception {
final String fileName = "file_name.txt";
final String originalContent = "original line";
final String newContent = "new line\n";
final String diff =
"diff file_name.txt file_name.txt\n"
+ "--- file_name.txt\n"
+ "+++ file_name.txt\n"
+ "@@ -1 +1 @@\n"
+ "-original line\n"
+ "+new line\n";
initBaseWithFile(fileName, originalContent);
ApplyPatchPatchSetInput in = buildInput(diff);
ChangeInfo result = applyPatch(in);
DiffInfo fileDiff = fetchDiffForFile(result, fileName);
assertDiffForFullyModifiedFile(
fileDiff, result.currentRevision, fileName, originalContent, newContent);
}
@Test
public void applyGerritBasedPatchWithSingleFile_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();
BinaryResult originalPatch = gApi.changes().id(baseCommit.getChangeId()).current().patch();
ApplyPatchPatchSetInput in = buildInput(originalPatch.asString());
createBranchWithRevision(BranchNameKey.create(project, DESTINATION_BRANCH), head);
ChangeInfo result = applyPatch(in);
BinaryResult resultPatch = gApi.changes().id(result.id).current().patch();
assertThat(normalizePatchForComparison(resultPatch))
.isEqualTo(normalizePatchForComparison(originalPatch));
}
@Test
public void applyGerritBasedPatchWithMultipleFiles_success() throws Exception {
PushOneCommit.Result commonBaseCommit =
createChange("File for modification", MODIFIED_FILE_NAME, MODIFIED_FILE_ORIGINAL_CONTENT);
commonBaseCommit.assertOkStatus();
String head = getHead(repo(), HEAD).name();
createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
PushOneCommit.Result commitToPatch =
createChange("Add file", ADDED_FILE_NAME, ADDED_FILE_CONTENT);
amendChange(
commitToPatch.getChangeId(), "Modify file", MODIFIED_FILE_NAME, MODIFIED_FILE_NEW_CONTENT);
commitToPatch.assertOkStatus();
BinaryResult originalPatch = gApi.changes().id(commitToPatch.getChangeId()).current().patch();
ApplyPatchPatchSetInput in = buildInput(originalPatch.asString());
createBranchWithRevision(BranchNameKey.create(project, DESTINATION_BRANCH), head);
ChangeInfo result = applyPatch(in);
BinaryResult resultPatch = gApi.changes().id(result.id).current().patch();
assertThat(normalizePatchForComparison(resultPatch))
.isEqualTo(normalizePatchForComparison(originalPatch));
}
@Test
public void applyGerritBasedPatchUsingRest_success() throws Exception {
String head = getHead(repo(), HEAD).name();
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);
RestResponse resp =
adminRestSession.post("/changes/" + destChange.getChangeId() + "/patch:apply", in);
resp.assertOK();
BinaryResult resultPatch = gApi.changes().id(destChange.getChangeId()).current().patch();
assertThat(normalizePatchForComparison(resultPatch))
.isEqualTo(normalizePatchForComparison(originalPatch));
}
@Test
public void applyGerritBasedPatchUsingRestWithEncodedPatch_success() throws Exception {
String head = getHead(repo(), HEAD).name();
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);
RestResponse resp =
adminRestSession.post("/changes/" + destChange.getChangeId() + "/patch:apply", in);
resp.assertOK();
BinaryResult resultPatch = gApi.changes().id(destChange.getChangeId()).current().patch();
assertThat(normalizePatchForComparison(resultPatch))
.isEqualTo(normalizePatchForComparison(originalDecodedPatch));
}
@Test
public void applyPatchWithConflict_appendErrorsToCommitMessage() throws Exception {
initBaseWithFile(MODIFIED_FILE_NAME, "Unexpected base content");
String patch = ADDED_FILE_DIFF + MODIFIED_FILE_DIFF;
ApplyPatchPatchSetInput in = buildInput(patch);
in.commitMessage = "subject";
ChangeInfo result = applyPatch(in);
assertThat(gApi.changes().id(result.id).current().commit(false).message)
.isEqualTo(
in.commitMessage
+ "\n\nNOTE FOR REVIEWERS - errors occurred while applying the patch."
+ "\nPLEASE REVIEW CAREFULLY.\nErrors:\nError applying patch in "
+ MODIFIED_FILE_NAME
+ ", hunk HunkHeader[1,2->1,1]: Hunk cannot be applied\n\nOriginal patch:\n "
+ removePatchHeader(patch)
+ "\n\nChange-Id: "
+ result.changeId
+ "\n");
// Error in MODIFIED_FILE should not affect ADDED_FILE results.
DiffInfo diff = fetchDiffForFile(result, ADDED_FILE_NAME);
assertDiffForNewFile(diff, result.currentRevision, ADDED_FILE_NAME, ADDED_FILE_CONTENT);
}
@Test
public void applyPatchWithConflict_appendErrorsToCommitMessageWithLargeOriginalPatch()
throws Exception {
initBaseWithFile(MODIFIED_FILE_NAME, "Unexpected base content");
String modifiedFileDiff =
"diff --git a/modified_file.txt b/modified_file.txt\n"
+ "--- a/modified_file.txt\n"
+ "+++ b/modified_file.txt\n"
+ "@@ -1,2 +1,1001 @@\n"
+ "-First original line\n"
+ "-Second original line\n"
+ "+Modified line\n"
+ "+1000 additional lines\n".repeat(1000);
String patch = ADDED_FILE_DIFF + modifiedFileDiff;
ApplyPatchPatchSetInput in = buildInput(patch);
in.commitMessage = "subject";
ChangeInfo result = applyPatch(in);
assertThat(gApi.changes().id(result.id).current().commit(false).message)
.isEqualTo(
in.commitMessage
+ "\n\nNOTE FOR REVIEWERS - errors occurred while applying the patch."
+ "\nPLEASE REVIEW CAREFULLY.\nErrors:\nError applying patch in "
+ MODIFIED_FILE_NAME
+ ", hunk HunkHeader[1,2->1,1001]: Hunk cannot be applied\n\nOriginal patch:\n "
+ removePatchHeader(patch).substring(0, 1024)
+ "\n[[[Original patch trimmed due to size. Decoded string size: "
+ removePatchHeader(patch).length()
+ ". Decoded string SHA1: "
+ Hashing.sha1().hashString(removePatchHeader(patch), UTF_8)
+ ".]]]"
+ "\n\nChange-Id: "
+ result.changeId
+ "\n");
// Error in MODIFIED_FILE should not affect ADDED_FILE results.
DiffInfo diff = fetchDiffForFile(result, ADDED_FILE_NAME);
assertDiffForNewFile(diff, result.currentRevision, ADDED_FILE_NAME, ADDED_FILE_CONTENT);
}
@Test
public void applyPatchWithoutAddPatchSetPermissions_fails() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
projectOperations
.project(allProjects)
.forUpdate()
.remove(
permissionKey(Permission.ADD_PATCH_SET)
.ref("refs/for/*")
.group(REGISTERED_USERS)
.build())
.update();
PushOneCommit.Result destChange = createChange("dest change", "a file", "with content");
// Add-patch is always allowed for the change owner, so we need to use another account.
requestScopeOperations.setApiUser(accountCreator.user2().id());
Throwable error =
assertThrows(
AuthException.class, () -> gApi.changes().id(destChange.getChangeId()).applyPatch(in));
assertThat(error).hasMessageThat().contains("patch set");
}
@Test
public void applyPatchWithCustomMessage_success() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.commitMessage = "custom commit message";
ChangeInfo result = applyPatch(in);
assertThat(gApi.changes().id(result.id).current().commit(false).message)
.contains(in.commitMessage);
}
@Test
public void applyPatchWithBaseCommit_success() throws Exception {
PushOneCommit.Result baseCommit =
createChange("base commit", MODIFIED_FILE_NAME, MODIFIED_FILE_ORIGINAL_CONTENT);
baseCommit.assertOkStatus();
PushOneCommit.Result ignoredCommit =
createChange("Ignored file modification", MODIFIED_FILE_NAME, "Ignored file modification");
ignoredCommit.assertOkStatus();
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(MODIFIED_FILE_DIFF);
in.base = baseCommit.getCommit().getName();
ChangeInfo result = applyPatch(in);
assertThat(gApi.changes().id(result.id).current().commit(false).parents.get(0).commit)
.isEqualTo(in.base);
}
@Test
public void applyPatchWithDefaultAuthor_success() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
ChangeInfo result = applyPatch(in);
GitPerson person = gApi.changes().id(result.id).current().commit(false).author;
GitPersonSubject.assertThat(person).email().isEqualTo(admin.email());
}
@Test
public void applyPatchWithAuthorOverrideMissingEmail_throwsIllegalArgument() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.author = new AccountInput();
in.author.name = "name";
Throwable error = assertThrows(IllegalArgumentException.class, () -> applyPatch(in));
assertThat(error).hasMessageThat().contains("E-mail");
}
@Test
public void applyPatchWithAuthorOverrideMissingName_throwsIllegalArgument() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.author = new AccountInput();
in.author.name = null;
in.author.email = "gerritlessjane@invalid";
Throwable error = assertThrows(IllegalArgumentException.class, () -> applyPatch(in));
assertThat(error).hasMessageThat().contains("Name");
}
@Test
public void applyPatchWithAuthorOverride_success() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.author = new AccountInput();
in.author.email = "gerritlessjane@invalid";
// This is an email address that doesn't exist as account on the Gerrit server.
in.author.name = "Gerritless Jane";
ChangeInfo result = applyPatch(in);
RevisionApi rApi = gApi.changes().id(result.id).current();
GitPerson author = rApi.commit(false).author;
GitPersonSubject.assertThat(author).email().isEqualTo(in.author.email);
GitPersonSubject.assertThat(author).name().isEqualTo(in.author.name);
GitPerson committer = rApi.commit(false).committer;
GitPersonSubject.assertThat(committer).email().isEqualTo(admin.getNameEmail().email());
}
@Test
public void applyPatchWithAuthorWithoutPermissions_fails() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.author = new AccountInput();
in.author.name = "Jane";
in.author.email = "jane@invalid";
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
.update();
Throwable error = assertThrows(ResourceConflictException.class, () -> applyPatch(in));
assertThat(error).hasMessageThat().contains("forge author");
}
@Test
public void applyPatchWithSelfAsForgedAuthor_success() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.author = new AccountInput();
in.author.name = admin.fullName();
in.author.email = admin.email();
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
.update();
ChangeInfo result = applyPatch(in);
GitPerson person = gApi.changes().id(result.id).current().commit(false).author;
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(normalizePatchForComparison(resultPatch))
.isEqualTo(normalizePatchForComparison(ADDED_FILE_DIFF));
}
@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);
}
@Test
public void commitMessage_providedMessage() throws Exception {
final String msg = "custom message";
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.commitMessage = msg;
ChangeInfo result = applyPatch(in);
ChangeInfo info = get(result.changeId, CURRENT_REVISION, CURRENT_COMMIT);
assertThat(info.revisions.get(info.currentRevision).commit.message)
.isEqualTo(msg + "\n\nChange-Id: " + result.changeId + "\n");
}
@Test
public void commitMessage_providedMessageWithCorrectChangeId() throws Exception {
initDestBranch();
String originalChangeId =
gApi.changes()
.create(new ChangeInput(project.get(), DESTINATION_BRANCH, "Default commit message"))
.info()
.changeId;
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.commitMessage = "custom commit message\n\nChange-Id: " + originalChangeId + "\n";
ChangeInfo result = gApi.changes().id(originalChangeId).applyPatch(in);
ChangeInfo info = get(result.changeId, CURRENT_REVISION, CURRENT_COMMIT);
assertThat(info.revisions.get(info.currentRevision).commit.message).isEqualTo(in.commitMessage);
}
@Test
public void commitMessage_providedMessageWithWrongChangeId() throws Exception {
initDestBranch();
String originalChangeId =
gApi.changes()
.create(new ChangeInput(project.get(), DESTINATION_BRANCH, "Default commit message"))
.info()
.changeId;
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
in.commitMessage = "custom commit message\n\nChange-Id: " + "I1234567890" + "\n";
assertThrows(
ResourceConflictException.class, () -> gApi.changes().id(originalChangeId).applyPatch(in));
}
@Test
public void commitMessage_defaultMessageAndPatchHeader() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput("Patch header\n" + ADDED_FILE_DIFF);
ChangeInfo result = applyPatch(in);
ChangeInfo info = get(result.changeId, CURRENT_REVISION, CURRENT_COMMIT);
assertThat(info.revisions.get(info.currentRevision).commit.message)
.isEqualTo("Default commit message\n\nChange-Id: " + result.changeId + "\n");
}
@Test
public void commitMessage_defaultMessageAndNoPatchHeader() throws Exception {
initDestBranch();
ApplyPatchPatchSetInput in = buildInput(ADDED_FILE_DIFF);
ChangeInfo result = applyPatch(in);
ChangeInfo info = get(result.changeId, CURRENT_REVISION, CURRENT_COMMIT);
assertThat(info.revisions.get(info.currentRevision).commit.message)
.isEqualTo("Default commit message\n\nChange-Id: " + result.changeId + "\n");
}
@Test
public void amendCommitWithValidTraditionalPatch_success() throws Exception {
final String fileName = "file_name.txt";
final String originalContent = "original line";
final String newContent = "new line\n";
final String diff =
"diff file_name.txt file_name.txt\n"
+ "--- file_name.txt\n"
+ "+++ file_name.txt\n"
+ "@@ -1 +1 @@\n"
+ "-original line\n"
+ "+new line\n";
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, "Test", fileName, "foo");
PushOneCommit.Result base = push.to("refs/heads/foo");
base.assertOkStatus();
PushOneCommit.Result firstPatchSet =
createChange(
testRepo, "foo", "Add original file: " + fileName, fileName, originalContent, null);
firstPatchSet.assertOkStatus();
ApplyPatchPatchSetInput in = new ApplyPatchPatchSetInput();
in.patch = new ApplyPatchInput();
in.patch.patch = diff;
in.amend = true;
in.responseFormatOptions =
ImmutableList.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT);
ChangeInfo result = gApi.changes().id(firstPatchSet.getChangeId()).applyPatch(in);
// Parent of patch set 2 = parent of patch set 1, so we actually amended
assertThat(result.revisions.get(result.currentRevision).commit.parents.get(0).commit)
.isEqualTo(base.getCommit().getId().getName());
DiffInfo fileDiff = gApi.changes().id(result.id).current().file(fileName).diff();
assertDiffForFullyModifiedFile(fileDiff, result.currentRevision, fileName, "foo", newContent);
assertThat(gApi.changes().id(firstPatchSet.getChangeId()).current().commit(false).message)
.isEqualTo(firstPatchSet.getCommit().getFullMessage());
}
@Test
public void amendCantBeUsedWithBase() throws Exception {
final String diff =
"diff file_name.txt file_name.txt\n"
+ "--- file_name.txt\n"
+ "+++ file_name.txt\n"
+ "@@ -1 +1 @@\n"
+ "-original line\n"
+ "+new line\n";
PushOneCommit.Result change = createChange();
ApplyPatchPatchSetInput in = new ApplyPatchPatchSetInput();
in.patch = new ApplyPatchInput();
in.patch.patch = diff;
in.amend = true;
in.base = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.changes().id(change.getChangeId()).applyPatch(in));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("amend only works with existing revisions. omit base.");
}
@Test
public void amendCommitWithConflict_appendErrorsToCommitMessage() throws Exception {
final String fileName = "file_name.txt";
final String originalContent = "original line";
final String diff =
"diff file_name.txt file_name.txt\n"
+ "--- file_name.txt\n"
+ "+++ file_name.txt\n"
+ "@@ -1 +1 @@\n"
+ "-xxx line\n"
+ "+new line\n";
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, "Test", fileName, "foo");
PushOneCommit.Result base = push.to("refs/heads/foo");
base.assertOkStatus();
PushOneCommit.Result firstPatchSet =
createChange(
testRepo, "foo", "Add original file: " + fileName, fileName, originalContent, null);
firstPatchSet.assertOkStatus();
ApplyPatchPatchSetInput in = new ApplyPatchPatchSetInput();
in.patch = new ApplyPatchInput();
in.patch.patch = diff;
in.amend = true;
in.responseFormatOptions =
ImmutableList.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT);
ChangeInfo result = gApi.changes().id(firstPatchSet.getChangeId()).applyPatch(in);
assertThat(gApi.changes().id(result.id).current().commit(false).message)
.startsWith(
"Add original file: file_name.txt\n"
+ "\n"
+ "NOTE FOR REVIEWERS - errors occurred while applying the patch.\n"
+ "PLEASE REVIEW CAREFULLY.\n"
+ "Errors:\n"
+ "Error applying patch in file_name.txt, hunk HunkHeader[1,1->1,1]: Hunk cannot be applied\n"
+ "\n"
+ "Original patch:\n"
+ " diff file_name.txt file_name.txt\n"
+ "--- file_name.txt\n"
+ "+++ file_name.txt\n"
+ "@@ -1 +1 @@\n"
+ "-xxx line\n"
+ "+new line");
}
@Test
public void amendCommitWithValidTraditionalPatchEmptyRepo_resourceNotFound() throws Exception {
final String fileName = "file_name.txt";
final String originalContent = "original line";
final String diff =
"diff file_name.txt file_name.txt\n"
+ "--- file_name.txt\n"
+ "+++ file_name.txt\n"
+ "@@ -1 +1 @@\n"
+ "-original line\n"
+ "+new line\n";
Project.NameKey emptyProject = projectOperations.newProject().noEmptyCommit().create();
TestRepository<InMemoryRepository> emptyClone = cloneProject(emptyProject);
PushOneCommit.Result firstPatchSet =
createChange(
emptyClone,
"master",
"Add original file: " + fileName,
fileName,
originalContent,
null);
firstPatchSet.assertOkStatus();
ApplyPatchPatchSetInput in = new ApplyPatchPatchSetInput();
in.patch = new ApplyPatchInput();
in.patch.patch = diff;
in.amend = true;
Throwable error =
assertThrows(
ResourceNotFoundException.class,
() -> gApi.changes().id(firstPatchSet.getChangeId()).applyPatch(in));
assertThat(error).hasMessageThat().contains("Branch refs/heads/master does not exist");
}
private void initDestBranch() throws Exception {
String head = getHead(repo(), HEAD).name();
createBranchWithRevision(BranchNameKey.create(project, ApplyPatchIT.DESTINATION_BRANCH), head);
}
private void initBaseWithFile(String fileName, String fileContent) throws Exception {
PushOneCommit.Result baseCommit =
createChange("Add original file: " + fileName, fileName, fileContent);
baseCommit.assertOkStatus();
initDestBranch();
}
private ApplyPatchPatchSetInput buildInput(String patch) {
ApplyPatchPatchSetInput in = new ApplyPatchPatchSetInput();
in.patch = new ApplyPatchInput();
in.patch.patch = patch;
return in;
}
private ChangeInfo applyPatch(ApplyPatchPatchSetInput input) throws RestApiException {
input.responseFormatOptions = ImmutableList.of(ListChangesOption.CURRENT_REVISION);
return gApi.changes()
.create(new ChangeInput(project.get(), DESTINATION_BRANCH, "Default commit message"))
.applyPatch(input);
}
private DiffInfo fetchDiffForFile(ChangeInfo result, String fileName) throws RestApiException {
return gApi.changes().id(result.id).current().file(fileName).diff();
}
}