cmd/go/internal/modfetch: fix Lookup, Import; add ImportRepoRev

- Explain Lookup and Import, module paths vs import paths.

- Add ImportRepoRev, because neither Lookup nor Import
  is appropriate for translating legacy versioning system configs.
  There's more to do in ImportRepoRev, but it's already more
  accurate than before.

- Drop the old ad-hoc logic about github/bitbucket/etc
  in favor of resolving source code hosts using the "go get" logic.
  This enables paths like git.apache.org/thrift.git/lib/go/thrift again.

- Structure $GOPATH/src/mod/cache a little more: make separate
  top-level directories mod/cache/download and mod/cache/vcs.

Fixes golang/go#23983.
Fixes golang/go#24687.
Fixes golang/go#25590.
Fixes golang/go#25654.

Change-Id: I0de2d75c5846a1c054079e1c67b9a1100ed161c8
Reviewed-on: https://go-review.googlesource.com/120042
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modfetch/bitbucket/fetch.go b/vendor/cmd/go/internal/modfetch/bitbucket/fetch.go
deleted file mode 100644
index 58e74a5..0000000
--- a/vendor/cmd/go/internal/modfetch/bitbucket/fetch.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package bitbucket
-
-import (
-	"fmt"
-	"strings"
-
-	"cmd/go/internal/modfetch/codehost"
-	"cmd/go/internal/modfetch/gitrepo"
-)
-
-func Lookup(path string) (codehost.Repo, string, error) {
-	f := strings.Split(path, "/")
-	if len(f) < 3 || f[0] != "bitbucket.org" {
-		return nil, "", fmt.Errorf("bitbucket repo must be bitbucket.org/org/project")
-	}
-	path = f[0] + "/" + f[1] + "/" + f[2]
-	repo, err := gitrepo.Repo("https://" + path)
-	return repo, path, err
-}
diff --git a/vendor/cmd/go/internal/modfetch/coderepo.go b/vendor/cmd/go/internal/modfetch/coderepo.go
index 17c8b92..dd0814b 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo.go
@@ -293,7 +293,7 @@
 		if convert == nil {
 			continue
 		}
-		if err := ConvertLegacyConfig(mf, file, data); err != nil {
+		if err := ConvertLegacyConfig(mf, path.Join(r.codeRoot+"@"+rev, dir, file), data); err != nil {
 			continue
 		}
 		break
diff --git a/vendor/cmd/go/internal/modfetch/coderepo_test.go b/vendor/cmd/go/internal/modfetch/coderepo_test.go
index c8233a5..91e9a27 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo_test.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo_test.go
@@ -243,8 +243,10 @@
 	},
 	{
 		// package in subdirectory - custom domain
-		path:    "golang.org/x/net/context",
-		lookerr: "module root is \"golang.org/x/net\"",
+		// In general we can't reject these definitively in Lookup,
+		// but gopkg.in is special.
+		path:    "gopkg.in/yaml.v2/abc",
+		lookerr: "invalid module path \"gopkg.in/yaml.v2/abc\"",
 	},
 	{
 		// package in subdirectory - github
@@ -329,13 +331,13 @@
 	for _, tt := range codeRepoTests {
 		t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, func(t *testing.T) {
 			repo, err := Lookup(tt.path)
-			if err != nil {
-				if tt.lookerr != "" {
-					if err.Error() == tt.lookerr {
-						return
-					}
-					t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookerr)
+			if tt.lookerr != "" {
+				if err != nil && err.Error() == tt.lookerr {
+					return
 				}
+				t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookerr)
+			}
+			if err != nil {
 				t.Fatalf("Lookup(%q): %v", tt.path, err)
 			}
 			if tt.mpath == "" {
@@ -442,7 +444,8 @@
 	},
 	{
 		path: "golang.org/x/foo/bar",
-		err:  "unknown module golang.org/x/foo/bar: no go-import tags",
+		// TODO(rsc): This error comes from old go get and is terrible. Fix.
+		err: `unrecognized import path "golang.org/x/foo/bar" (parse https://golang.org/x/foo/bar?go-get=1: no go-import meta tags ())`,
 	},
 }
 
@@ -452,14 +455,14 @@
 	for _, tt := range importTests {
 		t.Run(strings.Replace(tt.path, "/", "_", -1), func(t *testing.T) {
 			repo, info, err := Import(tt.path, nil)
-			if err != nil {
-				if tt.err != "" {
-					if err.Error() == tt.err {
-						return
-					}
-					t.Errorf("Import(%q): %v, want error %q", tt.path, err, tt.err)
+			if tt.err != "" {
+				if err != nil && err.Error() == tt.err {
+					return
 				}
-				t.Fatalf("Lookup(%q): %v", tt.path, err)
+				t.Fatalf("Import(%q): %v, want error %q", tt.path, err, tt.err)
+			}
+			if err != nil {
+				t.Fatalf("Import(%q): %v", tt.path, err)
 			}
 			if mpath := repo.ModulePath(); mpath != tt.mpath {
 				t.Errorf("repo.ModulePath() = %q (%v), want %q", mpath, info.Version, tt.mpath)
diff --git a/vendor/cmd/go/internal/modfetch/convert.go b/vendor/cmd/go/internal/modfetch/convert.go
index bebb6ee..e12d200 100644
--- a/vendor/cmd/go/internal/modfetch/convert.go
+++ b/vendor/cmd/go/internal/modfetch/convert.go
@@ -33,7 +33,7 @@
 	if convert == nil {
 		return fmt.Errorf("unknown legacy config file %s", file)
 	}
-	result, err := convert(file, data)
+	mf, err := convert(file, data)
 	if err != nil {
 		return fmt.Errorf("parsing %s: %v", file, err)
 	}
@@ -41,20 +41,12 @@
 	// Convert requirements block, which may use raw SHA1 hashes as versions,
 	// to valid semver requirement list, respecting major versions.
 	var work par.Work
-	for _, r := range result.Require {
+	for _, r := range mf.Require {
 		m := r.Mod
 		if m.Path == "" {
 			continue
 		}
-
-		// TODO: Something better here.
-		if strings.HasPrefix(m.Path, "github.com/") || strings.HasPrefix(m.Path, "golang.org/x/") {
-			f := strings.Split(m.Path, "/")
-			if len(f) > 3 {
-				m.Path = strings.Join(f[:3], "/")
-			}
-		}
-		work.Add(m)
+		work.Add(r.Mod)
 	}
 
 	var (
@@ -63,13 +55,14 @@
 	)
 	work.Do(10, func(item interface{}) {
 		r := item.(module.Version)
-		info, err := Stat(r.Path, r.Version)
+		repo, info, err := ImportRepoRev(r.Path, r.Version)
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "vgo: stat %s@%s: %v\n", r.Path, r.Version, err)
+			fmt.Fprintf(os.Stderr, "vgo: converting %s: stat %s@%s: %v\n", file, r.Path, r.Version, err)
 			return
 		}
 		mu.Lock()
-		need[r.Path] = semver.Max(need[r.Path], info.Version)
+		path := repo.ModulePath()
+		need[path] = semver.Max(need[path], info.Version)
 		mu.Unlock()
 	})
 
@@ -82,7 +75,7 @@
 		f.AddNewRequire(path, need[path])
 	}
 
-	for _, r := range result.Replace {
+	for _, r := range mf.Replace {
 		err := f.AddReplace(r.Old.Path, r.Old.Version, r.New.Path, r.New.Version)
 		if err != nil {
 			return fmt.Errorf("add replace: %v", err)
diff --git a/vendor/cmd/go/internal/modfetch/domain.go b/vendor/cmd/go/internal/modfetch/domain.go
deleted file mode 100644
index c1e6813..0000000
--- a/vendor/cmd/go/internal/modfetch/domain.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Support for custom domains.
-
-package modfetch
-
-import (
-	"encoding/xml"
-	"fmt"
-	"io"
-	"net/url"
-	"os"
-	"strings"
-
-	"cmd/go/internal/modfetch/codehost"
-	"cmd/go/internal/modfetch/gitrepo"
-)
-
-// metaImport represents the parsed <meta name="go-import"
-// content="prefix vcs reporoot" /> tags from HTML files.
-type metaImport struct {
-	Prefix, VCS, RepoRoot string
-}
-
-func lookupCustomDomain(path string) (Repo, error) {
-	dom := path
-	if i := strings.Index(dom, "/"); i >= 0 {
-		dom = dom[:i]
-	}
-	if !strings.Contains(dom, ".") {
-		return nil, fmt.Errorf("unknown module %s: not a domain name", path)
-	}
-	var body io.ReadCloser
-	err := webGetGoGet("https://"+path+"?go-get=1", &body)
-	if body != nil {
-		defer body.Close()
-	}
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "FindRepo: %v\n", err)
-		return nil, err
-	}
-	// Note: accepting a non-200 OK here, so people can serve a
-	// meta import in their http 404 page.
-	imports, err := parseMetaGoImports(body)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "findRepo: %v\n", err)
-		return nil, err
-	}
-	if len(imports) == 0 {
-		return nil, fmt.Errorf("unknown module %s: no go-import tags", path)
-	}
-
-	// First look for new module definition.
-	for _, imp := range imports {
-		if path == imp.Prefix || strings.HasPrefix(path, imp.Prefix+"/") {
-			if imp.VCS == "mod" {
-				u, err := url.Parse(imp.RepoRoot)
-				if err != nil {
-					return nil, fmt.Errorf("invalid module URL %q", imp.RepoRoot)
-				} else if u.Scheme != "https" {
-					// TODO: Allow -insecure flag as a build flag?
-					return nil, fmt.Errorf("invalid module URL %q: must be HTTPS", imp.RepoRoot)
-				}
-				return newProxyRepo(imp.RepoRoot, imp.Prefix), nil
-			}
-		}
-	}
-
-	// Fall back to redirections to known version control systems.
-	for _, imp := range imports {
-		if path == imp.Prefix {
-			if !strings.HasPrefix(imp.RepoRoot, "https://") {
-				// TODO: Allow -insecure flag as a build flag?
-				return nil, fmt.Errorf("invalid server URL %q: must be HTTPS", imp.RepoRoot)
-			}
-			if imp.VCS == "git" {
-				code, err := gitrepo.Repo(imp.RepoRoot)
-				if err != nil {
-					return nil, err
-				}
-				return newCodeRepo(code, imp.Prefix, path)
-			}
-			return nil, fmt.Errorf("unknown VCS, Repo: %s, %s", imp.VCS, imp.RepoRoot)
-		}
-	}
-
-	// Check for redirect to repo root.
-	for _, imp := range imports {
-		if strings.HasPrefix(path, imp.Prefix+"/") {
-			return nil, &ModuleSubdirError{imp.Prefix}
-		}
-	}
-
-	return nil, fmt.Errorf("unknown module %s: no matching go-import tags", path)
-}
-
-type ModuleSubdirError struct {
-	ModulePath string
-}
-
-func (e *ModuleSubdirError) Error() string {
-	return fmt.Sprintf("module root is %q", e.ModulePath)
-}
-
-type customPrefix struct {
-	codehost.Repo
-	root string
-}
-
-func (c *customPrefix) Root() string {
-	return c.root
-}
-
-// parseMetaGoImports returns meta imports from the HTML in r.
-// Parsing ends at the end of the <head> section or the beginning of the <body>.
-func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) {
-	d := xml.NewDecoder(r)
-	d.CharsetReader = charsetReader
-	d.Strict = false
-	var t xml.Token
-	for {
-		t, err = d.RawToken()
-		if err != nil {
-			if err == io.EOF || len(imports) > 0 {
-				err = nil
-			}
-			return
-		}
-		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
-			return
-		}
-		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
-			return
-		}
-		e, ok := t.(xml.StartElement)
-		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
-			continue
-		}
-		if attrValue(e.Attr, "name") != "go-import" {
-			continue
-		}
-		if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
-			imports = append(imports, metaImport{
-				Prefix:   f[0],
-				VCS:      f[1],
-				RepoRoot: f[2],
-			})
-		}
-	}
-}
-
-// attrValue returns the attribute value for the case-insensitive key
-// `name', or the empty string if nothing is found.
-func attrValue(attrs []xml.Attr, name string) string {
-	for _, a := range attrs {
-		if strings.EqualFold(a.Name.Local, name) {
-			return a.Value
-		}
-	}
-	return ""
-}
-
-// charsetReader returns a reader for the given charset. Currently
-// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
-// error which is printed by go get, so the user can find why the package
-// wasn't downloaded if the encoding is not supported. Note that, in
-// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
-// greater than 0x7f are not rejected).
-func charsetReader(charset string, input io.Reader) (io.Reader, error) {
-	switch strings.ToLower(charset) {
-	case "ascii":
-		return input, nil
-	default:
-		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
-	}
-}
diff --git a/vendor/cmd/go/internal/modfetch/github/fetch.go b/vendor/cmd/go/internal/modfetch/github/fetch.go
deleted file mode 100644
index bf5c720..0000000
--- a/vendor/cmd/go/internal/modfetch/github/fetch.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package github
-
-import (
-	"fmt"
-	"strings"
-
-	"cmd/go/internal/modfetch/codehost"
-	"cmd/go/internal/modfetch/gitrepo"
-)
-
-// Lookup returns the code repository enclosing the given module path,
-// which must begin with github.com/.
-func Lookup(path string) (codehost.Repo, string, error) {
-	f := strings.Split(path, "/")
-	if len(f) < 3 || f[0] != "github.com" {
-		return nil, "", fmt.Errorf("github repo must be github.com/org/project")
-	}
-	path = f[0] + "/" + f[1] + "/" + f[2]
-	repo, err := gitrepo.Repo("https://" + path)
-	return repo, path, err
-}
diff --git a/vendor/cmd/go/internal/modfetch/gopkgin.go b/vendor/cmd/go/internal/modfetch/gopkgin.go
deleted file mode 100644
index f79edc0..0000000
--- a/vendor/cmd/go/internal/modfetch/gopkgin.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// TODO: Figure out what gopkg.in should do.
-
-package modfetch
-
-import (
-	"cmd/go/internal/modfetch/codehost"
-	"cmd/go/internal/modfetch/gitrepo"
-	"cmd/go/internal/modfile"
-	"fmt"
-)
-
-func gopkginLookup(path string) (codehost.Repo, string, error) {
-	root, _, _, _, ok := modfile.ParseGopkgIn(path)
-	if !ok {
-		return nil, "", fmt.Errorf("invalid gopkg.in/ path: %q", path)
-	}
-	repo, err := gitrepo.Repo("https://" + root)
-	return repo, root, err
-}
diff --git a/vendor/cmd/go/internal/modfetch/repo.go b/vendor/cmd/go/internal/modfetch/repo.go
index 3542dcb..2de9225 100644
--- a/vendor/cmd/go/internal/modfetch/repo.go
+++ b/vendor/cmd/go/internal/modfetch/repo.go
@@ -5,22 +5,20 @@
 package modfetch
 
 import (
-	"errors"
 	"fmt"
 	"os"
 	pathpkg "path"
 	"sort"
-	"strings"
 	"time"
 
 	"cmd/go/internal/cfg"
-	"cmd/go/internal/modfetch/bitbucket"
+	"cmd/go/internal/get"
 	"cmd/go/internal/modfetch/codehost"
-	"cmd/go/internal/modfetch/github"
-	"cmd/go/internal/modfetch/googlesource"
+	"cmd/go/internal/modfetch/gitrepo"
 	"cmd/go/internal/module"
 	"cmd/go/internal/par"
 	"cmd/go/internal/semver"
+	web "cmd/go/internal/web"
 )
 
 const traceRepo = false // trace all repo actions, for debugging
@@ -65,9 +63,124 @@
 	Time    time.Time // commit time
 }
 
+// Re: module paths, import paths, repository roots, and lookups
+//
+// A module is a collection of Go packages stored in a file tree
+// with a go.mod file at the root of the tree.
+// The go.mod defines the module path, which is the import path
+// corresponding to the root of the file tree.
+// The import path of a directory within that file tree is the module path
+// joined with the name of the subdirectory relative to the root.
+//
+// For example, the module with path rsc.io/qr corresponds to the
+// file tree in the repository https://github.com/rsc/qr.
+// That file tree has a go.mod that says "module rsc.io/qr".
+// The package in the root directory has import path "rsc.io/qr".
+// The package in the gf256 subdirectory has import path "rsc.io/qr/gf256".
+// In this example, "rsc.io/qr" is both a module path and an import path.
+// But "rsc.io/qr/gf256" is only an import path, not a module path:
+// it names an importable package, but not a module.
+//
+// As a special case to incorporate code written before modules were
+// introduced, if a path p resolves using the pre-module "go get" lookup
+// to the root of a source code repository without a go.mod file,
+// that repository is treated as if it had a go.mod in its root directory
+// declaring module path p. (The go.mod is further considered to
+// contain requirements corresponding to any legacy version
+// tracking format such as Gopkg.lock, vendor/vendor.conf, and so on.)
+//
+// The presentation so far ignores the fact that a source code repository
+// has many different versions of a file tree, and those versions may
+// differ in whether a particular go.mod exists and what it contains.
+// In fact there is a well-defined mapping only from a module path, version
+// pair - often written path@version - to a particular file tree.
+// For example rsc.io/qr@v0.1.0 depends on the "implicit go.mod at root of
+// repository" rule, while rsc.io/qr@v0.2.0 has an explicit go.mod.
+// Because the "go get" import paths rsc.io/qr and github.com/rsc/qr
+// both redirect to the Git repository https://github.com/rsc/qr,
+// github.com/rsc/qr@v0.1.0 is the same file tree as rsc.io/qr@v0.1.0
+// but a different module (a different name). In contrast, since v0.2.0
+// of that repository has an explicit go.mod that declares path rsc.io/qr,
+// github.com/rsc/qr@v0.2.0 is an invalid module path, version pair.
+// Before modules, import comments would have had the same effect.
+//
+// The set of import paths associated with a given module path is
+// clearly not fixed: at the least, new directories with new import paths
+// can always be added. But another potential operation is to split a
+// subtree out of a module into its own module. If done carefully,
+// this operation can be done while preserving compatibility for clients.
+// For example, suppose that we want to split rsc.io/qr/gf256 into its
+// own module, so that there would be two modules rsc.io/qr and rsc.io/qr/gf256.
+// Then we can simultaneously issue rsc.io/qr v0.3.0 (dropping the gf256 subdirectory)
+// and rsc.io/qr/gf256 v0.1.0, including in their respective go.mod
+// cyclic requirements pointing at each other: rsc.io/qr v0.3.0 requires
+// rsc.io/qr/gf256 v0.1.0 and vice versa. Then a build can be
+// using an older rsc.io/qr module that includes the gf256 package, but if
+// it adds a requirement on either the newer rsc.io/qr or the newer
+// rsc.io/qr/gf256 module, it will automatically add the requirement
+// on the complementary half, ensuring both that rsc.io/qr/gf256 is
+// available for importing by the build and also that it is only defined
+// by a single module. The gf256 package could move back into the
+// original by another simultaneous release of rsc.io/qr v0.4.0 including
+// the gf256 subdirectory and an rsc.io/qr/gf256 v0.2.0 with no code
+// in its root directory, along with a new requirement cycle.
+// The ability to shift module boundaries in this way is expected to be
+// important in large-scale program refactorings, similar to the ones
+// described in https://talks.golang.org/2016/refactor.article.
+//
+// The possibility of shifting module boundaries reemphasizes
+// that you must know both the module path and its version
+// to determine the set of packages provided directly by that module.
+//
+// On top of all this, it is possible for a single code repository
+// to contain multiple modules, either in branches or subdirectories,
+// as a limited kind of monorepo. For example rsc.io/qr/v2,
+// the v2.x.x continuation of rsc.io/qr, is expected to be found
+// in v2-tagged commits in https://github.com/rsc/qr, either
+// in the root or in a v2 subdirectory, disambiguated by go.mod.
+// Again the precise file tree corresponding to a module
+// depends on which version we are considering.
+//
+// It is also possible for the underlying repository to change over time,
+// without changing the module path. If I copy the github repo over
+// to https://bitbucket.org/rsc/qr and update https://rsc.io/qr?go-get=1,
+// then clients of all versions should start fetching from bitbucket
+// instead of github. That is, in contrast to the exact file tree,
+// the location of the source code repository associated with a module path
+// does not depend on the module version. (This is by design, as the whole
+// point of these redirects is to allow package authors to establish a stable
+// name that can be updated as code moves from one service to another.)
+//
+// All of this is important background for the lookup APIs defined in this
+// file.
+//
+// The Lookup function takes a module path and returns a Repo representing
+// that module path. Lookup can do only a little with the path alone.
+// It can check that the path is well-formed (see semver.CheckPath)
+// and it can check that the path can be resolved to a target repository.
+// To avoid version control access except when absolutely necessary,
+// Lookup does not attempt to connect to the repository itself.
+//
+// The Import function takes an import path found in source code and
+// determines which module to add to the requirement list to satisfy
+// that import. It checks successive truncations of the import path
+// to determine possible modules and stops when it finds a module
+// in which the latest version satisfies the import path.
+//
+// The ImportRepoRev function is a variant of Import which is limited
+// to code in a source code repository at a particular revision identifier
+// (usually a commit hash or source code repository tag, not necessarily
+// a module version).
+// ImportRepoRev is used when converting legacy dependency requirements
+// from older systems into go.mod files. Those older systems worked
+// at either package or repository granularity, and most of the time they
+// recorded commit hashes, not tagged versions.
+
 var lookupCache par.Cache
 
 // Lookup returns the module with the given module path.
+// A successful return does not guarantee that the module
+// has any defined versions.
 func Lookup(path string) (Repo, error) {
 	if traceRepo {
 		defer logCall("Lookup(%q)", path)()
@@ -99,19 +212,49 @@
 	if proxyURL != "" {
 		return lookupProxy(path)
 	}
-	if code, root, err := lookupCodeHost(path, false); err != errNotHosted {
-		if err != nil {
-			return nil, err
-		}
-		return newCodeRepo(code, root, path)
+
+	rr, err := get.RepoRootForImportPath(path, get.PreferMod, web.Secure)
+	if err != nil {
+		// We don't know where to find code for a module with this path.
+		return nil, err
 	}
-	return lookupCustomDomain(path)
+
+	if rr.VCS == "mod" {
+		// Fetch module from proxy with base URL rr.Repo.
+		return newProxyRepo(rr.Repo, path), nil
+	}
+
+	code, err := lookupCodeRepo(rr)
+	if err != nil {
+		return nil, err
+	}
+	return newCodeRepo(code, rr.Root, path)
 }
 
+func lookupCodeRepo(rr *get.RepoRoot) (codehost.Repo, error) {
+	switch rr.VCS {
+	default:
+		return nil, fmt.Errorf("lookup %s: unknown VCS %s %s", rr.Root, rr.VCS, rr.Repo)
+	case "git":
+		return gitrepo.Repo(rr.Repo)
+		// TODO: "hg", "svn", "bzr", "fossil"
+	}
+}
+
+// Import returns the module repo and version to use to satisfy the given import path.
+// It considers a sequence of module paths starting with the import path and
+// removing successive path elements from the end. It stops when it finds a module
+// path for which the latest version of the module provides the expected package.
+// If non-nil, the allowed function is used to filter known versions of a given module
+// before determining which one is "latest".
 func Import(path string, allowed func(module.Version) bool) (Repo, *RevInfo, error) {
+	if cfg.BuildGetmode != "" {
+		return nil, nil, fmt.Errorf("import resolution disabled by -getmode=%s", cfg.BuildGetmode)
+	}
 	if traceRepo {
 		defer logCall("Import(%q, ...)", path)()
 	}
+
 	try := func(path string) (Repo, *RevInfo, error) {
 		r, err := Lookup(path)
 		if err != nil {
@@ -125,9 +268,14 @@
 		if err != nil {
 			return nil, nil, err
 		}
+		// TODO(rsc): Do what the docs promise: download the module
+		// source code and check that it actually contains code for the
+		// target import path. To do that efficiently we will need to move
+		// the unzipped code cache out of ../vgo into this package.
 		return r, info, nil
 	}
 
+	// Find enclosing module by walking up path element by element.
 	var firstErr error
 	for {
 		r, info, err := try(path)
@@ -146,20 +294,47 @@
 	return nil, nil, firstErr
 }
 
-var errNotHosted = errors.New("not hosted")
-
-func lookupCodeHost(path string, customDomain bool) (codehost.Repo, string, error) {
-	switch {
-	case strings.HasPrefix(path, "github.com/"):
-		return github.Lookup(path)
-	case strings.HasPrefix(path, "bitbucket.org/"):
-		return bitbucket.Lookup(path)
-	case customDomain && strings.HasSuffix(path[:strings.Index(path, "/")+1], ".googlesource.com/"):
-		return googlesource.Lookup(path)
-	case strings.HasPrefix(path, "gopkg.in/"):
-		return gopkginLookup(path)
+// ImportRepoRev returns the module and version to use to access
+// the given import path loaded from the source code repository that
+// the original "go get" would have used, at the specific repository revision
+// (typically a commit hash, but possibly also a source control tag).
+func ImportRepoRev(path, rev string) (Repo, *RevInfo, error) {
+	if cfg.BuildGetmode != "" {
+		return nil, nil, fmt.Errorf("repo version lookup disabled by -getmode=%s", cfg.BuildGetmode)
 	}
-	return nil, "", errNotHosted
+
+	// Note: Because we are converting a code reference from a legacy
+	// version control system, we ignore meta tags about modules
+	// and use only direct source control entries (get.IgnoreMod).
+	rr, err := get.RepoRootForImportPath(path, get.IgnoreMod, web.Secure)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	code, err := lookupCodeRepo(rr)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	revInfo, err := code.Stat(rev)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: Look in repo to find path, check for go.mod files.
+	// For now we're just assuming rr.Root is the module path,
+	// which is true in the absence of go.mod files.
+
+	repo, err := newCodeRepo(code, rr.Root, rr.Root)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	info, err := repo.(*codeRepo).convert(revInfo)
+	if err != nil {
+		return nil, nil, err
+	}
+	return repo, info, nil
 }
 
 func SortVersions(list []string) {
diff --git a/vendor/cmd/go/internal/vgo/fetch.go b/vendor/cmd/go/internal/vgo/fetch.go
index 7b91908..0723dfa 100644
--- a/vendor/cmd/go/internal/vgo/fetch.go
+++ b/vendor/cmd/go/internal/vgo/fetch.go
@@ -40,14 +40,14 @@
 	modpath := mod.Path + "@" + mod.Version
 	dir = filepath.Join(SrcMod, modpath)
 	if files, _ := ioutil.ReadDir(dir); len(files) == 0 {
-		zipfile := filepath.Join(SrcMod, "cache", mod.Path, "@v", mod.Version+".zip")
+		zipfile := filepath.Join(SrcMod, "cache/download", 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.
+			// This should only happen if the mod/cache directory is preinitialized
+			// or if src/mod/path was removed but not src/mod/cache/download.
 			fmt.Fprintf(os.Stderr, "vgo: extracting %s %s\n", mod.Path, mod.Version)
 		} else {
-			if err := os.MkdirAll(filepath.Join(SrcMod, "cache", mod.Path, "@v"), 0777); err != nil {
+			if err := os.MkdirAll(filepath.Join(SrcMod, "cache/download", 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(SrcMod, "cache", mod.Path, "@v", mod.Version+".ziphash"))
+	data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache/download", 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(SrcMod, "cache", mod.Path, "@v", mod.Version+".ziphash"))
+	data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache/download", 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 94741bb..e66ce72 100644
--- a/vendor/cmd/go/internal/vgo/init.go
+++ b/vendor/cmd/go/internal/vgo/init.go
@@ -188,8 +188,8 @@
 		os.Rename(srcV, SrcMod)
 	}
 
-	modfetch.CacheRoot = filepath.Join(SrcMod, "cache")
-	codehost.WorkRoot = filepath.Join(SrcMod, "cache/vcswork")
+	modfetch.CacheRoot = filepath.Join(SrcMod, "cache/download")
+	codehost.WorkRoot = filepath.Join(SrcMod, "cache/vcs")
 
 	if CmdModInit {
 		// Running go mod -init: do legacy module conversion
diff --git a/vendor/cmd/go/vgo_test.go b/vendor/cmd/go/vgo_test.go
index ef911bc..dcd90be 100644
--- a/vendor/cmd/go/vgo_test.go
+++ b/vendor/cmd/go/vgo_test.go
@@ -354,9 +354,9 @@
 	tg.cd(filepath.Join(wd, "testdata/badmod"))
 
 	tg.runFail("-vgo", "get", "appengine")
-	tg.grepStderr("unknown module appengine: not a domain name", "expected domain error")
+	tg.grepStderr(`unrecognized import path \"appengine\"`, "expected appengine error ")
 	tg.runFail("-vgo", "get", "x/y.z")
-	tg.grepStderr("unknown module x/y.z: not a domain name", "expected domain error")
+	tg.grepStderr(`unrecognized import path \"x/y.z\" \(import path does not begin with hostname\)`, "expected domain error")
 
 	tg.runFail("-vgo", "build")
 	tg.grepStderrNot("unknown module appengine: not a domain name", "expected nothing about appengine")
@@ -485,7 +485,7 @@
 	tg.grepStdout(`vendormod[/\\]w`, "expected w in vendormod/w")
 
 	tg.runFail("-vgo", "list", "-getmode=local", "-f={{.Dir}}", "newpkg")
-	tg.grepStderr(`module lookup disabled by -getmode=local`, "expected -getmode=local to avoid network")
+	tg.grepStderr(`disabled by -getmode=local`, "expected -getmode=local to avoid network")
 
 	if !testing.Short() {
 		tg.run("-vgo", "build")
@@ -625,7 +625,7 @@
 	// it would choose a newer version (like v0.8.0 or maybe
 	// something even newer). Check for the older version to
 	// make sure Gopkg.lock was properly used.
-	tg.grepStderr("v0.6.0", "expected github.com/pkg/errors at v0.6.0")
+	tg.grepStdout("v0.6.0", "expected github.com/pkg/errors at v0.6.0")
 }
 
 func TestVerifyNotDownloaded(t *testing.T) {