| // 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); |
| } |
| } |