Improve repo sync performance by avoid git forks

By resolving the current HEAD and the manifest revision using pure
Python, we can in the common case of "no changes" avoid a lot of
git operations and directly jump out of the local sync method.

This reduces the no-op `repo sync -l` time for Android's 114 projects
from more than 6s to under 0.8s.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/project.py b/project.py
index 086f0d7..9f9cf7b 100644
--- a/project.py
+++ b/project.py
@@ -416,22 +416,31 @@
 
 ## Publish / Upload ##
 
-  def WasPublished(self, branch):
+  def WasPublished(self, branch, all=None):
     """Was the branch published (uploaded) for code review?
        If so, returns the SHA-1 hash of the last published
        state for the branch.
     """
-    try:
-      return self.bare_git.rev_parse(R_PUB + branch)
-    except GitError:
-      return None
+    key = R_PUB + branch
+    if all is None:
+      try:
+        return self.bare_git.rev_parse(key)
+      except GitError:
+        return None
+    else:
+      try:
+        return all[key]
+      except KeyError:
+        return None
 
-  def CleanPublishedCache(self):
+  def CleanPublishedCache(self, all=None):
     """Prunes any stale published refs.
     """
+    if all is None:
+      all = self._allrefs
     heads = set()
     canrm = {}
-    for name, id in self._allrefs.iteritems():
+    for name, id in all.iteritems():
       if name.startswith(R_HEADS):
         heads.add(name)
       elif name.startswith(R_PUB):
@@ -567,17 +576,31 @@
        Network access is not required.
     """
     self._InitWorkTree()
-    self.CleanPublishedCache()
+    all = self.bare_ref.all
+    self.CleanPublishedCache(all)
 
     rem = self.GetRemote(self.remote.name)
     rev = rem.ToLocal(self.revision)
-    try:
-      self.bare_git.rev_parse('--verify', '%s^0' % rev)
-    except GitError:
-      raise ManifestInvalidRevisionError(
-        'revision %s in %s not found' % (self.revision, self.name))
+    if rev in all:
+      revid = all[rev]
+    elif IsId(rev):
+      revid = rev
+    else:
+      try:
+        revid = self.bare_git.rev_parse('--verify', '%s^0' % rev)
+      except GitError:
+        raise ManifestInvalidRevisionError(
+          'revision %s in %s not found' % (self.revision, self.name))
 
-    branch = self.CurrentBranch
+    head = self.work_git.GetHead()
+    if head.startswith(R_HEADS):
+      branch = head[len(R_HEADS):]
+      try:
+        head = all[head]
+      except KeyError:
+        head = None
+    else:
+      branch = None
 
     if branch is None or syncbuf.detach_head:
       # Currently on a detached HEAD.  The user is assumed to
@@ -588,6 +611,11 @@
         syncbuf.fail(self, _PriorSyncFailedError())
         return
 
+      if head == revid:
+        # No changes; don't do anything further.
+        #
+        return
+
       lost = self._revlist(not_rev(rev), HEAD)
       if lost:
         syncbuf.info(self, "discarding %d commits", len(lost))
@@ -599,6 +627,11 @@
       self._CopyFiles()
       return
 
+    if head == revid:
+      # No changes; don't do anything further.
+      #
+      return
+
     branch = self.GetBranch(branch)
     merge = branch.LocalMerge
 
@@ -618,7 +651,7 @@
       return
 
     upstream_gain = self._revlist(not_rev(HEAD), rev)
-    pub = self.WasPublished(branch.name)
+    pub = self.WasPublished(branch.name, all)
     if pub:
       not_merged = self._revlist(not_rev(rev), pub)
       if not_merged:
@@ -1142,6 +1175,7 @@
       if not old:
         old = self.rev_parse(name)
       self.update_ref('-d', name, old)
+      self._project.bare_ref.deleted(name)
 
     def rev_list(self, *args):
       cmdv = ['rev-list']