maintner/maintnerd: change definition of Go release to require release branch

Previously, the list-Go-releases RPC endpoint was more flexible and
considered a Go release to exist as long as there's a git tag for it,
regardless of whether a corresponding release branch existed.

Such a situation is very unlikely to come up, and we don't want to cause
all users of this API to filter out releases without a release branch.
So just re-define a Go release to require a release branch, and
make that the API promise.

In practice, this should not have any effect because all go tags
have corresponding release branches.

Updates golang/go#17626

Change-Id: Ia7a8354000483c969e123f0f3605fd360846c40b
Reviewed-on: https://go-review.googlesource.com/c/147200
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/maintner/maintnerd/apipb/api.pb.go b/maintner/maintnerd/apipb/api.pb.go
index 93a76bd..7fa279d 100644
--- a/maintner/maintnerd/apipb/api.pb.go
+++ b/maintner/maintnerd/apipb/api.pb.go
@@ -271,7 +271,6 @@
 	TagName   string `protobuf:"bytes,4,opt,name=tag_name,json=tagName" json:"tag_name,omitempty"`
 	TagCommit string `protobuf:"bytes,5,opt,name=tag_commit,json=tagCommit" json:"tag_commit,omitempty"`
 	// Release branch information for this major-minor version pair.
-	// Empty if the corresponding release branch doesn't exist.
 	BranchName   string `protobuf:"bytes,6,opt,name=branch_name,json=branchName" json:"branch_name,omitempty"`
 	BranchCommit string `protobuf:"bytes,7,opt,name=branch_commit,json=branchCommit" json:"branch_commit,omitempty"`
 }
@@ -361,8 +360,9 @@
 	GetRef(ctx context.Context, in *GetRefRequest, opts ...grpc.CallOption) (*GetRefResponse, error)
 	// GoFindTryWork finds trybot work for the coordinator to build & test.
 	GoFindTryWork(ctx context.Context, in *GoFindTryWorkRequest, opts ...grpc.CallOption) (*GoFindTryWorkResponse, error)
-	// ListGoReleases lists Go releases. A release is considered to exist
-	// if a tag for it exists.
+	// ListGoReleases lists Go releases. A release is considered to exist for
+	// each git tag named "goX", "goX.Y", or "goX.Y.Z", as long as it has a
+	// corresponding "release-branch.goX" or "release-branch.goX.Y" release branch.
 	ListGoReleases(ctx context.Context, in *ListGoReleasesRequest, opts ...grpc.CallOption) (*ListGoReleasesResponse, error)
 }
 
@@ -420,8 +420,9 @@
 	GetRef(context.Context, *GetRefRequest) (*GetRefResponse, error)
 	// GoFindTryWork finds trybot work for the coordinator to build & test.
 	GoFindTryWork(context.Context, *GoFindTryWorkRequest) (*GoFindTryWorkResponse, error)
-	// ListGoReleases lists Go releases. A release is considered to exist
-	// if a tag for it exists.
+	// ListGoReleases lists Go releases. A release is considered to exist for
+	// each git tag named "goX", "goX.Y", or "goX.Y.Z", as long as it has a
+	// corresponding "release-branch.goX" or "release-branch.goX.Y" release branch.
 	ListGoReleases(context.Context, *ListGoReleasesRequest) (*ListGoReleasesResponse, error)
 }
 
diff --git a/maintner/maintnerd/apipb/api.proto b/maintner/maintnerd/apipb/api.proto
index f830345..d300b59 100644
--- a/maintner/maintnerd/apipb/api.proto
+++ b/maintner/maintnerd/apipb/api.proto
@@ -79,7 +79,6 @@
   string tag_commit = 5;     // "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac"
 
   // Release branch information for this major-minor version pair.
-  // Empty if the corresponding release branch doesn't exist.
   string branch_name = 6;    // "release-branch.go1.11", etc.
   string branch_commit = 7;  // most recent commit on the release branch, e.g., "edb6c16b9b62ed8586d2e3e422911d646095b7e5"
 }
@@ -97,7 +96,8 @@
   // GoFindTryWork finds trybot work for the coordinator to build & test.
   rpc GoFindTryWork(GoFindTryWorkRequest) returns (GoFindTryWorkResponse);
 
-  // ListGoReleases lists Go releases. A release is considered to exist
-  // if a tag for it exists.
+  // ListGoReleases lists Go releases. A release is considered to exist for
+  // each git tag named "goX", "goX.Y", or "goX.Y.Z", as long as it has a
+  // corresponding "release-branch.goX" or "release-branch.goX.Y" release branch.
   rpc ListGoReleases(ListGoReleasesRequest) returns (ListGoReleasesResponse);
 }
diff --git a/maintner/maintnerd/maintapi/api.go b/maintner/maintnerd/maintapi/api.go
index 80139ac..a17b116 100644
--- a/maintner/maintnerd/maintapi/api.go
+++ b/maintner/maintnerd/maintapi/api.go
@@ -302,10 +302,16 @@
 		return nil, err
 	}
 
-	// Releases are considered only to exist if they've been tagged.
+	// A release is considered to exist for each git tag named "goX", "goX.Y", or "goX.Y.Z",
+	// as long as it has a corresponding "release-branch.goX" or "release-branch.goX.Y" release branch.
 	var rs []*apipb.GoRelease
 	for v, t := range tags {
-		b := branches[v]
+		b, ok := branches[v]
+		if !ok {
+			// In the unlikely case a tag exists but there's no release branch for it,
+			// don't consider it a release. This way, callers won't have to do this work.
+			continue
+		}
 		rs = append(rs, &apipb.GoRelease{
 			Major:        v.Major,
 			Minor:        v.Minor,
diff --git a/maintner/maintnerd/maintapi/api_test.go b/maintner/maintnerd/maintapi/api_test.go
index 461ef5c..f1c02f9 100644
--- a/maintner/maintnerd/maintapi/api_test.go
+++ b/maintner/maintnerd/maintapi/api_test.go
@@ -258,6 +258,9 @@
 					{"refs/heads/release-branch.go1.42", gitHash("362986e7a4b5edc911ed55324c37106c40abe3fb")},
 					{"refs/heads/release-branch.go2", gitHash("cfbe0f14bcbf1e773f8dd9a968c80cf0b9238c59")},
 					{"refs/heads/release-branch.go1.2", gitHash("6523e1eb33ef792df04e08462ed332b95311261e")},
+
+					// It doesn't count as a release if there's no corresponding release-branch.go1.43 release branch.
+					{"refs/tags/go1.43", gitHash("3aa7f7065ecf717b1dd6512bb7a9f40625fc8cb5")},
 				},
 			},
 			want: []*apipb.GoRelease{