Access control for branch reads
Enable ref level READ +1 access control, so permissions can be fine-grained down to the branch level within the same project. There are two parts to this change: - Filter the branches that the user can see in Upload and ReceiveCommits using the new RefFilter interface in JGit. This prevents a user from fetching something they are not allowed to read. - Ensure that any object created by the user only points to objects they can already reach. This prevents the user from being able to discover objects they can't read by uploading a change that points at them. Change-Id: I55a1811694e8f568e3404f625c5f0a8bf7000cac
This commit is contained in:
parent
ee1ad9b604
commit
c4c51feff4
@ -321,14 +321,6 @@ public class ProjectRightsPanel extends Composite {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Support per-branch READ access.
|
||||
if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
|
||||
referenceTxt.setText("");
|
||||
referenceTxt.setEnabled(false);
|
||||
} else {
|
||||
referenceTxt.setEnabled(true);
|
||||
}
|
||||
|
||||
int curIndex = 0, minIndex = -1, maxIndex = -1;
|
||||
rangeMinBox.clear();
|
||||
rangeMaxBox.clear();
|
||||
|
@ -327,6 +327,10 @@ class GitWebServlet extends HttpServlet {
|
||||
try {
|
||||
final Project.NameKey nameKey = new Project.NameKey(name);
|
||||
project = projectControl.validateFor(nameKey);
|
||||
if (!project.allRefsAreVisible()) {
|
||||
// Pretend the project doesn't exist
|
||||
throw new NoSuchProjectException(nameKey);
|
||||
}
|
||||
} catch (NoSuchProjectException e) {
|
||||
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
|
@ -156,13 +156,6 @@ class AddRefRight extends Handler<ProjectDetail> {
|
||||
throw new NoSuchRefException(refPattern);
|
||||
}
|
||||
|
||||
// TODO Support per-branch READ access.
|
||||
if (ApprovalCategory.READ.equals(categoryId)
|
||||
&& !refPattern.equals("refs/*")) {
|
||||
throw new UnsupportedOperationException("READ on " + refPattern
|
||||
+ " not yet supported.");
|
||||
}
|
||||
|
||||
final AccountGroup group = groupCache.get(groupName);
|
||||
if (group == null) {
|
||||
throw new NoSuchGroupException(groupName);
|
||||
|
@ -60,8 +60,9 @@ class ListBranches extends Handler<List<Branch>> {
|
||||
@Override
|
||||
public List<Branch> call() throws NoSuchProjectException,
|
||||
RepositoryNotFoundException {
|
||||
projectControlFactory.validateFor(projectName, ProjectControl.OWNER
|
||||
| ProjectControl.VISIBLE);
|
||||
final ProjectControl pctl = projectControlFactory.validateFor( //
|
||||
projectName, //
|
||||
ProjectControl.OWNER | ProjectControl.VISIBLE);
|
||||
|
||||
final List<Branch> branches = new ArrayList<Branch>();
|
||||
Branch headBranch = null;
|
||||
@ -85,20 +86,30 @@ class ListBranches extends Handler<List<Branch>> {
|
||||
}
|
||||
|
||||
for (final Ref ref : all.values()) {
|
||||
if (Constants.HEAD.equals(ref.getName()) && ref.isSymbolic()) {
|
||||
// HEAD is a symbolic reference to another branch, instead of
|
||||
if (ref.isSymbolic()) {
|
||||
// A symbolic reference to another branch, instead of
|
||||
// showing the resolved value, show the name it references.
|
||||
//
|
||||
headBranch = createBranch(Constants.HEAD);
|
||||
String target = ref.getTarget().getName();
|
||||
if (!pctl.controlForRef(target).isVisible()) {
|
||||
continue;
|
||||
}
|
||||
if (target.startsWith(Constants.R_HEADS)) {
|
||||
target = target.substring(Constants.R_HEADS.length());
|
||||
}
|
||||
headBranch.setRevision(new RevId(target));
|
||||
|
||||
Branch b = createBranch(Constants.HEAD);
|
||||
b.setRevision(new RevId(target));
|
||||
if (Constants.HEAD.equals(ref.getName())) {
|
||||
headBranch = b;
|
||||
} else {
|
||||
branches.add(b);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ref.getName().startsWith(Constants.R_HEADS)) {
|
||||
if (ref.getName().startsWith(Constants.R_HEADS)
|
||||
&& pctl.controlForRef(ref.getName()).isVisible()) {
|
||||
final Branch b = createBranch(ref.getName());
|
||||
if (ref.getObjectId() != null) {
|
||||
b.setRevision(new RevId(ref.getObjectId().name()));
|
||||
|
@ -29,6 +29,7 @@ import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.RefControl;
|
||||
import com.google.gwtorm.client.KeyUtil;
|
||||
import com.google.gwtorm.server.StandardKeyEncoder;
|
||||
|
||||
@ -42,6 +43,7 @@ import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.SymbolicRef;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -58,6 +60,7 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
private ProjectControl.Factory pcf;
|
||||
private ProjectControl pc;
|
||||
private GitRepositoryManager grm;
|
||||
private List<RefControl> refMocks;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@ -71,6 +74,7 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
pc = createStrictMock(ProjectControl.class);
|
||||
pcf = createStrictMock(ProjectControl.Factory.class);
|
||||
grm = createStrictMock(GitRepositoryManager.class);
|
||||
refMocks = new ArrayList<RefControl>();
|
||||
}
|
||||
|
||||
private IExpectationSetters<ProjectControl> validate()
|
||||
@ -81,10 +85,12 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
|
||||
private void doReplay() {
|
||||
replay(mockDb, pc, pcf, grm);
|
||||
replay(refMocks.toArray());
|
||||
}
|
||||
|
||||
private void doVerify() {
|
||||
verify(mockDb, pc, pcf, grm);
|
||||
verify(refMocks.toArray());
|
||||
}
|
||||
|
||||
private void set(String branch, ObjectId id) throws IOException {
|
||||
@ -117,12 +123,22 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
|
||||
private List<Branch> permitted(boolean getHead)
|
||||
throws NoSuchProjectException, IOException {
|
||||
Map<String, Ref> refs = realDb.getAllRefs();
|
||||
|
||||
validate().andReturn(pc);
|
||||
expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
|
||||
expect(mockDb.getAllRefs()).andDelegateTo(realDb);
|
||||
if (getHead) {
|
||||
expect(mockDb.getRef(HEAD)).andDelegateTo(realDb);
|
||||
if (!refs.containsKey(HEAD) && realDb.getRef(HEAD) != null) {
|
||||
refs.put(HEAD, realDb.getRef(HEAD));
|
||||
}
|
||||
}
|
||||
|
||||
for (Ref ref : refs.values()) {
|
||||
assumeVisible(ref, true);
|
||||
}
|
||||
|
||||
mockDb.close();
|
||||
expectLastCall();
|
||||
|
||||
@ -133,6 +149,18 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
return r;
|
||||
}
|
||||
|
||||
private void assumeVisible(Ref ref, boolean visible) {
|
||||
RefControl rc = createStrictMock(RefControl.class);
|
||||
refMocks.add(rc);
|
||||
expect(rc.isVisible()).andReturn(visible);
|
||||
|
||||
if (ref.isSymbolic()) {
|
||||
expect(pc.controlForRef(ref.getTarget().getName())).andReturn(rc);
|
||||
} else {
|
||||
expect(pc.controlForRef(ref.getName())).andReturn(rc);
|
||||
}
|
||||
}
|
||||
|
||||
public void testEmptyProject() throws Exception {
|
||||
List<Branch> r = permitted(true);
|
||||
assertEquals(1, r.size());
|
||||
@ -227,6 +255,9 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
validate().andReturn(pc);
|
||||
expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
|
||||
expect(mockDb.getAllRefs()).andReturn(u);
|
||||
for (Ref ref : u.values()) {
|
||||
assumeVisible(ref, true);
|
||||
}
|
||||
mockDb.close();
|
||||
expectLastCall();
|
||||
|
||||
@ -240,4 +271,56 @@ public class ListBranchesTest extends LocalDiskRepositoryTestCase {
|
||||
assertEquals("bar", r.get(1).getShortName());
|
||||
assertEquals("foo", r.get(2).getShortName());
|
||||
}
|
||||
|
||||
public void testHeadNotVisible() throws Exception {
|
||||
ObjectIdRef.Unpeeled bar =
|
||||
new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA);
|
||||
Map<String, Ref> u = new LinkedHashMap<String, Ref>();
|
||||
u.put(bar.getName(), bar);
|
||||
u.put(HEAD, new SymbolicRef(HEAD, bar));
|
||||
|
||||
validate().andReturn(pc);
|
||||
expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
|
||||
expect(mockDb.getAllRefs()).andReturn(u);
|
||||
assumeVisible(bar, false);
|
||||
assumeVisible(bar, false);
|
||||
mockDb.close();
|
||||
expectLastCall();
|
||||
|
||||
doReplay();
|
||||
final List<Branch> r = new ListBranches(pcf, grm, name).call();
|
||||
doVerify();
|
||||
assertNotNull(r);
|
||||
assertTrue(r.isEmpty());
|
||||
}
|
||||
|
||||
public void testHeadVisibleButBranchHidden() throws Exception {
|
||||
ObjectIdRef.Unpeeled bar =
|
||||
new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA);
|
||||
ObjectIdRef.Unpeeled foo =
|
||||
new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "foo", idA);
|
||||
|
||||
Map<String, Ref> u = new LinkedHashMap<String, Ref>();
|
||||
u.put(bar.getName(), bar);
|
||||
u.put(HEAD, new SymbolicRef(HEAD, bar));
|
||||
u.put(foo.getName(), foo);
|
||||
|
||||
validate().andReturn(pc);
|
||||
expect(grm.openRepository(eq(name.get()))).andReturn(mockDb);
|
||||
expect(mockDb.getAllRefs()).andReturn(u);
|
||||
assumeVisible(bar, true);
|
||||
assumeVisible(bar, true);
|
||||
assumeVisible(foo, false);
|
||||
mockDb.close();
|
||||
expectLastCall();
|
||||
|
||||
doReplay();
|
||||
final List<Branch> r = new ListBranches(pcf, grm, name).call();
|
||||
doVerify();
|
||||
assertNotNull(r);
|
||||
|
||||
assertEquals(2, r.size());
|
||||
assertEquals(HEAD, r.get(0).getShortName());
|
||||
assertEquals("bar", r.get(1).getShortName());
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,10 @@ public final class Change {
|
||||
r.fromString(str);
|
||||
return r;
|
||||
}
|
||||
|
||||
public static Id fromRef(final String ref) {
|
||||
return PatchSet.Id.fromRef(ref).getParentKey();
|
||||
}
|
||||
}
|
||||
|
||||
/** Globally unique identification of this change. */
|
||||
|
@ -35,6 +35,9 @@ public interface ChangeAccess extends Access<Change, Change.Id> {
|
||||
ResultSet<Change> byProjectKey(Project.NameKey p, Change.Key key)
|
||||
throws OrmException;
|
||||
|
||||
@Query("WHERE dest.projectName = ?")
|
||||
ResultSet<Change> byProject(Project.NameKey p) throws OrmException;
|
||||
|
||||
@Query("WHERE owner = ? AND open = true ORDER BY createdOn, changeId")
|
||||
ResultSet<Change> byOwnerOpen(Account.Id id) throws OrmException;
|
||||
|
||||
|
@ -102,6 +102,10 @@ ON changes (open, sort_key);
|
||||
CREATE INDEX changes_byProjectOpen
|
||||
ON changes (open, dest_project_name, sort_key);
|
||||
|
||||
-- covers: byProject
|
||||
CREATE INDEX changes_byProject
|
||||
ON changes (dest_project_name);
|
||||
|
||||
-- covers: allClosedPrev, allClosedNext
|
||||
CREATE INDEX changes_allClosed
|
||||
ON changes (open, status, sort_key);
|
||||
|
@ -154,6 +154,10 @@ CREATE INDEX changes_allClosed
|
||||
ON changes (status, sort_key)
|
||||
WHERE open = 'N';
|
||||
|
||||
-- covers: byProject
|
||||
CREATE INDEX changes_byProject
|
||||
ON changes (dest_project_name);
|
||||
|
||||
CREATE INDEX changes_key
|
||||
ON changes (change_key);
|
||||
|
||||
|
@ -192,6 +192,12 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
||||
rp.setAllowDeletes(true);
|
||||
rp.setAllowNonFastForwards(true);
|
||||
rp.setCheckReceivedObjects(true);
|
||||
|
||||
if (!projectControl.allRefsAreVisible()) {
|
||||
rp.setCheckReferencedObjectsAreReachable(true);
|
||||
rp.setRefFilter(new VisibleRefFilter(repo, projectControl, db));
|
||||
}
|
||||
|
||||
rp.setPreReceiveHook(this);
|
||||
rp.setPostReceiveHook(this);
|
||||
}
|
||||
@ -230,6 +236,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreReceive(final ReceivePack arg0,
|
||||
final Collection<ReceiveCommand> commands) {
|
||||
parseCommands(commands);
|
||||
@ -240,6 +247,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
||||
doReplaces();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostReceive(final ReceivePack arg0,
|
||||
final Collection<ReceiveCommand> commands) {
|
||||
for (final ReceiveCommand c : commands) {
|
||||
|
@ -0,0 +1,224 @@
|
||||
// Copyright (C) 2010 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.git;
|
||||
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevTag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.RefFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class VisibleRefFilter implements RefFilter {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(VisibleRefFilter.class);
|
||||
|
||||
private final Repository db;
|
||||
private final ProjectControl projectCtl;
|
||||
private final ReviewDb reviewDb;
|
||||
|
||||
public VisibleRefFilter(final Repository db,
|
||||
final ProjectControl projectControl, final ReviewDb reviewDb) {
|
||||
this.db = db;
|
||||
this.projectCtl = projectControl;
|
||||
this.reviewDb = reviewDb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Ref> filter(Map<String, Ref> refs) {
|
||||
final Set<Change.Id> visibleChanges = visibleChanges();
|
||||
final Map<String, Ref> result = new HashMap<String, Ref>();
|
||||
final List<Ref> deferredTags = new ArrayList<Ref>();
|
||||
|
||||
for (Ref ref : refs.values()) {
|
||||
if (PatchSet.isRef(ref.getName())) {
|
||||
// Reference to a patch set is visible if the change is visible.
|
||||
//
|
||||
if (visibleChanges.contains(Change.Id.fromRef(ref.getName()))) {
|
||||
result.put(ref.getName(), ref);
|
||||
}
|
||||
|
||||
} else if (isTag(ref)) {
|
||||
// If its a tag, consider it later.
|
||||
//
|
||||
deferredTags.add(ref);
|
||||
|
||||
} else if (projectCtl.controlForRef(ref.getLeaf().getName()).isVisible()) {
|
||||
// Use the leaf to lookup the control data. If the reference is
|
||||
// symbolic we want the control around the final target. If its
|
||||
// not symbolic then getLeaf() is a no-op returning ref itself.
|
||||
//
|
||||
result.put(ref.getName(), ref);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have tags that were deferred, we need to do a revision walk
|
||||
// to identify what tags we can actually reach, and what we cannot.
|
||||
//
|
||||
if (!deferredTags.isEmpty() && !result.isEmpty()) {
|
||||
addVisibleTags(result, deferredTags);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<Change.Id> visibleChanges() {
|
||||
final Project project = projectCtl.getProject();
|
||||
try {
|
||||
final Set<Change.Id> visibleChanges = new HashSet<Change.Id>();
|
||||
for (Change change : reviewDb.changes().byProject(project.getNameKey())) {
|
||||
if (projectCtl.controlFor(change).isVisible()) {
|
||||
visibleChanges.add(change.getId());
|
||||
}
|
||||
}
|
||||
return visibleChanges;
|
||||
} catch (OrmException e) {
|
||||
log.error("Cannot load changes for project " + project.getName()
|
||||
+ ", assuming no changes are visible", e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
private void addVisibleTags(final Map<String, Ref> result,
|
||||
final List<Ref> tags) {
|
||||
final RevWalk rw = new RevWalk(db);
|
||||
final RevFlag VISIBLE = rw.newFlag("VISIBLE");
|
||||
final List<RevCommit> starts;
|
||||
|
||||
rw.carry(VISIBLE);
|
||||
starts = lookupVisibleCommits(result, rw, VISIBLE);
|
||||
|
||||
for (Ref tag : tags) {
|
||||
if (isTagVisible(rw, VISIBLE, starts, tag)) {
|
||||
result.put(tag.getName(), tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<RevCommit> lookupVisibleCommits(final Map<String, Ref> result,
|
||||
final RevWalk rw, final RevFlag VISIBLE) {
|
||||
// Lookup and cache the roots of the graph that we know we can see.
|
||||
//
|
||||
final List<RevCommit> roots = new ArrayList<RevCommit>(result.size());
|
||||
for (Ref ref : result.values()) {
|
||||
try {
|
||||
RevObject c = rw.parseAny(ref.getObjectId());
|
||||
c.add(VISIBLE);
|
||||
if (c instanceof RevCommit) {
|
||||
roots.add((RevCommit) c);
|
||||
} else if (c instanceof RevTag) {
|
||||
roots.add(rw.parseCommit(c));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
private boolean isTagVisible(final RevWalk rw, final RevFlag VISIBLE,
|
||||
final List<RevCommit> starts, Ref tag) {
|
||||
try {
|
||||
final RevObject obj = peelTag(rw, tag);
|
||||
if (obj.has(VISIBLE)) {
|
||||
// If the target is immediately visible, continue on. This case
|
||||
// is quite common as tags are often sorted alphabetically by the
|
||||
// version number, so earlier tags usually compute the data needed
|
||||
// to answer later tags with no additional effort.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof RevCommit) {
|
||||
// Cast to a commit and traverse the history to determine if
|
||||
// the commit is reachable through one or more references.
|
||||
//
|
||||
final RevCommit c = (RevCommit) obj;
|
||||
walk(rw, VISIBLE, c, starts);
|
||||
return c.has(VISIBLE);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private RevObject peelTag(final RevWalk rw, final Ref tag)
|
||||
throws MissingObjectException, IOException {
|
||||
// Try to use the peeled object identity, because it may be
|
||||
// able to save us from parsing the tag object itself.
|
||||
//
|
||||
ObjectId target = tag.getPeeledObjectId();
|
||||
if (target == null) {
|
||||
target = tag.getObjectId();
|
||||
}
|
||||
RevObject o = rw.parseAny(target);
|
||||
while (o instanceof RevTag) {
|
||||
o = ((RevTag) o).getObject();
|
||||
rw.parseHeaders(o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
private void walk(final RevWalk rw, final RevFlag VISIBLE,
|
||||
final RevCommit tagged, final List<RevCommit> starts)
|
||||
throws MissingObjectException, IncorrectObjectTypeException, IOException {
|
||||
// Reset the traversal, but keep VISIBLE flags live as they aren't
|
||||
// invalidated by the change in starting points.
|
||||
//
|
||||
rw.resetRetain(VISIBLE);
|
||||
for (RevCommit o : starts) {
|
||||
try {
|
||||
rw.markStart(o);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse the history until the tag is found.
|
||||
//
|
||||
rw.markUninteresting(tagged);
|
||||
RevCommit c;
|
||||
while ((c = rw.next()) != null) {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTag(Ref ref) {
|
||||
return ref.getLeaf().getName().startsWith(Constants.R_TAGS);
|
||||
}
|
||||
}
|
@ -119,6 +119,11 @@ public class ProjectControl {
|
||||
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 1);
|
||||
}
|
||||
|
||||
/** Can this user see all the refs in this projects? */
|
||||
public boolean allRefsAreVisible() {
|
||||
return canPerformOnAllRefs(ApprovalCategory.READ, (short) 1);
|
||||
}
|
||||
|
||||
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
|
||||
public boolean isOwner() {
|
||||
return controlForRef(RefRight.ALL).isOwner()
|
||||
|
@ -32,7 +32,7 @@ import java.util.List;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
private static final Class<? extends SchemaVersion> C = Schema_30.class;
|
||||
private static final Class<? extends SchemaVersion> C = Schema_31.class;
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
|
@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2010 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.schema;
|
||||
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gwtorm.jdbc.JdbcSchema;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
class Schema_31 extends SchemaVersion {
|
||||
@Inject
|
||||
Schema_31(Provider<Schema_30> prior) {
|
||||
super(prior);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
|
||||
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
|
||||
try {
|
||||
stmt.execute("CREATE INDEX changes_byProject"
|
||||
+ " ON changes (dest_project_name)");
|
||||
} finally {
|
||||
stmt.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,9 +14,7 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.reviewdb.Branch;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.RevId;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.WildProjectName;
|
||||
@ -29,20 +27,12 @@ import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class ListProjects extends BaseCommand {
|
||||
@Inject
|
||||
@ -61,8 +51,7 @@ final class ListProjects extends BaseCommand {
|
||||
@WildProjectName
|
||||
private Project.NameKey wildProject;
|
||||
|
||||
@Option(name = "--show-branch", aliases = {"-b"},
|
||||
usage = "displays the sha of each project in the specified branch")
|
||||
@Option(name = "--show-branch", aliases = {"-b"}, usage = "displays the sha of each project in the specified branch")
|
||||
private String showBranch;
|
||||
|
||||
@Override
|
||||
@ -76,21 +65,6 @@ final class ListProjects extends BaseCommand {
|
||||
});
|
||||
}
|
||||
|
||||
private final ObjectId getObjectIdForBranch(Project.NameKey projectName,
|
||||
String branch) {
|
||||
try {
|
||||
final Repository r = repoManager.openRepository(projectName.get());
|
||||
try {
|
||||
Ref ref = r.getRef(branch);
|
||||
return ref == null ? null : ref.getObjectId();
|
||||
} finally {
|
||||
r.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void display() throws Failure {
|
||||
final PrintWriter stdout = toPrintWriter(out);
|
||||
try {
|
||||
@ -102,19 +76,34 @@ final class ListProjects extends BaseCommand {
|
||||
}
|
||||
|
||||
final ProjectState e = projectCache.get(p.getNameKey());
|
||||
if (e != null && e.controlFor(currentUser).isVisible()) {
|
||||
if (showBranch != null) {
|
||||
ObjectId id = getObjectIdForBranch(p.getNameKey(), showBranch);
|
||||
if (id != null) {
|
||||
stdout.print(id.name() + " ");
|
||||
stdout.print(p.getName());
|
||||
stdout.println();
|
||||
}
|
||||
} else {
|
||||
stdout.print(p.getName());
|
||||
stdout.println();
|
||||
}
|
||||
if (e == null) {
|
||||
// If we can't get it from the cache, pretend its not present.
|
||||
//
|
||||
continue;
|
||||
}
|
||||
|
||||
final ProjectControl pctl = e.controlFor(currentUser);
|
||||
if (!pctl.isVisible()) {
|
||||
// Require the project itself to be visible to the user.
|
||||
//
|
||||
continue;
|
||||
}
|
||||
|
||||
if (showBranch != null) {
|
||||
final Ref ref = getBranchRef(p.getNameKey());
|
||||
if (ref == null || ref.getObjectId() == null
|
||||
|| !pctl.controlForRef(ref.getLeaf().getName()).isVisible()) {
|
||||
// No branch, or the user can't see this branch, so skip it.
|
||||
//
|
||||
continue;
|
||||
}
|
||||
|
||||
stdout.print(ref.getObjectId().name());
|
||||
stdout.print(' ');
|
||||
}
|
||||
|
||||
stdout.print(p.getName());
|
||||
stdout.println();
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
throw new Failure(1, "fatal: database error", e);
|
||||
@ -122,4 +111,17 @@ final class ListProjects extends BaseCommand {
|
||||
stdout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private Ref getBranchRef(Project.NameKey projectName) {
|
||||
try {
|
||||
final Repository r = repoManager.openRepository(projectName.get());
|
||||
try {
|
||||
return r.getRef(showBranch);
|
||||
} finally {
|
||||
r.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,10 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.git.VisibleRefFilter;
|
||||
import com.google.gerrit.sshd.AbstractGitCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.transport.UploadPack;
|
||||
|
||||
@ -22,9 +25,16 @@ import java.io.IOException;
|
||||
|
||||
/** Publishes Git repositories over SSH using the Git upload-pack protocol. */
|
||||
final class Upload extends AbstractGitCommand {
|
||||
|
||||
@Inject
|
||||
private ReviewDb db;
|
||||
|
||||
@Override
|
||||
protected void runImpl() throws IOException {
|
||||
final UploadPack up = new UploadPack(repo);
|
||||
if (!projectControl.allRefsAreVisible()) {
|
||||
up.setRefFilter(new VisibleRefFilter(repo, projectControl, db));
|
||||
}
|
||||
up.upload(in, out, err);
|
||||
}
|
||||
}
|
||||
|
2
pom.xml
2
pom.xml
@ -46,7 +46,7 @@ limitations under the License.
|
||||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<jgitVersion>0.7.1.11-g2b6c555</jgitVersion>
|
||||
<jgitVersion>0.7.1.34-gf36df5d</jgitVersion>
|
||||
<gwtormVersion>1.1.4</gwtormVersion>
|
||||
<gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
|
||||
<gwtexpuiVersion>1.2.1</gwtexpuiVersion>
|
||||
|
Loading…
x
Reference in New Issue
Block a user