Merge "Reformat RevertSubmission"
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index a7d19d1..89b265f 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
@@ -34,6 +35,7 @@
 import com.google.gerrit.extensions.api.changes.RevertInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.RevertSubmissionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -185,6 +187,22 @@
     }
     List<ChangeData> changeDatas = queryProvider.get().bySubmissionId(submissionId);
 
+    checkPermissionsForAllChanges(changeResource, changeDatas);
+    input.topic = createTopic(input.topic, submissionId);
+    return Response.ok(revertSubmission(changeDatas, input));
+  }
+
+  private String createTopic(String topic, String submissionId) {
+    if (topic == null) {
+      return String.format(
+          "revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
+    }
+    return topic;
+  }
+
+  private void checkPermissionsForAllChanges(
+      ChangeResource changeResource, List<ChangeData> changeDatas)
+      throws IOException, AuthException, PermissionBackendException, ResourceConflictException {
     for (ChangeData changeData : changeDatas) {
       Change change = changeData.change();
 
@@ -203,23 +221,122 @@
               "current patch set %s of change %s not found",
               change.currentPatchSetId(), change.currentPatchSetId()));
     }
-
-    if (input.topic == null) {
-      input.topic =
-          String.format(
-              "revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
-    }
-
-    return Response.ok(revertSubmission(changeDatas, input));
   }
 
   private RevertSubmissionInfo revertSubmission(
       List<ChangeData> changeData, RevertInput revertInput)
       throws RestApiException, IOException, UpdateException, PermissionBackendException,
           NoSuchProjectException, ConfigInvalidException, StorageException {
+
     Multimap<BranchNameKey, ChangeData> changesPerProjectAndBranch = ArrayListMultimap.create();
     changeData.stream().forEach(c -> changesPerProjectAndBranch.put(c.change().getDest(), c));
+    cherryPickInput = createCherryPickInput(revertInput);
 
+    for (BranchNameKey projectAndBranch : changesPerProjectAndBranch.keySet()) {
+      cherryPickInput.base = null;
+      Project.NameKey project = projectAndBranch.project();
+      cherryPickInput.destination = projectAndBranch.branch();
+      Collection<ChangeData> changesInProjectAndBranch =
+          changesPerProjectAndBranch.get(projectAndBranch);
+
+      // Sort the changes topologically.
+      Iterator<PatchSetData> sortedChangesInProjectAndBranch =
+          sorter.sort(changesInProjectAndBranch).iterator();
+
+      Set<ObjectId> commitIdsInProjectAndBranch =
+          changesInProjectAndBranch.stream()
+              .map(c -> c.currentPatchSet().commitId())
+              .collect(Collectors.toSet());
+
+      revertAllChangesInProjectAndBranch(
+          revertInput, project, sortedChangesInProjectAndBranch, commitIdsInProjectAndBranch);
+    }
+    results.sort(Comparator.comparing(c -> c.revertOf));
+    RevertSubmissionInfo revertSubmissionInfo = new RevertSubmissionInfo();
+    revertSubmissionInfo.revertChanges = results;
+    return revertSubmissionInfo;
+  }
+
+  private void revertAllChangesInProjectAndBranch(
+      RevertInput revertInput,
+      NameKey project,
+      Iterator<PatchSetData> sortedChangesInProjectAndBranch,
+      Set<ObjectId> commitIdsInProjectAndBranch)
+      throws IOException, RestApiException, UpdateException, PermissionBackendException,
+          NoSuchProjectException, ConfigInvalidException {
+
+    String groupName = null;
+    while (sortedChangesInProjectAndBranch.hasNext()) {
+      ChangeNotes changeNotes = sortedChangesInProjectAndBranch.next().data().notes();
+      if (cherryPickInput.base == null) {
+        cherryPickInput.base = getBase(changeNotes, commitIdsInProjectAndBranch).name();
+      }
+
+      revertInput.message = getMessage(revertInput, 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.
+        craeteNormalRevert(revertInput, changeNotes);
+        groupName = cherryPickInput.base;
+      } else {
+        // This is the code in case this is the second revert (or more) of this project + branch.
+        if (groupName == null) {
+          groupName = cherryPickInput.base;
+        }
+        createCherryPickedRevert(revertInput, project, groupName, changeNotes);
+      }
+    }
+  }
+
+  private void createCherryPickedRevert(
+      RevertInput revertInput, NameKey project, String groupName, ChangeNotes changeNotes)
+      throws IOException, ConfigInvalidException, UpdateException, RestApiException {
+    ObjectId revCommitId =
+        commitUtil.createRevertCommit(revertInput.message, changeNotes, user.get());
+    // 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 = revertInput.message;
+    ObjectId generatedChangeId = Change.generateChangeId();
+    Change.Id cherryPickRevertChangeId = Change.id(seq.nextChangeId());
+    // TODO (paiking): In the the future, the timestamp should be the same for all the revert
+    // changes.
+    try (BatchUpdate bu = updateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+      bu.setNotify(
+          notifyResolver.resolve(
+              firstNonNull(cherryPickInput.notify, NotifyHandling.ALL),
+              cherryPickInput.notifyDetails));
+      bu.addOp(
+          changeNotes.getChange().getId(),
+          new CreateCherryPickOp(
+              revCommitId,
+              revertInput.topic,
+              generatedChangeId,
+              cherryPickRevertChangeId,
+              groupName));
+      bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
+      bu.addOp(
+          cherryPickRevertChangeId,
+          new NotifyOp(changeNotes.getChange(), cherryPickRevertChangeId));
+
+      bu.execute();
+    }
+  }
+
+  private void craeteNormalRevert(RevertInput revertInput, ChangeNotes changeNotes)
+      throws IOException, RestApiException, UpdateException, PermissionBackendException,
+          NoSuchProjectException, ConfigInvalidException {
+    ChangeInfo revertChangeInfo =
+        revert.apply(changeResourceFactory.create(changeNotes, user.get()), revertInput).value();
+    results.add(revertChangeInfo);
+    cherryPickInput.base =
+        changeNotesFactory
+            .createChecked(Change.id(revertChangeInfo._number))
+            .getCurrentPatchSet()
+            .commitId()
+            .getName();
+  }
+
+  private CherryPickInput createCherryPickInput(RevertInput revertInput) {
     cherryPickInput = new CherryPickInput();
     // To create a revert change, we create a revert commit that is then cherry-picked. The revert
     // change is created for the cherry-picked commit. Notifications are sent only for this change,
@@ -228,87 +345,7 @@
     cherryPickInput.notifyDetails = revertInput.notifyDetails;
     cherryPickInput.parent = 1;
     cherryPickInput.keepReviewers = true;
-
-    for (BranchNameKey projectAndBranch : changesPerProjectAndBranch.keySet()) {
-      String groupName = null;
-      Project.NameKey project = projectAndBranch.project();
-      cherryPickInput.destination = projectAndBranch.branch();
-      Collection<ChangeData> changesInProjectAndBranch =
-          changesPerProjectAndBranch.get(projectAndBranch);
-
-      // Sort the changes topologically.
-      Iterator<PatchSetData> sortedChangesInProject =
-          sorter.sort(changesInProjectAndBranch).iterator();
-
-      Set<ObjectId> commitIdsInProjectAndBranch =
-          changesInProjectAndBranch.stream()
-              .map(c -> c.currentPatchSet().commitId())
-              .collect(Collectors.toSet());
-
-      while (sortedChangesInProject.hasNext()) {
-        ChangeNotes changeNotes = sortedChangesInProject.next().data().notes();
-        if (cherryPickInput.base == null) {
-          cherryPickInput.base = getBase(changeNotes, commitIdsInProjectAndBranch).name();
-        }
-
-        revertInput.message = getMessage(revertInput, changeNotes);
-        // 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.
-        if (cherryPickInput.base.equals(changeNotes.getCurrentPatchSet().commitId().getName())) {
-          ChangeInfo revertChangeInfo =
-              revert
-                  .apply(changeResourceFactory.create(changeNotes, user.get()), revertInput)
-                  .value();
-          results.add(revertChangeInfo);
-          cherryPickInput.base =
-              changeNotesFactory
-                  .createChecked(Change.id(revertChangeInfo._number))
-                  .getCurrentPatchSet()
-                  .commitId()
-                  .getName();
-          groupName = cherryPickInput.base;
-        } else {
-          // This is the code in case this is the second revert (or more) of this project + branch.
-          ObjectId revCommitId =
-              commitUtil.createRevertCommit(revertInput.message, changeNotes, user.get());
-          // 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 = revertInput.message;
-          ObjectId generatedChangeId = Change.generateChangeId();
-          Change.Id cherryPickRevertChangeId = Change.id(seq.nextChangeId());
-          if (groupName == null) {
-            groupName = cherryPickInput.base;
-          }
-          // TODO (paiking): In the the future, the timestamp should be the same for all the revert
-          // changes.
-          try (BatchUpdate bu = updateFactory.create(project, user.get(), TimeUtil.nowTs())) {
-            bu.setNotify(
-                notifyResolver.resolve(
-                    firstNonNull(cherryPickInput.notify, NotifyHandling.ALL),
-                    cherryPickInput.notifyDetails));
-            bu.addOp(
-                changeNotes.getChange().getId(),
-                new CreateCherryPickOp(
-                    revCommitId,
-                    revertInput.topic,
-                    generatedChangeId,
-                    cherryPickRevertChangeId,
-                    groupName));
-            bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
-            bu.addOp(
-                cherryPickRevertChangeId,
-                new NotifyOp(changeNotes.getChange(), cherryPickRevertChangeId));
-
-            bu.execute();
-          }
-        }
-      }
-      cherryPickInput.base = null;
-    }
-    results.sort(Comparator.comparing(c -> c.revertOf));
-    RevertSubmissionInfo revertSubmissionInfo = new RevertSubmissionInfo();
-    revertSubmissionInfo.revertChanges = results;
-    return revertSubmissionInfo;
+    return cherryPickInput;
   }
 
   private String getMessage(RevertInput revertInput, ChangeNotes changeNotes) {