summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick Hiesel <hiesel@google.com>2020-10-06 15:56:30 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-10-06 15:56:30 +0000
commite1b692cca5a05964575205c089f69f7213cf11fa (patch)
tree5a090a15c099e8a70c68b60f49fef9469f23358c
parent980a8f0d1b1a0fcce145c0438bf90ad325f4f229 (diff)
parente8fc7974e8dd15052034f9e304ab7e4d76b09d53 (diff)
Merge "CreateMergePatchSet: Implement 'author' to tweak Git commit author"
-rw-r--r--Documentation/rest-api-changes.txt7
-rw-r--r--java/com/google/gerrit/extensions/common/MergePatchSetInput.java3
-rw-r--r--java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java17
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java406
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/CreateMergePatchSetIT.java644
5 files changed, 670 insertions, 407 deletions
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 8bd02a80dd..6b7348248e 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -7325,6 +7325,13 @@ it's set. Otherwise, the current branch tip of the destination branch will be us
|`merge` ||
The detail of the source commit for merge as a link:#merge-input[MergeInput]
entity.
+|`author` |optional|
+An link:rest-api-accounts.html#account-input[AccountInput] entity
+that will set the author of the commit to create. The author must be
+specified as name/email combination.
+The caller needs "Forge Author" permission when using this field.
+This field does not affect the owner of the change, which will
+continue to use the identity of the caller.
|==================================
[[move-input]]
diff --git a/java/com/google/gerrit/extensions/common/MergePatchSetInput.java b/java/com/google/gerrit/extensions/common/MergePatchSetInput.java
index 53f5e078c5..734d7e9840 100644
--- a/java/com/google/gerrit/extensions/common/MergePatchSetInput.java
+++ b/java/com/google/gerrit/extensions/common/MergePatchSetInput.java
@@ -14,9 +14,12 @@
package com.google.gerrit.extensions.common;
+import com.google.gerrit.extensions.api.accounts.AccountInput;
+
public class MergePatchSetInput {
public String subject;
public boolean inheritParent;
public String baseChange;
public MergeInput merge;
+ public AccountInput author;
}
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index 8ac214001f..af4bf694fc 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -55,6 +55,7 @@ import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.project.CommitsCollection;
@@ -128,6 +129,13 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
psUtil.checkPatchSetNotLocked(rsrc.getNotes());
rsrc.permissions().check(ChangePermission.ADD_PATCH_SET);
+ if (in.author != null) {
+ permissionBackend
+ .currentUser()
+ .project(rsrc.getProject())
+ .ref(rsrc.getChange().getDest().branch())
+ .check(RefPermission.FORGE_AUTHOR);
+ }
ProjectState projectState =
projectCache.get(rsrc.getProject()).orElseThrow(illegalState(rsrc.getProject()));
@@ -137,6 +145,10 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
if (merge == null || Strings.isNullOrEmpty(merge.source)) {
throw new BadRequestException("merge.source must be non-empty");
}
+ if (in.author != null
+ && (Strings.isNullOrEmpty(in.author.email) || Strings.isNullOrEmpty(in.author.name))) {
+ throw new BadRequestException("Author must specify name and email");
+ }
in.baseChange = Strings.nullToEmpty(in.baseChange).trim();
PatchSet ps = psUtil.current(rsrc.getNotes());
@@ -166,7 +178,10 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
Timestamp now = TimeUtil.nowTs();
IdentifiedUser me = user.get().asIdentifiedUser();
- PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
+ PersonIdent author =
+ in.author == null
+ ? me.newCommitterIdent(now, serverTimeZone)
+ : new PersonIdent(in.author.name, in.author.email, now, serverTimeZone);
CodeReviewCommit newCommit =
createMergeCommit(
in,
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index d4affb7571..410cb41d71 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -47,7 +47,6 @@ import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
-import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
@@ -142,14 +141,11 @@ import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.LabelInfo;
-import com.google.gerrit.extensions.common.MergeInput;
-import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.TrackingIdInfo;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
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.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -183,7 +179,6 @@ import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.name.Named;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -3189,407 +3184,6 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
- public void createMergePatchSet() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- String changeId = createChange().getChangeId();
-
- testRepo.reset(initialHead);
- PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
- currentMaster.assertOkStatus();
- String parent = currentMaster.getCommit().getName();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result changeA =
- pushFactory
- .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
- .to("refs/heads/dev");
- changeA.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- String subject = "update change by merge ps2";
- in.subject = subject;
-
- TestWorkInProgressStateChangedListener wipStateChangedListener =
- new TestWorkInProgressStateChangedListener();
- try (Registration registration =
- extensionRegistry.newRegistration().add(wipStateChangedListener)) {
- ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
- assertThat(changeInfo.subject).isEqualTo(in.subject);
- assertThat(changeInfo.containsGitConflicts).isNull();
- assertThat(changeInfo.workInProgress).isNull();
- }
- assertThat(wipStateChangedListener.invoked).isFalse();
-
- // 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.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
- .isEqualTo(parent);
-
- // 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("Uploaded patch set 2.");
-
- assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
- .contains(subject);
- }
-
- @Test
- public void createMergePatchSet_SubjectCarriesOverByDefault() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- PushOneCommit.Result result = createChange();
- String changeId = result.getChangeId();
- String subject = result.getChange().change().getSubject();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result pushResult =
- pushFactory.create(user.newIdent(), testRepo).to("refs/heads/dev");
- pushResult.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = null;
-
- // Ensure subject carries over
- gApi.changes().id(changeId).createMergePatchSet(in);
- ChangeInfo changeInfo = gApi.changes().id(changeId).get();
- assertThat(changeInfo.subject).isEqualTo(subject);
- }
-
- @Test
- public void createMergePatchSet_Conflict() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- String changeId = createChange().getChangeId();
-
- String fileName = "shared.txt";
- testRepo.reset(initialHead);
- PushOneCommit.Result currentMaster =
- pushFactory
- .create(admin.newIdent(), testRepo, "change 1", fileName, "content 1")
- .to("refs/heads/master");
- currentMaster.assertOkStatus();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result changeA =
- pushFactory
- .create(user.newIdent(), testRepo, "change 2", fileName, "content 2")
- .to("refs/heads/dev");
- changeA.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = "update change by merge ps2";
- ResourceConflictException thrown =
- assertThrows(
- ResourceConflictException.class,
- () -> gApi.changes().id(changeId).createMergePatchSet(in));
- assertThat(thrown).hasMessageThat().isEqualTo("merge conflict(s):\n" + fileName);
- }
-
- @Test
- public void createMergePatchSet_ConflictAllowed() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- String changeId = createChange().getChangeId();
-
- String fileName = "shared.txt";
- String sourceSubject = "source change";
- String sourceContent = "source content";
- String targetSubject = "target change";
- String targetContent = "target content";
- testRepo.reset(initialHead);
- PushOneCommit.Result currentMaster =
- pushFactory
- .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
- .to("refs/heads/master");
- currentMaster.assertOkStatus();
- String parent = currentMaster.getCommit().getName();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result changeA =
- pushFactory
- .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
- .to("refs/heads/dev");
- changeA.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- mergeInput.allowConflicts = true;
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = "update change by merge ps2";
-
- TestWorkInProgressStateChangedListener wipStateChangedListener =
- new TestWorkInProgressStateChangedListener();
- try (Registration registration =
- extensionRegistry.newRegistration().add(wipStateChangedListener)) {
- ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
- assertThat(changeInfo.subject).isEqualTo(in.subject);
- assertThat(changeInfo.containsGitConflicts).isTrue();
- assertThat(changeInfo.workInProgress).isTrue();
- }
- assertThat(wipStateChangedListener.invoked).isTrue();
- assertThat(wipStateChangedListener.wip).isTrue();
-
- // 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.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
- .isEqualTo(parent);
-
- // Verify that the file content in the created patch set is correct.
- // We expect that it has conflict markers to indicate the conflict.
- BinaryResult bin = gApi.changes().id(changeId).current().file(fileName).content();
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- bin.writeTo(os);
- String fileContent = new String(os.toByteArray(), UTF_8);
- String sourceSha1 = abbreviateName(changeA.getCommit(), 6);
- String targetSha1 = abbreviateName(currentMaster.getCommit(), 6);
- assertThat(fileContent)
- .isEqualTo(
- "<<<<<<< TARGET BRANCH ("
- + targetSha1
- + " "
- + targetSubject
- + ")\n"
- + targetContent
- + "\n"
- + "=======\n"
- + sourceContent
- + "\n"
- + ">>>>>>> SOURCE BRANCH ("
- + sourceSha1
- + " "
- + sourceSubject
- + ")\n");
-
- // 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(
- "Uploaded patch set 2.\n\n"
- + "The following files contain Git conflicts:\n"
- + "* "
- + fileName
- + "\n");
- }
-
- @Test
- public void createMergePatchSet_ConflictAllowedNotSupportedByMergeStrategy() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- String changeId = createChange().getChangeId();
-
- String fileName = "shared.txt";
- String sourceSubject = "source change";
- String sourceContent = "source content";
- String targetSubject = "target change";
- String targetContent = "target content";
- testRepo.reset(initialHead);
- PushOneCommit.Result currentMaster =
- pushFactory
- .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
- .to("refs/heads/master");
- currentMaster.assertOkStatus();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result changeA =
- pushFactory
- .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
- .to("refs/heads/dev");
- changeA.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- mergeInput.allowConflicts = true;
- mergeInput.strategy = "simple-two-way-in-core";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = "update change by merge ps2";
-
- BadRequestException ex =
- assertThrows(
- BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
- assertThat(ex)
- .hasMessageThat()
- .isEqualTo(
- "merge with conflicts is not supported with merge strategy: " + mergeInput.strategy);
- }
-
- @Test
- public void createMergePatchSetInheritParent() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- PushOneCommit.Result r = createChange();
- String changeId = r.getChangeId();
- String parent = r.getCommit().getParent(0).getName();
-
- // advance master branch
- testRepo.reset(initialHead);
- PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
- currentMaster.assertOkStatus();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result changeA =
- pushFactory
- .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
- .to("refs/heads/dev");
- changeA.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = "update change by merge ps2 inherit parent of ps1";
- in.inheritParent = true;
- gApi.changes().id(changeId).createMergePatchSet(in);
- ChangeInfo changeInfo =
- gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
-
- assertThat(changeInfo.revisions).hasSize(2);
- assertThat(changeInfo.subject).isEqualTo(in.subject);
- assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
- .isEqualTo(parent);
- assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
- .isNotEqualTo(currentMaster.getCommit().getName());
- }
-
- @Test
- public void createMergePatchSetCannotBaseOnInvisibleChange() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("foo");
- createBranch("bar");
-
- // Create a merged commit on 'foo' branch.
- merge(createChange("refs/for/foo"));
-
- // Create the base change on 'bar' branch.
- testRepo.reset(initialHead);
- String baseChange = createChange("refs/for/bar").getChangeId();
- gApi.changes().id(baseChange).setPrivate(true, "set private");
-
- // Create the destination change on 'master' branch.
- requestScopeOperations.setApiUser(user.id());
- testRepo.reset(initialHead);
- String changeId = createChange().getChangeId();
-
- UnprocessableEntityException thrown =
- assertThrows(
- UnprocessableEntityException.class,
- () ->
- gApi.changes()
- .id(changeId)
- .createMergePatchSet(createMergePatchSetInput(baseChange)));
- assertThat(thrown).hasMessageThat().contains("Read not permitted for " + baseChange);
- }
-
- @Test
- public void createMergePatchSetBaseOnChange() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("foo");
- createBranch("bar");
-
- // Create a merged commit on 'foo' branch.
- merge(createChange("refs/for/foo"));
-
- // Create the base change on 'bar' branch.
- testRepo.reset(initialHead);
- PushOneCommit.Result result = createChange("refs/for/bar");
- String baseChange = result.getChangeId();
- String expectedParent = result.getCommit().getName();
-
- // Create the destination change on 'master' branch.
- testRepo.reset(initialHead);
- String changeId = createChange().getChangeId();
-
- gApi.changes().id(changeId).createMergePatchSet(createMergePatchSetInput(baseChange));
-
- ChangeInfo changeInfo =
- gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
- assertThat(changeInfo.revisions).hasSize(2);
- assertThat(changeInfo.subject).isEqualTo("create ps2");
- assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
- .isEqualTo(expectedParent);
- }
-
- @Test
- public void createMergePatchSetWithUnupportedMergeStrategy() throws Exception {
- RevCommit initialHead = projectOperations.project(project).getHead("master");
- createBranch("dev");
-
- // create a change for master
- String changeId = createChange().getChangeId();
-
- String fileName = "shared.txt";
- String sourceSubject = "source change";
- String sourceContent = "source content";
- String targetSubject = "target change";
- String targetContent = "target content";
- testRepo.reset(initialHead);
- PushOneCommit.Result currentMaster =
- pushFactory
- .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
- .to("refs/heads/master");
- currentMaster.assertOkStatus();
-
- // push a commit into dev branch
- testRepo.reset(initialHead);
- PushOneCommit.Result changeA =
- pushFactory
- .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
- .to("refs/heads/dev");
- changeA.assertOkStatus();
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "dev";
- mergeInput.strategy = "unsupported-strategy";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = "update change by merge ps2";
-
- BadRequestException ex =
- assertThrows(
- BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
- assertThat(ex).hasMessageThat().isEqualTo("invalid merge strategy: " + mergeInput.strategy);
- }
-
- private MergePatchSetInput createMergePatchSetInput(String baseChange) {
- MergeInput mergeInput = new MergeInput();
- mergeInput.source = "foo";
- MergePatchSetInput in = new MergePatchSetInput();
- in.merge = mergeInput;
- in.subject = "create ps2";
- in.inheritParent = false;
- in.baseChange = baseChange;
- return in;
- }
-
- @Test
public void checkLabelsForUnsubmittedChange() throws Exception {
PushOneCommit.Result r = createChange();
ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/CreateMergePatchSetIT.java b/javatests/com/google/gerrit/acceptance/api/change/CreateMergePatchSetIT.java
new file mode 100644
index 0000000000..8b77d01349
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/change/CreateMergePatchSetIT.java
@@ -0,0 +1,644 @@
+package com.google.gerrit.acceptance.api.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT;
+import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
+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;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.extensions.api.accounts.AccountInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.MergeInput;
+import com.google.gerrit.extensions.common.MergePatchSetInput;
+import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
+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.UnprocessableEntityException;
+import com.google.inject.Inject;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CreateMergePatchSetIT extends AbstractDaemonTest {
+
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
+
+ @Before
+ public void setUp() {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ }
+
+ @Test
+ public void createMergePatchSet() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ String subject = "update change by merge ps2";
+ in.subject = subject;
+
+ TestWorkInProgressStateChangedListener wipStateChangedListener =
+ new TestWorkInProgressStateChangedListener();
+ try (ExtensionRegistry.Registration registration =
+ extensionRegistry.newRegistration().add(wipStateChangedListener)) {
+ ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(changeInfo.containsGitConflicts).isNull();
+ assertThat(changeInfo.workInProgress).isNull();
+ }
+ assertThat(wipStateChangedListener.invoked).isFalse();
+
+ // 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.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(parent);
+
+ // 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("Uploaded patch set 2.");
+
+ assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
+ .contains(subject);
+ }
+
+ @Test
+ public void createMergePatchSet_SubjectCarriesOverByDefault() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String subject = result.getChange().change().getSubject();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result pushResult =
+ pushFactory.create(user.newIdent(), testRepo).to("refs/heads/dev");
+ pushResult.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = null;
+
+ // Ensure subject carries over
+ gApi.changes().id(changeId).createMergePatchSet(in);
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ assertThat(changeInfo.subject).isEqualTo(subject);
+ }
+
+ @Test
+ public void createMergePatchSet_Conflict() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, "change 1", fileName, "content 1")
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change 2", fileName, "content 2")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(thrown).hasMessageThat().isEqualTo("merge conflict(s):\n" + fileName);
+ }
+
+ @Test
+ public void createMergePatchSet_ConflictAllowed() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.allowConflicts = true;
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ TestWorkInProgressStateChangedListener wipStateChangedListener =
+ new TestWorkInProgressStateChangedListener();
+ try (ExtensionRegistry.Registration registration =
+ extensionRegistry.newRegistration().add(wipStateChangedListener)) {
+ ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(changeInfo.containsGitConflicts).isTrue();
+ assertThat(changeInfo.workInProgress).isTrue();
+ }
+ assertThat(wipStateChangedListener.invoked).isTrue();
+ assertThat(wipStateChangedListener.wip).isTrue();
+
+ // 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.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(parent);
+
+ // Verify that the file content in the created patch set is correct.
+ // We expect that it has conflict markers to indicate the conflict.
+ BinaryResult bin = gApi.changes().id(changeId).current().file(fileName).content();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bin.writeTo(os);
+ String fileContent = new String(os.toByteArray(), UTF_8);
+ String sourceSha1 = abbreviateName(changeA.getCommit(), 6);
+ String targetSha1 = abbreviateName(currentMaster.getCommit(), 6);
+ assertThat(fileContent)
+ .isEqualTo(
+ "<<<<<<< TARGET BRANCH ("
+ + targetSha1
+ + " "
+ + targetSubject
+ + ")\n"
+ + targetContent
+ + "\n"
+ + "=======\n"
+ + sourceContent
+ + "\n"
+ + ">>>>>>> SOURCE BRANCH ("
+ + sourceSha1
+ + " "
+ + sourceSubject
+ + ")\n");
+
+ // 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(
+ "Uploaded patch set 2.\n\n"
+ + "The following files contain Git conflicts:\n"
+ + "* "
+ + fileName
+ + "\n");
+ }
+
+ @Test
+ public void createMergePatchSet_ConflictAllowedNotSupportedByMergeStrategy() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.allowConflicts = true;
+ mergeInput.strategy = "simple-two-way-in-core";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex)
+ .hasMessageThat()
+ .isEqualTo(
+ "merge with conflicts is not supported with merge strategy: " + mergeInput.strategy);
+ }
+
+ @Test
+ public void createMergePatchSetInheritParent() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+ String parent = r.getCommit().getParent(0).getName();
+
+ // advance master branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2 inherit parent of ps1";
+ in.inheritParent = true;
+ gApi.changes().id(changeId).createMergePatchSet(in);
+ ChangeInfo changeInfo =
+ gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
+
+ assertThat(changeInfo.revisions).hasSize(2);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(parent);
+ assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isNotEqualTo(currentMaster.getCommit().getName());
+ }
+
+ @Test
+ public void createMergePatchSetCannotBaseOnInvisibleChange() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "foo"));
+ createBranch(BranchNameKey.create(project, "bar"));
+
+ // Create a merged commit on 'foo' branch.
+ merge(createChange("refs/for/foo"));
+
+ // Create the base change on 'bar' branch.
+ testRepo.reset(initialHead);
+ String baseChange = createChange("refs/for/bar").getChangeId();
+ gApi.changes().id(baseChange).setPrivate(true, "set private");
+
+ // Create the destination change on 'master' branch.
+ requestScopeOperations.setApiUser(user.id());
+ testRepo.reset(initialHead);
+ String changeId = createChange().getChangeId();
+
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .createMergePatchSet(createMergePatchSetInput(baseChange)));
+ assertThat(thrown).hasMessageThat().contains("Read not permitted for " + baseChange);
+ }
+
+ @Test
+ public void createMergePatchSetBaseOnChange() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "foo"));
+ createBranch(BranchNameKey.create(project, "bar"));
+
+ // Create a merged commit on 'foo' branch.
+ merge(createChange("refs/for/foo"));
+
+ // Create the base change on 'bar' branch.
+ testRepo.reset(initialHead);
+ PushOneCommit.Result result = createChange("refs/for/bar");
+ String baseChange = result.getChangeId();
+ String expectedParent = result.getCommit().getName();
+
+ // Create the destination change on 'master' branch.
+ testRepo.reset(initialHead);
+ String changeId = createChange().getChangeId();
+
+ gApi.changes().id(changeId).createMergePatchSet(createMergePatchSetInput(baseChange));
+
+ ChangeInfo changeInfo =
+ gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
+ assertThat(changeInfo.revisions).hasSize(2);
+ assertThat(changeInfo.subject).isEqualTo("create ps2");
+ assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(expectedParent);
+ }
+
+ @Test
+ public void createMergePatchSetWithUnupportedMergeStrategy() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.strategy = "unsupported-strategy";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex).hasMessageThat().isEqualTo("invalid merge strategy: " + mergeInput.strategy);
+ }
+
+ @Test
+ public void createMergePatchSetWithOtherAuthor() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ String subject = "update change by merge ps2";
+ in.subject = subject;
+ in.author = new AccountInput();
+ in.author.name = "Other Author";
+ in.author.email = "otherauthor@example.com";
+ gApi.changes().id(changeId).createMergePatchSet(in);
+
+ // 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.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
+ .isEqualTo(parent);
+
+ // 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("Uploaded patch set 2.");
+
+ CommitInfo commitInfo = changeInfo.revisions.get(changeInfo.currentRevision).commit;
+ assertThat(commitInfo.message).contains(subject);
+ assertThat(commitInfo.author.name).isEqualTo("Other Author");
+ assertThat(commitInfo.author.email).isEqualTo("otherauthor@example.com");
+ }
+
+ @Test
+ public void createMergePatchSetWithSpecificAuthorButNoForgeAuthorPermission() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ String subject = "update change by merge ps2";
+ in.subject = subject;
+ in.author = new AccountInput();
+ in.author.name = "Foo";
+ in.author.email = "foo@example.com";
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(
+ TestProjectUpdate.permissionKey(Permission.FORGE_AUTHOR)
+ .ref("refs/*")
+ .group(REGISTERED_USERS))
+ .add(block(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ AuthException ex =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex).hasMessageThat().isEqualTo("not permitted: forge author on refs/heads/master");
+ }
+
+ @Test
+ public void createMergePatchSetWithMissingNameFailsWithBadRequestException() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ String subject = "update change by merge ps2";
+ in.subject = subject;
+ in.author = new AccountInput();
+ in.author.name = "Foo";
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex).hasMessageThat().isEqualTo("Author must specify name and email");
+ }
+
+ @Test
+ public void createMergePatchSetWithMissingEmailFailsWithBadRequestException() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch(BranchNameKey.create(project, "dev"));
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ String subject = "update change by merge ps2";
+ in.subject = subject;
+ in.author = new AccountInput();
+ in.author.email = "Foo";
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex).hasMessageThat().isEqualTo("Author must specify name and email");
+ }
+
+ private MergePatchSetInput createMergePatchSetInput(String baseChange) {
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "foo";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "create ps2";
+ in.inheritParent = false;
+ in.baseChange = baseChange;
+ return in;
+ }
+
+ private static class TestWorkInProgressStateChangedListener
+ implements WorkInProgressStateChangedListener {
+ boolean invoked;
+ Boolean wip;
+
+ @Override
+ public void onWorkInProgressStateChanged(Event event) {
+ this.invoked = true;
+ this.wip =
+ event.getChange().workInProgress != null ? event.getChange().workInProgress : false;
+ }
+ }
+}