Support listing of branches via REST
GET on /projects/<project-name>/branches returns a list with the
branches of the project.
Change-Id: Ic30e4fc563799e9a4352c4ed01c4db5316abb835
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index bb83048..879a35e 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -483,6 +483,54 @@
done.
----
+[[branch-endpoints]]
+Branch Endpoints
+----------------
+
+[[list-branches]]
+List Branches
+~~~~~~~~~~~~~
+[verse]
+'GET /projects/link:#project-name[\{project-name\}]/branches/'
+
+List the branches of a project.
+
+As result a list of link:#branch-info[BranchInfo] entries is
+returned.
+
+.Request
+----
+ GET /projects/work%2Fmy-project/branches/ HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ [
+ {
+ "ref": "HEAD",
+ "revision": "master"
+ },
+ {
+ "ref": "refs/meta/config",
+ "revision": "76016386a0d8ecc7b6be212424978bb45959d668"
+ },
+ {
+ "ref": "refs/heads/master",
+ "revision": "67ebf73496383c6777035e374d2d664009e2aa5c"
+ },
+ {
+ "ref": "refs/heads/stable",
+ "revision": "64ca533bd0eb5252d2fee83f63da67caae9b4674",
+ "can_delete": true
+ }
+ ]
+----
+
[[child-project-endpoints]]
Child Project Endpoints
-----------------------
@@ -871,6 +919,20 @@
JSON Entities
-------------
+[[branch-info]]
+BranchInfo
+~~~~~~~~~~
+The `BranchInfo` entity contains information about a branch.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=========================
+|Field Name ||Description
+|`ref` ||The ref of the branch.
+|`revision` ||The revision to which the branch points.
+|`can_delete`|`false` if not set|
+Whether the calling user can delete this branch.
+|=========================
+
[[dashboard-info]]
DashboardInfo
~~~~~~~~~~~~~
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
index 2366423..7330337 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
@@ -14,31 +14,24 @@
package com.google.gerrit.httpd.rpc.project;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.data.ListBranchesResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
-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 java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
class ListBranches extends Handler<ListBranchesResult> {
interface Factory {
@@ -46,127 +39,37 @@
}
private final ProjectControl.Factory projectControlFactory;
- private final GitRepositoryManager repoManager;
+ private final Provider<com.google.gerrit.server.project.ListBranches> listBranchesProvider;
private final Project.NameKey projectName;
@Inject
ListBranches(final ProjectControl.Factory projectControlFactory,
- final GitRepositoryManager repoManager,
-
+ final Provider<com.google.gerrit.server.project.ListBranches> listBranchesProvider,
@Assisted final Project.NameKey name) {
this.projectControlFactory = projectControlFactory;
- this.repoManager = repoManager;
+ this.listBranchesProvider = listBranchesProvider;
this.projectName = name;
}
@Override
public ListBranchesResult call() throws NoSuchProjectException, IOException {
- final ProjectControl pctl = projectControlFactory.validateFor( //
- projectName, //
- ProjectControl.OWNER | ProjectControl.VISIBLE);
-
- final List<Branch> branches = new ArrayList<Branch>();
- Branch headBranch = null;
- Branch configBranch = null;
- final Set<String> targets = new HashSet<String>();
-
- final Repository db;
+ ProjectControl pctl =
+ projectControlFactory.validateFor(projectName, ProjectControl.OWNER
+ | ProjectControl.VISIBLE);
try {
- db = repoManager.openRepository(projectName);
- } catch (RepositoryNotFoundException noGitRepository) {
- return new ListBranchesResult(branches, false, true);
- }
- try {
- final Map<String, Ref> all = db.getAllRefs();
-
- if (!all.containsKey(Constants.HEAD)) {
- // The branch pointed to by HEAD doesn't exist yet, so getAllRefs
- // filtered it out. If we ask for it individually we can find the
- // underlying target and put it into the map anyway.
- //
- try {
- Ref head = db.getRef(Constants.HEAD);
- if (head != null) {
- all.put(Constants.HEAD, head);
- }
- } catch (IOException e) {
- // Ignore the failure reading HEAD.
- }
+ List<Branch> branches = Lists.newArrayList();
+ List<BranchInfo> branchInfos = listBranchesProvider.get().apply(new ProjectResource(pctl));
+ for (BranchInfo info : branchInfos) {
+ Branch b = new Branch(new Branch.NameKey(projectName, info.ref));
+ b.setRevision(new RevId(info.revision));
+ b.setCanDelete(Objects.firstNonNull(info.canDelete, false));
+ branches.add(b);
}
-
- for (final Ref ref : all.values()) {
- if (ref.isSymbolic()) {
- targets.add(ref.getTarget().getName());
- }
- }
-
- for (final Ref ref : all.values()) {
- if (ref.isSymbolic()) {
- // A symbolic reference to another branch, instead of
- // showing the resolved value, show the name it references.
- //
- String target = ref.getTarget().getName();
- RefControl targetRefControl = pctl.controlForRef(target);
- if (!targetRefControl.isVisible()) {
- continue;
- }
- if (target.startsWith(Constants.R_HEADS)) {
- target = target.substring(Constants.R_HEADS.length());
- }
-
- Branch b = createBranch(ref.getName());
- b.setRevision(new RevId(target));
-
- if (Constants.HEAD.equals(ref.getName())) {
- b.setCanDelete(false);
- headBranch = b;
- } else {
- b.setCanDelete(targetRefControl.canDelete());
- branches.add(b);
- }
- continue;
- }
-
- final RefControl refControl = pctl.controlForRef(ref.getName());
- if (refControl.isVisible()) {
- if (ref.getName().startsWith(Constants.R_HEADS)) {
- branches.add(createBranch(ref, refControl, targets));
- } else if (GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
- configBranch = createBranch(ref, refControl, targets);
- }
- }
- }
- } finally {
- db.close();
+ return new ListBranchesResult(branches, pctl.canAddRefs(), false);
+ } catch (ResourceNotFoundException e) {
+ throw new NoSuchProjectException(projectName);
}
- Collections.sort(branches, new Comparator<Branch>() {
- @Override
- public int compare(final Branch a, final Branch b) {
- return a.getName().compareTo(b.getName());
- }
- });
- if (configBranch != null) {
- branches.add(0, configBranch);
- }
- if (headBranch != null) {
- branches.add(0, headBranch);
- }
- return new ListBranchesResult(branches, pctl.canAddRefs(), false);
- }
-
- private Branch createBranch(final Ref ref, final RefControl refControl,
- final Set<String> targets) {
- final Branch b = createBranch(ref.getName());
- if (ref.getObjectId() != null) {
- b.setRevision(new RevId(ref.getObjectId().name()));
- }
- b.setCanDelete(!targets.contains(ref.getName()) && refControl.canDelete());
- return b;
- }
-
- private Branch createBranch(final String name) {
- return new Branch(new Branch.NameKey(projectName, name));
}
}
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
index 5cfde6a..7265470 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
@@ -39,6 +39,7 @@
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.StandardKeyEncoder;
+import com.google.inject.Provider;
import org.easymock.IExpectationSetters;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
@@ -84,6 +85,7 @@
mockDb = createStrictMock(Repository.class);
pc = createStrictMock(ProjectControl.class);
+ expect(pc.getProject()).andReturn(new Project(name)).anyTimes();
pcf = createStrictMock(ProjectControl.Factory.class);
grm = createStrictMock(GitRepositoryManager.class);
refMocks = new ArrayList<RefControl>();
@@ -125,7 +127,7 @@
validate().andThrow(err);
doReplay();
try {
- new ListBranches(pcf, grm, name).call();
+ new ListBranches(pcf, createListBranchesProvider(grm), name).call();
fail("did not throw when expected not authorized");
} catch (NoSuchProjectException e2) {
assertSame(err, e2);
@@ -161,7 +163,8 @@
expectLastCall();
doReplay();
- final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
+ final ListBranchesResult r =
+ new ListBranches(pcf, createListBranchesProvider(grm), name).call();
doVerify();
assertNotNull(r);
assertNotNull(r.getBranches());
@@ -302,7 +305,8 @@
expectLastCall();
doReplay();
- final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
+ final ListBranchesResult r =
+ new ListBranches(pcf, createListBranchesProvider(grm), name).call();
doVerify();
assertNotNull(r);
@@ -330,7 +334,8 @@
expectLastCall();
doReplay();
- final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
+ final ListBranchesResult r =
+ new ListBranches(pcf, createListBranchesProvider(grm), name).call();
doVerify();
assertNotNull(r);
assertTrue(r.getBranches().isEmpty());
@@ -359,7 +364,8 @@
expectLastCall();
doReplay();
- final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
+ final ListBranchesResult r =
+ new ListBranches(pcf, createListBranchesProvider(grm), name).call();
doVerify();
assertNotNull(r);
@@ -371,4 +377,14 @@
assertEquals("bar", r.getBranches().get(1).getShortName());
assertFalse(r.getBranches().get(1).getCanDelete());
}
+
+ private static Provider<com.google.gerrit.server.project.ListBranches> createListBranchesProvider(
+ final GitRepositoryManager grm) {
+ return new Provider<com.google.gerrit.server.project.ListBranches>() {
+ @Override
+ public com.google.gerrit.server.project.ListBranches get() {
+ return new com.google.gerrit.server.project.ListBranches(grm);
+ }
+ };
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
new file mode 100644
index 0000000..016222d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 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.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.inject.TypeLiteral;
+
+public class BranchResource extends ProjectResource {
+ public static final TypeLiteral<RestView<BranchResource>> BRANCH_KIND =
+ new TypeLiteral<RestView<BranchResource>>() {};
+
+ private final Branch.NameKey branch;
+
+ public BranchResource(ProjectControl control, Branch.NameKey branch) {
+ super(control);
+ this.branch = branch;
+ }
+
+ public Branch.NameKey getBranch() {
+ return branch;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
new file mode 100644
index 0000000..fc7343b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 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.Provider;
+
+public class BranchesCollection implements
+ ChildCollection<ProjectResource, BranchResource> {
+ private final DynamicMap<RestView<BranchResource>> views;
+ private final Provider<ListBranches> list;
+
+ @Inject
+ BranchesCollection(DynamicMap<RestView<BranchResource>> views,
+ Provider<ListBranches> list) {
+ this.views = views;
+ this.list = list;
+ }
+
+ @Override
+ public RestView<ProjectResource> list() {
+ return list.get();
+ }
+
+ @Override
+ public BranchResource parse(ProjectResource parent, IdString id)
+ throws ResourceNotFoundException {
+ throw new ResourceNotFoundException(id);
+ }
+
+ @Override
+ public DynamicMap<RestView<BranchResource>> views() {
+ return views;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
new file mode 100644
index 0000000..b01c757
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -0,0 +1,160 @@
+// Copyright (C) 2013 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.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+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 java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ListBranches implements RestReadView<ProjectResource> {
+
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ public ListBranches(GitRepositoryManager repoManager) {
+ this.repoManager = repoManager;
+ }
+
+ @Override
+ public List<BranchInfo> apply(ProjectResource rsrc)
+ throws ResourceNotFoundException, IOException {
+ List<BranchInfo> branches = Lists.newArrayList();
+
+ BranchInfo headBranch = null;
+ BranchInfo configBranch = null;
+ final Set<String> targets = Sets.newHashSet();
+
+ final Repository db;
+ try {
+ db = repoManager.openRepository(rsrc.getNameKey());
+ } catch (RepositoryNotFoundException noGitRepository) {
+ throw new ResourceNotFoundException();
+ }
+
+ try {
+ final Map<String, Ref> all = db.getAllRefs();
+
+ if (!all.containsKey(Constants.HEAD)) {
+ // The branch pointed to by HEAD doesn't exist yet, so getAllRefs
+ // filtered it out. If we ask for it individually we can find the
+ // underlying target and put it into the map anyway.
+ //
+ try {
+ Ref head = db.getRef(Constants.HEAD);
+ if (head != null) {
+ all.put(Constants.HEAD, head);
+ }
+ } catch (IOException e) {
+ // Ignore the failure reading HEAD.
+ }
+ }
+
+ for (final Ref ref : all.values()) {
+ if (ref.isSymbolic()) {
+ targets.add(ref.getTarget().getName());
+ }
+ }
+
+ for (final Ref ref : all.values()) {
+ if (ref.isSymbolic()) {
+ // A symbolic reference to another branch, instead of
+ // showing the resolved value, show the name it references.
+ //
+ String target = ref.getTarget().getName();
+ RefControl targetRefControl = rsrc.getControl().controlForRef(target);
+ if (!targetRefControl.isVisible()) {
+ continue;
+ }
+ if (target.startsWith(Constants.R_HEADS)) {
+ target = target.substring(Constants.R_HEADS.length());
+ }
+
+ BranchInfo b = new BranchInfo();
+ b.ref = ref.getName();
+ b.revision = target;
+
+ if (Constants.HEAD.equals(ref.getName())) {
+ b.setCanDelete(false);
+ headBranch = b;
+ } else {
+ b.setCanDelete(targetRefControl.canDelete());
+ branches.add(b);
+ }
+ continue;
+ }
+
+ final RefControl refControl = rsrc.getControl().controlForRef(ref.getName());
+ if (refControl.isVisible()) {
+ if (ref.getName().startsWith(Constants.R_HEADS)) {
+ branches.add(createBranchInfo(ref, refControl, targets));
+ } else if (GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
+ configBranch = createBranchInfo(ref, refControl, targets);
+ }
+ }
+ }
+ } finally {
+ db.close();
+ }
+ Collections.sort(branches, new Comparator<BranchInfo>() {
+ @Override
+ public int compare(final BranchInfo a, final BranchInfo b) {
+ return a.ref.compareTo(b.ref);
+ }
+ });
+ if (configBranch != null) {
+ branches.add(0, configBranch);
+ }
+ if (headBranch != null) {
+ branches.add(0, headBranch);
+ }
+ return branches;
+ }
+
+ private static BranchInfo createBranchInfo(Ref ref, RefControl refControl,
+ Set<String> targets) {
+ BranchInfo b = new BranchInfo();
+ b.ref = ref.getName();
+ if (ref.getObjectId() != null) {
+ b.revision = ref.getObjectId().name();
+ }
+ b.setCanDelete(!targets.contains(ref.getName()) && refControl.canDelete());
+ return b;
+ }
+
+ public static class BranchInfo {
+ public String ref;
+ public String revision;
+ public Boolean canDelete;
+
+ void setCanDelete(boolean canDelete) {
+ this.canDelete = canDelete ? true : null;
+ }
+ }
+}
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 d979245..86e9a2e 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
@@ -14,13 +14,13 @@
package com.google.gerrit.server.project;
+import static com.google.gerrit.server.project.BranchResource.BRANCH_KIND;
import static com.google.gerrit.server.project.ChildProjectResource.CHILD_PROJECT_KIND;
import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND;
import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
-import com.google.gerrit.server.project.CreateProject;
import com.google.inject.assistedinject.FactoryModuleBuilder;
public class Module extends RestApiModule {
@@ -31,6 +31,7 @@
DynamicMap.mapOf(binder(), PROJECT_KIND);
DynamicMap.mapOf(binder(), CHILD_PROJECT_KIND);
+ DynamicMap.mapOf(binder(), BRANCH_KIND);
DynamicMap.mapOf(binder(), DASHBOARD_KIND);
put(PROJECT_KIND).to(PutProject.class);
@@ -51,6 +52,8 @@
get(PROJECT_KIND, "statistics.git").to(GetStatistics.class);
post(PROJECT_KIND, "gc").to(GarbageCollect.class);
+ child(PROJECT_KIND, "branches").to(BranchesCollection.class);
+
child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
get(DASHBOARD_KIND).to(GetDashboard.class);
put(DASHBOARD_KIND).to(SetDashboard.class);