Add REST endpoint for commit included in

+ API and acceptance tests for {Change,Commit}IncludedIn

Change-Id: I5000cc90b8f0457e2b7594e86e9165f1a6cb8aa5
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index d6338f7..f69b955 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1989,6 +1989,35 @@
   }
 ----
 
+[[get-included-in]]
+=== Get Included In
+--
+'GET /projects/link:#project-name[\{project-name\}]/commits/link:#commit-id[\{commit-id\}]/in'
+--
+
+Retrieves the branches and tags in which a change is included. As result
+an link:rest-api-changes.html#included-in-info[IncludedInInfo] entity is returned.
+
+.Request
+----
+  GET /projects/work%2Fmy-project/commits/a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96/in HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "branches": [
+      "master"
+    ],
+    "tags": []
+  }
+----
+
 [[get-content-from-commit]]
 === Get Content
 --
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 2974c96..095aaf3 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -23,6 +23,7 @@
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static java.util.stream.Collectors.toList;
 import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
@@ -1046,6 +1047,12 @@
     return getRemoteHead(project, "master");
   }
 
+  protected void grantTagPermissions() throws Exception {
+    grant(Permission.CREATE, project, R_TAGS + "*");
+    grant(Permission.CREATE_TAG, project, R_TAGS + "*");
+    grant(Permission.CREATE_SIGNED_TAG, project, R_TAGS + "*");
+  }
+
   protected void assertMailFrom(Message message, String email)
       throws Exception {
     assertThat(message.headers()).containsKey("Reply-To");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java
new file mode 100644
index 0000000..c37501d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java
@@ -0,0 +1,64 @@
+// 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.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.TagInput;
+import com.google.gerrit.reviewdb.client.Branch;
+
+import org.junit.Test;
+
+@NoHttpd
+public class ChangeIncludedInIT extends AbstractDaemonTest {
+
+  @Test
+  public void includedInOpenChange() throws Exception {
+    Result result = createChange();
+    assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
+        .isEmpty();
+    assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
+        .isEmpty();
+  }
+
+  @Test
+  public void includedInMergedChange() throws Exception {
+    Result result = createChange();
+    gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
+        .review(ReviewInput.approve());
+    gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
+        .submit();
+
+    assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
+        .containsExactly("master");
+    assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
+        .isEmpty();
+
+    grantTagPermissions();
+    gApi.projects().name(project.get()).tag("test-tag").create(new TagInput());
+
+    assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
+        .containsExactly("test-tag");
+
+    createBranch(new Branch.NameKey(project.get(), "test-branch"));
+
+    assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
+        .containsExactly("master", "test-branch");
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CommitIncludedInIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CommitIncludedInIT.java
new file mode 100644
index 0000000..dac922e
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CommitIncludedInIT.java
@@ -0,0 +1,72 @@
+// 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.acceptance.rest.project;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.TagInput;
+import com.google.gerrit.reviewdb.client.Branch;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class CommitIncludedInIT extends AbstractDaemonTest {
+  @Test
+  public void includedInOpenChange() throws Exception {
+    Result result = createChange();
+    assertThat(getIncludedIn(result.getCommit().getId()).branches).isEmpty();
+    assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
+  }
+
+  @Test
+  public void includedInMergedChange() throws Exception {
+    Result result = createChange();
+    gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
+        .review(ReviewInput.approve());
+    gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
+        .submit();
+
+    assertThat(getIncludedIn(result.getCommit().getId()).branches)
+        .containsExactly("master");
+    assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
+
+    grantTagPermissions();
+    gApi.projects().name(result.getChange().project().get()).tag("test-tag")
+        .create(new TagInput());
+
+    assertThat(getIncludedIn(result.getCommit().getId()).tags)
+        .containsExactly("test-tag");
+
+    createBranch(new Branch.NameKey(project.get(), "test-branch"));
+
+    assertThat(getIncludedIn(result.getCommit().getId()).branches)
+        .containsExactly("master", "test-branch");
+  }
+
+  private IncludedInInfo getIncludedIn(ObjectId id) throws Exception {
+    RestResponse r = userRestSession
+        .get("/projects/" + project.get() + "/commits/" + id.name() + "/in");
+    IncludedInInfo result =
+        newGson().fromJson(r.getReader(), IncludedInInfo.class);
+    r.consume();
+    return result;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
index c4aee29..fcaf7ce 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -337,12 +337,6 @@
     }
   }
 
-  private void grantTagPermissions() throws Exception {
-    grant(Permission.CREATE, project, R_TAGS + "*");
-    grant(Permission.CREATE_TAG, project, R_TAGS + "*");
-    grant(Permission.CREATE_SIGNED_TAG, project, R_TAGS + "*");
-  }
-
   private ListRefsRequest<TagInfo> getTags() throws Exception {
     return gApi.projects().name(project.get()).tags();
   }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index e9111cb..efdf764 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -120,6 +120,8 @@
   String topic() throws RestApiException;
   void topic(String topic) throws RestApiException;
 
+  IncludedInInfo includedIn() throws RestApiException;
+
   void addReviewer(AddReviewerInput in) throws RestApiException;
   void addReviewer(String in) throws RestApiException;
 
@@ -332,6 +334,11 @@
     }
 
     @Override
+    public IncludedInInfo includedIn() {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public void addReviewer(AddReviewerInput in) {
       throw new NotImplementedException();
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java
new file mode 100644
index 0000000..f1fc3ac
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/IncludedInInfo.java
@@ -0,0 +1,32 @@
+// 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.api.changes;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class IncludedInInfo {
+  public List<String> branches;
+  public List<String> tags;
+  public Map<String, Collection<String>> external;
+
+  public IncludedInInfo(List<String> branches,
+      List<String> tags,
+      Map<String, Collection<String>> external) {
+    this.branches = branches;
+    this.tags = tags;
+    this.external = external;
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index cf0a445..8275e8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.api.changes.Changes;
 import com.google.gerrit.extensions.api.changes.FixInput;
 import com.google.gerrit.extensions.api.changes.HashtagsInput;
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.api.changes.MoveInput;
 import com.google.gerrit.extensions.api.changes.RestoreInput;
 import com.google.gerrit.extensions.api.changes.RevertInput;
@@ -41,6 +42,7 @@
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.server.change.Abandon;
+import com.google.gerrit.server.change.ChangeIncludedIn;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.Check;
@@ -102,6 +104,7 @@
   private final DeleteChange deleteChange;
   private final GetTopic getTopic;
   private final PutTopic putTopic;
+  private final ChangeIncludedIn includedIn;
   private final PostReviewers postReviewers;
   private final ChangeJson.Factory changeJson;
   private final PostHashtags postHashtags;
@@ -134,6 +137,7 @@
       DeleteChange deleteChange,
       GetTopic getTopic,
       PutTopic putTopic,
+      ChangeIncludedIn includedIn,
       PostReviewers postReviewers,
       ChangeJson.Factory changeJson,
       PostHashtags postHashtags,
@@ -165,6 +169,7 @@
     this.deleteChange = deleteChange;
     this.getTopic = getTopic;
     this.putTopic = putTopic;
+    this.includedIn = includedIn;
     this.postReviewers = postReviewers;
     this.changeJson = changeJson;
     this.postHashtags = postHashtags;
@@ -350,6 +355,15 @@
   }
 
   @Override
+  public IncludedInInfo includedIn() throws RestApiException {
+    try {
+      return includedIn.apply(change);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Could not extract IncludedIn data", e);
+    }
+  }
+
+  @Override
   public void addReviewer(String reviewer) throws RestApiException {
     AddReviewerInput in = new AddReviewerInput();
     in.reviewer = reviewer;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeIncludedIn.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeIncludedIn.java
new file mode 100644
index 0000000..77e6942
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeIncludedIn.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2016 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.change;
+
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class ChangeIncludedIn implements RestReadView<ChangeResource> {
+  private Provider<ReviewDb> db;
+  private PatchSetUtil psUtil;
+  private IncludedIn includedIn;
+
+  @Inject
+  ChangeIncludedIn(Provider<ReviewDb> db,
+      PatchSetUtil psUtil,
+      IncludedIn includedIn) {
+    this.db = db;
+    this.psUtil = psUtil;
+    this.includedIn = includedIn;
+  }
+
+  @Override
+  public IncludedInInfo apply(ChangeResource rsrc)
+      throws RestApiException, OrmException, IOException {
+    ChangeControl ctl = rsrc.getControl();
+    PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
+    Project.NameKey project = ctl.getProject().getNameKey();
+    return includedIn.apply(project, ps.getRevision().get());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
index c7f1886..0c2d079 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
@@ -16,20 +16,15 @@
 
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.config.ExternalIncludedIn;
 import com.google.gerrit.extensions.registration.DynamicSet;
 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.PatchSet;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -40,40 +35,27 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
 
 @Singleton
-class IncludedIn implements RestReadView<ChangeResource> {
-
-  private final Provider<ReviewDb> db;
+public class IncludedIn {
   private final GitRepositoryManager repoManager;
-  private final PatchSetUtil psUtil;
-  private final DynamicSet<ExternalIncludedIn> includedIn;
+  private final DynamicSet<ExternalIncludedIn> externalIncludedIn;
 
   @Inject
-  IncludedIn(Provider<ReviewDb> db,
-      GitRepositoryManager repoManager,
-      PatchSetUtil psUtil,
-      DynamicSet<ExternalIncludedIn> includedIn) {
-    this.db = db;
+  IncludedIn(GitRepositoryManager repoManager,
+      DynamicSet<ExternalIncludedIn> externalIncludedIn) {
     this.repoManager = repoManager;
-    this.psUtil = psUtil;
-    this.includedIn = includedIn;
+    this.externalIncludedIn = externalIncludedIn;
   }
 
-  @Override
-  public IncludedInInfo apply(ChangeResource rsrc) throws BadRequestException,
-      ResourceConflictException, OrmException, IOException {
-    ChangeControl ctl = rsrc.getControl();
-    PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
-    Project.NameKey project = ctl.getProject().getNameKey();
+  public IncludedInInfo apply(Project.NameKey project, String revisionId)
+      throws RestApiException, IOException {
     try (Repository r = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(r)) {
       rw.setRetainBody(false);
       RevCommit rev;
       try {
-        rev = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+        rev = rw.parseCommit(ObjectId.fromString(revisionId));
       } catch (IncorrectObjectTypeException err) {
         throw new BadRequestException(err.getMessage());
       } catch (MissingObjectException err) {
@@ -83,27 +65,15 @@
       IncludedInResolver.Result d = IncludedInResolver.resolve(r, rw, rev);
       ListMultimap<String, String> external =
           MultimapBuilder.hashKeys().arrayListValues().build();
-      for (ExternalIncludedIn ext : includedIn) {
+      for (ExternalIncludedIn ext : externalIncludedIn) {
         ListMultimap<String, String> extIncludedIns = ext.getIncludedIn(
             project.get(), rev.name(), d.getTags(), d.getBranches());
         if (extIncludedIns != null) {
           external.putAll(extIncludedIns);
         }
       }
-      return new IncludedInInfo(d,
+      return new IncludedInInfo(d.getBranches(), d.getTags(),
           (!external.isEmpty() ? external.asMap() : null));
     }
   }
-
-  static class IncludedInInfo {
-    Collection<String> branches;
-    Collection<String> tags;
-    Map<String, Collection<String>> external;
-
-    IncludedInInfo(IncludedInResolver.Result in, Map<String, Collection<String>> e) {
-      branches = in.getBranches();
-      tags = in.getTags();
-      external = e;
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 66c1060..aca6ef1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -57,7 +57,7 @@
     post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class);
     get(CHANGE_KIND, "detail").to(GetDetail.class);
     get(CHANGE_KIND, "topic").to(GetTopic.class);
-    get(CHANGE_KIND, "in").to(IncludedIn.class);
+    get(CHANGE_KIND, "in").to(ChangeIncludedIn.class);
     get(CHANGE_KIND, "assignee").to(GetAssignee.class);
     get(CHANGE_KIND, "past_assignees").to(GetPastAssignees.class);
     put(CHANGE_KIND, "assignee").to(PutAssignee.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitIncludedIn.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitIncludedIn.java
new file mode 100644
index 0000000..297f138
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitIncludedIn.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2016 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.project;
+
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.change.IncludedIn;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+
+@Singleton
+class CommitIncludedIn implements RestReadView<CommitResource> {
+  private IncludedIn includedIn;
+
+  @Inject
+  CommitIncludedIn(IncludedIn includedIn) {
+    this.includedIn = includedIn;
+  }
+
+  @Override
+  public IncludedInInfo apply(CommitResource rsrc)
+      throws RestApiException, OrmException, IOException {
+    RevCommit commit = rsrc.getCommit();
+    Project.NameKey project = rsrc.getProject().getProject().getNameKey();
+    return includedIn.apply(project, commit.getId().getName());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 7579398..c0da5d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -76,6 +76,7 @@
 
     child(PROJECT_KIND, "commits").to(CommitsCollection.class);
     get(COMMIT_KIND).to(GetCommit.class);
+    get(COMMIT_KIND, "in").to(CommitIncludedIn.class);
     child(COMMIT_KIND, "files").to(FilesInCommitCollection.class);
 
     child(PROJECT_KIND, "tags").to(TagsCollection.class);