summaryrefslogtreecommitdiff
path: root/src/cmd/go/vcs.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/vcs.go')
-rw-r--r--src/cmd/go/vcs.go185
1 files changed, 168 insertions, 17 deletions
diff --git a/src/cmd/go/vcs.go b/src/cmd/go/vcs.go
index cf3410242..3634b606c 100644
--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -7,7 +7,9 @@ package main
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
+ "log"
"os"
"os/exec"
"path/filepath"
@@ -102,7 +104,7 @@ var vcsGit = &vcsCmd{
tagSyncCmd: "checkout {tag}",
tagSyncDefault: "checkout origin/master",
- scheme: []string{"git", "https", "http"},
+ scheme: []string{"git", "https", "http", "git+ssh"},
pingCmd: "ls-remote {scheme}://{repo}",
}
@@ -121,7 +123,7 @@ var vcsBzr = &vcsCmd{
tagSyncCmd: "update -r {tag}",
tagSyncDefault: "update -r revno:-1",
- scheme: []string{"https", "http", "bzr"},
+ scheme: []string{"https", "http", "bzr", "bzr+ssh"},
pingCmd: "info {scheme}://{repo}",
}
@@ -136,7 +138,7 @@ var vcsSvn = &vcsCmd{
// There is no tag command in subversion.
// The branch information is all in the path names.
- scheme: []string{"https", "http", "svn"},
+ scheme: []string{"https", "http", "svn", "svn+ssh"},
pingCmd: "info {scheme}://{repo}",
}
@@ -302,12 +304,58 @@ func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) {
return nil, "", fmt.Errorf("directory %q is not using a known version control system", dir)
}
-// vcsForImportPath analyzes importPath to determine the
+// repoRoot represents a version control system, a repo, and a root of
+// where to put it on disk.
+type repoRoot struct {
+ vcs *vcsCmd
+
+ // repo is the repository URL, including scheme
+ repo string
+
+ // root is the import path corresponding to the root of the
+ // repository
+ root string
+}
+
+// repoRootForImportPath analyzes importPath to determine the
// version control system, and code repository to use.
-// On return, repo is the repository URL and root is the
-// import path corresponding to the root of the repository
-// (thus root is a prefix of importPath).
-func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err error) {
+func repoRootForImportPath(importPath string) (*repoRoot, error) {
+ rr, err := repoRootForImportPathStatic(importPath, "")
+ if err == errUnknownSite {
+ rr, err = repoRootForImportDynamic(importPath)
+
+ // repoRootForImportDynamic returns error detail
+ // that is irrelevant if the user didn't intend to use a
+ // dynamic import in the first place.
+ // Squelch it.
+ if err != nil {
+ if buildV {
+ log.Printf("import %q: %v", importPath, err)
+ }
+ err = fmt.Errorf("unrecognized import path %q", importPath)
+ }
+ }
+
+ if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.root, "...") {
+ // Do not allow wildcards in the repo root.
+ rr = nil
+ err = fmt.Errorf("cannot expand ... in %q", importPath)
+ }
+ return rr, err
+}
+
+var errUnknownSite = errors.New("dynamic lookup required to find mapping")
+
+// repoRootForImportPathStatic attempts to map importPath to a
+// repoRoot using the commonly-used VCS hosting sites in vcsPaths
+// (github.com/user/dir), or from a fully-qualified importPath already
+// containing its VCS type (foo.com/repo.git/dir)
+//
+// If scheme is non-empty, that scheme is forced.
+func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
+ if strings.Contains(importPath, "://") {
+ return nil, fmt.Errorf("invalid import path %q", importPath)
+ }
for _, srv := range vcsPaths {
if !strings.HasPrefix(importPath, srv.prefix) {
continue
@@ -315,7 +363,7 @@ func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err er
m := srv.regexp.FindStringSubmatch(importPath)
if m == nil {
if srv.prefix != "" {
- return nil, "", "", fmt.Errorf("invalid %s import path %q", srv.prefix, importPath)
+ return nil, fmt.Errorf("invalid %s import path %q", srv.prefix, importPath)
}
continue
}
@@ -338,24 +386,127 @@ func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err er
}
if srv.check != nil {
if err := srv.check(match); err != nil {
- return nil, "", "", err
+ return nil, err
}
}
vcs := vcsByCmd(match["vcs"])
if vcs == nil {
- return nil, "", "", fmt.Errorf("unknown version control system %q", match["vcs"])
+ return nil, fmt.Errorf("unknown version control system %q", match["vcs"])
}
if srv.ping {
- for _, scheme := range vcs.scheme {
- if vcs.ping(scheme, match["repo"]) == nil {
- match["repo"] = scheme + "://" + match["repo"]
- break
+ if scheme != "" {
+ match["repo"] = scheme + "://" + match["repo"]
+ } else {
+ for _, scheme := range vcs.scheme {
+ if vcs.ping(scheme, match["repo"]) == nil {
+ match["repo"] = scheme + "://" + match["repo"]
+ break
+ }
}
}
}
- return vcs, match["repo"], match["root"], nil
+ rr := &repoRoot{
+ vcs: vcs,
+ repo: match["repo"],
+ root: match["root"],
+ }
+ return rr, nil
+ }
+ return nil, errUnknownSite
+}
+
+// repoRootForImportDynamic finds a *repoRoot for a custom domain that's not
+// statically known by repoRootForImportPathStatic.
+//
+// This handles "vanity import paths" like "name.tld/pkg/foo".
+func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
+ slash := strings.Index(importPath, "/")
+ if slash < 0 {
+ return nil, fmt.Errorf("missing / in import %q", importPath)
+ }
+ urlStr, body, err := httpsOrHTTP(importPath)
+ if err != nil {
+ return nil, fmt.Errorf("http/https fetch for import %q: %v", importPath, err)
+ }
+ defer body.Close()
+ metaImport, err := matchGoImport(parseMetaGoImports(body), importPath)
+ if err != nil {
+ if err != errNoMatch {
+ return nil, fmt.Errorf("parse %s: %v", urlStr, err)
+ }
+ return nil, fmt.Errorf("parse %s: no go-import meta tags", urlStr)
+ }
+ if buildV {
+ log.Printf("get %q: found meta tag %#v at %s", importPath, metaImport, urlStr)
+ }
+ // If the import was "uni.edu/bob/project", which said the
+ // prefix was "uni.edu" and the RepoRoot was "evilroot.com",
+ // make sure we don't trust Bob and check out evilroot.com to
+ // "uni.edu" yet (possibly overwriting/preempting another
+ // non-evil student). Instead, first verify the root and see
+ // if it matches Bob's claim.
+ if metaImport.Prefix != importPath {
+ if buildV {
+ log.Printf("get %q: verifying non-authoritative meta tag", importPath)
+ }
+ urlStr0 := urlStr
+ urlStr, body, err = httpsOrHTTP(metaImport.Prefix)
+ if err != nil {
+ return nil, fmt.Errorf("fetch %s: %v", urlStr, err)
+ }
+ imports := parseMetaGoImports(body)
+ if len(imports) == 0 {
+ return nil, fmt.Errorf("fetch %s: no go-import meta tag", urlStr)
+ }
+ metaImport2, err := matchGoImport(imports, importPath)
+ if err != nil || metaImport != metaImport2 {
+ return nil, fmt.Errorf("%s and %s disagree about go-import for %s", urlStr0, urlStr, metaImport.Prefix)
+ }
+ }
+
+ if !strings.Contains(metaImport.RepoRoot, "://") {
+ return nil, fmt.Errorf("%s: invalid repo root %q; no scheme", urlStr, metaImport.RepoRoot)
+ }
+ rr := &repoRoot{
+ vcs: vcsByCmd(metaImport.VCS),
+ repo: metaImport.RepoRoot,
+ root: metaImport.Prefix,
+ }
+ if rr.vcs == nil {
+ return nil, fmt.Errorf("%s: unknown vcs %q", urlStr, metaImport.VCS)
+ }
+ return rr, nil
+}
+
+// metaImport represents the parsed <meta name="go-import"
+// content="prefix vcs reporoot" /> tags from HTML files.
+type metaImport struct {
+ Prefix, VCS, RepoRoot string
+}
+
+// errNoMatch is returned from matchGoImport when there's no applicable match.
+var errNoMatch = errors.New("no import match")
+
+// matchGoImport returns the metaImport from imports matching importPath.
+// An error is returned if there are multiple matches.
+// errNoMatch is returned if none match.
+func matchGoImport(imports []metaImport, importPath string) (_ metaImport, err error) {
+ match := -1
+ for i, im := range imports {
+ if !strings.HasPrefix(importPath, im.Prefix) {
+ continue
+ }
+ if match != -1 {
+ err = fmt.Errorf("multiple meta tags match import path %q", importPath)
+ return
+ }
+ match = i
+ }
+ if match == -1 {
+ err = errNoMatch
+ return
}
- return nil, "", "", fmt.Errorf("unrecognized import path %q", importPath)
+ return imports[match], nil
}
// expand rewrites s to replace {k} with match[k] for each key k in match.