Stacked changes continue being stacked downstream.

Previously, if change A and B were uplaoded, where Bs first parent was
A, then the first parents of both A and B were HEAD of the downstream
branch.
Now, if changes A and B are uploaded, where Bs first parent is A, then for
downstream A' and B', the first parent of B' should be A'.

Change-Id: I09a9abc6320129f64958859efc2e379214903e54
diff --git a/src/main/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreator.java b/src/main/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreator.java
index b12a0f3..2045762 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreator.java
@@ -365,7 +365,7 @@
       String upstreamRevision, String topic, String downstreamBranch)
       throws RestApiException, InvalidQueryParameterException {
     List<Integer> downstreamChangeNumbers = new ArrayList<Integer>();
-    List<ChangeInfo> changes = getChangesInTopic(topic, downstreamBranch);
+    List<ChangeInfo> changes = getChangesInTopicAndBranch(topic, downstreamBranch);
 
     for (ChangeInfo change : changes) {
       String changeRevision = change.currentRevision;
@@ -415,6 +415,11 @@
     downstreamChangeInput.merge = mergeInput;
     downstreamChangeInput.notify = NotifyHandling.NONE;
 
+    downstreamChangeInput.baseChange =
+        getBaseChangeId(
+            getChangeParents(sdsMergeInput.changeNumber, sdsMergeInput.currentRevision),
+            sdsMergeInput.downstreamBranch);
+
     if (!sdsMergeInput.doMerge) {
       mergeInput.strategy = "ours";
       downstreamChangeInput.subject =
@@ -442,6 +447,42 @@
     return topic;
   }
 
+  /**
+   * Get the base change ID that the downstream change should be based off of, given the parents.
+   *
+   * <p>Given changes A and B where A is the first parent of B, and where A' is the change whose
+   * second parent is A, and B' is the change whose second parent is B, the first parent of B'
+   * should be A'.
+   *
+   * @param parents Parent commit SHAs of the change
+   * @return The base change ID that the change should be based off of, null if there is none.
+   * @throws InvalidQueryParameterException
+   * @throws RestApiException
+   */
+  private String getBaseChangeId(List<String> parents, String branch)
+      throws InvalidQueryParameterException, RestApiException {
+    if (parents.size() == 0) {
+      log.info("No base change id for change with no parents.");
+      return null;
+    }
+    // 1) Get topic of first parent
+    String firstParentTopic = getTopic(parents.get(0));
+    if (firstParentTopic == null) {
+      return null;
+    }
+    // 2) query that topic and use that to find A'
+    List<ChangeInfo> changesInTopic = getChangesInTopicAndBranch(firstParentTopic, branch);
+    String firstParent = parents.get(0);
+    for (ChangeInfo change : changesInTopic) {
+      List<CommitInfo> topicChangeParents =
+          change.revisions.get(change.currentRevision).commit.parents;
+      if (topicChangeParents.size() > 1 && topicChangeParents.get(1).commit.equals(firstParent)) {
+        return String.valueOf(change._number);
+      }
+    }
+    return null;
+  }
+
   private void automergeChanges(ChangeInfo change, RevisionInfo revisionInfo)
       throws RestApiException, IOException, ConfigInvalidException, InvalidQueryParameterException {
     if (revisionInfo.draft != null && revisionInfo.draft) {
@@ -476,7 +517,7 @@
     mdsMergeInput.changeNumber = change._number;
     mdsMergeInput.patchsetNumber = revisionInfo._number;
     mdsMergeInput.project = change.project;
-    mdsMergeInput.topic = change.topic;
+    mdsMergeInput.topic = getOrSetTopic(change._number, change.topic);
     mdsMergeInput.subject = change.subject;
     mdsMergeInput.obsoleteRevision = previousRevision;
     mdsMergeInput.currentRevision = currentRevision;
@@ -566,6 +607,20 @@
     return previousRevision;
   }
 
+  private List<String> getChangeParents(int changeNumber, String currentRevision)
+      throws RestApiException {
+    ChangeApi change = gApi.changes().id(changeNumber);
+    List<String> parents = new ArrayList<>();
+    Map<String, RevisionInfo> revisionMap =
+        change.get(EnumSet.of(ListChangesOption.ALL_REVISIONS, ListChangesOption.CURRENT_COMMIT))
+            .revisions;
+    List<CommitInfo> changeParents = revisionMap.get(currentRevision).commit.parents;
+    for (CommitInfo commit : changeParents) {
+      parents.add(commit.commit);
+    }
+    return parents;
+  }
+
   private void abandonChange(Integer changeNumber) throws RestApiException {
     log.debug("Abandoning change: {}", changeNumber);
     AbandonInput abandonInput = new AbandonInput();
@@ -574,7 +629,25 @@
     gApi.changes().id(changeNumber).abandon(abandonInput);
   }
 
-  private List<ChangeInfo> getChangesInTopic(String topic, String downstreamBranch)
+  private String getTopic(String revision) throws InvalidQueryParameterException, RestApiException {
+    QueryBuilder queryBuilder = new QueryBuilder();
+    queryBuilder.addParameter("commit", revision);
+    List<ChangeInfo> changes =
+        gApi.changes()
+            .query(queryBuilder.get())
+            .withOption(ListChangesOption.CURRENT_REVISION)
+            .get();
+    if (changes.size() > 0) {
+      for (ChangeInfo change : changes) {
+        if (change.currentRevision.equals(revision) && change.topic != "") {
+          return change.topic;
+        }
+      }
+    }
+    return null;
+  }
+
+  private List<ChangeInfo> getChangesInTopicAndBranch(String topic, String downstreamBranch)
       throws InvalidQueryParameterException, RestApiException {
     QueryBuilder queryBuilder = new QueryBuilder();
     queryBuilder.addParameter("topic", topic);
@@ -589,7 +662,8 @@
   private boolean isAlreadyMerged(SingleDownstreamMergeInput sdsMergeInput, String currentTopic)
       throws InvalidQueryParameterException, RestApiException {
     // If we've already merged this commit to this branch, don't do it again.
-    List<ChangeInfo> changes = getChangesInTopic(currentTopic, sdsMergeInput.downstreamBranch);
+    List<ChangeInfo> changes =
+        getChangesInTopicAndBranch(currentTopic, sdsMergeInput.downstreamBranch);
     for (ChangeInfo change : changes) {
       if (change.branch.equals(sdsMergeInput.downstreamBranch)) {
         List<CommitInfo> parents = change.revisions.get(change.currentRevision).commit.parents;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java b/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java
index 1368a3f..38f8d08 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java
@@ -224,6 +224,81 @@
   }
 
   @Test
+  public void testChangeStack() throws Exception {
+    Project.NameKey manifestNameKey = defaultSetup();
+    // Create initial change
+    PushOneCommit.Result result = createChange("subject", "filename", "content", "testtopic");
+    // Project name is scoped by test, so we need to get it from our initial change
+    String projectName = result.getChange().project().get();
+    createBranch(new Branch.NameKey(projectName, "ds_one"));
+    createBranch(new Branch.NameKey(projectName, "ds_two"));
+    pushConfig("automerger.config", manifestNameKey.get(), projectName, "ds_one", "ds_two");
+    // After we upload our config, we upload a new patchset to create the downstreams
+    amendChange(result.getChangeId());
+    result.assertOkStatus();
+    PushOneCommit.Result result2 = createChange("subject2", "filename2", "content2", "testtopic");
+    result2.assertOkStatus();
+    // Check that there are the correct number of changes in the topic
+    List<ChangeInfo> changesInTopic =
+        gApi.changes()
+            .query("topic: " + gApi.changes().id(result.getChangeId()).topic())
+            .withOptions(ListChangesOption.ALL_REVISIONS, ListChangesOption.CURRENT_COMMIT)
+            .get();
+    assertThat(changesInTopic).hasSize(6);
+    List<ChangeInfo> sortedChanges = sortedChanges(changesInTopic);
+
+    // Change A
+    ChangeInfo dsOneChangeInfo = sortedChanges.get(0);
+    assertThat(dsOneChangeInfo.branch).isEqualTo("ds_one");
+    // Change B
+    ChangeInfo dsOneChangeInfo2 = sortedChanges.get(1);
+    assertThat(dsOneChangeInfo2.branch).isEqualTo("ds_one");
+    String dsOneChangeInfo2FirstParentSha =
+        dsOneChangeInfo2
+            .revisions
+            .get(dsOneChangeInfo2.currentRevision)
+            .commit
+            .parents
+            .get(0)
+            .commit;
+    assertThat(dsOneChangeInfo.currentRevision).isEqualTo(dsOneChangeInfo2FirstParentSha);
+
+    // Change A'
+    ChangeInfo dsTwoChangeInfo = sortedChanges.get(2);
+    assertThat(dsTwoChangeInfo.branch).isEqualTo("ds_two");
+    // Change B'
+    ChangeInfo dsTwoChangeInfo2 = sortedChanges.get(3);
+    assertThat(dsTwoChangeInfo2.branch).isEqualTo("ds_two");
+    String dsTwoChangeInfo2FirstParentSha =
+        dsTwoChangeInfo2
+            .revisions
+            .get(dsTwoChangeInfo2.currentRevision)
+            .commit
+            .parents
+            .get(0)
+            .commit;
+    // Check that first parent of B' is A'
+    assertThat(dsTwoChangeInfo.currentRevision).isEqualTo(dsTwoChangeInfo2FirstParentSha);
+
+    // Change A''
+    ChangeInfo masterChangeInfo = sortedChanges.get(4);
+    assertThat(masterChangeInfo.branch).isEqualTo("master");
+    // Change B''
+    ChangeInfo masterChangeInfo2 = sortedChanges.get(5);
+    assertThat(masterChangeInfo2.branch).isEqualTo("master");
+    String masterChangeInfo2FirstParentSha =
+        masterChangeInfo2
+            .revisions
+            .get(masterChangeInfo2.currentRevision)
+            .commit
+            .parents
+            .get(0)
+            .commit;
+    // Check that first parent of B'' is A''
+    assertThat(masterChangeInfo.currentRevision).isEqualTo(masterChangeInfo2FirstParentSha);
+  }
+
+  @Test
   public void testBlankMerge() throws Exception {
     Project.NameKey manifestNameKey = defaultSetup();
     // Create initial change
@@ -605,7 +680,11 @@
         new Comparator<ChangeInfo>() {
           @Override
           public int compare(ChangeInfo c1, ChangeInfo c2) {
-            return c1.branch.compareTo(c2.branch);
+            int compareResult = c1.branch.compareTo(c2.branch);
+            if (compareResult == 0) {
+              return Integer.compare(c1._number, c2._number);
+            }
+            return compareResult;
           }
         });
     return listCopy;