Improve performance of ReceiveCommits by reducing RevWalk load

JGit RevWalk does not perform well when a large number of objects are
added to the start set by markStart or markUninteresting. Avoid
putting existing refs/changes/ or refs/tags/ into the RevWalk and
instead use only the refs/heads namespace and the name of the branch
used in the refs/for/ push line.

Catch existing changes by looking for their exact commit SHA-1, rather
than complete ancestory. This should have roughly the same outcome for
anyone pushing a new commit on top of an existing open change, but
with lower computional cost at the server.

Change-Id: Ie2bb9176799528f6422292f3f889e3d28cbf5135
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 8316f7b..ef3530e 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.git;
 
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
@@ -28,6 +29,7 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -1116,12 +1118,22 @@
     walk.sort(RevSort.TOPO);
     walk.sort(RevSort.REVERSE, true);
     try {
+      Set<ObjectId> existing = Sets.newHashSet();
       walk.markStart(walk.parseCommit(newChange.getNewId()));
-      for (ObjectId id : existingObjects()) {
-        try {
-          walk.markUninteresting(walk.parseCommit(id));
-        } catch (IOException e) {
+      for (Ref ref : repo.getAllRefs().values()) {
+        if (ref.getObjectId() == null) {
           continue;
+        } else if (ref.getName().startsWith("refs/changes/")) {
+          existing.add(ref.getObjectId());
+        } else if (ref.getName().startsWith(R_HEADS)
+            || ref.getName().equals(destBranchCtl.getRefName())) {
+          try {
+            walk.markUninteresting(walk.parseCommit(ref.getObjectId()));
+          } catch (IOException e) {
+            log.warn(String.format("Invalid ref %s in %s",
+                ref.getName(), project.getName()), e);
+            continue;
+          }
         }
       }
 
@@ -1131,7 +1143,7 @@
         if (c == null) {
           break;
         }
-        if (replaceByCommit.containsKey(c)) {
+        if (existing.contains(c) || replaceByCommit.containsKey(c)) {
           // This commit was already scheduled to replace an existing PatchSet.
           //
           continue;