cmd/go/internal/modfetch: cache info and gomod on disk

This on-disk caching was previously done inside package vgo,
but that means that other code calling modfetch directly
does not benefit from the cache entries and does unnecessary
network operations. Fix that.

This CL also renames the local cache directory root from
$GOPATH/src/v to $GOPATH/src/mod.

The "v" stood for versioned, but "mod" seems clearer
(it's the downloaded modules).

This CL also fixes a deadlock in the caching code: one Repo's
GoMod may need to call another repo's Stat and vice versa,
so it can't be that a Repo must only be used from one goroutine
at a time, or else we'll end up with a deadlock. Redefine that
any Repo must be allowed to be used from multiple goroutines
simultaneously and update the code accordingly. This eliminates
the potential deadlock.

This CL also makes the gitrepo implementation work a bit
harder to use its local information before doing any network
operations. In particular, locally-cached tags or commits are
now resolved without consulting the remote repo.

Fixes golang/go#25919.

Change-Id: I3443ffc97a1e3e465c8780fb81f263be3b6d77ae
Reviewed-on: https://go-review.googlesource.com/119475
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modcmd/verify.go b/vendor/cmd/go/internal/modcmd/verify.go
index 60a9871..e965ac9 100644
--- a/vendor/cmd/go/internal/modcmd/verify.go
+++ b/vendor/cmd/go/internal/modcmd/verify.go
@@ -29,9 +29,9 @@
 
 func verifyMod(mod module.Version) bool {
 	ok := true
-	zip := filepath.Join(vgo.SrcV, "cache", mod.Path, "/@v/", mod.Version+".zip")
+	zip := filepath.Join(vgo.SrcMod, "cache", mod.Path, "/@v/", mod.Version+".zip")
 	_, zipErr := os.Stat(zip)
-	dir := filepath.Join(vgo.SrcV, mod.Path+"@"+mod.Version)
+	dir := filepath.Join(vgo.SrcMod, mod.Path+"@"+mod.Version)
 	_, dirErr := os.Stat(dir)
 	data, err := ioutil.ReadFile(zip + "hash")
 	if err != nil {
diff --git a/vendor/cmd/go/internal/modfetch/cache.go b/vendor/cmd/go/internal/modfetch/cache.go
index c90602f..cc7a34b 100644
--- a/vendor/cmd/go/internal/modfetch/cache.go
+++ b/vendor/cmd/go/internal/modfetch/cache.go
@@ -5,11 +5,24 @@
 package modfetch
 
 import (
-	"sync"
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
 
+	"cmd/go/internal/modconv"
+	"cmd/go/internal/modfetch/codehost"
 	"cmd/go/internal/par"
+	"cmd/go/internal/semver"
 )
 
+var QuietLookup bool // do not print about lookups
+
+var CacheRoot string // $GOPATH/src/mod/cache
+
 // A cachingRepo is a cache around an underlying Repo,
 // avoiding redundant calls to ModulePath, Versions, Stat, Latest, and GoMod (but not Zip).
 // It is also safe for simultaneous use by multiple goroutines
@@ -18,9 +31,7 @@
 type cachingRepo struct {
 	path  string
 	cache par.Cache // cache for all operations
-
-	mu sync.Mutex // protects r's methods
-	r  Repo
+	r     Repo
 }
 
 func newCachingRepo(r Repo) *cachingRepo {
@@ -40,8 +51,6 @@
 		err  error
 	}
 	c := r.cache.Do("versions:"+prefix, func() interface{} {
-		r.mu.Lock()
-		defer r.mu.Unlock()
 		list, err := r.r.Versions(prefix)
 		return cached{list, err}
 	}).(cached)
@@ -52,17 +61,36 @@
 	return append([]string(nil), c.list...), nil
 }
 
+type cachedInfo struct {
+	info *RevInfo
+	err  error
+}
+
 func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
-	type cached struct {
-		info *RevInfo
-		err  error
-	}
 	c := r.cache.Do("stat:"+rev, func() interface{} {
-		r.mu.Lock()
-		defer r.mu.Unlock()
-		info, err := r.r.Stat(rev)
-		return cached{info, err}
-	}).(cached)
+		file, info, err := readDiskStat(r.path, rev)
+		if err == nil {
+			return cachedInfo{info, nil}
+		}
+
+		if !QuietLookup {
+			fmt.Fprintf(os.Stderr, "vgo: finding %s %s\n", r.path, rev)
+		}
+		info, err = r.r.Stat(rev)
+		if err == nil {
+			if err := writeDiskStat(file, info); err != nil {
+				fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err)
+			}
+			// If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78,
+			// then save the information under the proper version, for future use.
+			if info.Version != rev {
+				r.cache.Do("stat:"+info.Version, func() interface{} {
+					return cachedInfo{info, err}
+				})
+			}
+		}
+		return cachedInfo{info, err}
+	}).(cachedInfo)
 
 	if c.err != nil {
 		return nil, c.err
@@ -72,16 +100,28 @@
 }
 
 func (r *cachingRepo) Latest() (*RevInfo, error) {
-	type cached struct {
+	type cachedInfo struct {
 		info *RevInfo
 		err  error
 	}
 	c := r.cache.Do("latest:", func() interface{} {
-		r.mu.Lock()
-		defer r.mu.Unlock()
+		if !QuietLookup {
+			fmt.Fprintf(os.Stderr, "vgo: finding %s latest\n", r.path)
+		}
 		info, err := r.r.Latest()
-		return cached{info, err}
-	}).(cached)
+
+		// Save info for likely future Stat call.
+		if err == nil {
+			r.cache.Do("stat:"+info.Version, func() interface{} {
+				return cachedInfo{info, err}
+			})
+			if file, _, err := readDiskStat(r.path, info.Version); err != nil {
+				writeDiskStat(file, info)
+			}
+		}
+
+		return cachedInfo{info, err}
+	}).(cachedInfo)
 
 	if c.err != nil {
 		return nil, c.err
@@ -96,9 +136,17 @@
 		err  error
 	}
 	c := r.cache.Do("gomod:"+rev, func() interface{} {
-		r.mu.Lock()
-		defer r.mu.Unlock()
-		text, err := r.r.GoMod(rev)
+		file, text, err := readDiskGoMod(r.path, rev)
+		if err == nil {
+			return cached{text, nil}
+		}
+
+		text, err = r.r.GoMod(rev)
+		if err == nil {
+			if err := writeDiskGoMod(file, text); err != nil {
+				fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err)
+			}
+		}
 		return cached{text, err}
 	}).(cached)
 
@@ -109,7 +157,186 @@
 }
 
 func (r *cachingRepo) Zip(version, tmpdir string) (string, error) {
-	r.mu.Lock()
-	defer r.mu.Unlock()
 	return r.r.Zip(version, tmpdir)
 }
+
+// Stat is like Lookup(path).Stat(rev) but avoids the
+// repository path resolution in Lookup if the result is
+// already cached on local disk.
+func Stat(path, rev string) (*RevInfo, error) {
+	_, info, err := readDiskStat(path, rev)
+	if err == nil {
+		return info, nil
+	}
+	repo, err := Lookup(path)
+	if err != nil {
+		return nil, err
+	}
+	return repo.Stat(rev)
+}
+
+// GoMod is like Lookup(path).GoMod(rev) but avoids the
+// repository path resolution in Lookup if the result is
+// already cached on local disk.
+func GoMod(path, rev string) ([]byte, error) {
+	// Convert commit hash to pseudo-version
+	// to increase cache hit rate.
+	if !semver.IsValid(rev) {
+		info, err := Stat(path, rev)
+		if err != nil {
+			return nil, err
+		}
+		rev = info.Version
+	}
+	_, data, err := readDiskGoMod(path, rev)
+	if err == nil {
+		return data, nil
+	}
+	repo, err := Lookup(path)
+	if err != nil {
+		return nil, err
+	}
+	return repo.GoMod(rev)
+}
+
+var errNotCached = fmt.Errorf("not in cache")
+
+// readDiskStat reads a cached stat result from disk,
+// returning the name of the cache file and the result.
+// If the read fails, the caller can use
+// writeDiskStat(file, info) to write a new cache entry.
+func readDiskStat(path, rev string) (file string, info *RevInfo, err error) {
+	file, data, err := readDiskCache(path, rev, "info")
+	if err != nil {
+		if file, info, err := readDiskStatByHash(path, rev); err == nil {
+			return file, info, nil
+		}
+		return file, nil, err
+	}
+	info = new(RevInfo)
+	if err := json.Unmarshal(data, info); err != nil {
+		return file, nil, errNotCached
+	}
+	return file, info, nil
+}
+
+// readDiskStatByHash is a fallback for readDiskStat for the case
+// where rev is a commit hash instead of a proper semantic version.
+// In that case, we look for a cached pseudo-version that matches
+// the commit hash. If we find one, we use it.
+// This matters most for converting legacy package management
+// configs, when we are often looking up commits by full hash.
+// Without this check we'd be doing network I/O to the remote repo
+// just to find out about a commit we already know about
+// (and have cached under its pseudo-version).
+func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error) {
+	if !codehost.AllHex(rev) || len(rev) < 12 {
+		return "", nil, errNotCached
+	}
+	rev = rev[:12]
+	dir, err := os.Open(filepath.Join(CacheRoot, path, "@v"))
+	if err != nil {
+		return "", nil, errNotCached
+	}
+	names, err := dir.Readdirnames(-1)
+	dir.Close()
+	if err != nil {
+		return "", nil, errNotCached
+	}
+	suffix := "-" + rev + ".info"
+	for _, name := range names {
+		if strings.HasSuffix(name, suffix) && isPseudoVersion(strings.TrimSuffix(name, ".info")) {
+			return readDiskStat(path, strings.TrimSuffix(name, ".info"))
+		}
+	}
+	return "", nil, errNotCached
+}
+
+var vgoVersion = []byte(modconv.Prefix)
+
+// readDiskGoMod reads a cached stat result from disk,
+// returning the name of the cache file and the result.
+// If the read fails, the caller can use
+// writeDiskGoMod(file, data) to write a new cache entry.
+func readDiskGoMod(path, rev string) (file string, data []byte, err error) {
+	file, data, err = readDiskCache(path, rev, "mod")
+
+	// If go.mod has a //vgo comment at the start,
+	// it was auto-converted from a legacy lock file.
+	// The auto-conversion details may have bugs and
+	// may be fixed in newer versions of vgo.
+	// We ignore cached go.mod files if they do not match
+	// our own vgoVersion.
+	// This use of "vgo" appears in disk files and must be preserved
+	// even once we excise most of the mentions of vgo from the code.
+	if err == nil && bytes.HasPrefix(data, vgoVersion[:len("//vgo")]) && !bytes.HasPrefix(data, vgoVersion) {
+		err = errNotCached
+		data = nil
+	}
+
+	return file, data, err
+}
+
+// readDiskCache is the generic "read from a cache file" implementation.
+// It takes the revision and an identifying suffix for the kind of data being cached.
+// It returns the name of the cache file and the content of the file.
+// If the read fails, the caller can use
+// writeDiskCache(file, data) to write a new cache entry.
+func readDiskCache(path, rev, suffix string) (file string, data []byte, err error) {
+	if !semver.IsValid(rev) || CacheRoot == "" {
+		return "", nil, errNotCached
+	}
+	file = filepath.Join(CacheRoot, path, "@v", rev+"."+suffix)
+	data, err = ioutil.ReadFile(file)
+	if err != nil {
+		return file, nil, errNotCached
+	}
+	return file, data, nil
+}
+
+// writeDiskStat writes a stat result cache entry.
+// The file name must have been returned by a previous call to readDiskStat.
+func writeDiskStat(file string, info *RevInfo) error {
+	if file == "" {
+		return nil
+	}
+	js, err := json.Marshal(info)
+	if err != nil {
+		return err
+	}
+	return writeDiskCache(file, js)
+}
+
+// writeDiskGoMod writes a go.mod cache entry.
+// The file name must have been returned by a previous call to readDiskGoMod.
+func writeDiskGoMod(file string, text []byte) error {
+	return writeDiskCache(file, text)
+}
+
+// writeDiskCache is the generic "write to a cache file" implementation.
+// The file must have been returned by a previous call to readDiskCache.
+func writeDiskCache(file string, data []byte) error {
+	if file == "" {
+		return nil
+	}
+	// Make sure directory for file exists.
+	if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil {
+		return err
+	}
+	// Write data to temp file next to target file.
+	f, err := ioutil.TempFile(filepath.Dir(file), filepath.Base(file)+".tmp*")
+	if err != nil {
+		return err
+	}
+	defer os.Remove(f.Name())
+	defer f.Close()
+	if _, err := f.Write(data); err != nil {
+		return err
+	}
+	if err := f.Close(); err != nil {
+		return err
+	}
+	// Rename temp file onto cache file,
+	// so that the cache file is always a complete file.
+	return os.Rename(f.Name(), file)
+}
diff --git a/vendor/cmd/go/internal/modfetch/coderepo.go b/vendor/cmd/go/internal/modfetch/coderepo.go
index c208fb2..bd815e1 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo.go
@@ -15,6 +15,7 @@
 	"path/filepath"
 	"regexp"
 	"strings"
+	"sync"
 	"time"
 
 	"cmd/go/internal/modconv"
@@ -26,10 +27,12 @@
 
 // A codeRepo implements modfetch.Repo using an underlying codehost.Repo.
 type codeRepo struct {
-	modPath  string
+	modPath string
+
 	code     codehost.Repo
 	codeRoot string
 	codeDir  string
+	codeMu   sync.Mutex // protects code methods
 
 	path        string
 	pathPrefix  string
@@ -95,7 +98,9 @@
 	if r.codeDir != "" {
 		p = r.codeDir + "/" + p
 	}
+	r.codeMu.Lock()
 	tags, err := r.code.Tags(p)
+	r.codeMu.Unlock()
 	if err != nil {
 		return nil, err
 	}
@@ -125,7 +130,9 @@
 	if semver.IsValid(codeRev) && r.codeDir != "" {
 		codeRev = r.codeDir + "/" + codeRev
 	}
+	r.codeMu.Lock()
 	info, err := r.code.Stat(codeRev)
+	r.codeMu.Unlock()
 	if err != nil {
 		return nil, err
 	}
@@ -133,7 +140,9 @@
 }
 
 func (r *codeRepo) Latest() (*RevInfo, error) {
+	r.codeMu.Lock()
 	info, err := r.code.Latest()
+	r.codeMu.Unlock()
 	if err != nil {
 		return nil, err
 	}
@@ -199,7 +208,9 @@
 			return rev, "", nil, nil
 		}
 		file1 := path.Join(r.codeDir, "go.mod")
+		r.codeMu.Lock()
 		gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
+		r.codeMu.Unlock()
 		if err1 != nil {
 			return "", "", nil, fmt.Errorf("missing go.mod")
 		}
@@ -215,8 +226,10 @@
 	// a replace directive.
 	file1 := path.Join(r.codeDir, "go.mod")
 	file2 := path.Join(r.codeDir, r.pathMajor[1:], "go.mod")
+	r.codeMu.Lock()
 	gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
 	gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
+	r.codeMu.Unlock()
 	found1 := err1 == nil && isMajor(gomod1, r.pathMajor)
 	found2 := err2 == nil && isMajor(gomod2, r.pathMajor)
 
@@ -247,7 +260,9 @@
 	if gomod != nil {
 		return gomod, nil
 	}
+	r.codeMu.Lock()
 	data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod)
+	r.codeMu.Unlock()
 	if err != nil {
 		if e := strings.ToLower(err.Error()); strings.Contains(e, "not found") || strings.Contains(e, "404") { // TODO
 			return r.legacyGoMod(rev, dir), nil
@@ -274,7 +289,9 @@
 	mf := new(modfile.File)
 	mf.AddModuleStmt(r.modPath)
 	for _, file := range altConfigs {
+		r.codeMu.Lock()
 		data, err := r.code.ReadFile(rev, path.Join(dir, file), codehost.MaxGoMod)
+		r.codeMu.Unlock()
 		if err != nil {
 			continue
 		}
@@ -303,7 +320,9 @@
 	if err != nil {
 		return "", err
 	}
+	r.codeMu.Lock()
 	dl, actualDir, err := r.code.ReadZip(rev, dir, codehost.MaxZipFile)
+	r.codeMu.Unlock()
 	if err != nil {
 		return "", err
 	}
@@ -441,7 +460,10 @@
 	}
 
 	if !haveLICENSE && subdir != "" {
-		if data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE); err == nil {
+		r.codeMu.Lock()
+		data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE)
+		r.codeMu.Unlock()
+		if err == nil {
 			w, err := zw.Create(r.modPrefix(version) + "/LICENSE")
 			if err != nil {
 				return "", err
diff --git a/vendor/cmd/go/internal/modfetch/convert.go b/vendor/cmd/go/internal/modfetch/convert.go
index 5c482be..9e15116 100644
--- a/vendor/cmd/go/internal/modfetch/convert.go
+++ b/vendor/cmd/go/internal/modfetch/convert.go
@@ -9,9 +9,12 @@
 	"os"
 	"sort"
 	"strings"
+	"sync"
 
 	"cmd/go/internal/modconv"
 	"cmd/go/internal/modfile"
+	"cmd/go/internal/module"
+	"cmd/go/internal/par"
 	"cmd/go/internal/semver"
 )
 
@@ -37,7 +40,7 @@
 
 	// Convert requirements block, which may use raw SHA1 hashes as versions,
 	// to valid semver requirement list, respecting major versions.
-	need := make(map[string]string)
+	var work par.Work
 	for _, r := range require {
 		if r.Path == "" {
 			continue
@@ -50,20 +53,24 @@
 				r.Path = strings.Join(f[:3], "/")
 			}
 		}
+		work.Add(r)
+	}
 
-		repo, err := Lookup(r.Path)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "vgo: lookup %s: %v\n", r.Path, err)
-			continue
-		}
-		info, err := repo.Stat(r.Version)
+	var (
+		mu   sync.Mutex
+		need = make(map[string]string)
+	)
+	work.Do(10, func(item interface{}) {
+		r := item.(module.Version)
+		info, err := Stat(r.Path, r.Version)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "vgo: stat %s@%s: %v\n", r.Path, r.Version, err)
-			continue
+			return
 		}
-		path := repo.ModulePath()
-		need[path] = semver.Max(need[path], info.Version)
-	}
+		mu.Lock()
+		need[r.Path] = semver.Max(need[r.Path], info.Version)
+		mu.Unlock()
+	})
 
 	var paths []string
 	for path := range need {
@@ -71,7 +78,7 @@
 	}
 	sort.Strings(paths)
 	for _, path := range paths {
-		f.AddRequire(path, need[path])
+		f.AddNewRequire(path, need[path])
 	}
 
 	return nil
diff --git a/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go b/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go
index 0d6eb4b..532d3f3 100644
--- a/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go
+++ b/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go
@@ -94,12 +94,35 @@
 	refsOnce sync.Once
 	refs     map[string]string
 	refsErr  error
+
+	localTagsOnce sync.Once
+	localTags     map[string]bool
 }
 
 func (r *repo) Root() string {
 	return r.root
 }
 
+// loadLocalTags loads tag references from the local git cache
+// into the map r.localTags.
+// Should only be called as r.localTagsOnce.Do(r.loadLocalTags).
+func (r *repo) loadLocalTags() {
+	// The git protocol sends all known refs and ls-remote filters them on the client side,
+	// so we might as well record both heads and tags in one shot.
+	// Most of the time we only care about tags but sometimes we care about heads too.
+	out, err := codehost.Run(r.dir, "git", "tag", "-l")
+	if err != nil {
+		return
+	}
+
+	r.localTags = make(map[string]bool)
+	for _, line := range strings.Split(string(out), "\n") {
+		if line != "" {
+			r.localTags[line] = true
+		}
+	}
+}
+
 // loadRefs loads heads and tags references from the remote into the map r.refs.
 // Should only be called as r.refsOnce.Do(r.loadRefs).
 func (r *repo) loadRefs() {
@@ -190,11 +213,38 @@
 // (most do not, but maybe that will change), to let us minimize
 // the amount of code downloaded.
 func (r *repo) statOrArchive(rev string, archiveArgs ...string) (info *codehost.RevInfo, archive []byte, err error) {
-	// Do we have this rev?
+	var (
+		hash      string
+		tag       string
+		out       []byte
+		triedHash string
+	)
+
+	// Maybe rev is a hash we already have locally.
+	if len(rev) >= 12 && codehost.AllHex(rev) {
+		out, err = codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", rev)
+		if err == nil {
+			hash = strings.TrimSpace(string(out))
+			goto Found
+		}
+		triedHash = rev
+	}
+
+	// Maybe rev is a tag we already have locally.
+	r.localTagsOnce.Do(r.loadLocalTags)
+	if r.localTags["refs/tags/"+rev] {
+		out, err = codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", "refs/tags/"+rev)
+		if err == nil {
+			hash = strings.TrimSpace(string(out))
+			goto Found
+		}
+	}
+
+	// Maybe rev is a ref from the server.
 	r.refsOnce.Do(r.loadRefs)
-	var hash string
 	if k := "refs/tags/" + rev; r.refs[k] != "" {
 		hash = r.refs[k]
+		tag = k
 	} else if k := "refs/heads/" + rev; r.refs[k] != "" {
 		hash = r.refs[k]
 		rev = hash
@@ -207,10 +257,12 @@
 		return nil, nil, fmt.Errorf("unknown revision %q", rev)
 	}
 
-	out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash)
-	if err == nil {
-		hash = strings.TrimSpace(string(out))
-		goto Found
+	if hash != triedHash {
+		out, err = codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash)
+		if err == nil {
+			hash = strings.TrimSpace(string(out))
+			goto Found
+		}
 	}
 
 	// We don't have the rev. Can we fetch it?
@@ -265,7 +317,12 @@
 		if ref, ok := r.findRef(hash); ok {
 			name = ref
 		}
-		if _, err = codehost.Run(r.dir, "git", "fetch", "--depth=1", r.remote, name); err == nil {
+		if tag != "" {
+			_, err = codehost.Run(r.dir, "git", "fetch", "--depth=1", r.remote, tag+":"+tag)
+		} else {
+			_, err = codehost.Run(r.dir, "git", "fetch", "--depth=1", r.remote, name)
+		}
+		if err == nil {
 			goto Found
 		}
 		if !strings.Contains(err.Error(), "unadvertised object") && !strings.Contains(err.Error(), "no such remote ref") && !strings.Contains(err.Error(), "does not support shallow") {
diff --git a/vendor/cmd/go/internal/modfetch/query.go b/vendor/cmd/go/internal/modfetch/query.go
index 5e9d86c..5587890 100644
--- a/vendor/cmd/go/internal/modfetch/query.go
+++ b/vendor/cmd/go/internal/modfetch/query.go
@@ -28,11 +28,6 @@
 // result to revisions on a particular branch name.
 //
 func Query(path, vers string, allowed func(module.Version) bool) (*RevInfo, error) {
-	repo, err := Lookup(path)
-	if err != nil {
-		return nil, err
-	}
-
 	if strings.HasPrefix(vers, "v") && semver.IsValid(vers) {
 		// TODO: This turns query for "v2" into Stat "v2.0.0",
 		// but probably it should allow checking for a branch named "v2".
@@ -40,6 +35,21 @@
 		if allowed != nil && !allowed(module.Version{Path: path, Version: vers}) {
 			return nil, fmt.Errorf("%s@%s excluded", path, vers)
 		}
+
+		// Fast path that avoids network overhead of Lookup (resolving path to repo host),
+		// if we already have this stat information cached on disk.
+		info, err := Stat(path, vers)
+		if err == nil {
+			return info, nil
+		}
+	}
+
+	repo, err := Lookup(path)
+	if err != nil {
+		return nil, err
+	}
+
+	if semver.IsValid(vers) {
 		return repo.Stat(vers)
 	}
 	if strings.HasPrefix(vers, ">") || strings.HasPrefix(vers, "<") || vers == "latest" {
diff --git a/vendor/cmd/go/internal/modfetch/repo.go b/vendor/cmd/go/internal/modfetch/repo.go
index 83b2262..072bbbc 100644
--- a/vendor/cmd/go/internal/modfetch/repo.go
+++ b/vendor/cmd/go/internal/modfetch/repo.go
@@ -26,6 +26,7 @@
 const traceRepo = false // trace all repo actions, for debugging
 
 // A Repo represents a repository storing all versions of a single module.
+// It must be safe for simultaneous use by multiple goroutines.
 type Repo interface {
 	// ModulePath returns the module path.
 	ModulePath() string
diff --git a/vendor/cmd/go/internal/vgo/fetch.go b/vendor/cmd/go/internal/vgo/fetch.go
index fba60dc..7b91908 100644
--- a/vendor/cmd/go/internal/vgo/fetch.go
+++ b/vendor/cmd/go/internal/vgo/fetch.go
@@ -38,16 +38,16 @@
 	}
 
 	modpath := mod.Path + "@" + mod.Version
-	dir = filepath.Join(SrcV, modpath)
+	dir = filepath.Join(SrcMod, modpath)
 	if files, _ := ioutil.ReadDir(dir); len(files) == 0 {
-		zipfile := filepath.Join(SrcV, "cache", mod.Path, "@v", mod.Version+".zip")
+		zipfile := filepath.Join(SrcMod, "cache", mod.Path, "@v", mod.Version+".zip")
 		if _, err := os.Stat(zipfile); err == nil {
 			// Use it.
 			// This should only happen if the v/cache directory is preinitialized
 			// or if src/v/modpath was removed but not src/v/cache.
 			fmt.Fprintf(os.Stderr, "vgo: extracting %s %s\n", mod.Path, mod.Version)
 		} else {
-			if err := os.MkdirAll(filepath.Join(SrcV, "cache", mod.Path, "@v"), 0777); err != nil {
+			if err := os.MkdirAll(filepath.Join(SrcMod, "cache", mod.Path, "@v"), 0777); err != nil {
 				return "", err
 			}
 			fmt.Fprintf(os.Stderr, "vgo: downloading %s %s\n", mod.Path, mod.Version)
@@ -159,7 +159,7 @@
 		return
 	}
 
-	data, err := ioutil.ReadFile(filepath.Join(SrcV, "cache", mod.Path, "@v", mod.Version+".ziphash"))
+	data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache", mod.Path, "@v", mod.Version+".ziphash"))
 	if err != nil {
 		base.Fatalf("vgo: verifying %s %s: %v", mod.Path, mod.Version, err)
 	}
@@ -183,7 +183,7 @@
 }
 
 func findModHash(mod module.Version) string {
-	data, err := ioutil.ReadFile(filepath.Join(SrcV, "cache", mod.Path, "@v", mod.Version+".ziphash"))
+	data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache", mod.Path, "@v", mod.Version+".ziphash"))
 	if err != nil {
 		return ""
 	}
diff --git a/vendor/cmd/go/internal/vgo/init.go b/vendor/cmd/go/internal/vgo/init.go
index 5af38cc..ba8122f 100644
--- a/vendor/cmd/go/internal/vgo/init.go
+++ b/vendor/cmd/go/internal/vgo/init.go
@@ -40,7 +40,7 @@
 	Target   module.Version
 
 	gopath string
-	SrcV   string // GOPATH/src/v directory where versioned cache lives
+	SrcMod string // GOPATH/src/v directory where versioned cache lives
 
 	CmdModInit   bool   // go mod -init flag
 	CmdModModule string // go mod -module flag
@@ -170,8 +170,17 @@
 	if _, err := os.Stat(filepath.Join(gopath, "go.mod")); err == nil {
 		base.Fatalf("$GOPATH/go.mod exists but should not")
 	}
-	SrcV = filepath.Join(list[0], "src/v")
-	codehost.WorkRoot = filepath.Join(SrcV, "cache/vcswork")
+
+	srcV := filepath.Join(list[0], "src/v")
+	SrcMod = filepath.Join(list[0], "src/mod")
+	infoV, errV := os.Stat(srcV)
+	_, errMod := os.Stat(SrcMod)
+	if errV == nil && infoV.IsDir() && errMod != nil && os.IsNotExist(errMod) {
+		os.Rename(srcV, SrcMod)
+	}
+
+	modfetch.CacheRoot = filepath.Join(SrcMod, "cache")
+	codehost.WorkRoot = filepath.Join(SrcMod, "cache/vcswork")
 
 	if CmdModInit {
 		// Running go mod -init: do legacy module conversion
diff --git a/vendor/cmd/go/internal/vgo/list.go b/vendor/cmd/go/internal/vgo/list.go
index d3511c1..e56aae5 100644
--- a/vendor/cmd/go/internal/vgo/list.go
+++ b/vendor/cmd/go/internal/vgo/list.go
@@ -80,7 +80,7 @@
 	}
 	InitMod()
 
-	quietLookup = true // do not chatter in v.Lookup
+	modfetch.QuietLookup = true // do not chatter in v.Lookup
 	iterate(func(*loader) {})
 
 	var rows [][]string
diff --git a/vendor/cmd/go/internal/vgo/load.go b/vendor/cmd/go/internal/vgo/load.go
index 0f69bf0..de89526 100644
--- a/vendor/cmd/go/internal/vgo/load.go
+++ b/vendor/cmd/go/internal/vgo/load.go
@@ -6,7 +6,6 @@
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
 	"go/build"
 	"io/ioutil"
@@ -501,71 +500,16 @@
 		// TODO: return nil, fmt.Errorf("invalid semantic version %q", mod.Version)
 	}
 
-	gomod := filepath.Join(SrcV, "cache", mod.Path, "@v", mod.Version+".mod")
-	infofile := filepath.Join(SrcV, "cache", mod.Path, "@v", mod.Version+".info")
-	var f *modfile.File
-	if data, err := ioutil.ReadFile(gomod); err == nil {
-		// If go.mod has a //vgo comment at the start,
-		// it was auto-converted from a legacy lock file.
-		// The auto-conversion details may have bugs and
-		// may be fixed in newer versions of vgo.
-		// We ignore cached go.mod files if they do not match
-		// our own vgoVersion.
-		if !bytes.HasPrefix(data, vgoVersion[:len("//vgo")]) || bytes.HasPrefix(data, vgoVersion) {
-			f, err := modfile.Parse(gomod, data, nil)
-			if err != nil {
-				return nil, err
-			}
-			var list []module.Version
-			for _, r := range f.Require {
-				list = append(list, r.Mod)
-			}
-			return list, nil
-		}
-		f, err = modfile.Parse("go.mod", data, nil)
-		if err != nil {
-			return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
-		}
-	} else {
-		if !quietLookup {
-			fmt.Fprintf(os.Stderr, "vgo: finding %s %s\n", mod.Path, mod.Version)
-		}
-		repo, err := modfetch.Lookup(mod.Path)
-		if err != nil {
-			base.Errorf("vgo: %s: %v\n", mod.Path, err)
-			return nil, err
-		}
-		info, err := repo.Stat(mod.Version)
-		if err != nil {
-			base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err)
-			return nil, err
-		}
-		data, err := repo.GoMod(info.Version)
-		if err != nil {
-			base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err)
-			return nil, err
-		}
-
-		f, err = modfile.Parse("go.mod", data, nil)
-		if err != nil {
-			return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
-		}
-
-		dir := filepath.Dir(gomod)
-		if err := os.MkdirAll(dir, 0777); err != nil {
-			return nil, fmt.Errorf("caching go.mod: %v", err)
-		}
-		js, err := json.Marshal(info)
-		if err != nil {
-			return nil, fmt.Errorf("internal error: json failure: %v", err)
-		}
-		if err := ioutil.WriteFile(infofile, js, 0666); err != nil {
-			return nil, fmt.Errorf("caching info: %v", err)
-		}
-		if err := ioutil.WriteFile(gomod, data, 0666); err != nil {
-			return nil, fmt.Errorf("caching go.mod: %v", err)
-		}
+	data, err := modfetch.GoMod(mod.Path, mod.Version)
+	if err != nil {
+		base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err)
+		return nil, err
 	}
+	f, err := modfile.Parse("go.mod", data, nil)
+	if err != nil {
+		return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
+	}
+
 	if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
 		return nil, fmt.Errorf("downloaded %q and got module %q", mod.Path, mpath)
 	}
@@ -583,8 +527,6 @@
 	return list, nil
 }
 
-var quietLookup bool
-
 func (*mvsReqs) Max(v1, v2 string) string {
 	if semver.Compare(v1, v2) == -1 {
 		return v2