Merge branch 'stable-2.16'

* stable-2.16:
  ListChildProjects: Make settting recursive option chainable
  ListChildProjects: Add limit option
  ListChildProjects: Use parent predicate to find child projects
  QueryProjects: Make setter methods chainable
  Project index: Add parent predicate

ListChildProjectsIT#listChildrenWithLimit is adjusted to use the
ProjectOperations interface.

Change-Id: I185c204f6acbf542f7d80454fe67ce892147473a
This commit is contained in:
David Pursehouse
2019-02-05 10:16:20 +09:00
13 changed files with 133 additions and 56 deletions

View File

@@ -12,6 +12,11 @@ name:'NAME'::
+ +
Matches projects that have exactly the name 'NAME'. Matches projects that have exactly the name 'NAME'.
[[parent]]
parent:'PARENT'::
+
Matches projects that have 'PARENT' as parent project.
[[inname]] [[inname]]
inname:'NAME':: inname:'NAME'::
+ +

View File

@@ -106,6 +106,8 @@ public interface ProjectApi {
List<ProjectInfo> children(boolean recursive) throws RestApiException; List<ProjectInfo> children(boolean recursive) throws RestApiException;
List<ProjectInfo> children(int limit) throws RestApiException;
ChildProjectApi child(String name) throws RestApiException; ChildProjectApi child(String name) throws RestApiException;
/** /**
@@ -284,6 +286,11 @@ public interface ProjectApi {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@Override
public List<ProjectInfo> children(int limit) throws RestApiException {
throw new NotImplementedException();
}
@Override @Override
public ChildProjectApi child(String name) throws RestApiException { public ChildProjectApi child(String name) throws RestApiException {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -70,7 +70,6 @@ import com.google.gerrit.server.restapi.project.GetHead;
import com.google.gerrit.server.restapi.project.GetParent; import com.google.gerrit.server.restapi.project.GetParent;
import com.google.gerrit.server.restapi.project.Index; import com.google.gerrit.server.restapi.project.Index;
import com.google.gerrit.server.restapi.project.ListBranches; import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.gerrit.server.restapi.project.ListChildProjects;
import com.google.gerrit.server.restapi.project.ListDashboards; import com.google.gerrit.server.restapi.project.ListDashboards;
import com.google.gerrit.server.restapi.project.ListTags; import com.google.gerrit.server.restapi.project.ListTags;
import com.google.gerrit.server.restapi.project.ProjectsCollection; import com.google.gerrit.server.restapi.project.ProjectsCollection;
@@ -475,10 +474,17 @@ public class ProjectApiImpl implements ProjectApi {
@Override @Override
public List<ProjectInfo> children(boolean recursive) throws RestApiException { public List<ProjectInfo> children(boolean recursive) throws RestApiException {
ListChildProjects list = children.list();
list.setRecursive(recursive);
try { try {
return list.apply(checkExists()); return children.list().withRecursive(recursive).apply(checkExists());
} catch (Exception e) {
throw asRestApiException("Cannot list children", e);
}
}
@Override
public List<ProjectInfo> children(int limit) throws RestApiException {
try {
return children.list().withLimit(limit).apply(checkExists());
} catch (Exception e) { } catch (Exception e) {
throw asRestApiException("Cannot list children", e); throw asRestApiException("Cannot list children", e);
} }

View File

@@ -150,12 +150,12 @@ class ProjectsImpl implements Projects {
private List<ProjectInfo> query(QueryRequest r) throws RestApiException { private List<ProjectInfo> query(QueryRequest r) throws RestApiException {
try { try {
QueryProjects myQueryProjects = queryProvider.get(); return queryProvider
myQueryProjects.setQuery(r.getQuery()); .get()
myQueryProjects.setLimit(r.getLimit()); .withQuery(r.getQuery())
myQueryProjects.setStart(r.getStart()); .withLimit(r.getLimit())
.withStart(r.getStart())
return myQueryProjects.apply(TopLevelResource.INSTANCE); .apply(TopLevelResource.INSTANCE);
} catch (OrmException e) { } catch (OrmException e) {
throw new RestApiException("Cannot query projects", e); throw new RestApiException("Cannot query projects", e);
} }

View File

@@ -27,6 +27,10 @@ public class ProjectPredicates {
return new ProjectPredicate(ProjectField.NAME, nameKey.get()); return new ProjectPredicate(ProjectField.NAME, nameKey.get());
} }
public static Predicate<ProjectData> parent(Project.NameKey parentNameKey) {
return new ProjectPredicate(ProjectField.PARENT_NAME, parentNameKey.get());
}
public static Predicate<ProjectData> inname(String name) { public static Predicate<ProjectData> inname(String name) {
return new ProjectPredicate(ProjectField.NAME_PART, name.toLowerCase(Locale.US)); return new ProjectPredicate(ProjectField.NAME_PART, name.toLowerCase(Locale.US));
} }

View File

@@ -44,6 +44,11 @@ public class ProjectQueryBuilder extends QueryBuilder<ProjectData> {
return ProjectPredicates.name(new Project.NameKey(name)); return ProjectPredicates.name(new Project.NameKey(name));
} }
@Operator
public Predicate<ProjectData> parent(String parentName) {
return ProjectPredicates.parent(new Project.NameKey(parentName));
}
@Operator @Operator
public Predicate<ProjectData> inname(String namePart) { public Predicate<ProjectData> inname(String namePart) {
if (namePart.isEmpty()) { if (namePart.isEmpty()) {

View File

@@ -22,9 +22,8 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.projects.IndexProjectInput; import com.google.gerrit.extensions.api.projects.IndexProjectInput;
import com.google.gerrit.extensions.common.ProjectInfo; import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.index.project.ProjectIndexer; import com.google.gerrit.index.project.ProjectIndexer;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
@@ -59,15 +58,12 @@ public class Index implements RestModifyView<ProjectResource, IndexProjectInput>
@Override @Override
public Response.Accepted apply(ProjectResource rsrc, IndexProjectInput input) public Response.Accepted apply(ProjectResource rsrc, IndexProjectInput input)
throws IOException, AuthException, OrmException, PermissionBackendException, throws IOException, OrmException, PermissionBackendException, RestApiException {
ResourceConflictException {
String response = "Project " + rsrc.getName() + " submitted for reindexing"; String response = "Project " + rsrc.getName() + " submitted for reindexing";
reindex(rsrc.getNameKey(), input.async); reindex(rsrc.getNameKey(), input.async);
if (Boolean.TRUE.equals(input.indexChildren)) { if (Boolean.TRUE.equals(input.indexChildren)) {
ListChildProjects listChildProjects = listChildProjectsProvider.get(); for (ProjectInfo child : listChildProjectsProvider.get().withRecursive(true).apply(rsrc)) {
listChildProjects.setRecursive(true);
for (ProjectInfo child : listChildProjects.apply(rsrc)) {
reindex(new Project.NameKey(child.name), input.async); reindex(new Project.NameKey(child.name), input.async);
} }

View File

@@ -17,22 +17,21 @@ package com.google.gerrit.server.restapi.project;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import com.google.gerrit.extensions.common.ProjectInfo; import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission; import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ChildProjects; import com.google.gerrit.server.project.ChildProjects;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectJson;
import com.google.gerrit.server.project.ProjectResource; import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.util.HashMap; import com.google.inject.Provider;
import java.util.List; import java.util.List;
import java.util.Map;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
public class ListChildProjects implements RestReadView<ProjectResource> { public class ListChildProjects implements RestReadView<ProjectResource> {
@@ -40,33 +39,42 @@ public class ListChildProjects implements RestReadView<ProjectResource> {
@Option(name = "--recursive", usage = "to list child projects recursively") @Option(name = "--recursive", usage = "to list child projects recursively")
private boolean recursive; private boolean recursive;
private final ProjectCache projectCache; @Option(name = "--limit", usage = "maximum number of parents projects to list")
private int limit;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final AllProjectsName allProjects;
private final ProjectJson json;
private final ChildProjects childProjects; private final ChildProjects childProjects;
private final Provider<QueryProjects> queryProvider;
@Inject @Inject
ListChildProjects( ListChildProjects(
ProjectCache projectCache,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
AllProjectsName allProjectsName, ChildProjects childProjects,
ProjectJson json, Provider<QueryProjects> queryProvider) {
ChildProjects childProjects) {
this.projectCache = projectCache;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.allProjects = allProjectsName;
this.json = json;
this.childProjects = childProjects; this.childProjects = childProjects;
this.queryProvider = queryProvider;
} }
public void setRecursive(boolean recursive) { public ListChildProjects withRecursive(boolean recursive) {
this.recursive = recursive; this.recursive = recursive;
return this;
}
public ListChildProjects withLimit(int limit) {
this.limit = limit;
return this;
} }
@Override @Override
public List<ProjectInfo> apply(ProjectResource rsrc) public List<ProjectInfo> apply(ProjectResource rsrc)
throws PermissionBackendException, ResourceConflictException { throws PermissionBackendException, OrmException, RestApiException {
if (limit < 0) {
throw new BadRequestException("limit must be a positive number");
}
if (recursive && limit != 0) {
throw new ResourceConflictException("recursive and limit options are mutually exclusive");
}
rsrc.getProjectState().checkStatePermitsRead(); rsrc.getProjectState().checkStatePermitsRead();
if (recursive) { if (recursive) {
return childProjects.list(rsrc.getNameKey()); return childProjects.list(rsrc.getNameKey());
@@ -76,22 +84,19 @@ public class ListChildProjects implements RestReadView<ProjectResource> {
} }
private List<ProjectInfo> directChildProjects(Project.NameKey parent) private List<ProjectInfo> directChildProjects(Project.NameKey parent)
throws PermissionBackendException { throws OrmException, RestApiException {
Map<Project.NameKey, Project> children = new HashMap<>(); PermissionBackend.WithUser currentUser = permissionBackend.currentUser();
for (Project.NameKey name : projectCache.all()) { return queryProvider
ProjectState c = projectCache.get(name); .get()
if (c != null .withQuery("parent:" + parent.get())
&& parent.equals(c.getProject().getParent(allProjects)) .withLimit(limit)
&& c.statePermitsRead()) { .apply(TopLevelResource.INSTANCE)
children.put(c.getNameKey(), c.getProject());
}
}
return permissionBackend
.currentUser()
.filter(ProjectPermission.ACCESS, children.keySet())
.stream() .stream()
.sorted() .filter(
.map((p) -> json.format(children.get(p))) p ->
currentUser
.project(new Project.NameKey(p.name))
.testOrFalse(ProjectPermission.ACCESS))
.collect(toList()); .collect(toList());
} }
} }

View File

@@ -49,8 +49,9 @@ public class QueryProjects implements RestReadView<TopLevelResource> {
name = "--query", name = "--query",
aliases = {"-q"}, aliases = {"-q"},
usage = "project query") usage = "project query")
public void setQuery(String query) { public QueryProjects withQuery(String query) {
this.query = query; this.query = query;
return this;
} }
@Option( @Option(
@@ -58,8 +59,9 @@ public class QueryProjects implements RestReadView<TopLevelResource> {
aliases = {"-n"}, aliases = {"-n"},
metaVar = "CNT", metaVar = "CNT",
usage = "maximum number of projects to list") usage = "maximum number of projects to list")
public void setLimit(int limit) { public QueryProjects withLimit(int limit) {
this.limit = limit; this.limit = limit;
return this;
} }
@Option( @Option(
@@ -67,8 +69,9 @@ public class QueryProjects implements RestReadView<TopLevelResource> {
aliases = {"-S"}, aliases = {"-S"},
metaVar = "CNT", metaVar = "CNT",
usage = "number of projects to skip") usage = "number of projects to skip")
public void setStart(int start) { public QueryProjects withStart(int start) {
this.start = start; this.start = start;
return this;
} }
@Inject @Inject

View File

@@ -33,6 +33,7 @@ import com.google.gerrit.server.restapi.project.ListChildProjects;
import com.google.gerrit.server.restapi.project.SetParent; import com.google.gerrit.server.restapi.project.SetParent;
import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand; import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -111,7 +112,7 @@ final class SetParentCommand extends SshCommand {
childProjects.addAll(getChildrenForReparenting(oldParent)); childProjects.addAll(getChildrenForReparenting(oldParent));
} catch (PermissionBackendException e) { } catch (PermissionBackendException e) {
throw new Failure(1, "permissions unavailable", e); throw new Failure(1, "permissions unavailable", e);
} catch (RestApiException e) { } catch (OrmException | RestApiException e) {
throw new Failure(1, "failure in request", e); throw new Failure(1, "failure in request", e);
} }
} }
@@ -148,7 +149,7 @@ final class SetParentCommand extends SshCommand {
* reparenting. * reparenting.
*/ */
private List<Project.NameKey> getChildrenForReparenting(ProjectState parent) private List<Project.NameKey> getChildrenForReparenting(ProjectState parent)
throws PermissionBackendException, RestApiException { throws PermissionBackendException, OrmException, RestApiException {
final List<Project.NameKey> childProjects = new ArrayList<>(); final List<Project.NameKey> childProjects = new ArrayList<>();
final List<Project.NameKey> excluded = new ArrayList<>(excludedChildren.size()); final List<Project.NameKey> excluded = new ArrayList<>(excludedChildren.size());
for (ProjectState excludedChild : excludedChildren) { for (ProjectState excludedChild : excludedChildren) {

View File

@@ -52,6 +52,17 @@ public class ListChildProjectsIT extends AbstractDaemonTest {
.containsExactly(child1_1, child1_2); .containsExactly(child1_1, child1_2);
} }
@Test
public void listChildrenWithLimit() throws Exception {
String prefix = RandomStringUtils.randomAlphabetic(8);
Project.NameKey child1 = projectOperations.newProject().name(prefix + "p1").create();
Project.NameKey child1_1 =
projectOperations.newProject().parent(child1).name(prefix + "p1.1").create();
projectOperations.newProject().parent(child1).name(prefix + "p1.2").create();
assertThatNameList(gApi.projects().name(child1.get()).children(1)).containsExactly(child1_1);
}
@Test @Test
public void listChildrenRecursively() throws Exception { public void listChildrenRecursively() throws Exception {
String prefix = RandomStringUtils.randomAlphabetic(8); String prefix = RandomStringUtils.randomAlphabetic(8);

View File

@@ -20,6 +20,7 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher; import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.api.GerritApi; import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.access.AccessSectionInfo; import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo; import com.google.gerrit.extensions.api.access.PermissionInfo;
@@ -48,6 +49,7 @@ import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.AccountsUpdate; import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest; import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.schema.SchemaCreator; import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ManualRequestContext; import com.google.gerrit.server.util.ManualRequestContext;
@@ -62,6 +64,7 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
@@ -93,6 +96,8 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
@Inject protected AllProjectsName allProjects; @Inject protected AllProjectsName allProjects;
@Inject protected AllUsersName allUsers;
protected LifecycleManager lifecycle; protected LifecycleManager lifecycle;
protected Injector injector; protected Injector injector;
protected AccountInfo currentUserInfo; protected AccountInfo currentUserInfo;
@@ -159,6 +164,28 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
assertQuery("name:" + projectWithHyphen.name, projectWithHyphen); assertQuery("name:" + projectWithHyphen.name, projectWithHyphen);
} }
@Test
public void byParent() throws Exception {
assertQuery("parent:project");
ProjectInfo parent = createProject(name("parent"));
assertQuery("parent:" + parent.name);
ProjectInfo child = createProject(name("child"), parent.name);
assertQuery("parent:" + parent.name, child);
}
@Test
public void byParentOfAllProjects() throws Exception {
Set<String> excludedProjects = ImmutableSet.of(allProjects.get(), allUsers.get());
ProjectInfo[] projects =
gApi.projects()
.list()
.get()
.stream()
.filter(p -> !excludedProjects.contains(p.name))
.toArray(s -> new ProjectInfo[s]);
assertQuery("parent:" + allProjects.get(), projects);
}
@Test @Test
public void byInname() throws Exception { public void byInname() throws Exception {
String namePart = getSanitizedMethodName(); String namePart = getSanitizedMethodName();
@@ -296,6 +323,13 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
return gApi.projects().create(in).get(); return gApi.projects().create(in).get();
} }
protected ProjectInfo createProject(String name, String parent) throws Exception {
ProjectInput in = new ProjectInput();
in.name = name;
in.parent = parent;
return gApi.projects().create(in).get();
}
protected ProjectInfo createProjectWithDescription(String name, String description) protected ProjectInfo createProjectWithDescription(String name, String description)
throws Exception { throws Exception {
ProjectInput in = new ProjectInput(); ProjectInput in = new ProjectInput();