git-codereview: overlap 'git fetch -q' with Gerrit queries in pending

Right now 'git fetch -q' takes about one second because of Gerrit
advertising far too many refs. Overlap the fetch with all the other
work, to at least reduce the overall time for 'git pending'.

The Gerrit team is aware of the refs advertisement problem and
a fix is planned for the next few months. That fix will speed things
up even further.

Change-Id: Iad57da9f1af1c6eae144fe28e5f96f79234bf3cd
Reviewed-on: https://go-review.googlesource.com/67572
Reviewed-by: Austin Clements <austin@google.com>
diff --git a/git-codereview/branch.go b/git-codereview/branch.go
index 982c739..0a277ce 100644
--- a/git-codereview/branch.go
+++ b/git-codereview/branch.go
@@ -21,7 +21,6 @@
 	loadedPending bool      // following fields are valid
 	originBranch  string    // upstream origin branch
 	commitsAhead  int       // number of commits ahead of origin branch
-	commitsBehind int       // number of commits behind origin branch
 	branchpoint   string    // latest commit hash shared with origin branch
 	pending       []*Commit // pending commits, newest first (children before parents)
 }
@@ -141,6 +140,12 @@
 		return
 	}
 
+	// Note: This runs in parallel with "git fetch -q",
+	// so the commands may see a stale version of origin/master.
+	// The use of origin here is for identifying what the branch has
+	// in common with origin (what's old on the branch).
+	// Any new commits in origin do not affect that.
+
 	// Note: --topo-order means child first, then parent.
 	origin := b.OriginBranch()
 	const numField = 5
@@ -197,7 +202,12 @@
 		}
 	}
 	b.commitsAhead = len(b.pending)
-	b.commitsBehind = len(lines(cmdOutput("git", "log", "--format=format:x", b.FullName()+".."+b.OriginBranch(), "--")))
+}
+
+// CommitsBehind reports the number of commits present upstream
+// that are not present in the current branch.
+func (b *Branch) CommitsBehind() int {
+	return len(lines(cmdOutput("git", "log", "--format=format:x", b.FullName()+".."+b.OriginBranch(), "--")))
 }
 
 // Submitted reports whether some form of b's pending commit
diff --git a/git-codereview/change.go b/git-codereview/change.go
index 6e4fd54..782b2d7 100644
--- a/git-codereview/change.go
+++ b/git-codereview/change.go
@@ -61,8 +61,8 @@
 		// This applies to both local work branches and tracking branches.
 		// TODO(rsc): Test.
 		b.loadPending()
-		if b.commitsBehind > 0 {
-			printf("warning: %d commit%s behind %s; run 'git sync' to update.", b.commitsBehind, suffix(b.commitsBehind, "s"), b.OriginBranch())
+		if n := b.CommitsBehind(); n > 0 {
+			printf("warning: %d commit%s behind %s; run 'git sync' to update.", n, suffix(n, "s"), b.OriginBranch())
 		}
 	}
 
diff --git a/git-codereview/pending.go b/git-codereview/pending.go
index 83725c8..2e6fe60 100644
--- a/git-codereview/pending.go
+++ b/git-codereview/pending.go
@@ -90,9 +90,15 @@
 	}
 
 	// Fetch info about remote changes, so that we can say which branches need sync.
-	if !pendingLocal {
-		run("git", "fetch", "-q")
+	doneFetch := make(chan bool, 1)
+	if pendingLocal {
+		doneFetch <- true
+	} else {
 		http.DefaultClient.Timeout = 60 * time.Second
+		go func() {
+			run("git", "fetch", "-q")
+			doneFetch <- true
+		}()
 	}
 
 	// Build list of pendingBranch structs to be filled in.
@@ -129,6 +135,7 @@
 	for i := 0; i < n; i++ {
 		go func() {
 			for b := range work {
+				// This b.load may be using a stale origin/master ref, which is OK.
 				b.load()
 				done <- true
 			}
@@ -140,6 +147,7 @@
 	for range branches {
 		<-done
 	}
+	<-doneFetch
 
 	// Print output.
 	// If there are multiple changes in the current branch, the output splits them out into separate sections,
@@ -235,8 +243,8 @@
 		if allSubmitted(work) && len(work) > 0 {
 			tags = append(tags, "all submitted")
 		}
-		if b.commitsBehind > 0 {
-			tags = append(tags, fmt.Sprintf("%d behind", b.commitsBehind))
+		if n := b.CommitsBehind(); n > 0 {
+			tags = append(tags, fmt.Sprintf("%d behind", n))
 		}
 		if b.OriginBranch() != "origin/master" {
 			tags = append(tags, "tracking "+strings.TrimPrefix(b.OriginBranch(), "origin/"))