internal/stdlib: support opening local repo

By calling stdlib.SetGoRepoPath, users can point to a local clone of
the Go repo for stdlib to use. This can save download time. The intent
is to use it for cmd/pkgsite.

Change-Id: I20d89f283b7b0b4f333599846eee1038d09cb50e
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/347404
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/internal/stdlib/stdlib.go b/internal/stdlib/stdlib.go
index 2c57d76..3b05de4 100644
--- a/internal/stdlib/stdlib.go
+++ b/internal/stdlib/stdlib.go
@@ -19,6 +19,7 @@
 	"path/filepath"
 	"regexp"
 	"strings"
+	"sync"
 	"time"
 
 	"golang.org/x/mod/semver"
@@ -199,12 +200,35 @@
 	TestDevFuzzVersion = "v0.0.0-20190904010203-12de34vf56uz"
 )
 
+var (
+	goRepoPathMu sync.Mutex
+	goRepoPath   string
+)
+
+// SetGoRepoPath tells this package to obtain the Go repo from the
+// local filesystem at path, instead of cloning it.
+func SetGoRepoPath(path string) {
+	goRepoPathMu.Lock()
+	defer goRepoPathMu.Unlock()
+	goRepoPath = path
+
+}
+
+func getGoRepoPath() string {
+	goRepoPathMu.Lock()
+	defer goRepoPathMu.Unlock()
+	return goRepoPath
+}
+
 // getGoRepo returns a repo object for the Go repo at version.
 func getGoRepo(version string) (_ *git.Repository, _ plumbing.ReferenceName, err error) {
 	defer derrors.Wrap(&err, "getGoRepo(%q)", version)
 	if UseTestData {
 		return getTestGoRepo(version)
 	}
+	if path := getGoRepoPath(); path != "" {
+		return openGoRepo(path, version)
+	}
 	return cloneGoRepo(version)
 }
 
@@ -213,16 +237,9 @@
 func cloneGoRepo(v string) (_ *git.Repository, ref plumbing.ReferenceName, err error) {
 	defer derrors.Wrap(&err, "cloneGoRepo(%q)", v)
 
-	if v == version.Master {
-		ref = plumbing.HEAD
-	} else if SupportedBranches[v] {
-		ref = plumbing.NewBranchReferenceName(v)
-	} else {
-		tag, err := TagForVersion(v)
-		if err != nil {
-			return nil, "", err
-		}
-		ref = plumbing.NewTagReferenceName(tag)
+	ref, err = refNameForVersion(v)
+	if err != nil {
+		return nil, "", err
 	}
 	repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
 		URL:           GoRepoURL,
@@ -237,6 +254,33 @@
 	return repo, ref, nil
 }
 
+func openGoRepo(path, v string) (_ *git.Repository, _ plumbing.ReferenceName, err error) {
+	defer derrors.Wrap(&err, "openGoRepo(%q)", v)
+	repo, err := git.PlainOpen(path)
+	if err != nil {
+		return nil, "", err
+	}
+	ref, err := refNameForVersion(v)
+	if err != nil {
+		return nil, "", err
+	}
+	return repo, ref, nil
+}
+
+func refNameForVersion(v string) (plumbing.ReferenceName, error) {
+	if v == version.Master {
+		return plumbing.HEAD, nil
+	}
+	if SupportedBranches[v] {
+		return plumbing.NewBranchReferenceName(v), nil
+	}
+	tag, err := TagForVersion(v)
+	if err != nil {
+		return "", err
+	}
+	return plumbing.NewTagReferenceName(tag), nil
+}
+
 // getTestGoRepo gets a Go repo for testing.
 func getTestGoRepo(v string) (_ *git.Repository, _ plumbing.ReferenceName, err error) {
 	defer derrors.Wrap(&err, "getTestGoRepo(%q)", v)
diff --git a/internal/stdlib/stdlib_test.go b/internal/stdlib/stdlib_test.go
index 0e20402..811c8c1 100644
--- a/internal/stdlib/stdlib_test.go
+++ b/internal/stdlib/stdlib_test.go
@@ -15,7 +15,10 @@
 	"golang.org/x/pkgsite/internal/version"
 )
 
-var clone = flag.Bool("clone", false, "test actual clones of the Go repo")
+var (
+	clone    = flag.Bool("clone", false, "test actual clones of the Go repo")
+	repoPath = flag.String("path", "", "path to Go repo to test")
+)
 
 func TestTagForVersion(t *testing.T) {
 	for _, test := range []struct {
@@ -150,23 +153,34 @@
 	}
 }
 
-func TestContentDirClone(t *testing.T) {
-	if !*clone {
-		t.Skip("-clone not supplied")
+func TestContentDirCloneAndOpen(t *testing.T) {
+	if !*clone && *repoPath == "" {
+		t.Skip("-clone and -path not supplied")
 	}
-	for _, resolvedVersion := range []string{
-		"v1.3.2",
-		"v1.14.6",
-		version.Master,
-		version.Latest,
-	} {
-		t.Run(resolvedVersion, func(t *testing.T) {
-			cdir, _, _, err := ContentDir(resolvedVersion)
-			if err != nil {
-				t.Fatal(err)
-			}
-			checkContentDirFiles(t, cdir, resolvedVersion)
-		})
+
+	run := func(t *testing.T) {
+		for _, resolvedVersion := range []string{
+			"v1.3.2",
+			"v1.14.6",
+			version.Master,
+			version.Latest,
+		} {
+			t.Run(resolvedVersion, func(t *testing.T) {
+				cdir, _, _, err := ContentDir(resolvedVersion)
+				if err != nil {
+					t.Fatal(err)
+				}
+				checkContentDirFiles(t, cdir, resolvedVersion)
+			})
+		}
+	}
+
+	if *clone {
+		t.Run("clone", run)
+	}
+	if *repoPath != "" {
+		SetGoRepoPath(*repoPath)
+		t.Run("open", run)
 	}
 }