Merge "Specify exact dependencies for rollup-bin"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6f9367d..3fc4ea8d 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6177,6 +6177,12 @@
 `contains_git_conflicts` field in the link:#change-info[ChangeInfo]. If
 there are conflicts the cherry-pick change is marked as
 work-in-progress.
+|`topic`            |optional|
+The topic of the created cherry-picked change. If not set, the default depends
+on the source. If the source is a change with a topic, the resulting topic
+of the cherry-picked change will be {source_change_topic}-{destination_branch}.
+Otherwise, if the source change has no topic, or the source is a commit,
+the created change will have no topic.
 |===========================
 
 [[comment-info]]
diff --git a/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java b/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
index 5ac67e7..4ec6f01 100644
--- a/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
@@ -29,4 +29,5 @@
 
   public boolean keepReviewers;
   public boolean allowConflicts;
+  public String topic;
 }
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 82b138d..5960117 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -175,7 +175,6 @@
         null,
         null,
         null,
-        null,
         null);
   }
 
@@ -217,7 +216,6 @@
         null,
         null,
         null,
-        null,
         null);
   }
 
@@ -235,7 +233,6 @@
    * @param ignoreIdenticalTree When false, we throw an error when trying to cherry-pick creates an
    *     empty commit. When true, we allow creation of an empty commit.
    * @param timestamp the current timestamp.
-   * @param topic Topic name for the change created.
    * @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.
@@ -263,7 +260,6 @@
       BranchNameKey dest,
       boolean ignoreIdenticalTree,
       Timestamp timestamp,
-      @Nullable String topic,
       @Nullable Change.Id revertedChange,
       @Nullable ObjectId changeIdForNewChange,
       @Nullable Change.Id idForNewChange,
@@ -381,8 +377,11 @@
           } else {
             // Change key not found on destination branch. We can create a new
             // change.
-            String newTopic = topic;
-            if (topic == null
+            String newTopic = null;
+            if (input.topic != null) {
+              newTopic = Strings.emptyToNull(input.topic.trim());
+            }
+            if (newTopic == null
                 && sourceChange != null
                 && !Strings.isNullOrEmpty(sourceChange.getTopic())) {
               newTopic = sourceChange.getTopic() + "-" + newDest.shortName();
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 52cc46f..e7da89a 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -20,6 +20,7 @@
 import static com.google.gerrit.server.project.ProjectCache.illegalState;
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.flogger.FluentLogger;
@@ -195,6 +196,9 @@
   }
 
   private String createTopic(String topic, String submissionId) {
+    if (topic != null) {
+      topic = Strings.emptyToNull(topic.trim());
+    }
     if (topic == null) {
       return String.format(
           "revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
@@ -321,12 +325,7 @@
       bu.addOp(
           changeNotes.getChange().getId(),
           new CreateCherryPickOp(
-              revCommitId,
-              revertInput.topic,
-              generatedChangeId,
-              cherryPickRevertChangeId,
-              groupName,
-              timestamp));
+              revCommitId, generatedChangeId, cherryPickRevertChangeId, groupName, timestamp));
       bu.addOp(changeNotes.getChange().getId(), new PostRevertedMessageOp(generatedChangeId));
       bu.addOp(
           cherryPickRevertChangeId,
@@ -356,6 +355,7 @@
     cherryPickInput.notifyDetails = revertInput.notifyDetails;
     cherryPickInput.parent = 1;
     cherryPickInput.keepReviewers = true;
+    cherryPickInput.topic = revertInput.topic;
     return cherryPickInput;
   }
 
@@ -570,7 +570,6 @@
 
   private class CreateCherryPickOp implements BatchUpdateOp {
     private final ObjectId revCommitId;
-    private final String topic;
     private final ObjectId computedChangeId;
     private final Change.Id cherryPickRevertChangeId;
     private final String groupName;
@@ -578,13 +577,11 @@
 
     CreateCherryPickOp(
         ObjectId revCommitId,
-        String topic,
         ObjectId computedChangeId,
         Change.Id cherryPickRevertChangeId,
         String groupName,
         Timestamp timestamp) {
       this.revCommitId = revCommitId;
-      this.topic = topic;
       this.computedChangeId = computedChangeId;
       this.cherryPickRevertChangeId = cherryPickRevertChangeId;
       this.groupName = groupName;
@@ -604,7 +601,6 @@
                   change.getProject(), RefNames.fullName(cherryPickInput.destination)),
               true,
               timestamp,
-              topic,
               change.getId(),
               computedChangeId,
               cherryPickRevertChangeId,
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 50aaa27..fd681d8 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -3238,13 +3238,33 @@
 
     assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
         .contains(subject);
+  }
 
-    // No subject: reuse message from previous patchset.
+  @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 = gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
-    assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.message)
-        .contains(subject);
+    ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+    assertThat(changeInfo.subject).isEqualTo(subject);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
index 04625c5..e67770c 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
@@ -169,6 +169,21 @@
     assertThat(revInfo.commit.message).isEqualTo(input.message + "\n");
   }
 
+  @Test
+  public void cherryPickCommitWithSetTopic() throws Exception {
+    String branch = "foo";
+    RevCommit revCommit = createChange().getCommit();
+    gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
+    CherryPickInput input = new CherryPickInput();
+    input.destination = branch;
+    input.topic = "topic";
+    String changeId =
+        gApi.projects().name(project.get()).commit(revCommit.name()).cherryPick(input).get().id;
+
+    ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+    assertThat(changeInfo.topic).isEqualTo(input.topic);
+  }
+
   private IncludedInInfo getIncludedIn(ObjectId id) throws Exception {
     return gApi.projects().name(project.get()).commit(id.name()).includedIn();
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 3ec44e8..2c6d539 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -403,6 +403,19 @@
   }
 
   @Test
+  public void cherryPickWithSetTopic() throws Exception {
+    PushOneCommit.Result r = pushTo("refs/for/master");
+    CherryPickInput in = new CherryPickInput();
+    in.destination = "foo";
+    in.topic = "topic";
+    gApi.projects().name(project.get()).branch(in.destination).create(new BranchInput());
+    ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
+
+    ChangeApi cherry = orig.revision(r.getCommit().name()).cherryPick(in);
+    assertThat(cherry.get().topic).isEqualTo("topic");
+  }
+
+  @Test
   public void cherryPickWorkInProgressChange() throws Exception {
     PushOneCommit.Result r = pushTo("refs/for/master%wip");
     CherryPickInput in = new CherryPickInput();