cmd/gitmirror: kill subprocesses with SIGINT before SIGKILL

Hopefully this fixes golang/go#38887 by allowing the git command to
clean up its subprocesses.

Fixes golang/go#38887.

Change-Id: Iefcb2ee7591a8bfad0df44381a320d27a8f5b4fb
Reviewed-on: https://go-review.googlesource.com/c/build/+/325771
Trust: Heschi Kreinick <heschi@google.com>
Trust: Bryan C. Mills <bcmills@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/gitmirror/gitmirror.go b/cmd/gitmirror/gitmirror.go
index 38b87d2..c128703 100644
--- a/cmd/gitmirror/gitmirror.go
+++ b/cmd/gitmirror/gitmirror.go
@@ -375,7 +375,7 @@
 	defer cancel()
 
 	stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
-	cmd := exec.CommandContext(ctx, "git", args...)
+	cmd := exec.Command("git", args...)
 	if args[0] == "clone" {
 		// Small hack: if we're cloning, the root doesn't exist yet.
 		cmd.Dir = "/"
@@ -384,7 +384,7 @@
 	}
 	cmd.Env = append(os.Environ(), "HOME="+r.mirror.homeDir)
 	cmd.Stdout, cmd.Stderr = stdout, stderr
-	err := cmd.Run()
+	err := runCmdContext(ctx, cmd)
 	return stdout.Bytes(), stderr.Bytes(), err
 }
 
@@ -702,3 +702,31 @@
 		fmt.Fprintf(w, "%s\n", kv)
 	}
 }
+
+// runCommandContext runs cmd controlled by ctx.
+func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
+	if err := cmd.Start(); err != nil {
+		return err
+	}
+	resChan := make(chan error, 1)
+	go func() {
+		resChan <- cmd.Wait()
+	}()
+
+	select {
+	case err := <-resChan:
+		return err
+	case <-ctx.Done():
+	}
+	// Canceled. Interrupt and see if it ends voluntarily.
+	cmd.Process.Signal(os.Interrupt)
+	select {
+	case <-resChan:
+		return ctx.Err()
+	case <-time.After(time.Second):
+	}
+	// Didn't shut down in response to interrupt. Kill it hard.
+	cmd.Process.Kill()
+	<-resChan
+	return ctx.Err()
+}