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.