blob: ffc3fd8e198185e1ef0aac62e65d77170c5e34a1 [file] [log] [blame]
// 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);
try {
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);
}
}
} finally {
rw.release();
}
}
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);
}
}