Optimize BranchesCollection.parse for large repositories
Repositories with excessively large number of branches suffer from very slow times looking up a branch in either the REST API, or the internal GerritApi.projects().project(p).branch(b) API that may be commonly used by plugins. Remove BranchInfo from BranchResource; it only needs to know the name and the revision. Defer construction of the full BranchInfo to GetBranch.apply(), where the action map and additional data is actually required. During BranchesCollection.parse(), verify the branch exists using the single-ref exactRef() operation, and check the caller has access to the branch according to PermissionBackend. This avoids considering every single branch in the repository when the operation only needs information on a single branch to continue. Change-Id: If2284dbcc45dcac8b2467426c5719e39be83e30a
This commit is contained in:
parent
6467883f3a
commit
41c36201f9
@ -23,12 +23,14 @@ import com.google.gerrit.extensions.api.projects.ReflogEntryInfo;
|
|||||||
import com.google.gerrit.extensions.restapi.BinaryResult;
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
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.permissions.PermissionBackendException;
|
||||||
import com.google.gerrit.server.project.BranchResource;
|
import com.google.gerrit.server.project.BranchResource;
|
||||||
import com.google.gerrit.server.project.BranchesCollection;
|
import com.google.gerrit.server.project.BranchesCollection;
|
||||||
import com.google.gerrit.server.project.CreateBranch;
|
import com.google.gerrit.server.project.CreateBranch;
|
||||||
import com.google.gerrit.server.project.DeleteBranch;
|
import com.google.gerrit.server.project.DeleteBranch;
|
||||||
import com.google.gerrit.server.project.FileResource;
|
import com.google.gerrit.server.project.FileResource;
|
||||||
import com.google.gerrit.server.project.FilesCollection;
|
import com.google.gerrit.server.project.FilesCollection;
|
||||||
|
import com.google.gerrit.server.project.GetBranch;
|
||||||
import com.google.gerrit.server.project.GetContent;
|
import com.google.gerrit.server.project.GetContent;
|
||||||
import com.google.gerrit.server.project.GetReflog;
|
import com.google.gerrit.server.project.GetReflog;
|
||||||
import com.google.gerrit.server.project.ProjectResource;
|
import com.google.gerrit.server.project.ProjectResource;
|
||||||
@ -46,6 +48,7 @@ public class BranchApiImpl implements BranchApi {
|
|||||||
private final CreateBranch.Factory createBranchFactory;
|
private final CreateBranch.Factory createBranchFactory;
|
||||||
private final DeleteBranch deleteBranch;
|
private final DeleteBranch deleteBranch;
|
||||||
private final FilesCollection filesCollection;
|
private final FilesCollection filesCollection;
|
||||||
|
private final GetBranch getBranch;
|
||||||
private final GetContent getContent;
|
private final GetContent getContent;
|
||||||
private final GetReflog getReflog;
|
private final GetReflog getReflog;
|
||||||
private final String ref;
|
private final String ref;
|
||||||
@ -57,6 +60,7 @@ public class BranchApiImpl implements BranchApi {
|
|||||||
CreateBranch.Factory createBranchFactory,
|
CreateBranch.Factory createBranchFactory,
|
||||||
DeleteBranch deleteBranch,
|
DeleteBranch deleteBranch,
|
||||||
FilesCollection filesCollection,
|
FilesCollection filesCollection,
|
||||||
|
GetBranch getBranch,
|
||||||
GetContent getContent,
|
GetContent getContent,
|
||||||
GetReflog getReflog,
|
GetReflog getReflog,
|
||||||
@Assisted ProjectResource project,
|
@Assisted ProjectResource project,
|
||||||
@ -65,6 +69,7 @@ public class BranchApiImpl implements BranchApi {
|
|||||||
this.createBranchFactory = createBranchFactory;
|
this.createBranchFactory = createBranchFactory;
|
||||||
this.deleteBranch = deleteBranch;
|
this.deleteBranch = deleteBranch;
|
||||||
this.filesCollection = filesCollection;
|
this.filesCollection = filesCollection;
|
||||||
|
this.getBranch = getBranch;
|
||||||
this.getContent = getContent;
|
this.getContent = getContent;
|
||||||
this.getReflog = getReflog;
|
this.getReflog = getReflog;
|
||||||
this.project = project;
|
this.project = project;
|
||||||
@ -84,7 +89,7 @@ public class BranchApiImpl implements BranchApi {
|
|||||||
@Override
|
@Override
|
||||||
public BranchInfo get() throws RestApiException {
|
public BranchInfo get() throws RestApiException {
|
||||||
try {
|
try {
|
||||||
return resource().getBranchInfo();
|
return getBranch.apply(resource());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw asRestApiException("Cannot read branch", e);
|
throw asRestApiException("Cannot read branch", e);
|
||||||
}
|
}
|
||||||
@ -113,12 +118,13 @@ public class BranchApiImpl implements BranchApi {
|
|||||||
public List<ReflogEntryInfo> reflog() throws RestApiException {
|
public List<ReflogEntryInfo> reflog() throws RestApiException {
|
||||||
try {
|
try {
|
||||||
return getReflog.apply(resource());
|
return getReflog.apply(resource());
|
||||||
} catch (IOException e) {
|
} catch (IOException | PermissionBackendException e) {
|
||||||
throw new RestApiException("Cannot retrieve reflog", e);
|
throw new RestApiException("Cannot retrieve reflog", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BranchResource resource() throws RestApiException, IOException {
|
private BranchResource resource()
|
||||||
|
throws RestApiException, IOException, PermissionBackendException {
|
||||||
return branches.parse(project, IdString.fromDecoded(ref));
|
return branches.parse(project, IdString.fromDecoded(ref));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,37 +14,35 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
|
||||||
import com.google.gerrit.extensions.restapi.RestView;
|
import com.google.gerrit.extensions.restapi.RestView;
|
||||||
import com.google.gerrit.reviewdb.client.Branch;
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
|
||||||
public class BranchResource extends RefResource {
|
public class BranchResource extends RefResource {
|
||||||
public static final TypeLiteral<RestView<BranchResource>> BRANCH_KIND =
|
public static final TypeLiteral<RestView<BranchResource>> BRANCH_KIND =
|
||||||
new TypeLiteral<RestView<BranchResource>>() {};
|
new TypeLiteral<RestView<BranchResource>>() {};
|
||||||
|
|
||||||
private final BranchInfo branchInfo;
|
private final String refName;
|
||||||
|
private final String revision;
|
||||||
|
|
||||||
public BranchResource(ProjectControl control, BranchInfo branchInfo) {
|
public BranchResource(ProjectControl control, Ref ref) {
|
||||||
super(control);
|
super(control);
|
||||||
this.branchInfo = branchInfo;
|
this.refName = ref.getName();
|
||||||
}
|
this.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
|
||||||
|
|
||||||
public BranchInfo getBranchInfo() {
|
|
||||||
return branchInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Branch.NameKey getBranchKey() {
|
public Branch.NameKey getBranchKey() {
|
||||||
return new Branch.NameKey(getNameKey(), branchInfo.ref);
|
return new Branch.NameKey(getNameKey(), refName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRef() {
|
public String getRef() {
|
||||||
return branchInfo.ref;
|
return refName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRevision() {
|
public String getRevision() {
|
||||||
return branchInfo.revision;
|
return revision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,36 +14,51 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
|
||||||
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.AcceptsCreate;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
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;
|
||||||
import com.google.gerrit.extensions.restapi.RestView;
|
import com.google.gerrit.extensions.restapi.RestView;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
|
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||||
|
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||||
|
import com.google.gerrit.server.permissions.RefPermission;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class BranchesCollection
|
public class BranchesCollection
|
||||||
implements ChildCollection<ProjectResource, BranchResource>, AcceptsCreate<ProjectResource> {
|
implements ChildCollection<ProjectResource, BranchResource>, AcceptsCreate<ProjectResource> {
|
||||||
private final DynamicMap<RestView<BranchResource>> views;
|
private final DynamicMap<RestView<BranchResource>> views;
|
||||||
private final Provider<ListBranches> list;
|
private final Provider<ListBranches> list;
|
||||||
|
private final PermissionBackend permissionBackend;
|
||||||
|
private final Provider<CurrentUser> user;
|
||||||
|
private final GitRepositoryManager repoManager;
|
||||||
private final CreateBranch.Factory createBranchFactory;
|
private final CreateBranch.Factory createBranchFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BranchesCollection(
|
BranchesCollection(
|
||||||
DynamicMap<RestView<BranchResource>> views,
|
DynamicMap<RestView<BranchResource>> views,
|
||||||
Provider<ListBranches> list,
|
Provider<ListBranches> list,
|
||||||
|
PermissionBackend permissionBackend,
|
||||||
|
Provider<CurrentUser> user,
|
||||||
|
GitRepositoryManager repoManager,
|
||||||
CreateBranch.Factory createBranchFactory) {
|
CreateBranch.Factory createBranchFactory) {
|
||||||
this.views = views;
|
this.views = views;
|
||||||
this.list = list;
|
this.list = list;
|
||||||
|
this.permissionBackend = permissionBackend;
|
||||||
|
this.user = user;
|
||||||
|
this.repoManager = repoManager;
|
||||||
this.createBranchFactory = createBranchFactory;
|
this.createBranchFactory = createBranchFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,18 +69,28 @@ public class BranchesCollection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BranchResource parse(ProjectResource parent, IdString id)
|
public BranchResource parse(ProjectResource parent, IdString id)
|
||||||
throws ResourceNotFoundException, IOException, BadRequestException {
|
throws ResourceNotFoundException, IOException, PermissionBackendException {
|
||||||
String branchName = id.get();
|
Project.NameKey project = parent.getNameKey();
|
||||||
if (!branchName.equals(Constants.HEAD)) {
|
try (Repository repo = repoManager.openRepository(project)) {
|
||||||
branchName = RefNames.fullName(branchName);
|
Ref ref = repo.exactRef(RefNames.fullName(id.get()));
|
||||||
}
|
if (ref == null) {
|
||||||
List<BranchInfo> branches = list.get().apply(parent);
|
throw new ResourceNotFoundException(id);
|
||||||
for (BranchInfo b : branches) {
|
|
||||||
if (branchName.equals(b.ref)) {
|
|
||||||
return new BranchResource(parent.getControl(), b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListBranches checks the target of a symbolic reference to determine access
|
||||||
|
// rights on the symbolic reference itself. This check prevents seeing a hidden
|
||||||
|
// branch simply because the symbolic reference name was visible.
|
||||||
|
permissionBackend
|
||||||
|
.user(user)
|
||||||
|
.project(project)
|
||||||
|
.ref(ref.isSymbolic() ? ref.getTarget().getName() : ref.getName())
|
||||||
|
.check(RefPermission.READ);
|
||||||
|
return new BranchResource(parent.getControl(), ref);
|
||||||
|
} catch (AuthException notAllowed) {
|
||||||
|
throw new ResourceNotFoundException(id);
|
||||||
|
} catch (RepositoryNotFoundException noRepo) {
|
||||||
|
throw new ResourceNotFoundException();
|
||||||
}
|
}
|
||||||
throw new ResourceNotFoundException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,14 +15,24 @@
|
|||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class GetBranch implements RestReadView<BranchResource> {
|
public class GetBranch implements RestReadView<BranchResource> {
|
||||||
|
private final Provider<ListBranches> list;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
GetBranch(Provider<ListBranches> list) {
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BranchInfo apply(BranchResource rsrc) {
|
public BranchInfo apply(BranchResource rsrc) throws ResourceNotFoundException, IOException {
|
||||||
return rsrc.getBranchInfo();
|
return list.get().toBranchInfo(rsrc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import com.google.common.collect.ComparisonChain;
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
||||||
import com.google.gerrit.extensions.common.ActionInfo;
|
import com.google.gerrit.extensions.common.ActionInfo;
|
||||||
@ -128,6 +129,18 @@ public class ListBranches implements RestReadView<ProjectResource> {
|
|||||||
.filter(allBranches(rsrc));
|
.filter(allBranches(rsrc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BranchInfo toBranchInfo(BranchResource rsrc) throws IOException, ResourceNotFoundException {
|
||||||
|
try (Repository db = repoManager.openRepository(rsrc.getNameKey())) {
|
||||||
|
Ref r = db.exactRef(rsrc.getRef());
|
||||||
|
if (r == null) {
|
||||||
|
throw new ResourceNotFoundException();
|
||||||
|
}
|
||||||
|
return toBranchInfo(rsrc, ImmutableList.of(r)).get(0);
|
||||||
|
} catch (RepositoryNotFoundException noRepo) {
|
||||||
|
throw new ResourceNotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<BranchInfo> allBranches(ProjectResource rsrc)
|
private List<BranchInfo> allBranches(ProjectResource rsrc)
|
||||||
throws IOException, ResourceNotFoundException {
|
throws IOException, ResourceNotFoundException {
|
||||||
List<Ref> refs;
|
List<Ref> refs;
|
||||||
@ -142,7 +155,10 @@ public class ListBranches implements RestReadView<ProjectResource> {
|
|||||||
} catch (RepositoryNotFoundException noGitRepository) {
|
} catch (RepositoryNotFoundException noGitRepository) {
|
||||||
throw new ResourceNotFoundException();
|
throw new ResourceNotFoundException();
|
||||||
}
|
}
|
||||||
|
return toBranchInfo(rsrc, refs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BranchInfo> toBranchInfo(ProjectResource rsrc, List<Ref> refs) {
|
||||||
Set<String> targets = Sets.newHashSetWithExpectedSize(1);
|
Set<String> targets = Sets.newHashSetWithExpectedSize(1);
|
||||||
for (Ref ref : refs) {
|
for (Ref ref : refs) {
|
||||||
if (ref.isSymbolic()) {
|
if (ref.isSymbolic()) {
|
||||||
@ -213,7 +229,7 @@ public class ListBranches implements RestReadView<ProjectResource> {
|
|||||||
info.canDelete =
|
info.canDelete =
|
||||||
!targets.contains(ref.getName()) && perm.testOrFalse(RefPermission.DELETE) ? true : null;
|
!targets.contains(ref.getName()) && perm.testOrFalse(RefPermission.DELETE) ? true : null;
|
||||||
|
|
||||||
BranchResource rsrc = new BranchResource(pctl, info);
|
BranchResource rsrc = new BranchResource(pctl, ref);
|
||||||
for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
|
for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
|
||||||
if (info.actions == null) {
|
if (info.actions == null) {
|
||||||
info.actions = new TreeMap<>();
|
info.actions = new TreeMap<>();
|
||||||
|
@ -25,7 +25,6 @@ public class PutBranch implements RestModifyView<BranchResource, BranchInput> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BranchInfo apply(BranchResource rsrc, BranchInput input) throws ResourceConflictException {
|
public BranchInfo apply(BranchResource rsrc, BranchInput input) throws ResourceConflictException {
|
||||||
throw new ResourceConflictException(
|
throw new ResourceConflictException("Branch \"" + rsrc.getRef() + "\" already exists");
|
||||||
"Branch \"" + rsrc.getBranchInfo().ref + "\" already exists");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user