SubmittedTogether: Also work for already submitted changes

We know which changes were submitted together by querying the database
for the SUBMISSION_ID field.

Change-Id: I4aa7ab9ccd9a51f1a87abea3d762d4b78ac40fbc
Signed-off-by: Stefan Beller <sbeller@google.com>
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 2af3c25..9aa4b34 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -49,8 +49,10 @@
     PushOneCommit.Result change = createChange();
     PushOneCommit.Result change2 = createChange();
 
-    approve(change.getChangeId());
-    submit(change2.getChangeId());
+    String id1 = change.getChangeId();
+    String id2 = change2.getChangeId();
+    approve(id1);
+    submit(id2);
 
     RevCommit head = getRemoteHead();
     assertThat(head.getId()).isEqualTo(change2.getCommitId());
@@ -59,6 +61,8 @@
     assertSubmitter(change2.getChangeId(), 1);
     assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
     assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
+    assertSubmittedTogether(id1, id1, id2);
+    assertSubmittedTogether(id2, id1, id2);
   }
 
   @Test
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index ae1a608..9612f71 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -228,11 +228,9 @@
           new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
     } else {
       // TODO(sbeller): show only on latest revision
-      if (info.status().isOpen()) {
-        ChangeApi.change(info.legacyId().get()).view("submitted_together")
-            .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
-                info.project(), revision));
-      }
+      ChangeApi.change(info.legacyId().get()).view("submitted_together")
+          .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
+              info.project(), revision));
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
index a4f9fef..dec56a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
@@ -14,15 +14,20 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.ChangeSet;
 import com.google.gerrit.server.git.MergeSuperSet;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -43,14 +48,17 @@
 
   private final ChangeJson.Factory json;
   private final Provider<ReviewDb> dbProvider;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final MergeSuperSet mergeSuperSet;
 
   @Inject
   SubmittedTogether(ChangeJson.Factory json,
       Provider<ReviewDb> dbProvider,
+      Provider<InternalChangeQuery> queryProvider,
       MergeSuperSet mergeSuperSet) {
     this.json = json;
     this.dbProvider = dbProvider;
+    this.queryProvider = queryProvider;
     this.mergeSuperSet = mergeSuperSet;
   }
 
@@ -59,16 +67,32 @@
       throws AuthException, BadRequestException,
       ResourceConflictException, Exception {
     try {
-      ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(),
-          resource.getChange());
-      if (cs.size() > 1) {
-        return json.create(EnumSet.of(
-            ListChangesOption.CURRENT_REVISION,
-            ListChangesOption.CURRENT_COMMIT))
-          .format(cs.ids());
+      Change c = resource.getChange();
+      List<Change.Id> ids;
+      if (c.getStatus().isOpen()) {
+        ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(), c);
+        ids = cs.ids().asList();
+      } else if (c.getStatus().asChangeStatus() == ChangeStatus.MERGED) {
+        ids = Lists.newArrayList();
+        String subId = c.getSubmissionId();
+        if (subId.isEmpty()) {
+          ids = Collections.emptyList();
+        } else {
+          for (ChangeData cd : queryProvider.get().bySubmissionId(subId)) {
+            ids.add(cd.getId());
+          }
+        }
       } else {
-        return Collections.emptyList();
+        // ABANDONED
+        ids = Collections.emptyList();
       }
+      if (ids.size() <= 1) {
+        ids = Collections.emptyList();
+      }
+      return json.create(EnumSet.of(
+          ListChangesOption.CURRENT_REVISION,
+          ListChangesOption.CURRENT_COMMIT))
+        .format(ids);
     } catch (OrmException | IOException e) {
       log.error("Error on getting a ChangeSet", e);
       throw e;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index e711306..4fa5cd3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -207,6 +207,21 @@
         }
       };
 
+  /** Submission id assigned by MergeOp. */
+  public static final FieldDef<ChangeData, String> SUBMISSIONID =
+      new FieldDef.Single<ChangeData, String>(
+          "submissionid", FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Change c = input.change();
+          if (c == null) {
+            return null;
+          }
+          return c.getSubmissionId();
+        }
+      };
+
   /** Last update time since January 1, 1970. */
   public static final FieldDef<ChangeData, Timestamp> UPDATED =
       new FieldDef.Single<ChangeData, Timestamp>(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index a8a97a8..4789a14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -377,6 +377,42 @@
       ChangeField.AUTHOR,
       ChangeField.COMMITTER);
 
+  static final Schema<ChangeData> V25 = schema(
+      ChangeField.LEGACY_ID2,
+      ChangeField.ID,
+      ChangeField.STATUS,
+      ChangeField.PROJECT,
+      ChangeField.PROJECTS,
+      ChangeField.REF,
+      ChangeField.EXACT_TOPIC,
+      ChangeField.FUZZY_TOPIC,
+      ChangeField.UPDATED,
+      ChangeField.FILE_PART,
+      ChangeField.PATH,
+      ChangeField.OWNER,
+      ChangeField.REVIEWER,
+      ChangeField.COMMIT,
+      ChangeField.TR,
+      ChangeField.LABEL,
+      ChangeField.COMMIT_MESSAGE,
+      ChangeField.COMMENT,
+      ChangeField.CHANGE,
+      ChangeField.APPROVAL,
+      ChangeField.MERGEABLE,
+      ChangeField.ADDED,
+      ChangeField.DELETED,
+      ChangeField.DELTA,
+      ChangeField.HASHTAG,
+      ChangeField.COMMENTBY,
+      ChangeField.PATCH_SET,
+      ChangeField.GROUP,
+      ChangeField.SUBMISSIONID,
+      ChangeField.EDITBY,
+      ChangeField.REVIEWEDBY,
+      ChangeField.EXACT_COMMIT,
+      ChangeField.AUTHOR,
+      ChangeField.COMMITTER);
+
   private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
     return new Schema<>(ImmutableList.copyOf(fields));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 31c35c2..bb11c05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -41,6 +41,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -194,6 +195,14 @@
     return query(commit(schema(indexes), id.name()));
   }
 
+  public List<ChangeData> bySubmissionId(String cs) throws OrmException {
+    if (cs.isEmpty()) {
+      return Collections.emptyList();
+    } else {
+      return query(new SubmissionIdPredicate(cs));
+    }
+  }
+
   public List<ChangeData> byProjectGroups(Project.NameKey project,
       Collection<String> groups) throws OrmException {
     List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
new file mode 100644
index 0000000..3b2dd94
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+class SubmissionIdPredicate extends IndexPredicate<ChangeData> {
+
+  SubmissionIdPredicate(String changeSet) {
+    super(ChangeField.SUBMISSIONID, changeSet);
+  }
+
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    Change change = object.change();
+    if (change == null) {
+      return false;
+    }
+    if (change.getSubmissionId() == null) {
+      return false;
+    }
+    return getValue().equals(change.getSubmissionId());
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}