Merge "Add REST API endpoint to create a tag on a project"

This commit is contained in:
David Pursehouse 2016-06-24 10:19:59 +00:00 committed by Gerrit Code Review
commit 43b88be9a9
10 changed files with 533 additions and 78 deletions

View File

@ -1568,6 +1568,57 @@ describes the child project.
[[tag-endpoints]] [[tag-endpoints]]
== Tag Endpoints == Tag Endpoints
[[create-tag]]
=== Create Tag
--
'PUT /projects/link:#project-name[\{project-name\}]/tags/link:#tag-id[\{tag-id\}]'
--
Create a new tag on the project.
In the request body additional data for the tag can be provided as
link:#tag-input[TagInput].
If a message is provided in the input, the tag is created as an
annotated tag with the current user as tagger. Signed tags are not
supported.
.Request
----
PUT /projects/MyProject/tags/v1.0 HTTP/1.0
Content-Type: application/json; charset=UTF-8
{
"message": "annotation",
"revision": "c83117624b5b5d8a7f86093824e2f9c1ed309d63"
}
----
As response a link:#tag-info[TagInfo] entity is returned that describes
the created tag.
.Response
----
HTTP/1.1 201 Created
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
"object": "d48d304adc4b6674e11dc2c018ea05fcbdda32fd",
"message": "annotation",
"tagger": {
"name": "David Pursehouse",
"email": "dpursehouse@collab.net",
"date": "2016-06-06 01:22:03.000000000",
"tz": 540
},
"ref": "refs/tags/v1.0",
"revision": "c83117624b5b5d8a7f86093824e2f9c1ed309d63"
}
----
[[list-tags]] [[list-tags]]
=== List Tags === List Tags
-- --
@ -2615,6 +2666,22 @@ the signature.
link:rest-api-changes.html#git-person-info[GitPersonInfo] entity. link:rest-api-changes.html#git-person-info[GitPersonInfo] entity.
|========================= |=========================
[[tag-input]]
=== TagInput
The `TagInput` entity contains information for creating a tag.
[options="header",cols="1,^2,4"]
|=========================
|Field Name ||Description
|`ref` ||The name of the tag. The leading `refs/tags/` is optional.
|`revision` |optional|The revision to which the tag should point. If not
specified, the project's `HEAD` will be used.
|`message` |optional|The tag message. When set, the tag will be created
as an annotated tag.
|=========================
[[theme-info]] [[theme-info]]
=== ThemeInfo === ThemeInfo
The `ThemeInfo` entity describes a theme. The `ThemeInfo` entity describes a theme.

View File

@ -15,23 +15,25 @@
package com.google.gerrit.acceptance.rest.project; package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static org.eclipse.jgit.lib.Constants.R_TAGS; import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd; import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest; import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.api.projects.TagApi;
import com.google.gerrit.extensions.api.projects.TagInfo; import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.junit.Test; import org.junit.Test;
import java.util.List; import java.util.List;
@ -41,6 +43,19 @@ public class TagsIT extends AbstractDaemonTest {
private static final List<String> testTags = ImmutableList.of( private static final List<String> testTags = ImmutableList.of(
"tag-A", "tag-B", "tag-C", "tag-D", "tag-E", "tag-F", "tag-G", "tag-H"); "tag-A", "tag-B", "tag-C", "tag-D", "tag-E", "tag-F", "tag-G", "tag-H");
private static final String SIGNED_ANNOTATION = "annotation\n"
+ "-----BEGIN PGP SIGNATURE-----\n"
+ "Version: GnuPG v1\n"
+ "\n"
+ "iQEcBAABAgAGBQJVeGg5AAoJEPfTicJkUdPkUggH/RKAeI9/i/LduuiqrL/SSdIa\n"
+ "9tYaSqJKLbXz63M/AW4Sp+4u+dVCQvnAt/a35CVEnpZz6hN4Kn/tiswOWVJf4CO7\n"
+ "htNubGs5ZMwvD6sLYqKAnrM3WxV/2TbbjzjZW6Jkidz3jz/WRT4SmjGYiEO7aA+V\n"
+ "4ZdIS9f7sW5VsHHYlNThCA7vH8Uu48bUovFXyQlPTX0pToSgrWV3JnTxDNxfn3iG\n"
+ "IL0zTY/qwVCdXgFownLcs6J050xrrBWIKqfcWr3u4D2aCLyR0v+S/KArr7ulZygY\n"
+ "+SOklImn8TAZiNxhWtA6ens66IiammUkZYFv7SSzoPLFZT4dC84SmGPWgf94NoQ=\n"
+ "=XFeC\n"
+ "-----END PGP SIGNATURE-----";
@Test @Test
public void listTagsOfNonExistingProject() throws Exception { public void listTagsOfNonExistingProject() throws Exception {
exception.expect(ResourceNotFoundException.class); exception.expect(ResourceNotFoundException.class);
@ -105,83 +120,198 @@ public class TagsIT extends AbstractDaemonTest {
@Test @Test
public void listTagsOfNonVisibleBranch() throws Exception { public void listTagsOfNonVisibleBranch() throws Exception {
grantTagPermissions(); grantTagPermissions();
grant(Permission.SUBMIT, project, "refs/for/refs/heads/hidden");
PushOneCommit.Tag tag1 = new PushOneCommit.Tag("v1.0");
PushOneCommit push1 = pushFactory.create(db, admin.getIdent(), testRepo); PushOneCommit push1 = pushFactory.create(db, admin.getIdent(), testRepo);
push1.setTag(tag1); PushOneCommit.Result r1 = push1.to("refs/heads/master");
PushOneCommit.Result r1 = push1.to("refs/for/master%submit");
r1.assertOkStatus(); r1.assertOkStatus();
TagInput tag1 = new TagInput();
tag1.ref = "v1.0";
tag1.revision = r1.getCommit().getName();
TagInfo result = tag(tag1.ref).create(tag1).get();
assertThat(result.ref).isEqualTo(R_TAGS + tag1.ref);
assertThat(result.revision).isEqualTo(tag1.revision);
pushTo("refs/heads/hidden"); pushTo("refs/heads/hidden");
PushOneCommit.Tag tag2 = new PushOneCommit.Tag("v2.0");
PushOneCommit push2 = pushFactory.create(db, admin.getIdent(), testRepo); PushOneCommit push2 = pushFactory.create(db, admin.getIdent(), testRepo);
push2.setTag(tag2); PushOneCommit.Result r2 = push2.to("refs/heads/hidden");
PushOneCommit.Result r2 = push2.to("refs/for/hidden%submit");
r2.assertOkStatus(); r2.assertOkStatus();
List<TagInfo> result = getTags().get(); TagInput tag2 = new TagInput();
assertThat(result).hasSize(2); tag2.ref = "v2.0";
assertThat(result.get(0).ref).isEqualTo(R_TAGS + tag1.name); tag2.revision = r2.getCommit().getName();
assertThat(result.get(0).revision).isEqualTo(r1.getCommit().getName()); result = tag(tag2.ref).create(tag2).get();
assertThat(result.get(1).ref).isEqualTo(R_TAGS + tag2.name); assertThat(result.ref).isEqualTo(R_TAGS + tag2.ref);
assertThat(result.get(1).revision).isEqualTo(r2.getCommit().getName()); assertThat(result.revision).isEqualTo(tag2.revision);
List<TagInfo> tags = getTags().get();
assertThat(tags).hasSize(2);
assertThat(tags.get(0).ref).isEqualTo(R_TAGS + tag1.ref);
assertThat(tags.get(0).revision).isEqualTo(tag1.revision);
assertThat(tags.get(1).ref).isEqualTo(R_TAGS + tag2.ref);
assertThat(tags.get(1).revision).isEqualTo(tag2.revision);
blockRead("refs/heads/hidden"); blockRead("refs/heads/hidden");
result = getTags().get(); tags = getTags().get();
assertThat(result).hasSize(1); assertThat(tags).hasSize(1);
assertThat(result.get(0).ref).isEqualTo(R_TAGS + tag1.name); assertThat(tags.get(0).ref).isEqualTo(R_TAGS + tag1.ref);
assertThat(result.get(0).revision).isEqualTo(r1.getCommit().getName()); assertThat(tags.get(0).revision).isEqualTo(tag1.revision);
} }
@Test @Test
public void lightweightTag() throws Exception { public void lightweightTag() throws Exception {
grantTagPermissions(); grantTagPermissions();
PushOneCommit.Tag tag = new PushOneCommit.Tag("v1.0");
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
push.setTag(tag); PushOneCommit.Result r = push.to("refs/heads/master");
PushOneCommit.Result r = push.to("refs/for/master%submit");
r.assertOkStatus(); r.assertOkStatus();
TagInfo tagInfo = getTag(tag.name); TagInput input = new TagInput();
assertThat(tagInfo.ref).isEqualTo(R_TAGS + tag.name); input.ref = "v1.0";
assertThat(tagInfo.revision).isEqualTo(r.getCommit().getName()); input.revision = r.getCommit().getName();
TagInfo result = tag(input.ref).create(input).get();
assertThat(result.ref).isEqualTo(R_TAGS + input.ref);
assertThat(result.revision).isEqualTo(input.revision);
input.ref = "refs/tags/v2.0";
result = tag(input.ref).create(input).get();
assertThat(result.ref).isEqualTo(input.ref);
assertThat(result.revision).isEqualTo(input.revision);
eventRecorder.assertRefUpdatedEvents(project.get(), result.ref,
null, result.revision);
} }
@Test @Test
public void annotatedTag() throws Exception { public void annotatedTag() throws Exception {
grantTagPermissions(); grantTagPermissions();
PushOneCommit.AnnotatedTag tag =
new PushOneCommit.AnnotatedTag("v2.0", "annotation", admin.getIdent());
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
push.setTag(tag); PushOneCommit.Result r = push.to("refs/heads/master");
PushOneCommit.Result r = push.to("refs/for/master%submit");
r.assertOkStatus(); r.assertOkStatus();
TagInfo tagInfo = getTag(tag.name); TagInput input = new TagInput();
assertThat(tagInfo.ref).isEqualTo(R_TAGS + tag.name); input.ref = "v1.0";
assertThat(tagInfo.object).isEqualTo(r.getCommit().getName()); input.revision = r.getCommit().getName();
assertThat(tagInfo.message).isEqualTo(tag.message); input.message = "annotation message";
assertThat(tagInfo.tagger.name).isEqualTo(tag.tagger.getName());
assertThat(tagInfo.tagger.email).isEqualTo(tag.tagger.getEmailAddress()); TagInfo result = tag(input.ref).create(input).get();
assertThat(result.ref).isEqualTo(R_TAGS + input.ref);
assertThat(result.object).isEqualTo(input.revision);
assertThat(result.message).isEqualTo(input.message);
assertThat(result.tagger.name).isEqualTo(admin.fullName);
assertThat(result.tagger.email).isEqualTo(admin.email);
eventRecorder.assertRefUpdatedEvents(project.get(), result.ref,
null, result.revision);
// A second tag pushed on the same ref should have the same ref // A second tag pushed on the same ref should have the same ref
String tag2ref = R_TAGS + "v2.0.1"; TagInput input2 = new TagInput();
PushCommand pushCmd = testRepo.git().push(); input2.ref = "refs/tags/v2.0";
pushCmd.setRefSpecs(new RefSpec(tag.name + ":" + tag2ref)); input2.revision = input.revision;
Iterable<PushResult> result = pushCmd.call(); input2.message = "second annotation message";
assertThat( TagInfo result2 = tag(input2.ref).create(input2).get();
Iterables.getOnlyElement(result).getRemoteUpdate(tag2ref).getStatus()) assertThat(result2.ref).isEqualTo(input2.ref);
.isEqualTo(Status.OK); assertThat(result2.object).isEqualTo(input2.revision);
assertThat(result2.message).isEqualTo(input2.message);
assertThat(result2.tagger.name).isEqualTo(admin.fullName);
assertThat(result2.tagger.email).isEqualTo(admin.email);
tagInfo = getTag(tag2ref); eventRecorder.assertRefUpdatedEvents(project.get(), result2.ref,
assertThat(tagInfo.ref).isEqualTo(tag2ref); null, result2.revision);
assertThat(tagInfo.object).isEqualTo(r.getCommit().getName()); }
assertThat(tagInfo.message).isEqualTo(tag.message);
assertThat(tagInfo.tagger.name).isEqualTo(tag.tagger.getName()); @Test
assertThat(tagInfo.tagger.email).isEqualTo(tag.tagger.getEmailAddress()); public void createExistingTag() throws Exception {
grantTagPermissions();
TagInput input = new TagInput();
input.ref = "test";
TagInfo result = tag(input.ref).create(input).get();
assertThat(result.ref).isEqualTo(R_TAGS + "test");
input.ref = "refs/tags/test";
exception.expect(ResourceConflictException.class);
exception.expectMessage("tag \"" + R_TAGS + "test\" already exists");
tag(input.ref).create(input);
}
@Test
public void createTagNotAllowed() throws Exception {
TagInput input = new TagInput();
input.ref = "test";
exception.expect(AuthException.class);
exception.expectMessage("Cannot create tag \"" + R_TAGS + "test\"");
tag(input.ref).create(input);
}
@Test
public void createAnnotatedTagNotAllowed() throws Exception {
block(Permission.PUSH_TAG, REGISTERED_USERS, R_TAGS + "*");
TagInput input = new TagInput();
input.ref = "test";
input.message = "annotation";
exception.expect(AuthException.class);
exception.expectMessage(
"Cannot create annotated tag \"" + R_TAGS + "test\"");
tag(input.ref).create(input);
}
@Test
public void createSignedTagNotSupported() throws Exception {
TagInput input = new TagInput();
input.ref = "test";
input.message = SIGNED_ANNOTATION;
exception.expect(MethodNotAllowedException.class);
exception.expectMessage("Cannot create signed tag \"" + R_TAGS + "test\"");
tag(input.ref).create(input);
}
@Test
public void mismatchedInput() throws Exception {
TagInput input = new TagInput();
input.ref = "test";
exception.expect(BadRequestException.class);
exception.expectMessage("ref must match URL");
tag("TEST").create(input);
}
@Test
public void invalidTagName() throws Exception {
grantTagPermissions();
TagInput input = new TagInput();
input.ref = "refs/heads/test";
exception.expect(BadRequestException.class);
exception.expectMessage("invalid tag name \"" + input.ref + "\"");
tag(input.ref).create(input);
}
@Test
public void invalidTagNameOnlySlashes() throws Exception {
grantTagPermissions();
TagInput input = new TagInput();
input.ref = "//";
exception.expect(BadRequestException.class);
exception.expectMessage("invalid tag name \"refs/tags/\"");
tag(input.ref).create(input);
}
@Test
public void invalidBaseRevision() throws Exception {
grantTagPermissions();
TagInput input = new TagInput();
input.ref = "test";
input.revision = "abcdefg";
exception.expect(BadRequestException.class);
exception.expectMessage("Invalid base revision");
tag(input.ref).create(input);
} }
private void assertTagList(FluentIterable<String> expected, private void assertTagList(FluentIterable<String> expected,
@ -194,26 +324,29 @@ public class TagsIT extends AbstractDaemonTest {
private void createTags() throws Exception { private void createTags() throws Exception {
grantTagPermissions(); grantTagPermissions();
String revision = pushTo("refs/heads/master").getCommit().name();
TagInput input = new TagInput();
input.revision = revision;
for (String tagname : testTags) { for (String tagname : testTags) {
PushOneCommit.Tag tag = new PushOneCommit.Tag(tagname); TagInfo result = tag(tagname).create(input).get();
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); assertThat(result.revision).isEqualTo(input.revision);
push.setTag(tag); assertThat(result.ref).isEqualTo(R_TAGS + tagname);
PushOneCommit.Result result = push.to("refs/for/master%submit");
result.assertOkStatus();
} }
} }
private void grantTagPermissions() throws Exception { private void grantTagPermissions() throws Exception {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
grant(Permission.CREATE, project, R_TAGS + "*"); grant(Permission.CREATE, project, R_TAGS + "*");
grant(Permission.PUSH, project, R_TAGS + "*"); grant(Permission.PUSH_TAG, project, R_TAGS + "*");
grant(Permission.PUSH_SIGNED_TAG, project, R_TAGS + "*");
} }
private ListRefsRequest<TagInfo> getTags() throws Exception { private ListRefsRequest<TagInfo> getTags() throws Exception {
return gApi.projects().name(project.get()).tags(); return gApi.projects().name(project.get()).tags();
} }
private TagInfo getTag(String ref) throws Exception { private TagApi tag(String tagname) throws Exception {
return gApi.projects().name(project.get()).tag(ref).get(); return gApi.projects().name(project.get()).tag(tagname);
} }
} }

View File

@ -18,6 +18,8 @@ import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
public interface TagApi { public interface TagApi {
TagApi create(TagInput input) throws RestApiException;
TagInfo get() throws RestApiException; TagInfo get() throws RestApiException;
/** /**
@ -25,6 +27,11 @@ public interface TagApi {
* when adding new methods to the interface. * when adding new methods to the interface.
**/ **/
class NotImplemented implements TagApi { class NotImplemented implements TagApi {
@Override
public TagApi create(TagInput input) throws RestApiException {
throw new NotImplementedException();
}
@Override @Override
public TagInfo get() throws RestApiException { public TagInfo get() throws RestApiException {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -0,0 +1,24 @@
// 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.extensions.api.projects;
import com.google.gerrit.extensions.restapi.DefaultInput;
public class TagInput {
@DefaultInput
public String ref;
public String revision;
public String message;
}

View File

@ -16,8 +16,10 @@ package com.google.gerrit.server.api.projects;
import com.google.gerrit.extensions.api.projects.TagApi; import com.google.gerrit.extensions.api.projects.TagApi;
import com.google.gerrit.extensions.api.projects.TagInfo; import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.project.CreateTag;
import com.google.gerrit.server.project.ListTags; import com.google.gerrit.server.project.ListTags;
import com.google.gerrit.server.project.ProjectResource; import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -31,18 +33,31 @@ public class TagApiImpl implements TagApi {
} }
private final ListTags listTags; private final ListTags listTags;
private final CreateTag.Factory createTagFactory;
private final String ref; private final String ref;
private final ProjectResource project; private final ProjectResource project;
@Inject @Inject
TagApiImpl(ListTags listTags, TagApiImpl(ListTags listTags,
CreateTag.Factory createTagFactory,
@Assisted ProjectResource project, @Assisted ProjectResource project,
@Assisted String ref) { @Assisted String ref) {
this.listTags = listTags; this.listTags = listTags;
this.createTagFactory = createTagFactory;
this.project = project; this.project = project;
this.ref = ref; this.ref = ref;
} }
@Override
public TagApi create(TagInput input) throws RestApiException {
try {
createTagFactory.create(ref).apply(project, input);
return this;
} catch (IOException e) {
throw new RestApiException("Cannot create tag", e);
}
}
@Override @Override
public TagInfo get() throws RestApiException { public TagInfo get() throws RestApiException {
try { try {

View File

@ -0,0 +1,167 @@
// 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 static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.base.Strings;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.project.RefUtil.InvalidRevisionException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.TagCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TimeZone;
public class CreateTag implements RestModifyView<ProjectResource, TagInput> {
private static final Logger log = LoggerFactory.getLogger(CreateTag.class);
public interface Factory {
CreateTag create(String ref);
}
private final Provider<IdentifiedUser> identifiedUser;
private final GitRepositoryManager repoManager;
private final TagCache tagCache;
private final GitReferenceUpdated referenceUpdated;
private final ChangeHooks hooks;
private String ref;
@Inject
CreateTag(Provider<IdentifiedUser> identifiedUser,
GitRepositoryManager repoManager,
TagCache tagCache,
GitReferenceUpdated referenceUpdated,
ChangeHooks hooks,
@Assisted String ref) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.tagCache = tagCache;
this.referenceUpdated = referenceUpdated;
this.hooks = hooks;
this.ref = ref;
}
@Override
public TagInfo apply(ProjectResource resource, TagInput input)
throws RestApiException, IOException {
if (input == null) {
input = new TagInput();
}
if (input.ref != null && !ref.equals(input.ref)) {
throw new BadRequestException("ref must match URL");
}
if (input.revision == null) {
input.revision = Constants.HEAD;
}
while (ref.startsWith("/")) {
ref = ref.substring(1);
}
if (ref.startsWith(R_REFS) && !ref.startsWith(R_TAGS)) {
throw new BadRequestException("invalid tag name \"" + ref + "\"");
}
if (!ref.startsWith(R_TAGS)) {
ref = R_TAGS + ref;
}
if (!Repository.isValidRefName(ref)) {
throw new BadRequestException("invalid tag name \"" + ref + "\"");
}
RefControl refControl = resource.getControl().controlForRef(ref);
try (Repository repo = repoManager.openRepository(resource.getNameKey())) {
ObjectId revid = RefUtil.parseBaseRevision(
repo, resource.getNameKey(), input.revision);
RevWalk rw = RefUtil.verifyConnected(repo, revid);
RevObject object = rw.parseAny(revid);
rw.reset();
boolean isAnnotated = Strings.emptyToNull(input.message) != null;
boolean isSigned = isAnnotated
&& input.message.contains("-----BEGIN PGP SIGNATURE-----\n");
if (isSigned) {
throw new MethodNotAllowedException(
"Cannot create signed tag \"" + ref + "\"");
} else if (isAnnotated && !refControl.canPerform(Permission.PUSH_TAG)) {
throw new AuthException("Cannot create annotated tag \"" + ref + "\"");
} else if (!refControl.canPerform(Permission.CREATE)) {
throw new AuthException("Cannot create tag \"" + ref + "\"");
}
if (repo.getRefDatabase().exactRef(ref) != null) {
throw new ResourceConflictException(
"tag \"" + ref + "\" already exists");
}
try (Git git = new Git(repo)) {
TagCommand tag = git.tag()
.setObjectId(object)
.setName(ref.substring(R_TAGS.length()))
.setAnnotated(isAnnotated)
.setSigned(isSigned);
if (isAnnotated) {
tag.setMessage(input.message)
.setTagger(identifiedUser.get()
.newCommitterIdent(TimeUtil.nowTs(), TimeZone.getDefault()));
}
Ref result = tag.call();
tagCache.updateFastForward(resource.getNameKey(), ref,
ObjectId.zeroId(), result.getObjectId());
referenceUpdated.fire(resource.getNameKey(), ref,
ObjectId.zeroId(), result.getObjectId(),
identifiedUser.get().getAccount());
hooks.doRefUpdatedHook(new Branch.NameKey(resource.getNameKey(), ref),
ObjectId.zeroId(), result.getObjectId(),
identifiedUser.get().getAccount());
try (RevWalk w = new RevWalk(repo)) {
return ListTags.createTagInfo(result, w);
}
}
} catch (InvalidRevisionException e) {
throw new BadRequestException("Invalid base revision");
} catch (GitAPIException e) {
log.error("Cannot create tag \"" + ref + "\"", e);
throw new IOException(e);
}
}
}

View File

@ -141,22 +141,7 @@ public class ListTags implements RestReadView<ProjectResource> {
throw new ResourceNotFoundException(id); throw new ResourceNotFoundException(id);
} }
private Repository getRepository(Project.NameKey project) public static TagInfo createTagInfo(Ref ref, RevWalk rw)
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, changeNotesFactory, changeCache, repo,
control, dbProvider.get(), false).filter(tags, true);
}
private static TagInfo createTagInfo(Ref ref, RevWalk rw)
throws MissingObjectException, IOException { throws MissingObjectException, IOException {
RevObject object = rw.parseAny(ref.getObjectId()); RevObject object = rw.parseAny(ref.getObjectId());
if (object instanceof RevTag) { if (object instanceof RevTag) {
@ -176,4 +161,19 @@ public class ListTags implements RestReadView<ProjectResource> {
ref.getName(), ref.getName(),
ref.getObjectId().getName()); ref.getObjectId().getName());
} }
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, changeNotesFactory, changeCache, repo,
control, dbProvider.get(), false).filter(tags, true);
}
} }

View File

@ -78,6 +78,8 @@ public class Module extends RestApiModule {
child(PROJECT_KIND, "tags").to(TagsCollection.class); child(PROJECT_KIND, "tags").to(TagsCollection.class);
get(TAG_KIND).to(GetTag.class); get(TAG_KIND).to(GetTag.class);
put(TAG_KIND).to(PutTag.class);
factory(CreateTag.Factory.class);
child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class); child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
get(DASHBOARD_KIND).to(GetDashboard.class); get(DASHBOARD_KIND).to(GetDashboard.class);

View File

@ -0,0 +1,29 @@
// 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.projects.TagInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
public class PutTag implements RestModifyView<TagResource, TagInput> {
@Override
public Object apply(TagResource resource, TagInput input)
throws ResourceConflictException {
throw new ResourceConflictException("Tag \"" + resource.getTagInfo().ref
+ "\" already exists");
}
}

View File

@ -15,6 +15,7 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.ChildCollection; import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@ -27,15 +28,19 @@ import java.io.IOException;
@Singleton @Singleton
public class TagsCollection implements public class TagsCollection implements
ChildCollection<ProjectResource, TagResource> { ChildCollection<ProjectResource, TagResource>,
AcceptsCreate<ProjectResource> {
private final DynamicMap<RestView<TagResource>> views; private final DynamicMap<RestView<TagResource>> views;
private final Provider<ListTags> list; private final Provider<ListTags> list;
private final CreateTag.Factory createTagFactory;
@Inject @Inject
public TagsCollection(DynamicMap<RestView<TagResource>> views, public TagsCollection(DynamicMap<RestView<TagResource>> views,
Provider<ListTags> list) { Provider<ListTags> list,
CreateTag.Factory createTagFactory) {
this.views = views; this.views = views;
this.list = list; this.list = list;
this.createTagFactory = createTagFactory;
} }
@Override @Override
@ -53,4 +58,10 @@ public class TagsCollection implements
public DynamicMap<RestView<TagResource>> views() { public DynamicMap<RestView<TagResource>> views() {
return views; return views;
} }
@SuppressWarnings("unchecked")
@Override
public CreateTag create(ProjectResource resource, IdString name) {
return createTagFactory.create(name.get());
}
} }