[build_init] Add a spec_revision flag to specify the revision of
the remote repo to fetch the spec from.

This will only affect fetching the spec for builds with a change for
a different repo (not the spec repo). Otherwise, it will use the
revision of the gitiles_commit associated with the build.

Bug: 37069
Change-Id: I5c3001bb7c021330c845bcf62dd21966b464d32c
diff --git a/cmd/build_init/checkout/checkout.go b/cmd/build_init/checkout/checkout.go
index a78ebcc..a055edb 100644
--- a/cmd/build_init/checkout/checkout.go
+++ b/cmd/build_init/checkout/checkout.go
@@ -13,8 +13,8 @@
 )
 
 // Checkout checks out the git repo at repoURL according to the given build input.
-func Checkout(input buildbucketpb.Build_Input, repoURL url.URL) error {
-	strat, err := newStrategy(input, repoURL)
+func Checkout(input buildbucketpb.Build_Input, repoURL url.URL, specRef string) error {
+	strat, err := newStrategy(input, repoURL, specRef)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/build_init/checkout/checkout_integration_test.go b/cmd/build_init/checkout/checkout_integration_test.go
index dc3504a..1d1f3c3 100644
--- a/cmd/build_init/checkout/checkout_integration_test.go
+++ b/cmd/build_init/checkout/checkout_integration_test.go
@@ -136,7 +136,7 @@
 	if isInGitCheckout() {
 		return errors.New("failed test precondition: already in a git checkout")
 	}
-	if err := Checkout(input, repoURL); err != nil {
+	if err := Checkout(input, repoURL, "HEAD"); err != nil {
 		return fmt.Errorf("checkout failed: %v", err)
 	}
 	if !isInGitCheckout() {
diff --git a/cmd/build_init/checkout/strategy.go b/cmd/build_init/checkout/strategy.go
index 2754cc5..fa295ce 100644
--- a/cmd/build_init/checkout/strategy.go
+++ b/cmd/build_init/checkout/strategy.go
@@ -34,8 +34,8 @@
 //   If the build input indicates otherwise:
 //     * error if there is no Gitiles commit or Gerrit change, to avoid masking the bug
 //       that there is no build input (nothing to test).
-//     * otherwise checkout from head.
-func newStrategy(input buildbucketpb.Build_Input, repoURL url.URL) (strategy, error) {
+//     * otherwise checkout from specRef (default is HEAD).
+func newStrategy(input buildbucketpb.Build_Input, repoURL url.URL, specRef string) (strategy, error) {
 	if len(input.GerritChanges) == 0 && input.GitilesCommit == nil {
 		return nil, errors.New("build input has no changes")
 	}
@@ -65,7 +65,7 @@
 	commit := &buildbucketpb.GitilesCommit{
 		Host:    repoURL.Host,
 		Project: strings.TrimLeft(repoURL.Path, "/"),
-		Id:      "HEAD",
+		Id:      specRef,
 	}
 	return checkoutCommit{commit}, nil
 }
diff --git a/cmd/build_init/checkout/strategy_test.go b/cmd/build_init/checkout/strategy_test.go
index 9a1af00..e953e8f 100644
--- a/cmd/build_init/checkout/strategy_test.go
+++ b/cmd/build_init/checkout/strategy_test.go
@@ -22,6 +22,9 @@
 		// The Buildbucket build input.
 		input buildbucketpb.Build_Input
 
+		// The spec ref.
+		specRef string
+
 		// The name of the expected strategy.
 		expectedStrategy strategy
 
@@ -29,7 +32,7 @@
 		expectErr bool
 	}{
 		{
-			name: "should checkout from HEAD if the gerrit change is for a different project",
+			name: "should checkout from specRef if the gerrit change is for a different project",
 			specRepoURL: url.URL{
 				Scheme: "https",
 				Host:   "fuchsia.googlesource.com",
@@ -41,11 +44,12 @@
 					Project: "different",
 				}},
 			},
+			specRef: "deadbeef",
 			expectedStrategy: &checkoutCommit{
 				commit: &buildbucketpb.GitilesCommit{
 					Host:    "fuchsia.googlesource.com",
 					Project: "repo",
-					Id:      "HEAD",
+					Id:      "deadbeef",
 				},
 			},
 		},
@@ -80,7 +84,7 @@
 			},
 		},
 		{
-			name: "should checkout from HEAD if the gitiles commit is for a different project",
+			name: "should checkout from specRef if the gitiles commit is for a different project",
 			specRepoURL: url.URL{
 				Scheme: "https",
 				Host:   "fuchsia.googlesource.com",
@@ -92,11 +96,12 @@
 					Project: "different",
 				},
 			},
+			specRef: "deadbeef",
 			expectedStrategy: &checkoutCommit{
 				commit: &buildbucketpb.GitilesCommit{
 					Host:    "fuchsia.googlesource.com",
 					Project: "repo",
-					Id:      "HEAD",
+					Id:      "deadbeef",
 				},
 			},
 		},
@@ -148,7 +153,10 @@
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			strategy, err := newStrategy(tt.input, tt.specRepoURL)
+			if tt.specRef == "" {
+				tt.specRef = "HEAD"
+			}
+			strategy, err := newStrategy(tt.input, tt.specRepoURL, tt.specRef)
 			switch {
 			case err != nil && !tt.expectErr:
 				t.Errorf("unexpected error: %v", err)
diff --git a/cmd/build_init/main.go b/cmd/build_init/main.go
index 2ade1a9..1d710ec 100644
--- a/cmd/build_init/main.go
+++ b/cmd/build_init/main.go
@@ -55,6 +55,9 @@
 
 	// The URL of the spec repository.
 	specRemote string
+
+	// The git ref of the specRemote to fetch the spec from.
+	specRef string
 )
 
 // Variables derived from user input.
@@ -70,6 +73,7 @@
 	// Required
 	flag.StringVar(&specRemote, "spec_remote", "", "The URL of the spec repository")
 	flag.StringVar(&specPath, "spec_path", "", "The path to the spec file relative to the repo root")
+	flag.StringVar(&specRef, "spec_ref", "HEAD", "The ref to fetch the spec from")
 }
 
 func main() {
@@ -77,7 +81,7 @@
 	if err := validateInputs(); err != nil {
 		log.Fatal(err)
 	}
-	os.Exit(Execute(context.Background(), build, specRepoURL, specPath))
+	os.Exit(Execute(context.Background(), build, specRepoURL, specPath, specRef))
 }
 
 func validateInputs() (err error) {
@@ -125,8 +129,8 @@
 }
 
 // Execute runs the program. Public for testing.
-func Execute(ctx context.Context, build *buildbucketpb.Build, specRepoURL *url.URL, specPath string) (code int) {
-	if err := checkout.Checkout(*build.Input, *specRepoURL); err != nil {
+func Execute(ctx context.Context, build *buildbucketpb.Build, specRepoURL *url.URL, specPath string, specRef string) (code int) {
+	if err := checkout.Checkout(*build.Input, *specRepoURL, specRef); err != nil {
 		log.Printf("failed to checkout %s: %v", specRepoURL.String(), err)
 		return ExitFatal
 	}
diff --git a/cmd/build_init/main_integration_test.go b/cmd/build_init/main_integration_test.go
index 86ab84f..6b2edf6 100644
--- a/cmd/build_init/main_integration_test.go
+++ b/cmd/build_init/main_integration_test.go
@@ -79,7 +79,7 @@
 			tb.Setup()
 			defer tb.Teardown()
 
-			code := Execute(context.Background(), tt.build, tt.repoURL, tt.specPath)
+			code := Execute(context.Background(), tt.build, tt.repoURL, tt.specPath, "HEAD")
 			if code != tt.expectedCode {
 				t.Errorf("wanted exit code %d but got %d", tt.expectedCode, code)
 			}
diff --git a/go.sum b/go.sum
index 0f596ca..eaf509a 100644
--- a/go.sum
+++ b/go.sum
@@ -99,8 +99,7 @@
 github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g=
 go.chromium.org/luci v0.0.0-20190622001554-9aebd979f94a h1:pgFYi0ZKaOOo8m2UrEaz2tO+/1RrmNzoGGRDpifQa6o=
 go.chromium.org/luci v0.0.0-20190622001554-9aebd979f94a/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM=
-go.fuchsia.dev/tools v0.0.0-20190830185646-0d28ffc4f387 h1:pWUuYRIo2XmvOn6DBuevgGTwlVmzuKJ90p1cWD7joAI=
-go.fuchsia.dev/tools v0.0.0-20190830185646-0d28ffc4f387/go.mod h1:znzJJzHv56puY8HPB9lntj0U3v3VxTxqlkHXOaFCKlE=
+go.fuchsia.dev/tools v0.0.0-20190902223517-00b2baf53fff h1:FKDLyKcMCfPoFxjcNp97H5t4T2Zr2pKt+e4e9PGW+2g=
 go.fuchsia.dev/tools v0.0.0-20190902223517-00b2baf53fff/go.mod h1:znzJJzHv56puY8HPB9lntj0U3v3VxTxqlkHXOaFCKlE=
 go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=