Add trackingid into ChangeInfo

Change-Id: Id2a88b2662d8a5d79727202c07db9f3b08955790
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 09fea83..fad4b9c 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -334,6 +334,12 @@
   server.
 --
 
+[[tracking-ids]]
+--
+* `TRACKING_IDS`: include references to external tracking systems
+  as link:#tracking-id-info[TrackingIdInfo].
+--
+
 .Request
 ----
   GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
@@ -5739,6 +5745,10 @@
 Only set if link:#current-revision[the current revision] is requested
 (in which case it will only contain a key for the current revision) or
 if link:#all-revisions[all revisions] are requested.
+|`tracking_ids`       |optional|
+A list of link:#tracking-id-info[TrackingIdInfo] entities describing
+references to external tracking systems. Only set if
+link:#tracking-ids[tracking ids] are requested.
 |`_more_changes`      |optional, not set if `false`|
 Whether the query would deliver more results if not limited. +
 Only set on the last change that is returned.
@@ -7063,6 +7073,17 @@
 The topic will be deleted if not set.
 |===========================
 
+[[tracking-id-info]]
+=== TrackingIdInfo
+The `TrackingIdInfo` entity describes a reference to an external tracking system.
+
+[options="header",cols="1,6"]
+|======================
+|Field Name|Description
+|`system`  |The name of the external tracking system.
+|`id`      |The tracking id.
+|======================
+
 [[voting-range-info]]
 === VotingRangeInfo
 The `VotingRangeInfo` entity describes the continuous voting range from min
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 69315a2..ed19575 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -87,6 +87,7 @@
 import com.google.gerrit.extensions.common.MergeInput;
 import com.google.gerrit.extensions.common.MergePatchSetInput;
 import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.common.TrackingIdInfo;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -3190,4 +3191,29 @@
       return true;
     }
   }
+
+  @Test
+  @GerritConfig(name = "trackingid.jira-bug.footer", value = "Bug:")
+  @GerritConfig(name = "trackingid.jira-bug.match", value = "JRA\\d{2,8}")
+  @GerritConfig(name = "trackingid.jira-bug.system", value = "JIRA")
+  public void trackingIds() throws Exception {
+    PushOneCommit push =
+        pushFactory.create(
+            db,
+            admin.getIdent(),
+            testRepo,
+            PushOneCommit.SUBJECT + "\n\n" + "Bug:JRA001",
+            PushOneCommit.FILE_NAME,
+            PushOneCommit.FILE_CONTENT);
+    PushOneCommit.Result result = push.to("refs/for/master");
+    result.assertOkStatus();
+
+    ChangeInfo change =
+        gApi.changes().id(result.getChangeId()).get(EnumSet.of(ListChangesOption.TRACKING_IDS));
+    Collection<TrackingIdInfo> trackingIds = change.trackingIds;
+    assertThat(trackingIds).isNotNull();
+    assertThat(trackingIds).hasSize(1);
+    assertThat(trackingIds.iterator().next().system).isEqualTo("JIRA");
+    assertThat(trackingIds.iterator().next().id).isEqualTo("JRA001");
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
index 787725c..ee7d039 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -72,7 +72,10 @@
   REVIEWER_UPDATES(19),
 
   /** Set the submittable boolean. */
-  SUBMITTABLE(20);
+  SUBMITTABLE(20),
+
+  /** If tracking Ids are included, include detailed tracking Ids info. */
+  TRACKING_IDS(21);
 
   private final int value;
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 0ae45dd5..706482f 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -70,4 +70,5 @@
 
   public List<ProblemInfo> problems;
   public List<PluginDefinedInfo> plugins;
+  public Collection<TrackingIdInfo> trackingIds;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TrackingIdInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TrackingIdInfo.java
new file mode 100644
index 0000000..0c5ed68
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TrackingIdInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2017 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.extensions.common;
+
+public class TrackingIdInfo {
+  public String system;
+  public String id;
+
+  public TrackingIdInfo(String system, String id) {
+    this.system = system;
+    this.id = id;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 3ceeb24..fecf551 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -34,6 +34,7 @@
 import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
 import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
 import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
+import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
 import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
 import static com.google.gerrit.server.CommonConverters.toGitPerson;
 import static java.util.stream.Collectors.toList;
@@ -48,6 +49,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.MultimapBuilder;
@@ -75,6 +77,7 @@
 import com.google.gerrit.extensions.common.PushCertificateInfo;
 import com.google.gerrit.extensions.common.ReviewerUpdateInfo;
 import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.common.TrackingIdInfo;
 import com.google.gerrit.extensions.common.VotingRangeInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.config.DownloadCommand;
@@ -104,6 +107,7 @@
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.api.accounts.AccountInfoComparator;
 import com.google.gerrit.server.api.accounts.GpgApiAdapter;
+import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.index.change.ChangeField;
@@ -215,7 +219,7 @@
   private final ChangeIndexCollection indexes;
   private final ApprovalsUtil approvalsUtil;
   private final RemoveReviewerControl removeReviewerControl;
-
+  private final TrackingFooters trackingFooters;
   private boolean lazyLoad = true;
   private AccountLoader accountLoader;
   private FixInput fix;
@@ -247,6 +251,7 @@
       ChangeIndexCollection indexes,
       ApprovalsUtil approvalsUtil,
       RemoveReviewerControl removeReviewerControl,
+      TrackingFooters trackingFooters,
       @Assisted Iterable<ListChangesOption> options) {
     this.db = db;
     this.userProvider = user;
@@ -273,6 +278,7 @@
     this.approvalsUtil = approvalsUtil;
     this.removeReviewerControl = removeReviewerControl;
     this.options = Sets.immutableEnumSet(options);
+    this.trackingFooters = trackingFooters;
   }
 
   public ChangeJson lazyLoad(boolean load) {
@@ -601,6 +607,15 @@
       actionJson.addChangeActions(out, ctl);
     }
 
+    if (has(TRACKING_IDS)) {
+      ListMultimap<String, String> set = trackingFooters.extract(cd.commitFooters());
+      out.trackingIds =
+          set.entries()
+              .stream()
+              .map(e -> new TrackingIdInfo(e.getKey(), e.getValue()))
+              .collect(toList());
+    }
+
     return out;
   }