Merge "Fix duplicate revert messages in `RevertSubmission`."
diff --git a/java/com/google/gerrit/server/git/CommitUtil.java b/java/com/google/gerrit/server/git/CommitUtil.java
index c9c090d..3c5da5b 100644
--- a/java/com/google/gerrit/server/git/CommitUtil.java
+++ b/java/com/google/gerrit/server/git/CommitUtil.java
@@ -179,6 +179,26 @@
   public Change.Id createRevertChange(
       ChangeNotes notes, CurrentUser user, RevertInput input, Instant timestamp)
       throws RestApiException, UpdateException, ConfigInvalidException, IOException {
+    return createRevertChange(notes, user, input, timestamp, false);
+  }
+
+  /**
+   * Allows creating a revert change with an option to specify if it is a submission revert.
+   *
+   * @param notes ChangeNotes of the change being reverted.
+   * @param user Current User performing the revert.
+   * @param input the RevertInput entity for conducting the revert.
+   * @param timestamp timestamp for the created change.
+   * @param isSubmission whether the revert is being done as part of reverting a submission.
+   * @return ObjectId that represents the newly created commit.
+   */
+  public Change.Id createRevertChange(
+      ChangeNotes notes,
+      CurrentUser user,
+      RevertInput input,
+      Instant timestamp,
+      boolean isSubmission)
+      throws RestApiException, UpdateException, ConfigInvalidException, IOException {
     String message = Strings.emptyToNull(input.message);
     try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
       try (Repository git = repoManager.openRepository(notes.getProjectName());
@@ -187,7 +207,8 @@
           CodeReviewRevWalk revWalk = CodeReviewCommit.newRevWalk(reader)) {
         ObjectId generatedChangeId = CommitMessageUtil.generateChangeId();
         CodeReviewCommit revertCommit =
-            createRevertCommit(message, notes, user, timestamp, oi, revWalk, generatedChangeId);
+            createRevertCommit(
+                message, notes, user, timestamp, oi, revWalk, generatedChangeId, isSubmission);
         return createRevertChangeFromCommit(
             revertCommit, input, notes, user, generatedChangeId, timestamp, oi, revWalk, git);
       } catch (RepositoryNotFoundException e) {
@@ -208,12 +229,28 @@
   public CodeReviewCommit createRevertCommit(
       String message, ChangeNotes notes, CurrentUser user, Instant ts)
       throws RestApiException, IOException {
+    return createRevertCommit(message, notes, user, ts, false);
+  }
+
+  /**
+   * Wrapper function for creating a revert Commit with an option to specify if it is a submission.
+   *
+   * @param message Commit message for the revert commit.
+   * @param notes ChangeNotes of the change being reverted.
+   * @param user Current User performing the revert.
+   * @param ts Timestamp of creation for the commit.
+   * @param isSubmission whether the revert is being done as part of reverting a submission.
+   * @return that newly created revert commit.
+   */
+  public CodeReviewCommit createRevertCommit(
+      String message, ChangeNotes notes, CurrentUser user, Instant ts, boolean isSubmission)
+      throws RestApiException, IOException {
 
     try (Repository git = repoManager.openRepository(notes.getProjectName());
         ObjectInserter oi = git.newObjectInserter();
         ObjectReader reader = oi.newReader();
         CodeReviewRevWalk revWalk = CodeReviewCommit.newRevWalk(reader)) {
-      return createRevertCommit(message, notes, user, ts, oi, revWalk, null);
+      return createRevertCommit(message, notes, user, ts, oi, revWalk, null, isSubmission);
     } catch (RepositoryNotFoundException e) {
       throw new ResourceNotFoundException(notes.getProjectName().toString(), e);
     }
@@ -274,6 +311,7 @@
    * @param revWalk Used for parsing the original commit.
    * @param generatedChangeId The changeId for the commit message, can be null since it is not
    *     needed for commits, only for changes.
+   * @param isSubmission whether the revert is being done as part of reverting a submission.
    * @return ObjectId that represents the newly created commit.
    * @throws ResourceConflictException Can't revert the initial commit.
    * @throws IOException Thrown in case of I/O errors.
@@ -285,7 +323,8 @@
       Instant ts,
       ObjectInserter oi,
       CodeReviewRevWalk revWalk,
-      @Nullable ObjectId generatedChangeId)
+      @Nullable ObjectId generatedChangeId,
+      boolean isSubmission)
       throws ResourceConflictException, IOException {
 
     PatchSet patch = notes.getCurrentPatchSet();
@@ -304,7 +343,7 @@
     Change changeToRevert = notes.getChange();
     String subject = changeToRevert.getSubject();
 
-    message = getRevertMessage(message, subject, patch.commitId().name(), false);
+    message = getRevertMessage(message, subject, patch.commitId().name(), isSubmission);
 
     String newFooters = getBugAndIssueFooters(commitToRevert);
     if (!newFooters.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 3d37424..3c2ef66 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -243,7 +243,6 @@
     cherryPickInput = createCherryPickInput(revertInput);
     Instant timestamp = TimeUtil.now();
 
-    String initialMessage = revertInput.message;
     for (BranchNameKey projectAndBranch : changesPerProjectAndBranch.keySet()) {
       cherryPickInput.base = null;
       Project.NameKey project = projectAndBranch.project();
@@ -260,7 +259,6 @@
               .collect(Collectors.toSet());
 
       revertAllChangesInProjectAndBranch(
-          initialMessage,
           revertInput,
           project,
           sortedChangesInProjectAndBranch,
@@ -273,9 +271,7 @@
     return revertSubmissionInfo;
   }
 
-  // Warning: reuses and modifies revertInput.message.
   private void revertAllChangesInProjectAndBranch(
-      String initialMessage,
       RevertInput revertInput,
       Project.NameKey project,
       Iterator<PatchSetData> sortedChangesInProjectAndBranch,
@@ -293,8 +289,6 @@
         cherryPickInput.base = getBase(changeNotes, commitIdsInProjectAndBranch).name();
       }
 
-      // Set revert message for the current revert change.
-      revertInput.message = getMessage(initialMessage, changeNotes);
       if (cherryPickInput.base.equals(changeNotes.getCurrentPatchSet().commitId().getName())) {
         // This is the code in case this is the first revert of this project + branch, and the
         // revert would be on top of the change being reverted.
@@ -309,7 +303,8 @@
       RevertInput revertInput, Project.NameKey project, ChangeNotes changeNotes, Instant timestamp)
       throws IOException, ConfigInvalidException, UpdateException, RestApiException {
     RevCommit revCommit =
-        commitUtil.createRevertCommit(revertInput.message, changeNotes, user.get(), timestamp);
+        commitUtil.createRevertCommit(
+            revertInput.message, changeNotes, user.get(), timestamp, true);
     // TODO (paiking): As a future change, the revert should just be done directly on the
     // target rather than just creating a commit and then cherry-picking it.
     cherryPickInput.message = revCommit.getFullMessage();
@@ -351,7 +346,7 @@
       throws IOException, RestApiException, UpdateException, ConfigInvalidException {
 
     Change.Id revertId =
-        commitUtil.createRevertChange(changeNotes, user.get(), revertInput, timestamp);
+        commitUtil.createRevertChange(changeNotes, user.get(), revertInput, timestamp, true);
     results.add(json.noOptions().format(changeNotes.getProjectName(), revertId));
     ChangeNotes revertChange =
         changeNotesFactory.createChecked(changeNotes.getProjectName(), revertId);
@@ -376,14 +371,6 @@
     return cherryPickInput;
   }
 
-  private String getMessage(String initialMessage, ChangeNotes changeNotes) {
-    return commitUtil.getRevertMessage(
-        initialMessage,
-        changeNotes.getChange().getSubject(),
-        changeNotes.getCurrentPatchSet().commitId().name(),
-        true);
-  }
-
   /**
    * This function finds the base that the first revert in a project + branch should be based on.
    *
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
index 6de06b0..ccb9b13 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
@@ -1095,6 +1095,68 @@
   }
 
   @Test
+  public void revertSubmissionOfRevertSubmissionWithSetMessage() throws Exception {
+    String firstResult = createChange("first change", "a.txt", "message").getChangeId();
+    String secondResult = createChange("second change", "b.txt", "message").getChangeId();
+    approve(firstResult);
+    approve(secondResult);
+    gApi.changes().id(secondResult).current().submit();
+
+    // First revert
+    List<ChangeInfo> revertChanges =
+        gApi.changes().id(firstResult).revertSubmission().revertChanges;
+    ChangeInfo firstRevert =
+        revertChanges.stream()
+            .filter(c -> c.subject.equals("Revert \"first change\""))
+            .findFirst()
+            .get();
+    ChangeInfo secondRevert =
+        revertChanges.stream()
+            .filter(c -> c.subject.equals("Revert \"second change\""))
+            .findFirst()
+            .get();
+
+    approve(firstRevert.id);
+    approve(secondRevert.id);
+    gApi.changes().id(firstRevert.id).current().submit();
+
+    // Revert of revert with custom message
+    RevertInput revertInput = new RevertInput();
+    String commitMessage = "Custom message for double revert";
+    revertInput.message = commitMessage;
+    List<ChangeInfo> doubleRevertChanges =
+        gApi.changes().id(firstRevert.id).revertSubmission(revertInput).revertChanges;
+
+    ChangeInfo doubleRevertFirst =
+        doubleRevertChanges.stream()
+            .filter(c -> c.subject.equals("Revert^2 \"first change\""))
+            .findFirst()
+            .get();
+    ChangeInfo doubleRevertSecond =
+        doubleRevertChanges.stream()
+            .filter(c -> c.subject.equals("Revert^2 \"second change\""))
+            .findFirst()
+            .get();
+
+    String firstRevertCommit = gApi.changes().id(firstRevert.id).current().commit(false).commit;
+    String commitMessage1 = gApi.changes().id(doubleRevertFirst.id).current().commit(false).message;
+    assertThat(commitMessage1)
+        .isEqualTo(
+            String.format(
+                "Revert^2 \"first change\"\n\nThis reverts commit %s.\n\n%s\n\nChange-Id: %s\n",
+                firstRevertCommit, commitMessage, doubleRevertFirst.changeId));
+
+    String secondRevertCommit = gApi.changes().id(secondRevert.id).current().commit(false).commit;
+    String commitMessage2 =
+        gApi.changes().id(doubleRevertSecond.id).current().commit(false).message;
+    assertThat(commitMessage2)
+        .isEqualTo(
+            String.format(
+                "Revert^2 \"second change\"\n\nThis reverts commit %s.\n\n%s\n\nChange-Id: %s\n",
+                secondRevertCommit, commitMessage, doubleRevertSecond.changeId));
+  }
+
+  @Test
   public void revertSubmissionWithSetMessageChangeIdIgnored() throws Exception {
     String firstResult = createChange("first change", "a.txt", "message").getChangeId();
     String secondResult = createChange("second change", "b.txt", "message").getChangeId();