maintner: account for CLs changing branches

Fixes golang/go#23007

Change-Id: I1e791d1a96033e77edf077909c2a20afaea511c3
Reviewed-on: https://go-review.googlesource.com/83635
Reviewed-by: Andrew Bonventre <andybons@golang.org>
diff --git a/maintner/gerrit.go b/maintner/gerrit.go
index abb4745..f0f151a 100644
--- a/maintner/gerrit.go
+++ b/maintner/gerrit.go
@@ -262,6 +262,11 @@
 	// Previous versions are available via GerritProject.remote.
 	Commit *GitCommit
 
+	// branch is a cache of the latest "Branch: " value seen from
+	// MetaCommits' commit message values, stripped of any
+	// "refs/heads/" prefix. It's usually "master".
+	branch string
+
 	// Meta is the head of the most recent Gerrit "meta" commit
 	// for this CL. This is guaranteed to be a linear history
 	// back to a CL-specific root commit for this meta branch.
@@ -332,9 +337,17 @@
 }
 
 // Branch returns the CL's branch, with any "refs/heads/" prefix removed.
-func (cl *GerritCL) Branch() string {
-	branch, _ := lineValue(cl.firstMetaCommit().Msg, "Branch:")
-	return branch
+func (cl *GerritCL) Branch() string { return cl.branch }
+
+func (cl *GerritCL) updateBranch() {
+	for i := len(cl.MetaCommits) - 1; i >= 0; i-- {
+		mc := cl.MetaCommits[i]
+		branch, _ := lineValue(mc.Msg, "Branch:")
+		if branch != "" {
+			cl.branch = strings.TrimPrefix(branch, "refs/heads/")
+			return
+		}
+	}
 }
 
 // lineValue extracts a value from an RFC 822-style "key: value" series of lines.
@@ -803,6 +816,7 @@
 	for i := len(backwardMeta) - 1; i >= 0; i-- {
 		cl.MetaCommits = append(cl.MetaCommits, backwardMeta[i])
 	}
+	cl.updateBranch()
 }
 
 // clSliceContains reports whether cls contains cl.
diff --git a/maintner/godata/godata_test.go b/maintner/godata/godata_test.go
index 3e1b6c4..128e03d 100644
--- a/maintner/godata/godata_test.go
+++ b/maintner/godata/godata_test.go
@@ -154,3 +154,31 @@
 		}
 	}
 }
+
+// Issue 23007: a Gerrit CL can switch branches. Make sure we handle that.
+func TestGerritCLChangingBranches(t *testing.T) {
+	c := getGoData(t)
+
+	tests := []struct {
+		server, project string
+		cl              int32
+		want            string
+	}{
+		// Changed branch in the middle:
+		// (Unsubmitted at the time of this test, so if it changes back, this test
+		// may break.)
+		{"go.googlesource.com", "go", 33776, "master"},
+
+		// Submitted to boringcrypto:
+		{"go.googlesource.com", "go", 82138, "dev.boringcrypto"},
+		// Submitted to master:
+		{"go.googlesource.com", "go", 83578, "master"},
+	}
+
+	for _, tt := range tests {
+		cl := c.Gerrit().Project(tt.server, tt.project).CL(tt.cl)
+		if got := cl.Branch(); got != tt.want {
+			t.Errorf("%q, %q, CL %d = branch %q; want %q", tt.server, tt.project, tt.cl, got, tt.want)
+		}
+	}
+}