Add idForNewChange parameter for cherry-picking a change

Add capability when performing the cherry-pick change method to
specify the numeric change-id of the new change that is to be created.
Cherry-pick is also possible to an existing change, and it will just
create a new patchset; in this case, we check to see that the inputs
given to the function agree with the Change-Id and the Id of that
change, otherwise we throw an error.

This change is useful when performing a batchUpdate. When performing a
batchUpdate it is necessary to know the numeric change id in advance, so
without passing the numeric change id as a parameter it will not be
possible to perform a batchUpdate with both the cherry-pick and any
other update that involves the newly created change (e.g sending
notifications).

Change-Id: I156d30ff719106041f499f0e73b456478f1e1e97
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 1973b00..35ede48 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -170,6 +170,7 @@
         dest,
         null,
         null,
+        null,
         null);
   }
 
@@ -204,7 +205,16 @@
       throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException,
           RestApiException, ConfigInvalidException, NoSuchProjectException {
     return cherryPick(
-        batchUpdateFactory, sourceChange, project, sourceCommit, input, dest, null, null, null);
+        batchUpdateFactory,
+        sourceChange,
+        project,
+        sourceCommit,
+        input,
+        dest,
+        null,
+        null,
+        null,
+        null);
   }
 
   /**
@@ -223,12 +233,15 @@
    * @param revertedChange The id of the change that is reverted. This is used for the "revertOf"
    *     field to mark the created cherry pick change as "revertOf" the original change that was
    *     reverted.
-   * @param changeIdForNewChange The Change-Id that the new change that of the cherry pick will
-   *     have.
+   * @param changeIdForNewChange The Change-Id that the new change of the cherry pick will have.
+   * @param idForNewChange The ID that the new change of the cherry pick will have. If provided and
+   *     the cherry-pick doesn't result in creating a new change, then
+   *     InvalidChangeOperationException is thrown.
    * @return Result object that describes the cherry pick.
    * @throws IOException Unable to open repository or read from the database.
    * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
-   *     key exist in the branch.
+   *     key exist in the branch. Also thrown when idForNewChange is not null but cherry-pick only
+   *     creates a new patchset rather than a new change.
    * @throws IntegrationException Merge conflict or trees are identical after cherry pick.
    * @throws UpdateException Problem updating the database using batchUpdateFactory.
    * @throws RestApiException Error such as invalid SHA1
@@ -244,7 +257,8 @@
       BranchNameKey dest,
       @Nullable String topic,
       @Nullable Change.Id revertedChange,
-      @Nullable ObjectId changeIdForNewChange)
+      @Nullable ObjectId changeIdForNewChange,
+      @Nullable Change.Id idForNewChange)
       throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException,
           RestApiException, ConfigInvalidException, NoSuchProjectException {
 
@@ -336,7 +350,19 @@
           Change.Id changeId;
           if (destChanges.size() == 1) {
             // The change key exists on the destination branch. The cherry pick
-            // will be added as a new patch set.
+            // will be added as a new patch set. If "idForNewChange" is not null we must fail,
+            // since we are not expecting an already existing change.
+            if (idForNewChange != null) {
+              throw new InvalidChangeOperationException(
+                  String.format(
+                      "Expected that cherry-pick of commit %s with Change-Id %s to branch %s"
+                          + "in project %s creates a new change, but found existing change %d",
+                      sourceCommit.getName(),
+                      changeKey,
+                      dest.branch(),
+                      dest.project(),
+                      destChanges.get(0).getId().get()));
+            }
             changeId = insertPatchSet(bu, git, destChanges.get(0).notes(), cherryPickCommit);
           } else {
             // Change key not found on destination branch. We can create a new
@@ -356,7 +382,8 @@
                     sourceChange,
                     sourceCommit,
                     input,
-                    revertedChange);
+                    revertedChange,
+                    idForNewChange);
           }
           bu.execute();
           return Result.create(changeId, cherryPickCommit.getFilesWithGitConflicts());
@@ -436,9 +463,10 @@
       @Nullable Change sourceChange,
       @Nullable ObjectId sourceCommit,
       CherryPickInput input,
-      @Nullable Change.Id revertOf)
+      @Nullable Change.Id revertOf,
+      @Nullable Change.Id idForNewChange)
       throws IOException {
-    Change.Id changeId = Change.id(seq.nextChangeId());
+    Change.Id changeId = idForNewChange != null ? idForNewChange : Change.id(seq.nextChangeId());
     ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
     ins.setRevertOf(revertOf);
     BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();