Merge "VisibilityCache/Checker: Use streams for the reachability starters"
diff --git a/java/com/google/gitiles/VisibilityCache.java b/java/com/google/gitiles/VisibilityCache.java
index 49743d9..1d0467a 100644
--- a/java/com/google/gitiles/VisibilityCache.java
+++ b/java/com/google/gitiles/VisibilityCache.java
@@ -14,18 +14,15 @@
package com.google.gitiles;
-import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Objects.hash;
-import static java.util.stream.Collectors.toList;
-import static org.eclipse.jgit.lib.Constants.R_HEADS;
-import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.Streams;
import com.google.common.util.concurrent.ExecutionError;
import java.io.IOException;
import java.util.Arrays;
@@ -33,8 +30,10 @@
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
import java.util.stream.Stream;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
@@ -163,34 +162,38 @@
return true;
}
+ Stream<ObjectId> reachableTips =
+ importantRefsFirst(refDb.getRefsByPrefix(RefDatabase.ALL))
+ .map(VisibilityCache::refToObjectId);
+
// Check heads first under the assumption that most requests are for refs close to a head. Tags
// tend to be much further back in history and just clutter up the priority queue in the common
// case.
- return checker.isReachableFrom("knownReachable", walk, commit, knownReachable)
- || isReachableFromRefs("heads", walk, commit, refDb.getRefsByPrefix(R_HEADS).stream())
- || isReachableFromRefs("tags", walk, commit, refDb.getRefsByPrefix(R_TAGS).stream())
- || isReachableFromRefs(
- "other", walk, commit, refDb.getRefs().stream().filter(VisibilityCache::otherRefs));
+ Stream<RevCommit> startCommits =
+ Stream.concat(knownReachable.stream(), reachableTips)
+ .map(objId -> VisibilityChecker.objectIdToRevCommit(walk, objId))
+ .filter(Objects::nonNull); // Ignore missing tips
+
+ return checker.isReachableFrom("known and sorted refs", walk, commit, startCommits);
}
- private static boolean refStartsWith(Ref ref, String prefix) {
- return ref.getName().startsWith(prefix);
+ static Stream<Ref> importantRefsFirst(Collection<Ref> visibleRefs) {
+ Predicate<Ref> startsWithRefsHeads = ref -> ref.getName().startsWith(Constants.R_HEADS);
+ Predicate<Ref> startsWithRefsTags = ref -> ref.getName().startsWith(Constants.R_TAGS);
+ Predicate<Ref> startsWithRefsChanges = ref -> ref.getName().startsWith("refs/changes");
+ Predicate<Ref> allOther =
+ ref ->
+ !startsWithRefsHeads.test(ref)
+ && !startsWithRefsTags.test(ref)
+ && !startsWithRefsChanges.test(ref);
+
+ return Streams.concat(
+ visibleRefs.stream().filter(startsWithRefsHeads),
+ visibleRefs.stream().filter(startsWithRefsTags),
+ visibleRefs.stream().filter(allOther));
}
- private static boolean otherRefs(Ref r) {
- return !(refStartsWith(r, R_HEADS)
- || refStartsWith(r, R_TAGS)
- || refStartsWith(r, "refs/changes/"));
- }
-
- private boolean isReachableFromRefs(String desc, RevWalk walk, RevCommit commit, Stream<Ref> refs)
- throws IOException {
- return isReachableFrom(
- desc, walk, commit, refs.map(r -> firstNonNull(r.getPeeledObjectId(), r.getObjectId())));
- }
-
- private boolean isReachableFrom(String desc, RevWalk walk, RevCommit commit, Stream<ObjectId> ids)
- throws IOException {
- return checker.isReachableFrom(desc, walk, commit, ids.collect(toList()));
+ private static ObjectId refToObjectId(Ref ref) {
+ return ref.getObjectId() != null ? ref.getObjectId() : ref.getPeeledObjectId();
}
}
diff --git a/java/com/google/gitiles/VisibilityChecker.java b/java/com/google/gitiles/VisibilityChecker.java
index 0183ea9..4c4ac3a 100644
--- a/java/com/google/gitiles/VisibilityChecker.java
+++ b/java/com/google/gitiles/VisibilityChecker.java
@@ -17,12 +17,17 @@
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Stream;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Checks for object visibility
@@ -31,6 +36,8 @@
*/
public class VisibilityChecker {
+ private static final Logger log = LoggerFactory.getLogger(VisibilityChecker.class);
+
/**
* Check if any of the refs in {@code refDb} points to the object {@code id}.
*
@@ -55,38 +62,48 @@
* or ids referring to other kinds of objects are ignored.
* @return true if we can get to {@code commit} from the {@code starters}
* @throws IOException a pack file or loose object could not be read
+ * @deprecated see {@link #isReachableFrom(String, RevWalk, RevCommit, Stream)}
*/
+ @Deprecated
protected boolean isReachableFrom(
String description, RevWalk walk, RevCommit commit, Collection<ObjectId> starters)
throws IOException {
- if (starters.isEmpty()) {
- return false;
- }
-
- ImmutableList<RevCommit> startCommits = objectIdsToCommits(walk, starters);
- if (startCommits.isEmpty()) {
- return false;
- }
-
- return !walk.getObjectReader()
- .createReachabilityChecker(walk)
- .areAllReachable(ImmutableList.of(commit), startCommits.stream())
- .isPresent();
+ Stream<RevCommit> startCommits =
+ starters.stream().map(objId -> objectIdToRevCommit(walk, objId)).filter(Objects::nonNull);
+ return isReachableFrom(description, walk, commit, startCommits);
}
- private static ImmutableList<RevCommit> objectIdsToCommits(RevWalk walk, Collection<ObjectId> ids)
- throws IOException {
- ImmutableList.Builder<RevCommit> commits = ImmutableList.builder();
- for (ObjectId id : ids) {
- try {
- commits.add(walk.parseCommit(id));
- } catch (MissingObjectException e) {
- // TODO(ifrade): ResolveParser has already checked that the object exists in the repo.
- // Report as AssertionError.
- } catch (IncorrectObjectTypeException e) {
- // Ignore, doesn't affect commit reachability
- }
+ /**
+ * Check if {@code commit} is reachable starting from {@code starters}.
+ *
+ * @param description Description of the ids (e.g. "heads"). Mainly for tracing.
+ * @param walk The walk to use for the reachability check
+ * @param commit The starting commit. It *MUST* come from the walk in use
+ * @param starters visible commits. Anything reachable from these commits is visible. Missing ids
+ * or ids referring to other kinds of objects are ignored.
+ * @return true if we can get to {@code commit} from the {@code starters}
+ * @throws IOException a pack file or loose object could not be read
+ */
+ protected boolean isReachableFrom(
+ String description, RevWalk walk, RevCommit commit, Stream<RevCommit> starters)
+ throws MissingObjectException, IncorrectObjectTypeException, IOException {
+ return walk.getObjectReader()
+ .createReachabilityChecker(walk)
+ .areAllReachable(ImmutableList.of(commit), starters)
+ .isEmpty();
+ }
+
+ @Nullable
+ static RevCommit objectIdToRevCommit(RevWalk walk, ObjectId objectId) {
+ if (objectId == null) {
+ return null;
}
- return commits.build();
+
+ try {
+ return walk.parseCommit(objectId);
+ } catch (IOException e) {
+ log.warn("Cannot translate %s to RevCommit: %s", objectId.name(), e.getMessage());
+ return null;
+ }
}
}