[lkgs] Add -gitiles-ref option

By default, LKGS will consider any successful build,
regardless of its gitiles ref. When using this option,
only builds matching the specified -gitiles-ref will be
considered.

Usage: lkgs -builder-id fuchsia/ci/garnet-x64 -gitiles-ref
       refs/heads/master

Bug: IN-1697

Change-Id: I8dcc31eeb524af540c5b1e1d872aa385b8e5ad82
diff --git a/cmd/lkgs/main.go b/cmd/lkgs/main.go
index 6022d9f..7202d71 100644
--- a/cmd/lkgs/main.go
+++ b/cmd/lkgs/main.go
@@ -57,8 +57,9 @@
 )
 
 type buildPollError struct {
-	builder string
-	kind    pollErrorType
+	builder    string
+	gitilesRef string
+	kind       pollErrorType
 }
 
 func (bpe buildPollError) Error() string {
@@ -66,7 +67,11 @@
 	case noBuilds:
 		return fmt.Sprintf("no builds found for builder %s", bpe.builder)
 	case noGreenBuilds:
-		return fmt.Sprintf("no green build found for builder %s", bpe.builder)
+		refFilterSuffix := ""
+		if bpe.gitilesRef != "" {
+			refFilterSuffix = fmt.Sprintf(" on ref %s", bpe.gitilesRef)
+		}
+		return fmt.Sprintf("no green build found for builder %s%s", bpe.builder, refFilterSuffix)
 	case buildBucketFailure:
 		return fmt.Sprintf("buildbucker error %s", bpe.builder)
 	}
@@ -76,6 +81,7 @@
 var (
 	host       string
 	builderIDs builderFlag
+	gitilesRef string
 	output     string
 
 	// LUCI flags used to parse command-line authentication options.
@@ -86,6 +92,7 @@
 func init() {
 	flag.StringVar(&host, "host", "cr-buildbucket.appspot.com", "the buildbucket host to use (default is cr-buildbucket.appspot.com)")
 	flag.Var(&builderIDs, "builder-id", "[repeatable] name of the builders to use as a reference (e.g. fuchsia/ci/garnet-x64)")
+	flag.StringVar(&gitilesRef, "gitiles-ref", "", "optionally filter builds for matching gitiles ref (e.g. refs/heads/master). by default, no filter applied")
 	flag.StringVar(&output, "output-file", "", "name of the file to write snapshot to (default is stdout)")
 
 	authFlags = authcli.Flags{}
@@ -118,15 +125,16 @@
 			// Retrieve only the Infra & Input fields
 			// Infra.Logdog is how we retrieve the Jiri snapshot
 			// Input.GitilesCommit.Id is what we match builds on
+			// Input.GitilesCommit.Ref is what we optionally filter builds on
 			Fields: &field_mask.FieldMask{
 				Paths: []string{"builds.*.infra", "builds.*.input"},
 			},
 		})
 		if err != nil {
-			return nil, buildPollError{builderID.String(), buildBucketFailure}
+			return nil, buildPollError{builder: builderID.String(), kind: buildBucketFailure}
 		}
 		if len(res.Builds) == 0 {
-			return nil, buildPollError{builderID.String(), noBuilds}
+			return nil, buildPollError{builder: builderID.String(), kind: noBuilds}
 		}
 		buildMap[builderID.String()] = res.Builds
 	}
@@ -136,6 +144,10 @@
 		if currentBuild.Input.GitilesCommit == nil {
 			continue
 		}
+		// If gitilesRef is specified, filter out builds which do not match refs
+		if gitilesRef != "" && currentBuild.Input.GitilesCommit.Ref != gitilesRef {
+			continue
+		}
 		// If previousBuild is nil, we are at the top-level and can recurse, otherwise only recurse
 		// if we found a matching green build
 		if previousBuild == nil || currentBuild.Input.GitilesCommit.Id == previousBuild.Input.GitilesCommit.Id {
@@ -167,7 +179,7 @@
 			return currentBuild, nil
 		}
 	}
-	return nil, buildPollError{builderID.String(), noGreenBuilds}
+	return nil, buildPollError{builder: builderID.String(), gitilesRef: gitilesRef, kind: noGreenBuilds}
 }
 
 // getSnapshot retrieves the jiri snapshot from LogDog related to the build using that build's LogDog details.