diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt index 542c3c7844..4d92ada5b8 100644 --- a/Documentation/rest-api-projects.txt +++ b/Documentation/rest-api-projects.txt @@ -535,6 +535,63 @@ returned that describe the child projects. ] ---- +To resolve the child projects of a project recursively the parameter +`recursive` can be set. + +Child projects that are not visible to the calling user are ignored and +are not resolved further. + +.Request +---- + GET /projects/Public-Projects/children/?recursive HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + [ + { + "kind": "gerritcodereview#project", + "id": "gerrit", + "name": "gerrit", + "parent": "Public-Projects", + "description": "Gerrit Code Review" + }, + { + "kind": "gerritcodereview#project", + "id": "plugins%2Freplication", + "name": "plugins/replication", + "parent": "Public-Plugins", + "description": "Copies to other servers using the Git protocol" + }, + { + "kind": "gerritcodereview#project", + "id": "plugins%2Freviewnotes", + "name": "plugins/reviewnotes", + "parent": "Public-Plugins", + "description": "Annotates merged commits using notes on refs/notes/review." + }, + { + "kind": "gerritcodereview#project", + "id": "plugins%2Fsingleusergroup", + "name": "plugins/singleusergroup", + "parent": "Public-Plugins", + "description": "GroupBackend enabling users to be directly added to access rules" + }, + { + "kind": "gerritcodereview#project", + "id": "Public-Plugins", + "name": "Public-Plugins", + "parent": "Public-Projects", + "description": "Parent project for plugins/*" + } + ] +---- + [[get-child-project]] Get Child Project ~~~~~~~~~~~~~~~~~ diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java index 227f905d7b..57e938ae50 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java @@ -93,6 +93,29 @@ public class ListChildProjectsIT extends AbstractDaemonTest { assertProjects(Arrays.asList(child1, child2), children); } + @Test + public void listChildrenRecursively() throws IOException, JSchException { + SshSession sshSession = new SshSession(admin); + Project.NameKey child1 = new Project.NameKey("p1"); + createProject(sshSession, child1.get()); + createProject(sshSession, "p2"); + Project.NameKey child1_1 = new Project.NameKey("p1.1"); + createProject(sshSession, child1_1.get(), child1); + Project.NameKey child1_2 = new Project.NameKey("p1.2"); + createProject(sshSession, child1_2.get(), child1); + Project.NameKey child1_1_1 = new Project.NameKey("p1.1.1"); + createProject(sshSession, child1_1_1.get(), child1_1); + Project.NameKey child1_1_1_1 = new Project.NameKey("p1.1.1.1"); + createProject(sshSession, child1_1_1_1.get(), child1_1_1); + + RestResponse r = GET("/projects/" + child1.get() + "/children/?recursive"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + List children = + (new Gson()).fromJson(r.getReader(), + new TypeToken>() {}.getType()); + assertProjects(Arrays.asList(child1_1, child1_2, child1_1_1, child1_1_1_1), children); + } + private RestResponse GET(String endpoint) throws IOException { return session.get(endpoint); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java index e3019214ab..d4b84dd087 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java @@ -15,30 +15,49 @@ package com.google.gerrit.server.project; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.project.ProjectJson.ProjectInfo; import com.google.inject.Inject; +import org.kohsuke.args4j.Option; + import java.util.List; +import java.util.Map; public class ListChildProjects implements RestReadView { + @Option(name = "--recursive", usage = "to list child projects recursively") + private boolean recursive; + private final ProjectCache projectCache; private final AllProjectsName allProjects; private final ProjectJson json; + private final ProjectNode.Factory projectNodeFactory; @Inject ListChildProjects(ProjectCache projectCache, AllProjectsName allProjects, - ProjectJson json) { + ProjectJson json, ProjectNode.Factory projectNodeFactory) { this.projectCache = projectCache; this.allProjects = allProjects; this.json = json; + this.projectNodeFactory = projectNodeFactory; } @Override public List apply(ProjectResource rsrc) { + if (recursive) { + return getChildProjectsRecursively(rsrc.getNameKey(), + rsrc.getControl().getCurrentUser()); + } else { + return getDirectChildProjects(rsrc.getNameKey()); + } + } + + private List getDirectChildProjects(Project.NameKey parent) { List childProjects = Lists.newArrayList(); for (Project.NameKey projectName : projectCache.all()) { ProjectState e = projectCache.get(projectName); @@ -46,10 +65,42 @@ public class ListChildProjects implements RestReadView { // If we can't get it from the cache, pretend it's not present. continue; } - if (rsrc.getNameKey().equals(e.getProject().getParent(allProjects))) { + if (parent.equals(e.getProject().getParent(allProjects))) { childProjects.add(json.format(e.getProject())); } } return childProjects; } + + private List getChildProjectsRecursively(Project.NameKey parent, + CurrentUser user) { + Map projects = Maps.newHashMap(); + for (Project.NameKey name : projectCache.all()) { + ProjectState p = projectCache.get(name); + if (p == null) { + // If we can't get it from the cache, pretend it's not present. + continue; + } + projects.put(name, projectNodeFactory.create(p.getProject(), + p.controlFor(user).isVisible())); + } + for (ProjectNode key : projects.values()) { + ProjectNode node = projects.get(key.getParentName()); + if (node != null) { + node.addChild(key); + } + } + return getChildProjectsRecursively(projects.get(parent)); + } + + private List getChildProjectsRecursively(ProjectNode p) { + List allChildren = Lists.newArrayList(); + for (ProjectNode c : p.getChildren()) { + if (c.isVisible()) { + allChildren.add(json.format(c.getProject())); + allChildren.addAll(getChildProjectsRecursively(c)); + } + } + return allChildren; + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java index 1c4d7c4f76..5b4b334bde 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java @@ -57,6 +57,10 @@ public class ProjectNode implements TreeNode, Comparable { return allProjectsName.equals(project.getNameKey()); } + public Project getProject() { + return project; + } + @Override public String getDisplayName() { return project.getName(); @@ -68,7 +72,7 @@ public class ProjectNode implements TreeNode, Comparable { } @Override - public SortedSet getChildren() { + public SortedSet getChildren() { return children; }