cmd/go/internal/modfetch/codehost: add general vcs support

Add support for code fetch from bzr, fossil, hg, svn repositories.
Table-driven in a similar manner to old go get, but a different
operation set, so different code.

The git support remains a separate, special case, making use of
git-specific optimizations that wouldn't fit into the general
framework being introduced for the other version control systems.

Now vgo can handle launchpad.net/gocheck and bitbucket.org/ww/goautoneg.

Support for Subversion zip files remains a TODO (#26092).

Change-Id: I6f296a7f6588df08d48b39b98fe255d9b07c76d0
Reviewed-on: https://go-review.googlesource.com/120998
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/get/vcs.go b/vendor/cmd/go/internal/get/vcs.go
index 73b3dd0..82392d3 100644
--- a/vendor/cmd/go/internal/get/vcs.go
+++ b/vendor/cmd/go/internal/get/vcs.go
@@ -735,11 +735,14 @@
 					if security == web.Secure && !vcs.isSecureScheme(scheme) {
 						continue
 					}
-					if vcs.ping(scheme, match["repo"]) == nil {
+					if vcs.pingCmd != "" && vcs.ping(scheme, match["repo"]) == nil {
 						match["repo"] = scheme + "://" + match["repo"]
-						break
+						goto Found
 					}
 				}
+				// No scheme found. Fall back to the first one.
+				match["repo"] = vcs.scheme[0] + "://" + match["repo"]
+			Found:
 			}
 		}
 		rr := &RepoRoot{
diff --git a/vendor/cmd/go/internal/modfetch/cache.go b/vendor/cmd/go/internal/modfetch/cache.go
index b7cf4a5..ed5efcd 100644
--- a/vendor/cmd/go/internal/modfetch/cache.go
+++ b/vendor/cmd/go/internal/modfetch/cache.go
@@ -100,10 +100,6 @@
 }
 
 func (r *cachingRepo) Latest() (*RevInfo, error) {
-	type cachedInfo struct {
-		info *RevInfo
-		err  error
-	}
 	c := r.cache.Do("latest:", func() interface{} {
 		if !QuietLookup {
 			fmt.Fprintf(os.Stderr, "vgo: finding %s latest\n", r.path)
diff --git a/vendor/cmd/go/internal/modfetch/codehost/codehost.go b/vendor/cmd/go/internal/modfetch/codehost/codehost.go
index 85c0fe7..7a345db 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/codehost.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/codehost.go
@@ -115,21 +115,20 @@
 	key := typ + ":" + name
 	dir := filepath.Join(WorkRoot, fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
 	data, err := ioutil.ReadFile(dir + ".info")
-	if err == nil {
+	info, err2 := os.Stat(dir)
+	if err == nil && err2 == nil && info.IsDir() {
+		// Info file and directory both already exist: reuse.
 		have := strings.TrimSuffix(string(data), "\n")
 		if have != key {
 			return "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key)
 		}
-		_, err := os.Stat(dir)
-		if err != nil {
-			return "", fmt.Errorf("%s exists but %s does not", dir+".info", dir)
-		}
 		if cfg.BuildX {
 			fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name)
 		}
 		return dir, nil
 	}
 
+	// Info file or directory missing. Start from scratch.
 	if cfg.BuildX {
 		fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name)
 	}
diff --git a/vendor/cmd/go/internal/modfetch/codehost/git.go b/vendor/cmd/go/internal/modfetch/codehost/git.go
index ee616b2..66fdc0b 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/git.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/git.go
@@ -359,9 +359,6 @@
 func (r *gitRepo) statLocal(version, rev string) (*RevInfo, error) {
 	out, err := Run(r.dir, "git", "log", "-n1", "--format=format:%H %ct", rev)
 	if err != nil {
-		if AllHex(rev) {
-			return nil, fmt.Errorf("unknown hash %s", rev)
-		}
 		return nil, fmt.Errorf("unknown revision %s", rev)
 	}
 	f := strings.Fields(string(out))
@@ -387,6 +384,9 @@
 }
 
 func (r *gitRepo) Stat(rev string) (*RevInfo, error) {
+	if rev == "latest" {
+		return r.Latest()
+	}
 	type cached struct {
 		info *RevInfo
 		err  error
diff --git a/vendor/cmd/go/internal/modfetch/codehost/git_test.go b/vendor/cmd/go/internal/modfetch/codehost/git_test.go
index a2faa8e..3d0f834 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/git_test.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/git_test.go
@@ -13,6 +13,7 @@
 	"log"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"reflect"
 	"strings"
@@ -24,7 +25,18 @@
 	os.Exit(testMain(m))
 }
 
-const gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1"
+const (
+	gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1"
+	hgrepo1  = "https://vcs-test.golang.org/hg/hgrepo1"
+)
+
+var altRepos = []string{
+	"localGitRepo",
+	hgrepo1,
+}
+
+// TODO: Convert gitrepo1 to svn, bzr, fossil and add tests.
+// For now, at least the hgrepo1 tests check the general vcs.go logic.
 
 // localGitRepo is like gitrepo1 but allows archive access.
 var localGitRepo string
@@ -60,11 +72,17 @@
 	return m.Run()
 }
 
-func testGitRepo(remote string) (Repo, error) {
+func testRepo(remote string) (Repo, error) {
 	if remote == "localGitRepo" {
-		remote = "file://" + filepath.ToSlash(localGitRepo)
+		return LocalGitRepo("file://" + filepath.ToSlash(localGitRepo))
 	}
-	return LocalGitRepo(remote)
+	kind := "git"
+	for _, k := range []string{"hg"} {
+		if strings.Contains(remote, "/"+k+"/") {
+			kind = k
+		}
+	}
+	return NewRepo(kind, remote)
 }
 
 var tagsTests = []struct {
@@ -79,13 +97,13 @@
 	{gitrepo1, "2", []string{}},
 }
 
-func TestGitTags(t *testing.T) {
+func TestTags(t *testing.T) {
 	testenv.MustHaveExternalNetwork(t)
 	testenv.MustHaveExec(t)
 
 	for _, tt := range tagsTests {
 		f := func(t *testing.T) {
-			r, err := testGitRepo(tt.repo)
+			r, err := testRepo(tt.repo)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -97,10 +115,11 @@
 				t.Errorf("Tags: incorrect tags\nhave %v\nwant %v", tags, tt.tags)
 			}
 		}
-		t.Run(tt.repo+"/"+tt.prefix, f)
+		t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
 		if tt.repo == gitrepo1 {
-			tt.repo = "localGitRepo"
-			t.Run(tt.repo+"/"+tt.prefix, f)
+			for _, tt.repo = range altRepos {
+				t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
+			}
 		}
 	}
 }
@@ -118,15 +137,24 @@
 			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
 		},
 	},
+	{
+		hgrepo1,
+		&RevInfo{
+			Name:    "18518c07eb8ed5c80221e997e518cccaa8c0c287",
+			Short:   "18518c07eb8e",
+			Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
+			Time:    time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC),
+		},
+	},
 }
 
-func TestGitLatest(t *testing.T) {
+func TestLatest(t *testing.T) {
 	testenv.MustHaveExternalNetwork(t)
 	testenv.MustHaveExec(t)
 
 	for _, tt := range latestTests {
 		f := func(t *testing.T) {
-			r, err := testGitRepo(tt.repo)
+			r, err := testRepo(tt.repo)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -138,10 +166,10 @@
 				t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
 			}
 		}
-		t.Run(tt.repo, f)
+		t.Run(path.Base(tt.repo), f)
 		if tt.repo == gitrepo1 {
 			tt.repo = "localGitRepo"
-			t.Run(tt.repo, f)
+			t.Run(path.Base(tt.repo), f)
 		}
 	}
 }
@@ -155,7 +183,7 @@
 }{
 	{
 		repo: gitrepo1,
-		rev:  "HEAD",
+		rev:  "latest",
 		file: "README",
 		data: "",
 	},
@@ -173,13 +201,13 @@
 	},
 }
 
-func TestGitReadFile(t *testing.T) {
+func TestReadFile(t *testing.T) {
 	testenv.MustHaveExternalNetwork(t)
 	testenv.MustHaveExec(t)
 
 	for _, tt := range readFileTests {
 		f := func(t *testing.T) {
-			r, err := testGitRepo(tt.repo)
+			r, err := testRepo(tt.repo)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -203,10 +231,11 @@
 				t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data)
 			}
 		}
-		t.Run(tt.repo+"/"+tt.rev+"/"+tt.file, f)
+		t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
 		if tt.repo == gitrepo1 {
-			tt.repo = "localGitRepo"
-			t.Run(tt.repo+"/"+tt.rev+"/"+tt.file, f)
+			for _, tt.repo = range altRepos {
+				t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
+			}
 		}
 	}
 }
@@ -230,6 +259,17 @@
 		},
 	},
 	{
+		repo:   hgrepo1,
+		rev:    "v2.3.4",
+		subdir: "",
+		files: map[string]uint64{
+			"prefix/.hg_archival.txt": ^uint64(0),
+			"prefix/README":           0,
+			"prefix/v2":               3,
+		},
+	},
+
+	{
 		repo:   gitrepo1,
 		rev:    "v2",
 		subdir: "",
@@ -242,6 +282,19 @@
 		},
 	},
 	{
+		repo:   hgrepo1,
+		rev:    "v2",
+		subdir: "",
+		files: map[string]uint64{
+			"prefix/.hg_archival.txt": ^uint64(0),
+			"prefix/README":           0,
+			"prefix/v2":               3,
+			"prefix/another.txt":      8,
+			"prefix/foo.txt":          13,
+		},
+	},
+
+	{
 		repo:   gitrepo1,
 		rev:    "v3",
 		subdir: "",
@@ -255,6 +308,18 @@
 		},
 	},
 	{
+		repo:   hgrepo1,
+		rev:    "v3",
+		subdir: "",
+		files: map[string]uint64{
+			"prefix/.hg_archival.txt":    ^uint64(0),
+			"prefix/.hgtags":             405,
+			"prefix/v3/sub/dir/file.txt": 16,
+			"prefix/README":              0,
+		},
+	},
+
+	{
 		repo:   gitrepo1,
 		rev:    "v3",
 		subdir: "v3/sub/dir",
@@ -267,6 +332,15 @@
 		},
 	},
 	{
+		repo:   hgrepo1,
+		rev:    "v3",
+		subdir: "v3/sub/dir",
+		files: map[string]uint64{
+			"prefix/v3/sub/dir/file.txt": 16,
+		},
+	},
+
+	{
 		repo:   gitrepo1,
 		rev:    "v3",
 		subdir: "v3/sub",
@@ -279,12 +353,28 @@
 		},
 	},
 	{
+		repo:   hgrepo1,
+		rev:    "v3",
+		subdir: "v3/sub",
+		files: map[string]uint64{
+			"prefix/v3/sub/dir/file.txt": 16,
+		},
+	},
+
+	{
 		repo:   gitrepo1,
 		rev:    "aaaaaaaaab",
 		subdir: "",
-		err:    "unknown hash",
+		err:    "unknown revision",
 	},
 	{
+		repo:   hgrepo1,
+		rev:    "aaaaaaaaab",
+		subdir: "",
+		err:    "unknown revision",
+	},
+
+	{
 		repo:   "https://github.com/rsc/vgotest1",
 		rev:    "submod/v1.0.4",
 		subdir: "submod",
@@ -303,13 +393,13 @@
 	size int64
 }
 
-func TestGitReadZip(t *testing.T) {
+func TestReadZip(t *testing.T) {
 	testenv.MustHaveExternalNetwork(t)
 	testenv.MustHaveExec(t)
 
 	for _, tt := range readZipTests {
 		f := func(t *testing.T) {
-			r, err := testGitRepo(tt.repo)
+			r, err := testRepo(tt.repo)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -349,7 +439,7 @@
 					continue
 				}
 				have[f.Name] = true
-				if f.UncompressedSize64 != size {
+				if size != ^uint64(0) && f.UncompressedSize64 != size {
 					t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size)
 				}
 			}
@@ -359,14 +449,22 @@
 				}
 			}
 		}
-		t.Run(tt.repo+"/"+tt.rev+"/"+tt.subdir, f)
+		t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
 		if tt.repo == gitrepo1 {
 			tt.repo = "localGitRepo"
-			t.Run(tt.repo+"/"+tt.rev+"/"+tt.subdir, f)
+			t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
 		}
 	}
 }
 
+var hgmap = map[string]string{
+	"HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d", // not tip due to bad hgrepo1 conversion
+	"9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e",
+	"76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0",
+	"ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d",
+	"97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d",
+}
+
 var statTests = []struct {
 	repo string
 	rev  string
@@ -456,17 +554,17 @@
 	{
 		repo: gitrepo1,
 		rev:  "aaaaaaaaab",
-		err:  "unknown hash",
+		err:  "unknown revision",
 	},
 }
 
-func TestGitStat(t *testing.T) {
+func TestStat(t *testing.T) {
 	testenv.MustHaveExternalNetwork(t)
 	testenv.MustHaveExec(t)
 
 	for _, tt := range statTests {
 		f := func(t *testing.T) {
-			r, err := testGitRepo(tt.repo)
+			r, err := testRepo(tt.repo)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -487,10 +585,39 @@
 				t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
 			}
 		}
-		t.Run(filepath.Base(tt.repo)+"/"+tt.rev, f)
+		t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
 		if tt.repo == gitrepo1 {
-			tt.repo = "localGitRepo"
-			t.Run(filepath.Base(tt.repo)+"/"+tt.rev, f)
+			for _, tt.repo = range altRepos {
+				old := tt
+				var m map[string]string
+				if tt.repo == hgrepo1 {
+					m = hgmap
+				}
+				if tt.info != nil {
+					info := *tt.info
+					tt.info = &info
+					tt.info.Name = remap(tt.info.Name, m)
+					tt.info.Version = remap(tt.info.Version, m)
+					tt.info.Short = remap(tt.info.Short, m)
+				}
+				tt.rev = remap(tt.rev, m)
+				t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
+				tt = old
+			}
 		}
 	}
 }
+
+func remap(name string, m map[string]string) string {
+	if m[name] != "" {
+		return m[name]
+	}
+	if AllHex(name) {
+		for k, v := range m {
+			if strings.HasPrefix(k, name) {
+				return v[:len(name)]
+			}
+		}
+	}
+	return name
+}
diff --git a/vendor/cmd/go/internal/modfetch/codehost/shell.go b/vendor/cmd/go/internal/modfetch/codehost/shell.go
new file mode 100644
index 0000000..7b813c3
--- /dev/null
+++ b/vendor/cmd/go/internal/modfetch/codehost/shell.go
@@ -0,0 +1,140 @@
+// 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.
+
+// +build ignore
+
+// Interactive debugging shell for codehost.Repo implementations.
+
+package main
+
+import (
+	"archive/zip"
+	"bufio"
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+	"time"
+
+	"cmd/go/internal/modfetch/codehost"
+)
+
+func usage() {
+	fmt.Fprintf(os.Stderr, "usage: go run shell.go vcs remote\n")
+	os.Exit(2)
+}
+
+func main() {
+	codehost.WorkRoot = "/tmp/vcswork"
+	log.SetFlags(0)
+	log.SetPrefix("shell: ")
+	flag.Usage = usage
+	flag.Parse()
+	if flag.NArg() != 2 {
+		usage()
+	}
+
+	repo, err := codehost.NewRepo(flag.Arg(0), flag.Arg(1))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	b := bufio.NewReader(os.Stdin)
+	for {
+		fmt.Fprintf(os.Stderr, ">>> ")
+		line, err := b.ReadString('\n')
+		if err != nil {
+			log.Fatal(err)
+		}
+		f := strings.Fields(line)
+		if len(f) == 0 {
+			continue
+		}
+		switch f[0] {
+		default:
+			fmt.Fprintf(os.Stderr, "?unknown command\n")
+			continue
+		case "tags":
+			prefix := ""
+			if len(f) == 2 {
+				prefix = f[1]
+			}
+			if len(f) > 2 {
+				fmt.Fprintf(os.Stderr, "?usage: tags [prefix]\n")
+				continue
+			}
+			tags, err := repo.Tags(prefix)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "?%s\n", err)
+				continue
+			}
+			for _, tag := range tags {
+				fmt.Printf("%s\n", tag)
+			}
+
+		case "stat":
+			if len(f) != 2 {
+				fmt.Fprintf(os.Stderr, "?usage: stat rev\n")
+				continue
+			}
+			info, err := repo.Stat(f[1])
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "?%s\n", err)
+				continue
+			}
+			fmt.Printf("name=%s short=%s version=%s time=%s\n", info.Name, info.Short, info.Version, info.Time.UTC().Format(time.RFC3339))
+
+		case "read":
+			if len(f) != 3 {
+				fmt.Fprintf(os.Stderr, "?usage: read rev file\n")
+				continue
+			}
+			data, err := repo.ReadFile(f[1], f[2], 10<<20)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "?%s\n", err)
+				continue
+			}
+			os.Stdout.Write(data)
+
+		case "zip":
+			if len(f) != 4 {
+				fmt.Fprintf(os.Stderr, "?usage: zip rev subdir output\n")
+				continue
+			}
+			subdir := f[2]
+			if subdir == "-" {
+				subdir = ""
+			}
+			rc, _, err := repo.ReadZip(f[1], subdir, 10<<20)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "?%s\n", err)
+				continue
+			}
+			data, err := ioutil.ReadAll(rc)
+			rc.Close()
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "?%s\n", err)
+				continue
+			}
+
+			if f[3] != "-" {
+				if err := ioutil.WriteFile(f[3], data, 0666); err != nil {
+					fmt.Fprintf(os.Stderr, "?%s\n", err)
+					continue
+				}
+			}
+			z, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "?%s\n", err)
+				continue
+			}
+			for _, f := range z.File {
+				fmt.Printf("%s %d\n", f.Name, f.UncompressedSize64)
+			}
+		}
+	}
+}
diff --git a/vendor/cmd/go/internal/modfetch/codehost/vcs.go b/vendor/cmd/go/internal/modfetch/codehost/vcs.go
new file mode 100644
index 0000000..9c985f9
--- /dev/null
+++ b/vendor/cmd/go/internal/modfetch/codehost/vcs.go
@@ -0,0 +1,497 @@
+// 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 codehost
+
+import (
+	"encoding/xml"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"cmd/go/internal/par"
+	"cmd/go/internal/str"
+)
+
+func NewRepo(vcs, remote string) (Repo, error) {
+	type key struct {
+		vcs    string
+		remote string
+	}
+	type cached struct {
+		repo Repo
+		err  error
+	}
+	c := vcsRepoCache.Do(key{vcs, remote}, func() interface{} {
+		repo, err := newVCSRepo(vcs, remote)
+		return cached{repo, err}
+	}).(cached)
+
+	return c.repo, c.err
+}
+
+var vcsRepoCache par.Cache
+
+type vcsRepo struct {
+	remote string
+	cmd    *vcsCmd
+	dir    string
+
+	tagsOnce sync.Once
+	tags     map[string]bool
+
+	branchesOnce sync.Once
+	branches     map[string]bool
+
+	fetchOnce sync.Once
+	fetchErr  error
+}
+
+func newVCSRepo(vcs, remote string) (Repo, error) {
+	if vcs == "git" {
+		return newGitRepo(remote, false)
+	}
+	cmd := vcsCmds[vcs]
+	if cmd == nil {
+		return nil, fmt.Errorf("unknown vcs: %s %s", vcs, remote)
+	}
+	if !strings.Contains(remote, "://") {
+		return nil, fmt.Errorf("invalid vcs remote: %s %s", vcs, remote)
+	}
+	r := &vcsRepo{remote: remote, cmd: cmd}
+	if cmd.init == nil {
+		return r, nil
+	}
+	dir, err := WorkDir(vcsWorkDirType+vcs, r.remote)
+	if err != nil {
+		return nil, err
+	}
+	r.dir = dir
+	if _, err := os.Stat(filepath.Join(dir, "."+vcs)); err != nil {
+		if _, err := Run(dir, cmd.init(r.remote)); err != nil {
+			os.RemoveAll(dir)
+			return nil, err
+		}
+	}
+	return r, nil
+}
+
+const vcsWorkDirType = "vcs1."
+
+type vcsCmd struct {
+	vcs           string                                            // vcs name "hg"
+	init          func(remote string) []string                      // cmd to init repo to track remote
+	tags          func(remote string) []string                      // cmd to list local tags
+	tagRE         *regexp.Regexp                                    // regexp to extract tag names from output of tags cmd
+	branches      func(remote string) []string                      // cmd to list local branches
+	branchRE      *regexp.Regexp                                    // regexp to extract branch names from output of tags cmd
+	badLocalRevRE *regexp.Regexp                                    // regexp of names that must not be served out of local cache without doing fetch first
+	statLocal     func(rev, remote string) []string                 // cmd to stat local rev
+	parseStat     func(rev, out string) (*RevInfo, error)           // cmd to parse output of statLocal
+	fetch         []string                                          // cmd to fetch everything from remote
+	latest        string                                            // name of latest commit on remote (tip, HEAD, etc)
+	readFile      func(rev, file, remote string) []string           // cmd to read rev's file
+	readZip       func(rev, subdir, remote, target string) []string // cmd to read rev's subdir as zip file
+}
+
+var re = regexp.MustCompile
+
+var vcsCmds = map[string]*vcsCmd{
+	"hg": {
+		vcs: "hg",
+		init: func(remote string) []string {
+			return []string{"hg", "clone", "-U", remote, "."}
+		},
+		tags: func(remote string) []string {
+			return []string{"hg", "tags", "-q"}
+		},
+		tagRE: re(`(?m)^[^\n]+$`),
+		branches: func(remote string) []string {
+			return []string{"hg", "branches", "-c", "-q"}
+		},
+		branchRE:      re(`(?m)^[^\n]+$`),
+		badLocalRevRE: re(`(?m)^(tip)$`),
+		statLocal: func(rev, remote string) []string {
+			return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate}"}
+		},
+		parseStat: hgParseStat,
+		fetch:     []string{"hg", "pull", "-f"},
+		latest:    "tip",
+		readFile: func(rev, file, remote string) []string {
+			return []string{"hg", "cat", "-r", rev, file}
+		},
+		readZip: func(rev, subdir, remote, target string) []string {
+			pattern := []string{}
+			if subdir != "" {
+				pattern = []string{"-I", subdir + "/**"}
+			}
+			return str.StringList("hg", "archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/", pattern, target)
+		},
+	},
+
+	"svn": {
+		vcs:  "svn",
+		init: nil, // no local checkout
+		tags: func(remote string) []string {
+			return []string{"svn", "list", strings.TrimSuffix(remote, "/trunk") + "/tags"}
+		},
+		tagRE: re(`(?m)^(.*?)/?$`),
+		statLocal: func(rev, remote string) []string {
+			suffix := "@" + rev
+			if rev == "latest" {
+				suffix = ""
+			}
+			return []string{"svn", "log", "-l1", "--xml", remote + suffix}
+		},
+		parseStat: svnParseStat,
+		latest:    "latest",
+		readFile: func(rev, file, remote string) []string {
+			return []string{"svn", "cat", remote + "/" + file + "@" + rev}
+		},
+		// TODO: zip
+	},
+
+	"bzr": {
+		vcs: "bzr",
+		init: func(remote string) []string {
+			return []string{"bzr", "branch", "--use-existing-dir", remote, "."}
+		},
+		fetch: []string{
+			"bzr", "pull", "--overwrite-tags",
+		},
+		tags: func(remote string) []string {
+			return []string{"bzr", "tags"}
+		},
+		tagRE:         re(`(?m)^\S+`),
+		badLocalRevRE: re(`^revno:-`),
+		statLocal: func(rev, remote string) []string {
+			return []string{"bzr", "log", "-l1", "--long", "--show-ids", "-r", rev}
+		},
+		parseStat: bzrParseStat,
+		latest:    "revno:-1",
+		readFile: func(rev, file, remote string) []string {
+			return []string{"bzr", "cat", "-r", rev, file}
+		},
+		readZip: func(rev, subdir, remote, target string) []string {
+			extra := []string{}
+			if subdir != "" {
+				extra = []string{"./" + subdir}
+			}
+			return str.StringList("bzr", "export", "--format=zip", "-r", rev, "--root=prefix/", target, extra)
+		},
+	},
+
+	"fossil": {
+		vcs: "fossil",
+		init: func(remote string) []string {
+			return []string{"fossil", "clone", remote, ".fossil"}
+		},
+		fetch: []string{"fossil", "pull", "-R", ".fossil"},
+		tags: func(remote string) []string {
+			return []string{"fossil", "tag", "-R", ".fossil", "list"}
+		},
+		tagRE: re(`XXXTODO`),
+		statLocal: func(rev, remote string) []string {
+			return []string{"fossil", "info", "-R", ".fossil", rev}
+		},
+		parseStat: fossilParseStat,
+		latest:    "trunk",
+		readFile: func(rev, file, remote string) []string {
+			return []string{"fossil", "cat", "-R", ".fossil", "-r", rev, file}
+		},
+		readZip: func(rev, subdir, remote, target string) []string {
+			extra := []string{}
+			if subdir != "" && !strings.ContainsAny(subdir, "*?[],") {
+				extra = []string{"--include", subdir}
+			}
+			// Note that vcsRepo.ReadZip below rewrites this command
+			// to run in a different directory, to work around a fossil bug.
+			return str.StringList("fossil", "zip", "-R", ".fossil", "--name", "prefix", extra, rev, target)
+		},
+	},
+}
+
+func (r *vcsRepo) loadTags() {
+	out, err := Run(r.dir, r.cmd.tags(r.remote))
+	if err != nil {
+		return
+	}
+
+	// Run tag-listing command and extract tags.
+	r.tags = make(map[string]bool)
+	for _, tag := range r.cmd.tagRE.FindAllString(string(out), -1) {
+		if r.cmd.badLocalRevRE != nil && r.cmd.badLocalRevRE.MatchString(tag) {
+			continue
+		}
+		r.tags[tag] = true
+	}
+}
+
+func (r *vcsRepo) loadBranches() {
+	if r.cmd.branches == nil {
+		return
+	}
+
+	out, err := Run(r.dir, r.cmd.branches(r.remote))
+	if err != nil {
+		return
+	}
+
+	r.branches = make(map[string]bool)
+	for _, branch := range r.cmd.branchRE.FindAllString(string(out), -1) {
+		if r.cmd.badLocalRevRE != nil && r.cmd.badLocalRevRE.MatchString(branch) {
+			continue
+		}
+		r.branches[branch] = true
+	}
+}
+
+func (r *vcsRepo) Tags(prefix string) ([]string, error) {
+	r.tagsOnce.Do(r.loadTags)
+
+	tags := []string{}
+	for tag := range r.tags {
+		if strings.HasPrefix(tag, prefix) {
+			tags = append(tags, tag)
+		}
+	}
+	sort.Strings(tags)
+	return tags, nil
+}
+
+func (r *vcsRepo) Stat(rev string) (*RevInfo, error) {
+	if rev == "latest" {
+		rev = r.cmd.latest
+	}
+	r.branchesOnce.Do(r.loadBranches)
+	revOK := (r.cmd.badLocalRevRE == nil || !r.cmd.badLocalRevRE.MatchString(rev)) && !r.branches[rev]
+	if revOK {
+		if info, err := r.statLocal(rev); err == nil {
+			return info, nil
+		}
+	}
+
+	r.fetchOnce.Do(r.fetch)
+	if r.fetchErr != nil {
+		return nil, r.fetchErr
+	}
+	info, err := r.statLocal(rev)
+	if err != nil {
+		return nil, err
+	}
+	if !revOK {
+		info.Version = info.Name
+	}
+	return info, nil
+}
+
+func (r *vcsRepo) fetch() {
+	_, r.fetchErr = Run(r.dir, r.cmd.fetch)
+}
+
+func (r *vcsRepo) statLocal(rev string) (*RevInfo, error) {
+	out, err := Run(r.dir, r.cmd.statLocal(rev, r.remote))
+	if err != nil {
+		return nil, fmt.Errorf("unknown revision %s", rev)
+	}
+	return r.cmd.parseStat(rev, string(out))
+}
+
+func (r *vcsRepo) Latest() (*RevInfo, error) {
+	return r.Stat("latest")
+}
+
+func (r *vcsRepo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
+	if rev == "latest" {
+		rev = r.cmd.latest
+	}
+	_, err := r.Stat(rev) // download rev into local repo
+	if err != nil {
+		return nil, err
+	}
+	out, err := Run(r.dir, r.cmd.readFile(rev, file, r.remote))
+	if err != nil {
+		return nil, os.ErrNotExist
+	}
+	return out, nil
+}
+
+func (r *vcsRepo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) {
+	if rev == "latest" {
+		rev = r.cmd.latest
+	}
+	f, err := ioutil.TempFile("", "vgo-readzip-*.zip")
+	if err != nil {
+		return nil, "", err
+	}
+	if r.cmd.vcs == "fossil" {
+		// If you run
+		//	fossil zip -R .fossil --name prefix trunk /tmp/x.zip
+		// fossil fails with "unable to create directory /tmp" [sic].
+		// Change the command to run in /tmp instead,
+		// replacing the -R argument with an absolute path.
+		args := r.cmd.readZip(rev, subdir, r.remote, filepath.Base(f.Name()))
+		for i := range args {
+			if args[i] == ".fossil" {
+				args[i] = filepath.Join(r.dir, ".fossil")
+			}
+		}
+		_, err = Run(filepath.Dir(f.Name()), args)
+	} else {
+		_, err = Run(r.dir, r.cmd.readZip(rev, subdir, r.remote, f.Name()))
+	}
+	if err != nil {
+		f.Close()
+		os.Remove(f.Name())
+		return nil, "", err
+	}
+	return &deleteCloser{f}, "", nil
+}
+
+// deleteCloser is a file that gets deleted on Close.
+type deleteCloser struct {
+	*os.File
+}
+
+func (d *deleteCloser) Close() error {
+	defer os.Remove(d.File.Name())
+	return d.File.Close()
+}
+
+func hgParseStat(rev, out string) (*RevInfo, error) {
+	f := strings.Fields(string(out))
+	if len(f) != 3 {
+		return nil, fmt.Errorf("unexpected response from hg log: %q", out)
+	}
+	hash := f[0]
+	version := rev
+	if strings.HasPrefix(hash, version) {
+		version = hash // extend to full hash
+	}
+	t, err := strconv.ParseInt(f[1], 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("invalid time from hg log: %q", out)
+	}
+
+	info := &RevInfo{
+		Name:    hash,
+		Short:   ShortenSHA1(hash),
+		Time:    time.Unix(t, 0).UTC(),
+		Version: version,
+	}
+	return info, nil
+}
+
+func svnParseStat(rev, out string) (*RevInfo, error) {
+	var log struct {
+		Logentry struct {
+			Revision int64  `xml:"revision,attr"`
+			Date     string `xml:"date"`
+		} `xml:"logentry"`
+	}
+	if err := xml.Unmarshal([]byte(out), &log); err != nil {
+		return nil, fmt.Errorf("unexpected response from svn log --xml: %v\n%s", err, out)
+	}
+
+	t, err := time.Parse(time.RFC3339, log.Logentry.Date)
+	if err != nil {
+		return nil, fmt.Errorf("unexpected response from svn log --xml: %v\n%s", err, out)
+	}
+
+	info := &RevInfo{
+		Name:    fmt.Sprintf("%d", log.Logentry.Revision),
+		Short:   fmt.Sprintf("%012d", log.Logentry.Revision),
+		Time:    t.UTC(),
+		Version: rev,
+	}
+	return info, nil
+}
+
+func bzrParseStat(rev, out string) (*RevInfo, error) {
+	var revno int64
+	var tm time.Time
+	for _, line := range strings.Split(out, "\n") {
+		if line == "" || line[0] == ' ' || line[0] == '\t' {
+			// End of header, start of commit message.
+			break
+		}
+		if line[0] == '-' {
+			continue
+		}
+		i := strings.Index(line, ":")
+		if i < 0 {
+			// End of header, start of commit message.
+			break
+		}
+		key, val := line[:i], strings.TrimSpace(line[i+1:])
+		switch key {
+		case "revno":
+			if j := strings.Index(val, " "); j >= 0 {
+				val = val[:j]
+			}
+			i, err := strconv.ParseInt(val, 10, 64)
+			if err != nil {
+				return nil, fmt.Errorf("unexpected revno from bzr log: %q", line)
+			}
+			revno = i
+		case "timestamp":
+			j := strings.Index(val, " ")
+			if j < 0 {
+				return nil, fmt.Errorf("unexpected timestamp from bzr log: %q", line)
+			}
+			t, err := time.Parse("2006-01-02 15:04:05 -0700", val[j+1:])
+			if err != nil {
+				return nil, fmt.Errorf("unexpected timestamp from bzr log: %q", line)
+			}
+			tm = t.UTC()
+		}
+	}
+	if revno == 0 || tm.IsZero() {
+		return nil, fmt.Errorf("unexpected response from bzr log: %q", out)
+	}
+
+	info := &RevInfo{
+		Name:    fmt.Sprintf("%d", revno),
+		Short:   fmt.Sprintf("%012d", revno),
+		Time:    tm,
+		Version: rev,
+	}
+	return info, nil
+}
+
+func fossilParseStat(rev, out string) (*RevInfo, error) {
+	for _, line := range strings.Split(out, "\n") {
+		if strings.HasPrefix(line, "uuid:") {
+			f := strings.Fields(line)
+			if len(f) != 5 || len(f[1]) != 40 || f[4] != "UTC" {
+				return nil, fmt.Errorf("unexpected response from fossil info: %q", line)
+			}
+			t, err := time.Parse("2006-01-02 15:04:05", f[2]+" "+f[3])
+			if err != nil {
+				return nil, fmt.Errorf("unexpected response from fossil info: %q", line)
+			}
+			hash := f[1]
+			version := rev
+			if strings.HasPrefix(hash, version) {
+				version = hash // extend to full hash
+			}
+			info := &RevInfo{
+				Name:    hash,
+				Short:   ShortenSHA1(hash),
+				Time:    t,
+				Version: version,
+			}
+			return info, nil
+		}
+	}
+	return nil, fmt.Errorf("unexpected response from fossil info: %q", out)
+}
diff --git a/vendor/cmd/go/internal/modfetch/coderepo.go b/vendor/cmd/go/internal/modfetch/coderepo.go
index dd0814b..fb14453 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo.go
@@ -417,6 +417,11 @@
 		if !strings.HasPrefix(name, subdir) {
 			continue
 		}
+		if name == ".hg_archival.txt" {
+			// Inserted by hg archive.
+			// Not correct to drop from other version control systems, but too bad.
+			continue
+		}
 		name = strings.TrimPrefix(name, subdir)
 		if isVendoredPackage(name) {
 			continue
diff --git a/vendor/cmd/go/internal/modfetch/coderepo_test.go b/vendor/cmd/go/internal/modfetch/coderepo_test.go
index 91e9a27..f760837 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo_test.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo_test.go
@@ -34,6 +34,15 @@
 	return m.Run()
 }
 
+const (
+	vgotest1git = "github.com/rsc/vgotest1"
+	vgotest1hg  = "vcs-test.golang.org/hg/vgotest1.hg"
+)
+
+var altVgotests = []string{
+	vgotest1hg,
+}
+
 var codeRepoTests = []struct {
 	path     string
 	lookerr  string
@@ -329,7 +338,7 @@
 	}
 	defer os.RemoveAll(tmpdir)
 	for _, tt := range codeRepoTests {
-		t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, func(t *testing.T) {
+		f := func(t *testing.T) {
 			repo, err := Lookup(tt.path)
 			if tt.lookerr != "" {
 				if err != nil && err.Error() == tt.lookerr {
@@ -417,10 +426,58 @@
 					t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
 				}
 			}
-		})
+		}
+		t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, f)
+		if strings.HasPrefix(tt.path, vgotest1git) {
+			for _, alt := range altVgotests {
+				// Note: Communicating with f through tt; should be cleaned up.
+				old := tt
+				tt.path = alt + strings.TrimPrefix(tt.path, vgotest1git)
+				if strings.HasPrefix(tt.mpath, vgotest1git) {
+					tt.mpath = alt + strings.TrimPrefix(tt.mpath, vgotest1git)
+				}
+				var m map[string]string
+				if alt == vgotest1hg {
+					m = hgmap
+				}
+				tt.version = remap(tt.version, m)
+				tt.name = remap(tt.name, m)
+				tt.short = remap(tt.short, m)
+				tt.rev = remap(tt.rev, m)
+				t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, f)
+				tt = old
+			}
+		}
 	}
 }
 
+var hgmap = map[string]string{
+	"f18795870fb14388a21ef3ebc1d75911c8694f31": "a9ad6d1d14eb544f459f446210c7eb3b009807c6",
+	"ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9": "f1fc0f22021b638d073d31c752847e7bf385def7",
+	"b769f2de407a4db81af9c5de0a06016d60d2ea09": "92c7eb888b4fac17f1c6bd2e1060a1b881a3b832",
+	"8afe2b2efed96e0880ecd2a69b98a53b8c2738b6": "4e58084d459ae7e79c8c2264d0e8e9a92eb5cd44",
+	"2f615117ce481c8efef46e0cc0b4b4dccfac8fea": "879ea98f7743c8eff54f59a918f3a24123d1cf46",
+	"80d85c5d4d17598a0e9055e7c175a32b415d6128": "e125018e286a4b09061079a81e7b537070b7ff71",
+	"1f863feb76bc7029b78b21c5375644838962f88d": "bf63880162304a9337477f3858f5b7e255c75459",
+}
+
+func remap(name string, m map[string]string) string {
+	if m[name] != "" {
+		return m[name]
+	}
+	if codehost.AllHex(name) {
+		for k, v := range m {
+			if strings.HasPrefix(k, name) {
+				return v[:len(name)]
+			}
+		}
+	}
+	if i := strings.LastIndex(name, "-"); i >= 0 { // remap tail of pseudo-version
+		return name[:i+1] + remap(name[i+1:], m)
+	}
+	return name
+}
+
 var importTests = []struct {
 	path  string
 	mpath string
diff --git a/vendor/cmd/go/internal/modfetch/repo.go b/vendor/cmd/go/internal/modfetch/repo.go
index b91790c..46dec0d 100644
--- a/vendor/cmd/go/internal/modfetch/repo.go
+++ b/vendor/cmd/go/internal/modfetch/repo.go
@@ -231,13 +231,11 @@
 }
 
 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 codehost.GitRepo(rr.Repo)
-		// TODO: "hg", "svn", "bzr", "fossil"
+	code, err := codehost.NewRepo(rr.VCS, rr.Repo)
+	if err != nil {
+		return nil, fmt.Errorf("lookup %s: %v", rr.Root, err)
 	}
+	return code, nil
 }
 
 // Import returns the module repo and version to use to satisfy the given import path.