cmd/racebuild: support cherry-picking CL into Go repo

When testing a change to the Go repo that is needed to help build/test
a new version of the race detector, it helps to be able to do a
racebuild run from a specific Go revision plus a cherry-picked CL on
top of that revision. This patch adds a new flag ("-cherrypick") to
support that, also a "-checkout" that can be used to check out a
stack of pending changes on Gerrit.

Updates golang/go#35006.
Updates golang/go#53539.

Change-Id: Id6c508f21e11a445c89df8457dc6a65020eee0fb
Reviewed-on: https://go-review.googlesource.com/c/build/+/415674
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/racebuild/racebuild.go b/cmd/racebuild/racebuild.go
index b37366f..95fb9a4 100644
--- a/cmd/racebuild/racebuild.go
+++ b/cmd/racebuild/racebuild.go
@@ -31,10 +31,12 @@
 )
 
 var (
-	flagGoroot    = flag.String("goroot", "", "path to Go repository to update (required)")
-	flagRev       = flag.String("rev", "", "llvm-project git revision from https://github.com/llvm/llvm-project (required)")
-	flagGoRev     = flag.String("gorev", "HEAD", "Go repository revision to use; HEAD is relative to --goroot")
-	flagPlatforms = flag.String("platforms", "all", `comma-separated platforms (such as "linux/amd64") to rebuild, or "all"`)
+	flagGoroot     = flag.String("goroot", "", "path to Go repository to update (required)")
+	flagRev        = flag.String("rev", "", "llvm-project git revision from https://github.com/llvm/llvm-project (required)")
+	flagCherryPick = flag.String("cherrypick", "", "go.googlesource.com CL reference to cherry-pick on top of Go repo (takes form 'refs/changes/NNN/<CL number>/<patchset number>') (optional)")
+	flagCheckout   = flag.String("checkout", "", "go.googlesource.com CL reference to check out on top of Go repo (takes form 'refs/changes/NNN/<CL number>/<patchset number>') (optional)")
+	flagGoRev      = flag.String("gorev", "HEAD", "Go repository revision to use; HEAD is relative to --goroot")
+	flagPlatforms  = flag.String("platforms", "all", `comma-separated platforms (such as "linux/amd64") to rebuild, or "all"`)
 )
 
 // goRev is the resolved commit ID of flagGoRev.
@@ -51,6 +53,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -68,6 +74,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -86,6 +96,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -121,6 +135,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -141,6 +159,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -180,6 +202,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 workdir=$(pwd)
 pushd /tmp
@@ -203,6 +229,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -220,6 +250,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -248,6 +282,12 @@
 cd go
 git checkout %GOREV%
 if %errorlevel% neq 0 exit /b %errorlevel%
+if "%$GOGITOP%"=="" goto nogogitop
+git fetch https://go.googlesource.com/go %GOSRCREF%
+if %errorlevel% neq 0 exit /b %errorlevel%
+git %GOGITOP% FETCH_HEAD
+if %errorlevel% neq 0 exit /b %errorlevel%
+:nogogitop
 cd ..
 git clone https://github.com/llvm/llvm-project
 if %errorlevel% neq 0 exit /b %errorlevel%
@@ -277,6 +317,10 @@
 git clone https://go.googlesource.com/go
 pushd go
 git checkout $GOREV
+if [ "$GOGITOP" != "" ]; then
+  git fetch https://go.googlesource.com/go "$GOSRCREF"
+  git $GOGITOP FETCH_HEAD
+fi
 popd
 git clone https://github.com/llvm/llvm-project
 (cd llvm-project && git checkout $REV)
@@ -337,6 +381,9 @@
 		flag.PrintDefaults()
 		os.Exit(1)
 	}
+	if *flagCherryPick != "" && *flagCheckout != "" {
+		log.Fatalf("select at most one of -cherrypick and -checkout")
+	}
 	parsePlatformsFlag()
 
 	cmd := exec.Command("git", "rev-parse", *flagGoRev)
@@ -403,6 +450,15 @@
 	return fmt.Sprintf("race_%v_%s.syso", p.OS, p.Arch)
 }
 
+func setupForGoRepoGitOp() (string, string) {
+	if *flagCherryPick != "" {
+		return "cherry-pick -n", *flagCherryPick
+	} else if *flagCheckout != "" {
+		return "checkout", *flagCheckout
+	}
+	return "", ""
+}
+
 func (p *Platform) Build(ctx context.Context) error {
 	// Create gomote instance (or reuse an existing instance for debugging).
 	var lastErr error
@@ -453,7 +509,10 @@
 	if _, err := p.Gomote(ctx, "put", "-mode=0700", p.Inst, script.Name(), targetName); err != nil {
 		return err
 	}
-	if _, err := p.Gomote(ctx, "run", "-e=REV="+*flagRev, "-e=GOREV="+goRev, p.Inst, targetName); err != nil {
+	gogitop, gosrcref := setupForGoRepoGitOp()
+	if _, err := p.Gomote(ctx, "run", "-e=REV="+*flagRev, "-e=GOREV="+goRev,
+		"-e=GOGITOP="+gogitop, "-e=GOSRCREF="+gosrcref,
+		p.Inst, targetName); err != nil {
 		return err
 	}