cmd/dist: skip GOCACHE and .git when making GOROOT read-only

When we run tests, we may need to write the test binary (and/or test
variants of its dependencies) to GOCACHE. (This also fixes several
test cases in cmd/go, which preserves the GOCACHE variable for
efficiency.)

It is highly unlikely that tests will try to modify .git, and that
directory contains many files, so don't bother with it.

Updates #30316

Change-Id: Id11136c6c64d8f0afc6c6ba5d94c9269df231052
Reviewed-on: https://go-review.googlesource.com/c/go/+/207441
Run-TryBot: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go
index dc22aad..559c61a 100644
--- a/src/cmd/dist/test.go
+++ b/src/cmd/dist/test.go
@@ -1449,7 +1449,25 @@
 		}
 	}
 
+	gocache := os.Getenv("GOCACHE")
+	if gocache == "" {
+		panic("GOCACHE not set")
+	}
+	gocacheSubdir, _ := filepath.Rel(dir, gocache)
+
 	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if suffix := strings.TrimPrefix(path, dir+string(filepath.Separator)); suffix != "" {
+			if suffix == gocacheSubdir {
+				// Leave GOCACHE writable: we may need to write test binaries into it.
+				return filepath.SkipDir
+			}
+			if suffix == ".git" {
+				// Leave Git metadata in whatever state it was in. It may contain a lot
+				// of files, and it is highly unlikely that a test will try to modify
+				// anything within that directory.
+				return filepath.SkipDir
+			}
+		}
 		if err == nil {
 			mode := info.Mode()
 			if mode&0222 != 0 && (mode.IsDir() || mode.IsRegular()) {