Add REST endpoint for commit included in

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

Change-Id: I5000cc90b8f0457e2b7594e86e9165f1a6cb8aa5
This commit is contained in:
Gustaf Lundh 2016-10-19 23:19:08 +02:00 committed by David Pursehouse
parent 0fd8d6c208
commit 1386d59c01
13 changed files with 340 additions and 49 deletions

View File

@ -1989,6 +1989,35 @@ is returned that describes the commit.
}
----
[[get-included-in]]
=== Get Included In
--
'GET /projects/link:#project-name[\{project-name\}]/commits/link:#commit-id[\{commit-id\}]/in'
--
Retrieves the branches and tags in which a change is included. As result
an link:rest-api-changes.html#included-in-info[IncludedInInfo] entity is returned.
.Request
----
GET /projects/work%2Fmy-project/commits/a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96/in HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
{
"branches": [
"master"
],
"tags": []
}
----
[[get-content-from-commit]]
=== Get Content
--

View File

@ -23,6 +23,7 @@ import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@ -1046,6 +1047,12 @@ public abstract class AbstractDaemonTest {
return getRemoteHead(project, "master");
}
protected void grantTagPermissions() throws Exception {
grant(Permission.CREATE, project, R_TAGS + "*");
grant(Permission.CREATE_TAG, project, R_TAGS + "*");
grant(Permission.CREATE_SIGNED_TAG, project, R_TAGS + "*");
}
protected void assertMailFrom(Message message, String email)
throws Exception {
assertThat(message.headers()).containsKey("Reply-To");

View File

@ -0,0 +1,64 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.reviewdb.client.Branch;
import org.junit.Test;
@NoHttpd
public class ChangeIncludedInIT extends AbstractDaemonTest {
@Test
public void includedInOpenChange() throws Exception {
Result result = createChange();
assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
.isEmpty();
assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
.isEmpty();
}
@Test
public void includedInMergedChange() throws Exception {
Result result = createChange();
gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
.review(ReviewInput.approve());
gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
.submit();
assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
.containsExactly("master");
assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
.isEmpty();
grantTagPermissions();
gApi.projects().name(project.get()).tag("test-tag").create(new TagInput());
assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
.containsExactly("test-tag");
createBranch(new Branch.NameKey(project.get(), "test-branch"));
assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
.containsExactly("master", "test-branch");
}
}

View File

@ -0,0 +1,72 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.reviewdb.client.Branch;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class CommitIncludedInIT extends AbstractDaemonTest {
@Test
public void includedInOpenChange() throws Exception {
Result result = createChange();
assertThat(getIncludedIn(result.getCommit().getId()).branches).isEmpty();
assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
}
@Test
public void includedInMergedChange() throws Exception {
Result result = createChange();
gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
.review(ReviewInput.approve());
gApi.changes().id(result.getChangeId()).revision(result.getCommit().name())
.submit();
assertThat(getIncludedIn(result.getCommit().getId()).branches)
.containsExactly("master");
assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
grantTagPermissions();
gApi.projects().name(result.getChange().project().get()).tag("test-tag")
.create(new TagInput());
assertThat(getIncludedIn(result.getCommit().getId()).tags)
.containsExactly("test-tag");
createBranch(new Branch.NameKey(project.get(), "test-branch"));
assertThat(getIncludedIn(result.getCommit().getId()).branches)
.containsExactly("master", "test-branch");
}
private IncludedInInfo getIncludedIn(ObjectId id) throws Exception {
RestResponse r = userRestSession
.get("/projects/" + project.get() + "/commits/" + id.name() + "/in");
IncludedInInfo result =
newGson().fromJson(r.getReader(), IncludedInInfo.class);
r.consume();
return result;
}
}

View File

@ -337,12 +337,6 @@ public class TagsIT extends AbstractDaemonTest {
}
}
private void grantTagPermissions() throws Exception {
grant(Permission.CREATE, project, R_TAGS + "*");
grant(Permission.CREATE_TAG, project, R_TAGS + "*");
grant(Permission.CREATE_SIGNED_TAG, project, R_TAGS + "*");
}
private ListRefsRequest<TagInfo> getTags() throws Exception {
return gApi.projects().name(project.get()).tags();
}

View File

@ -120,6 +120,8 @@ public interface ChangeApi {
String topic() throws RestApiException;
void topic(String topic) throws RestApiException;
IncludedInInfo includedIn() throws RestApiException;
void addReviewer(AddReviewerInput in) throws RestApiException;
void addReviewer(String in) throws RestApiException;
@ -331,6 +333,11 @@ public interface ChangeApi {
throw new NotImplementedException();
}
@Override
public IncludedInInfo includedIn() {
throw new NotImplementedException();
}
@Override
public void addReviewer(AddReviewerInput in) {
throw new NotImplementedException();

View File

@ -0,0 +1,32 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.extensions.api.changes;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class IncludedInInfo {
public List<String> branches;
public List<String> tags;
public Map<String, Collection<String>> external;
public IncludedInInfo(List<String> branches,
List<String> tags,
Map<String, Collection<String>> external) {
this.branches = branches;
this.tags = tags;
this.external = external;
}
}

View File

@ -22,6 +22,7 @@ import com.google.gerrit.extensions.api.changes.ChangeEditApi;
import com.google.gerrit.extensions.api.changes.Changes;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.api.changes.MoveInput;
import com.google.gerrit.extensions.api.changes.RestoreInput;
import com.google.gerrit.extensions.api.changes.RevertInput;
@ -41,6 +42,7 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeIncludedIn;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.Check;
@ -102,6 +104,7 @@ class ChangeApiImpl implements ChangeApi {
private final DeleteChange deleteChange;
private final GetTopic getTopic;
private final PutTopic putTopic;
private final ChangeIncludedIn includedIn;
private final PostReviewers postReviewers;
private final ChangeJson.Factory changeJson;
private final PostHashtags postHashtags;
@ -134,6 +137,7 @@ class ChangeApiImpl implements ChangeApi {
DeleteChange deleteChange,
GetTopic getTopic,
PutTopic putTopic,
ChangeIncludedIn includedIn,
PostReviewers postReviewers,
ChangeJson.Factory changeJson,
PostHashtags postHashtags,
@ -165,6 +169,7 @@ class ChangeApiImpl implements ChangeApi {
this.deleteChange = deleteChange;
this.getTopic = getTopic;
this.putTopic = putTopic;
this.includedIn = includedIn;
this.postReviewers = postReviewers;
this.changeJson = changeJson;
this.postHashtags = postHashtags;
@ -349,6 +354,15 @@ class ChangeApiImpl implements ChangeApi {
}
}
@Override
public IncludedInInfo includedIn() throws RestApiException {
try {
return includedIn.apply(change);
} catch (OrmException | IOException e) {
throw new RestApiException("Could not extract IncludedIn data", e);
}
}
@Override
public void addReviewer(String reviewer) throws RestApiException {
AddReviewerInput in = new AddReviewerInput();

View File

@ -0,0 +1,55 @@
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.change;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
public class ChangeIncludedIn implements RestReadView<ChangeResource> {
private Provider<ReviewDb> db;
private PatchSetUtil psUtil;
private IncludedIn includedIn;
@Inject
ChangeIncludedIn(Provider<ReviewDb> db,
PatchSetUtil psUtil,
IncludedIn includedIn) {
this.db = db;
this.psUtil = psUtil;
this.includedIn = includedIn;
}
@Override
public IncludedInInfo apply(ChangeResource rsrc)
throws RestApiException, OrmException, IOException {
ChangeControl ctl = rsrc.getControl();
PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
Project.NameKey project = ctl.getProject().getNameKey();
return includedIn.apply(project, ps.getRevision().get());
}
}

View File

@ -16,20 +16,15 @@ package com.google.gerrit.server.change;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.config.ExternalIncludedIn;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -40,40 +35,27 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
@Singleton
class IncludedIn implements RestReadView<ChangeResource> {
private final Provider<ReviewDb> db;
public class IncludedIn {
private final GitRepositoryManager repoManager;
private final PatchSetUtil psUtil;
private final DynamicSet<ExternalIncludedIn> includedIn;
private final DynamicSet<ExternalIncludedIn> externalIncludedIn;
@Inject
IncludedIn(Provider<ReviewDb> db,
GitRepositoryManager repoManager,
PatchSetUtil psUtil,
DynamicSet<ExternalIncludedIn> includedIn) {
this.db = db;
IncludedIn(GitRepositoryManager repoManager,
DynamicSet<ExternalIncludedIn> externalIncludedIn) {
this.repoManager = repoManager;
this.psUtil = psUtil;
this.includedIn = includedIn;
this.externalIncludedIn = externalIncludedIn;
}
@Override
public IncludedInInfo apply(ChangeResource rsrc) throws BadRequestException,
ResourceConflictException, OrmException, IOException {
ChangeControl ctl = rsrc.getControl();
PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
Project.NameKey project = ctl.getProject().getNameKey();
public IncludedInInfo apply(Project.NameKey project, String revisionId)
throws RestApiException, IOException {
try (Repository r = repoManager.openRepository(project);
RevWalk rw = new RevWalk(r)) {
rw.setRetainBody(false);
RevCommit rev;
try {
rev = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
rev = rw.parseCommit(ObjectId.fromString(revisionId));
} catch (IncorrectObjectTypeException err) {
throw new BadRequestException(err.getMessage());
} catch (MissingObjectException err) {
@ -83,27 +65,15 @@ class IncludedIn implements RestReadView<ChangeResource> {
IncludedInResolver.Result d = IncludedInResolver.resolve(r, rw, rev);
ListMultimap<String, String> external =
MultimapBuilder.hashKeys().arrayListValues().build();
for (ExternalIncludedIn ext : includedIn) {
for (ExternalIncludedIn ext : externalIncludedIn) {
ListMultimap<String, String> extIncludedIns = ext.getIncludedIn(
project.get(), rev.name(), d.getTags(), d.getBranches());
if (extIncludedIns != null) {
external.putAll(extIncludedIns);
}
}
return new IncludedInInfo(d,
return new IncludedInInfo(d.getBranches(), d.getTags(),
(!external.isEmpty() ? external.asMap() : null));
}
}
static class IncludedInInfo {
Collection<String> branches;
Collection<String> tags;
Map<String, Collection<String>> external;
IncludedInInfo(IncludedInResolver.Result in, Map<String, Collection<String>> e) {
branches = in.getBranches();
tags = in.getTags();
external = e;
}
}
}

View File

@ -57,7 +57,7 @@ public class Module extends RestApiModule {
post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class);
get(CHANGE_KIND, "detail").to(GetDetail.class);
get(CHANGE_KIND, "topic").to(GetTopic.class);
get(CHANGE_KIND, "in").to(IncludedIn.class);
get(CHANGE_KIND, "in").to(ChangeIncludedIn.class);
get(CHANGE_KIND, "assignee").to(GetAssignee.class);
get(CHANGE_KIND, "past_assignees").to(GetPastAssignees.class);
put(CHANGE_KIND, "assignee").to(PutAssignee.class);

View File

@ -0,0 +1,46 @@
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.project;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.IncludedIn;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.revwalk.RevCommit;
import java.io.IOException;
@Singleton
class CommitIncludedIn implements RestReadView<CommitResource> {
private IncludedIn includedIn;
@Inject
CommitIncludedIn(IncludedIn includedIn) {
this.includedIn = includedIn;
}
@Override
public IncludedInInfo apply(CommitResource rsrc)
throws RestApiException, OrmException, IOException {
RevCommit commit = rsrc.getCommit();
Project.NameKey project = rsrc.getProject().getProject().getNameKey();
return includedIn.apply(project, commit.getId().getName());
}
}

View File

@ -76,6 +76,7 @@ public class Module extends RestApiModule {
child(PROJECT_KIND, "commits").to(CommitsCollection.class);
get(COMMIT_KIND).to(GetCommit.class);
get(COMMIT_KIND, "in").to(CommitIncludedIn.class);
child(COMMIT_KIND, "files").to(FilesInCommitCollection.class);
child(PROJECT_KIND, "tags").to(TagsCollection.class);