Add REST APIs to list tags and get a specified tag of a project
GET on /projects/project-name/tags/ lists the tags of that project.
GET on /projects/project-name/tags/tag-name gets information about
that tag.
Change-Id: Icfff2647045b937b6ffc199fae1a3ee5565aa76a
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 20648dc..296184b 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1356,6 +1356,102 @@
}
----
+[[tag-endpoints]]
+== Tag Endpoints
+
+[[list-tags]]
+=== List Tags
+--
+'GET /projects/link:#project-name[\{project-name\}]/tags/'
+--
+
+List the tags of a project.
+
+As result a list of link:#tag-info[TagInfo] entries is returned.
+
+Only includes tags under the `refs/tags/` namespace.
+
+.Request
+----
+ GET /projects/work%2Fmy-project/tags/ HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "refs/tags/v1.0",
+ "revision": "49ce77fdcfd3398dc0dedbe016d1a425fd52d666",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Annotated tag",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 07:35:03.000000000",
+ "tz": 540
+ }
+ },
+ {
+ "ref": "refs/tags/v2.0",
+ "revision": "1624f5af8ae89148d1a3730df8c290413e3dcf30"
+ },
+ {
+ "ref": "refs/tags/v3.0",
+ "revision": "c628685b3c5a3614571ecb5c8fceb85db9112313",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Signed tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.11 (GNU/Linux)\n\niQEcBAABAgAGBQJUMlqYAAoJEPI2qVPgglptp7MH/j+KFcittFbxfSnZjUl8n5IZ\nveZo7wE+syjD9sUbMH4EGv0WYeVjphNTyViBof+stGTNkB0VQzLWI8+uWmOeiJ4a\nzj0LsbDOxodOEMI5tifo02L7r4Lzj++EbqtKv8IUq2kzYoQ2xjhKfFiGjeYOn008\n9PGnhNblEHZgCHguGR6GsfN8bfA2XNl9B5Ysl5ybX1kAVs/TuLZR4oDMZ/pW2S75\nhuyNnSgcgq7vl2gLGefuPs9lxkg5Fj3GZr7XPZk4pt/x1oiH7yXxV4UzrUwg2CC1\nfHrBlNbQ4uJNY8TFcwof52Z0cagM5Qb/ZSLglHbqEDGA68HPqbwf5z2hQyU2/U4\u003d\n\u003dZtUX\n-----END PGP SIGNATURE-----",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 09:02:16.000000000",
+ "tz": 540
+ }
+ }
+ ]
+----
+
+[[get-tag]]
+=== Get Tag
+--
+'GET /projects/link:#project-name[\{project-name\}]/tags/link:#tag-id[\{tag-id\}]'
+--
+
+Retrieves a tag of a project.
+
+.Request
+----
+ GET /projects/work%2Fmy-project/tags/v1.0 HTTP/1.0
+----
+
+As response a link:#tag-info[TagInfo] entity is returned that describes the tag.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "ref": "refs/tags/v1.0",
+ "revision": "49ce77fdcfd3398dc0dedbe016d1a425fd52d666",
+ "object": "1624f5af8ae89148d1a3730df8c290413e3dcf30",
+ "message": "Annotated tag",
+ "tagger": {
+ "name": "David Pursehouse",
+ "email": "david.pursehouse@sonymobile.com",
+ "date": "2014-10-06 07:35:03.000000000",
+ "tz": 540
+ }
+ }
+----
+
+
[[commit-endpoints]]
== Commit Endpoints
@@ -1664,6 +1760,10 @@
=== \{commit-id\}
Commit ID.
+[[tag-id]]
+=== \{tag-id\}
+The name of a tag. The prefix `refs/tags/` can be omitted.
+
[[dashboard-id]]
=== \{dashboard-id\}
The ID of a dashboard in the format '<ref>:<path>'.
@@ -2168,6 +2268,24 @@
|`size_of_packed_objects` |Size of packed objects in bytes.
|======================================
+[[tag-info]]
+=== TagInfo
+The `TagInfo` entity contains information about a tag.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=========================
+|Field Name ||Description
+|`ref` ||The ref of the tag.
+|`revision` ||For lightweight tags, the revision of the commit to which the tag
+points. For annotated tags, the revision of the tag object.
+|`object`|Only set for annotated tags.|The revision of the object to which the
+tag points.
+|`message`|Only set for annotated tags.|The tag message. For signed tags, includes
+the signature.
+|`tagger`|Only set for annotated tags.|The tagger as a
+link:rest-api-changes.html#git-person-info[GitPersonInfo] entity.
+|=========================
+
[[theme-info]]
=== ThemeInfo
The `ThemeInfo` entity describes a theme.
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
new file mode 100644
index 0000000..58dc065
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2014 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 org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+import java.util.List;
+
+public class TagsIT extends AbstractDaemonTest {
+ @Test
+ public void listTagsOfNonExistingProject_NotFound() throws Exception {
+ assertEquals(HttpStatus.SC_NOT_FOUND,
+ adminSession.get("/projects/non-existing/tags").getStatusCode());
+ }
+
+ @Test
+ public void listTagsOfNonVisibleProject_NotFound() throws Exception {
+ blockRead(project, "refs/*");
+ assertEquals(HttpStatus.SC_NOT_FOUND,
+ userSession.get("/projects/" + project.get() + "/tags").getStatusCode());
+ }
+
+ @Test
+ public void listTags() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+
+ PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent());
+ push1.setTag(tag1);
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master%submit");
+ r1.assertOkStatus();
+
+ PushOneCommit.AnnotatedTag tag2 =
+ new PushOneCommit.AnnotatedTag("v2.0", "annotation", admin.getIdent());
+ PushOneCommit push2 = pushFactory.create(db, admin.getIdent());
+ push2.setTag(tag2);
+ PushOneCommit.Result r2 = push2.to(git, "refs/for/master%submit");
+ r2.assertOkStatus();
+
+ List<TagInfo> result =
+ toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ assertEquals(2, result.size());
+
+ TagInfo t = result.get(0);
+ assertEquals("refs/tags/" + tag1.name, t.ref);
+ assertEquals(r1.getCommitId().getName(), t.revision);
+
+ t = result.get(1);
+ assertEquals("refs/tags/" + tag2.name, t.ref);
+ assertEquals(r2.getCommitId().getName(), t.object);
+ assertEquals(tag2.message, t.message);
+ assertEquals(tag2.tagger.getName(), t.tagger.name);
+ assertEquals(tag2.tagger.getEmailAddress(), t.tagger.email);
+ }
+
+ @Test
+ public void listTagsOfNonVisibleBranch() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/hidden");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+
+ PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent());
+ push1.setTag(tag1);
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master%submit");
+ r1.assertOkStatus();
+
+ pushTo("refs/heads/hidden");
+ PushOneCommit.Tag tag2 = new PushOneCommit.Tag("v2.0");
+ PushOneCommit push2 = pushFactory.create(db, admin.getIdent());
+ push2.setTag(tag2);
+ PushOneCommit.Result r2 = push2.to(git, "refs/for/hidden%submit");
+ r2.assertOkStatus();
+
+ List<TagInfo> result =
+ toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ assertEquals(2, result.size());
+ assertEquals("refs/tags/" + tag1.name, result.get(0).ref);
+ assertEquals(r1.getCommitId().getName(), result.get(0).revision);
+ assertEquals("refs/tags/" + tag2.name, result.get(1).ref);
+ assertEquals(r2.getCommitId().getName(), result.get(1).revision);
+
+ blockRead(project, "refs/heads/hidden");
+ result =
+ toTagInfoList(adminSession.get("/projects/" + project.get() + "/tags"));
+ assertEquals(1, result.size());
+ assertEquals("refs/tags/" + tag1.name, result.get(0).ref);
+ assertEquals(r1.getCommitId().getName(), result.get(0).revision);
+ }
+
+ @Test
+ public void getTag() throws Exception {
+ grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
+ grant(Permission.CREATE, project, "refs/tags/*");
+ grant(Permission.PUSH, project, "refs/tags/*");
+
+ PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
+ PushOneCommit push1 = pushFactory.create(db, admin.getIdent());
+ push1.setTag(tag1);
+ PushOneCommit.Result r1 = push1.to(git, "refs/for/master%submit");
+ r1.assertOkStatus();
+
+ RestResponse response =
+ adminSession.get("/projects/" + project.get() + "/tags/" + tag1.name);
+ TagInfo tagInfo =
+ newGson().fromJson(response.getReader(), TagInfo.class);
+ assertEquals("refs/tags/" + tag1.name, tagInfo.ref);
+ assertEquals(r1.getCommitId().getName(), tagInfo.revision);
+ }
+
+ private static List<TagInfo> toTagInfoList(RestResponse r) throws Exception {
+ List<TagInfo> result =
+ newGson().fromJson(r.getReader(),
+ new TypeToken<List<TagInfo>>() {}.getType());
+ return result;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java
new file mode 100644
index 0000000..3e3d8db
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/TagInfo.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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 TagInfo {
+ public String ref;
+ public String revision;
+ public String object;
+ public String message;
+ public GitPerson tagger;
+
+ public TagInfo(String ref, String revision) {
+ this.ref = ref;
+ this.revision = revision;
+ }
+
+ public TagInfo(String ref, String revision, String object,
+ String message, GitPerson tagger) {
+ this(ref, revision);
+ this.object = object;
+ this.message = message;
+ this.tagger = tagger;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
new file mode 100644
index 0000000..5b78e08
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetTag.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 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.common.TagInfo;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetTag implements RestReadView<TagResource> {
+
+ @Override
+ public TagInfo apply(TagResource resource) {
+ return resource.getTagInfo();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
new file mode 100644
index 0000000..e12b38a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
@@ -0,0 +1,159 @@
+// Copyright (C) 2014 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.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.common.TagInfo;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CommonConverters;
+import com.google.gerrit.server.git.ChangeCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.git.VisibleRefFilter;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+@Singleton
+public class ListTags implements RestReadView<ProjectResource> {
+ private final GitRepositoryManager repoManager;
+ private final Provider<ReviewDb> dbProvider;
+ private final TagCache tagCache;
+ private final ChangeCache changeCache;
+
+ @Inject
+ public ListTags(GitRepositoryManager repoManager,
+ Provider<ReviewDb> dbProvider,
+ TagCache tagCache,
+ ChangeCache changeCache) {
+ this.repoManager = repoManager;
+ this.dbProvider = dbProvider;
+ this.tagCache = tagCache;
+ this.changeCache = changeCache;
+ }
+
+ @Override
+ public List<TagInfo> apply(ProjectResource resource) throws IOException,
+ ResourceNotFoundException {
+ List<TagInfo> tags = Lists.newArrayList();
+
+ Repository repo = getRepository(resource.getNameKey());
+
+ try {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ Map<String, Ref> all = visibleTags(resource.getControl(), repo,
+ repo.getRefDatabase().getRefs(Constants.R_TAGS));
+ for (Ref ref : all.values()) {
+ tags.add(createTagInfo(ref, rw));
+ }
+ } finally {
+ rw.dispose();
+ }
+ } finally {
+ repo.close();
+ }
+
+ Collections.sort(tags, new Comparator<TagInfo>() {
+ @Override
+ public int compare(TagInfo a, TagInfo b) {
+ return a.ref.compareTo(b.ref);
+ }
+ });
+
+ return tags;
+ }
+
+ public TagInfo get(ProjectResource resource, IdString id)
+ throws ResourceNotFoundException, IOException {
+ Repository repo = getRepository(resource.getNameKey());
+
+ String tagName = id.get();
+ if (!tagName.startsWith(Constants.R_TAGS)) {
+ tagName = Constants.R_TAGS + tagName;
+ }
+
+ try {
+ RevWalk rw = new RevWalk(repo);
+ try {
+ Ref ref = repo.getRefDatabase().getRef(tagName);
+ if (ref != null && !visibleTags(resource.getControl(), repo,
+ ImmutableMap.of(ref.getName(), ref)).isEmpty()) {
+ return createTagInfo(ref, rw);
+ }
+ } finally {
+ rw.dispose();
+ }
+ } finally {
+ repo.close();
+ }
+ throw new ResourceNotFoundException(id);
+ }
+
+ private Repository getRepository(Project.NameKey project)
+ throws ResourceNotFoundException, IOException {
+ try {
+ return repoManager.openRepository(project);
+ } catch (RepositoryNotFoundException noGitRepository) {
+ throw new ResourceNotFoundException();
+ }
+ }
+
+ private Map<String, Ref> visibleTags(ProjectControl control, Repository repo,
+ Map<String, Ref> tags) {
+ return new VisibleRefFilter(tagCache, changeCache, repo,
+ control, dbProvider.get(), false).filter(tags, true);
+ }
+
+ private static TagInfo createTagInfo(Ref ref, RevWalk rw)
+ throws MissingObjectException, IOException {
+ RevObject object = rw.parseAny(ref.getObjectId());
+ if (object instanceof RevTag) {
+ RevTag tag = (RevTag)object;
+ // Annotated or signed tag
+ return new TagInfo(
+ Constants.R_TAGS + tag.getTagName(),
+ tag.getName(),
+ tag.getObject().getName(),
+ tag.getFullMessage().trim(),
+ CommonConverters.toGitPerson(tag.getTaggerIdent()));
+ } else {
+ // Lightweight tag
+ return new TagInfo(
+ ref.getName(),
+ ref.getObjectId().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 7b50b0f..ace221d 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
@@ -20,6 +20,7 @@
import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND;
import static com.google.gerrit.server.project.FileResource.FILE_KIND;
import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
+import static com.google.gerrit.server.project.TagResource.TAG_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -37,6 +38,7 @@
DynamicMap.mapOf(binder(), DASHBOARD_KIND);
DynamicMap.mapOf(binder(), FILE_KIND);
DynamicMap.mapOf(binder(), COMMIT_KIND);
+ DynamicMap.mapOf(binder(), TAG_KIND);
put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
@@ -71,6 +73,9 @@
get(COMMIT_KIND).to(GetCommit.class);
child(COMMIT_KIND, "files").to(FilesInCommitCollection.class);
+ child(PROJECT_KIND, "tags").to(TagsCollection.class);
+ get(TAG_KIND).to(GetTag.class);
+
child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
get(DASHBOARD_KIND).to(GetDashboard.class);
put(DASHBOARD_KIND).to(SetDashboard.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
new file mode 100644
index 0000000..12be5d3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 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.common.TagInfo;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class TagResource extends ProjectResource {
+ public static final TypeLiteral<RestView<TagResource>> TAG_KIND =
+ new TypeLiteral<RestView<TagResource>>() {};
+
+ private final TagInfo tag;
+
+ public TagResource(ProjectControl control, TagInfo tag) {
+ super(control);
+ this.tag = tag;
+ }
+
+ public TagInfo getTagInfo() {
+ return tag;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
new file mode 100644
index 0000000..0c70285
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2014 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.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+@Singleton
+public class TagsCollection implements
+ ChildCollection<ProjectResource, TagResource> {
+ private final DynamicMap<RestView<TagResource>> views;
+ private final ListTags list;
+
+ @Inject
+ public TagsCollection(DynamicMap<RestView<TagResource>> views,
+ ListTags list) {
+ this.views = views;
+ this.list = list;
+ }
+
+ @Override
+ public RestView<ProjectResource> list() throws ResourceNotFoundException {
+ return list;
+ }
+
+ @Override
+ public TagResource parse(ProjectResource resource, IdString id)
+ throws ResourceNotFoundException, IOException {
+ return new TagResource(resource.getControl(), list.get(resource, id));
+ }
+
+ @Override
+ public DynamicMap<RestView<TagResource>> views() {
+ return views;
+ }
+}