diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java index 11f5f2b2ba..926575bdad 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java @@ -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(); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java index 04f85a7fe7..957e08831b 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java @@ -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; diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java index 86f6c60b2d..5ff2976c72 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java @@ -156,13 +156,6 @@ class AddRefRight extends Handler { 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); 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 bc4d870819..158bed1908 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 @@ -60,8 +60,9 @@ class ListBranches extends Handler> { @Override public List call() throws NoSuchProjectException, RepositoryNotFoundException { - projectControlFactory.validateFor(projectName, ProjectControl.OWNER - | ProjectControl.VISIBLE); + final ProjectControl pctl = projectControlFactory.validateFor( // + projectName, // + ProjectControl.OWNER | ProjectControl.VISIBLE); final List branches = new ArrayList(); Branch headBranch = null; @@ -85,20 +86,30 @@ class ListBranches extends Handler> { } 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())); 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 5c83767437..1a1e9a7874 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 @@ -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 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(); } private IExpectationSetters 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 permitted(boolean getHead) throws NoSuchProjectException, IOException { + Map 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 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 u = new LinkedHashMap(); + 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 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 u = new LinkedHashMap(); + 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 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()); + } } diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java index 2571f4a598..7fafb13517 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java @@ -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. */ diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java index b95204bbb2..b589877f8f 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java @@ -35,6 +35,9 @@ public interface ChangeAccess extends Access { ResultSet byProjectKey(Project.NameKey p, Change.Key key) throws OrmException; + @Query("WHERE dest.projectName = ?") + ResultSet byProject(Project.NameKey p) throws OrmException; + @Query("WHERE owner = ? AND open = true ORDER BY createdOn, changeId") ResultSet byOwnerOpen(Account.Id id) throws OrmException; diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql index d09adb85b4..8318ad8a8f 100644 --- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql +++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql @@ -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); diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql index 86f5a93dd5..bbf0c3df91 100644 --- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql +++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql @@ -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); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java index cbc0c0cecd..c05cfb70b0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java @@ -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 commands) { parseCommands(commands); @@ -240,6 +247,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook { doReplaces(); } + @Override public void onPostReceive(final ReceivePack arg0, final Collection commands) { for (final ReceiveCommand c : commands) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java new file mode 100644 index 0000000000..05da2bede0 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java @@ -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 filter(Map refs) { + final Set visibleChanges = visibleChanges(); + final Map result = new HashMap(); + final List deferredTags = new ArrayList(); + + 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 visibleChanges() { + final Project project = projectCtl.getProject(); + try { + final Set visibleChanges = new HashSet(); + 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 result, + final List tags) { + final RevWalk rw = new RevWalk(db); + final RevFlag VISIBLE = rw.newFlag("VISIBLE"); + final List 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 lookupVisibleCommits(final Map result, + final RevWalk rw, final RevFlag VISIBLE) { + // Lookup and cache the roots of the graph that we know we can see. + // + final List roots = new ArrayList(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 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 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); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index 23a50f38c8..a2072c09c1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java @@ -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() diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java index ee05575c7f..489c16436f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java @@ -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 C = Schema_30.class; + private static final Class C = Schema_31.class; public static class Module extends AbstractModule { @Override diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_31.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_31.java new file mode 100644 index 0000000000..62f57e2109 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_31.java @@ -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 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(); + } + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java index fb714fe909..fda0448b6c 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java @@ -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; + } + } } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java index 85b8ff2274..fc67265db5 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java @@ -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); } } diff --git a/pom.xml b/pom.xml index a75fe9c37b..adac80b8c9 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ limitations under the License. - 0.7.1.11-g2b6c555 + 0.7.1.34-gf36df5d 1.1.4 1.2.2 1.2.1