Merge "Add REST API endpoint to create a tag on a project"
This commit is contained in:
commit
43b88be9a9
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user