Reuse visibility check of revision when checking old revision

The most typical revision/old revision pair is foo and foo^. Since we
just checked that foo is reachable, reuse this information when
checking that foo^ is reachable.

Change-Id: I689ace9acfb248689f3893c4e77b8e87a6341c3f
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/RevisionParser.java b/gitiles-servlet/src/main/java/com/google/gitiles/RevisionParser.java
index 2d27e2c..743b909 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/RevisionParser.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/RevisionParser.java
@@ -217,11 +217,12 @@
       // Name contains a visible ref; skip expensive reachability check.
       return true;
     }
-    if (!cache.isVisible(repo, walk, access, result.getRevision().getId())) {
+    ObjectId id = result.getRevision().getId();
+    if (!cache.isVisible(repo, walk, access, id)) {
       return false;
     }
     if (result.getOldRevision() != null && result.getOldRevision() != Revision.NULL) {
-      return cache.isVisible(repo, walk, access, result.getOldRevision().getId());
+      return cache.isVisible(repo, walk, access, result.getOldRevision().getId(), id);
     } else {
       return true;
     }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/VisibilityCache.java b/gitiles-servlet/src/main/java/com/google/gitiles/VisibilityCache.java
index 449f415..c1b11f8 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/VisibilityCache.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/VisibilityCache.java
@@ -23,6 +23,7 @@
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -38,11 +39,13 @@
 import org.eclipse.jgit.revwalk.RevSort;
 import org.eclipse.jgit.revwalk.RevWalk;
 
+import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Throwables;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.Collections2;
 import com.google.common.util.concurrent.ExecutionError;
 
 /** Cache of per-user object visibility. */
@@ -107,14 +110,14 @@
   }
 
   boolean isVisible(final Repository repo, final RevWalk walk, GitilesAccess access,
-      final ObjectId id) throws IOException {
+      final ObjectId id, final ObjectId... knownReachable) throws IOException {
     try {
       return cache.get(
           new Key(access.getUserKey(), access.getRepositoryName(), id),
           new Callable<Boolean>() {
             @Override
             public Boolean call() throws IOException {
-              return isVisible(repo, walk, id);
+              return isVisible(repo, walk, id, Arrays.asList(knownReachable));
             }
           });
     } catch (ExecutionException e) {
@@ -131,7 +134,8 @@
     }
   }
 
-  private boolean isVisible(Repository repo, RevWalk walk, ObjectId id) throws IOException {
+  private boolean isVisible(Repository repo, RevWalk walk, ObjectId id,
+      Collection<ObjectId> knownReachable) throws IOException {
     RevCommit commit;
     try {
       commit = walk.parseCommit(id);
@@ -153,9 +157,10 @@
     // 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 isReachableFrom(walk, commit, filter(allRefs, refStartsWith(R_HEADS)))
-        || isReachableFrom(walk, commit, filter(allRefs, refStartsWith(R_TAGS)))
-        || isReachableFrom(walk, commit, filter(allRefs, otherRefs()));
+    return isReachableFrom(walk, commit, knownReachable)
+        || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_HEADS)))
+        || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_TAGS)))
+        || isReachableFromRefs(walk, commit, filter(allRefs, otherRefs()));
   }
 
   private static Predicate<Ref> refStartsWith(final String prefix) {
@@ -172,9 +177,24 @@
     return not(or(refStartsWith(R_HEADS), refStartsWith(R_TAGS), refStartsWith("refs/changes/")));
   }
 
-  private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<Ref> refs)
+  private boolean isReachableFromRefs(RevWalk walk, RevCommit commit,
+      Collection<Ref> refs) throws IOException {
+    return isReachableFrom(walk, commit,
+        Collections2.transform(refs, new Function<Ref, ObjectId>() {
+          @Override
+          public ObjectId apply(Ref ref) {
+            if (ref.getPeeledObjectId() != null) {
+              return ref.getPeeledObjectId();
+            } else {
+              return ref.getObjectId();
+            }
+          }
+        }));
+  }
+
+  private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids)
       throws IOException {
-    if (refs.isEmpty()) {
+    if (ids.isEmpty()) {
       return false;
     }
     walk.reset();
@@ -182,14 +202,10 @@
       walk.sort(RevSort.TOPO);
     }
     walk.markStart(commit);
-    for (Ref ref : refs) {
-      if (ref.getPeeledObjectId() != null) {
-        markUninteresting(walk, ref.getPeeledObjectId());
-      } else {
-        markUninteresting(walk, ref.getObjectId());
-      }
+    for (ObjectId id : ids) {
+      markUninteresting(walk, id);
     }
-    // If the commit is reachable from any branch head, it will appear to be
+    // If the commit is reachable from any given tip, it will appear to be
     // uninteresting to the RevWalk and no output will be produced.
     return walk.next() == null;
   }