From 663e9d46b291feb3e1338210af3c66d531a2a235 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Thu, 7 Jun 2018 19:31:22 -0400 Subject: [PATCH] Add golang.org/x/tools/go/vcs to dev-tools/vendor --- dev-tools/vendor/golang.org/x/tools/LICENSE | 27 + dev-tools/vendor/golang.org/x/tools/PATENTS | 22 + .../golang.org/x/tools/go/vcs/discovery.go | 76 ++ .../vendor/golang.org/x/tools/go/vcs/env.go | 39 + .../vendor/golang.org/x/tools/go/vcs/http.go | 80 ++ .../vendor/golang.org/x/tools/go/vcs/vcs.go | 755 ++++++++++++++++++ dev-tools/vendor/vendor.json | 4 + 7 files changed, 1003 insertions(+) create mode 100644 dev-tools/vendor/golang.org/x/tools/LICENSE create mode 100644 dev-tools/vendor/golang.org/x/tools/PATENTS create mode 100644 dev-tools/vendor/golang.org/x/tools/go/vcs/discovery.go create mode 100644 dev-tools/vendor/golang.org/x/tools/go/vcs/env.go create mode 100644 dev-tools/vendor/golang.org/x/tools/go/vcs/http.go create mode 100644 dev-tools/vendor/golang.org/x/tools/go/vcs/vcs.go diff --git a/dev-tools/vendor/golang.org/x/tools/LICENSE b/dev-tools/vendor/golang.org/x/tools/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/dev-tools/vendor/golang.org/x/tools/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/dev-tools/vendor/golang.org/x/tools/PATENTS b/dev-tools/vendor/golang.org/x/tools/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/dev-tools/vendor/golang.org/x/tools/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/dev-tools/vendor/golang.org/x/tools/go/vcs/discovery.go b/dev-tools/vendor/golang.org/x/tools/go/vcs/discovery.go new file mode 100644 index 00000000000..f431dc1c5bc --- /dev/null +++ b/dev-tools/vendor/golang.org/x/tools/go/vcs/discovery.go @@ -0,0 +1,76 @@ +// Copyright 2012 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 vcs + +import ( + "encoding/xml" + "fmt" + "io" + "strings" +) + +// 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) + } +} + +// parseMetaGoImports returns meta imports from the HTML in r. +// Parsing ends at the end of the section or the beginning of the . +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.Token() + 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 "" +} diff --git a/dev-tools/vendor/golang.org/x/tools/go/vcs/env.go b/dev-tools/vendor/golang.org/x/tools/go/vcs/env.go new file mode 100644 index 00000000000..e846f5b3b86 --- /dev/null +++ b/dev-tools/vendor/golang.org/x/tools/go/vcs/env.go @@ -0,0 +1,39 @@ +// Copyright 2013 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 vcs + +import ( + "os" + "strings" +) + +// envForDir returns a copy of the environment +// suitable for running in the given directory. +// The environment is the current process's environment +// but with an updated $PWD, so that an os.Getwd in the +// child will be faster. +func envForDir(dir string) []string { + env := os.Environ() + // Internally we only use rooted paths, so dir is rooted. + // Even if dir is not rooted, no harm done. + return mergeEnvLists([]string{"PWD=" + dir}, env) +} + +// mergeEnvLists merges the two environment lists such that +// variables with the same name in "in" replace those in "out". +func mergeEnvLists(in, out []string) []string { +NextVar: + for _, inkv := range in { + k := strings.SplitAfterN(inkv, "=", 2)[0] + for i, outkv := range out { + if strings.HasPrefix(outkv, k) { + out[i] = inkv + continue NextVar + } + } + out = append(out, inkv) + } + return out +} diff --git a/dev-tools/vendor/golang.org/x/tools/go/vcs/http.go b/dev-tools/vendor/golang.org/x/tools/go/vcs/http.go new file mode 100644 index 00000000000..96188185cb6 --- /dev/null +++ b/dev-tools/vendor/golang.org/x/tools/go/vcs/http.go @@ -0,0 +1,80 @@ +// Copyright 2012 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 vcs + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" +) + +// httpClient is the default HTTP client, but a variable so it can be +// changed by tests, without modifying http.DefaultClient. +var httpClient = http.DefaultClient + +// httpGET returns the data from an HTTP GET request for the given URL. +func httpGET(url string) ([]byte, error) { + resp, err := httpClient.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return nil, fmt.Errorf("%s: %s", url, resp.Status) + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("%s: %v", url, err) + } + return b, nil +} + +// httpsOrHTTP returns the body of either the importPath's +// https resource or, if unavailable, the http resource. +func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) { + fetch := func(scheme string) (urlStr string, res *http.Response, err error) { + u, err := url.Parse(scheme + "://" + importPath) + if err != nil { + return "", nil, err + } + u.RawQuery = "go-get=1" + urlStr = u.String() + if Verbose { + log.Printf("Fetching %s", urlStr) + } + res, err = httpClient.Get(urlStr) + return + } + closeBody := func(res *http.Response) { + if res != nil { + res.Body.Close() + } + } + urlStr, res, err := fetch("https") + if err != nil || res.StatusCode != 200 { + if Verbose { + if err != nil { + log.Printf("https fetch failed.") + } else { + log.Printf("ignoring https fetch with status code %d", res.StatusCode) + } + } + closeBody(res) + urlStr, res, err = fetch("http") + } + if err != nil { + closeBody(res) + return "", nil, err + } + // Note: accepting a non-200 OK here, so people can serve a + // meta import in their http 404 page. + if Verbose { + log.Printf("Parsing meta tags from %s (status code %d)", urlStr, res.StatusCode) + } + return urlStr, res.Body, nil +} diff --git a/dev-tools/vendor/golang.org/x/tools/go/vcs/vcs.go b/dev-tools/vendor/golang.org/x/tools/go/vcs/vcs.go new file mode 100644 index 00000000000..89319be04b9 --- /dev/null +++ b/dev-tools/vendor/golang.org/x/tools/go/vcs/vcs.go @@ -0,0 +1,755 @@ +// Copyright 2012 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 vcs exposes functions for resolving import paths +// and using version control systems, which can be used to +// implement behavior similar to the standard "go get" command. +// +// This package is a copy of internal code in package cmd/go/internal/get, +// modified to make the identifiers exported. It's provided here +// for developers who want to write tools with similar semantics. +// It needs to be manually kept in sync with upstream when changes are +// made to cmd/go/internal/get; see https://golang.org/issues/11490. +// +package vcs // import "golang.org/x/tools/go/vcs" + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "log" + "net/url" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +// Verbose enables verbose operation logging. +var Verbose bool + +// ShowCmd controls whether VCS commands are printed. +var ShowCmd bool + +// A Cmd describes how to use a version control system +// like Mercurial, Git, or Subversion. +type Cmd struct { + Name string + Cmd string // name of binary to invoke command + + CreateCmd string // command to download a fresh copy of a repository + DownloadCmd string // command to download updates into an existing repository + + TagCmd []TagCmd // commands to list tags + TagLookupCmd []TagCmd // commands to lookup tags before running tagSyncCmd + TagSyncCmd string // command to sync to specific tag + TagSyncDefault string // command to sync to default tag + + LogCmd string // command to list repository changelogs in an XML format + + Scheme []string + PingCmd string +} + +// A TagCmd describes a command to list available tags +// that can be passed to Cmd.TagSyncCmd. +type TagCmd struct { + Cmd string // command to list tags + Pattern string // regexp to extract tags from list +} + +// vcsList lists the known version control systems +var vcsList = []*Cmd{ + vcsHg, + vcsGit, + vcsSvn, + vcsBzr, +} + +// ByCmd returns the version control system for the given +// command name (hg, git, svn, bzr). +func ByCmd(cmd string) *Cmd { + for _, vcs := range vcsList { + if vcs.Cmd == cmd { + return vcs + } + } + return nil +} + +// vcsHg describes how to use Mercurial. +var vcsHg = &Cmd{ + Name: "Mercurial", + Cmd: "hg", + + CreateCmd: "clone -U {repo} {dir}", + DownloadCmd: "pull", + + // We allow both tag and branch names as 'tags' + // for selecting a version. This lets people have + // a go.release.r60 branch and a go1 branch + // and make changes in both, without constantly + // editing .hgtags. + TagCmd: []TagCmd{ + {"tags", `^(\S+)`}, + {"branches", `^(\S+)`}, + }, + TagSyncCmd: "update -r {tag}", + TagSyncDefault: "update default", + + LogCmd: "log --encoding=utf-8 --limit={limit} --template={template}", + + Scheme: []string{"https", "http", "ssh"}, + PingCmd: "identify {scheme}://{repo}", +} + +// vcsGit describes how to use Git. +var vcsGit = &Cmd{ + Name: "Git", + Cmd: "git", + + CreateCmd: "clone {repo} {dir}", + DownloadCmd: "pull --ff-only", + + TagCmd: []TagCmd{ + // tags/xxx matches a git tag named xxx + // origin/xxx matches a git branch named xxx on the default remote repository + {"show-ref", `(?:tags|origin)/(\S+)$`}, + }, + TagLookupCmd: []TagCmd{ + {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`}, + }, + TagSyncCmd: "checkout {tag}", + TagSyncDefault: "checkout master", + + Scheme: []string{"git", "https", "http", "git+ssh"}, + PingCmd: "ls-remote {scheme}://{repo}", +} + +// vcsBzr describes how to use Bazaar. +var vcsBzr = &Cmd{ + Name: "Bazaar", + Cmd: "bzr", + + CreateCmd: "branch {repo} {dir}", + + // Without --overwrite bzr will not pull tags that changed. + // Replace by --overwrite-tags after http://pad.lv/681792 goes in. + DownloadCmd: "pull --overwrite", + + TagCmd: []TagCmd{{"tags", `^(\S+)`}}, + TagSyncCmd: "update -r {tag}", + TagSyncDefault: "update -r revno:-1", + + Scheme: []string{"https", "http", "bzr", "bzr+ssh"}, + PingCmd: "info {scheme}://{repo}", +} + +// vcsSvn describes how to use Subversion. +var vcsSvn = &Cmd{ + Name: "Subversion", + Cmd: "svn", + + CreateCmd: "checkout {repo} {dir}", + DownloadCmd: "update", + + // There is no tag command in subversion. + // The branch information is all in the path names. + + LogCmd: "log --xml --limit={limit}", + + Scheme: []string{"https", "http", "svn", "svn+ssh"}, + PingCmd: "info {scheme}://{repo}", +} + +func (v *Cmd) String() string { + return v.Name +} + +// run runs the command line cmd in the given directory. +// keyval is a list of key, value pairs. run expands +// instances of {key} in cmd into value, but only after +// splitting cmd into individual arguments. +// If an error occurs, run prints the command line and the +// command's combined stdout+stderr to standard error. +// Otherwise run discards the command's output. +func (v *Cmd) run(dir string, cmd string, keyval ...string) error { + _, err := v.run1(dir, cmd, keyval, true) + return err +} + +// runVerboseOnly is like run but only generates error output to standard error in verbose mode. +func (v *Cmd) runVerboseOnly(dir string, cmd string, keyval ...string) error { + _, err := v.run1(dir, cmd, keyval, false) + return err +} + +// runOutput is like run but returns the output of the command. +func (v *Cmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) { + return v.run1(dir, cmd, keyval, true) +} + +// run1 is the generalized implementation of run and runOutput. +func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([]byte, error) { + m := make(map[string]string) + for i := 0; i < len(keyval); i += 2 { + m[keyval[i]] = keyval[i+1] + } + args := strings.Fields(cmdline) + for i, arg := range args { + args[i] = expand(m, arg) + } + + _, err := exec.LookPath(v.Cmd) + if err != nil { + fmt.Fprintf(os.Stderr, + "go: missing %s command. See http://golang.org/s/gogetcmd\n", + v.Name) + return nil, err + } + + cmd := exec.Command(v.Cmd, args...) + cmd.Dir = dir + cmd.Env = envForDir(cmd.Dir) + if ShowCmd { + fmt.Printf("cd %s\n", dir) + fmt.Printf("%s %s\n", v.Cmd, strings.Join(args, " ")) + } + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + err = cmd.Run() + out := buf.Bytes() + if err != nil { + if verbose || Verbose { + fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.Cmd, strings.Join(args, " ")) + os.Stderr.Write(out) + } + return nil, err + } + return out, nil +} + +// Ping pings the repo to determine if scheme used is valid. +// This repo must be pingable with this scheme and VCS. +func (v *Cmd) Ping(scheme, repo string) error { + return v.runVerboseOnly(".", v.PingCmd, "scheme", scheme, "repo", repo) +} + +// Create creates a new copy of repo in dir. +// The parent of dir must exist; dir must not. +func (v *Cmd) Create(dir, repo string) error { + return v.run(".", v.CreateCmd, "dir", dir, "repo", repo) +} + +// CreateAtRev creates a new copy of repo in dir at revision rev. +// The parent of dir must exist; dir must not. +// rev must be a valid revision in repo. +func (v *Cmd) CreateAtRev(dir, repo, rev string) error { + if err := v.Create(dir, repo); err != nil { + return err + } + return v.run(dir, v.TagSyncCmd, "tag", rev) +} + +// Download downloads any new changes for the repo in dir. +// dir must be a valid VCS repo compatible with v. +func (v *Cmd) Download(dir string) error { + return v.run(dir, v.DownloadCmd) +} + +// Tags returns the list of available tags for the repo in dir. +// dir must be a valid VCS repo compatible with v. +func (v *Cmd) Tags(dir string) ([]string, error) { + var tags []string + for _, tc := range v.TagCmd { + out, err := v.runOutput(dir, tc.Cmd) + if err != nil { + return nil, err + } + re := regexp.MustCompile(`(?m-s)` + tc.Pattern) + for _, m := range re.FindAllStringSubmatch(string(out), -1) { + tags = append(tags, m[1]) + } + } + return tags, nil +} + +// TagSync syncs the repo in dir to the named tag, which is either a +// tag returned by Tags or the empty string (the default tag). +// dir must be a valid VCS repo compatible with v and the tag must exist. +func (v *Cmd) TagSync(dir, tag string) error { + if v.TagSyncCmd == "" { + return nil + } + if tag != "" { + for _, tc := range v.TagLookupCmd { + out, err := v.runOutput(dir, tc.Cmd, "tag", tag) + if err != nil { + return err + } + re := regexp.MustCompile(`(?m-s)` + tc.Pattern) + m := re.FindStringSubmatch(string(out)) + if len(m) > 1 { + tag = m[1] + break + } + } + } + if tag == "" && v.TagSyncDefault != "" { + return v.run(dir, v.TagSyncDefault) + } + return v.run(dir, v.TagSyncCmd, "tag", tag) +} + +// Log logs the changes for the repo in dir. +// dir must be a valid VCS repo compatible with v. +func (v *Cmd) Log(dir, logTemplate string) ([]byte, error) { + if err := v.Download(dir); err != nil { + return []byte{}, err + } + + const N = 50 // how many revisions to grab + return v.runOutput(dir, v.LogCmd, "limit", strconv.Itoa(N), "template", logTemplate) +} + +// LogAtRev logs the change for repo in dir at the rev revision. +// dir must be a valid VCS repo compatible with v. +// rev must be a valid revision for the repo in dir. +func (v *Cmd) LogAtRev(dir, rev, logTemplate string) ([]byte, error) { + if err := v.Download(dir); err != nil { + return []byte{}, err + } + + // Append revision flag to LogCmd. + logAtRevCmd := v.LogCmd + " --rev=" + rev + return v.runOutput(dir, logAtRevCmd, "limit", strconv.Itoa(1), "template", logTemplate) +} + +// A vcsPath describes how to convert an import path into a +// version control system and repository name. +type vcsPath struct { + prefix string // prefix this description applies to + re string // pattern for import path + repo string // repository to use (expand with match of re) + vcs string // version control system to use (expand with match of re) + check func(match map[string]string) error // additional checks + ping bool // ping for scheme to use to download repo + + regexp *regexp.Regexp // cached compiled form of re +} + +// FromDir inspects dir and its parents to determine the +// version control system and code repository to use. +// On return, root is the import path +// corresponding to the root of the repository. +func FromDir(dir, srcRoot string) (vcs *Cmd, root string, err error) { + // Clean and double-check that dir is in (a subdirectory of) srcRoot. + dir = filepath.Clean(dir) + srcRoot = filepath.Clean(srcRoot) + if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator { + return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot) + } + + var vcsRet *Cmd + var rootRet string + + origDir := dir + for len(dir) > len(srcRoot) { + for _, vcs := range vcsList { + if _, err := os.Stat(filepath.Join(dir, "."+vcs.Cmd)); err == nil { + root := filepath.ToSlash(dir[len(srcRoot)+1:]) + // Record first VCS we find, but keep looking, + // to detect mistakes like one kind of VCS inside another. + if vcsRet == nil { + vcsRet = vcs + rootRet = root + continue + } + // Allow .git inside .git, which can arise due to submodules. + if vcsRet == vcs && vcs.Cmd == "git" { + continue + } + // Otherwise, we have one VCS inside a different VCS. + return nil, "", fmt.Errorf("directory %q uses %s, but parent %q uses %s", + filepath.Join(srcRoot, rootRet), vcsRet.Cmd, filepath.Join(srcRoot, root), vcs.Cmd) + } + } + + // Move to parent. + ndir := filepath.Dir(dir) + if len(ndir) >= len(dir) { + // Shouldn't happen, but just in case, stop. + break + } + dir = ndir + } + + if vcsRet != nil { + return vcsRet, rootRet, nil + } + + return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir) +} + +// RepoRoot represents a version control system, a repo, and a root of +// where to put it on disk. +type RepoRoot struct { + VCS *Cmd + + // 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. +func RepoRootForImportPath(importPath string, verbose bool) (*RepoRoot, error) { + rr, err := RepoRootForImportPathStatic(importPath, "") + if err == errUnknownSite { + rr, err = RepoRootForImportDynamic(importPath, verbose) + + // 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 Verbose { + 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 + } + m := srv.regexp.FindStringSubmatch(importPath) + if m == nil { + if srv.prefix != "" { + return nil, fmt.Errorf("invalid %s import path %q", srv.prefix, importPath) + } + continue + } + + // Build map of named subexpression matches for expand. + match := map[string]string{ + "prefix": srv.prefix, + "import": importPath, + } + for i, name := range srv.regexp.SubexpNames() { + if name != "" && match[name] == "" { + match[name] = m[i] + } + } + if srv.vcs != "" { + match["vcs"] = expand(match, srv.vcs) + } + if srv.repo != "" { + match["repo"] = expand(match, srv.repo) + } + if srv.check != nil { + if err := srv.check(match); err != nil { + return nil, err + } + } + vcs := ByCmd(match["vcs"]) + if vcs == nil { + return nil, fmt.Errorf("unknown version control system %q", match["vcs"]) + } + if srv.ping { + 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 + } + } + } + } + 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 custom import paths like "name.tld/pkg/foo" or just "name.tld". +func RepoRootForImportDynamic(importPath string, verbose bool) (*RepoRoot, error) { + slash := strings.Index(importPath, "/") + if slash < 0 { + slash = len(importPath) + } + host := importPath[:slash] + if !strings.Contains(host, ".") { + return nil, errors.New("import path doesn't contain a hostname") + } + urlStr, body, err := httpsOrHTTP(importPath) + if err != nil { + return nil, fmt.Errorf("http/https fetch: %v", err) + } + defer body.Close() + imports, err := parseMetaGoImports(body) + if err != nil { + return nil, fmt.Errorf("parsing %s: %v", importPath, err) + } + metaImport, err := matchGoImport(imports, 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 verbose { + 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 verbose { + 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, err := parseMetaGoImports(body) + if err != nil { + return nil, fmt.Errorf("parsing %s: %v", importPath, err) + } + 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 err := validateRepoRoot(metaImport.RepoRoot); err != nil { + return nil, fmt.Errorf("%s: invalid repo root %q: %v", urlStr, metaImport.RepoRoot, err) + } + rr := &RepoRoot{ + VCS: ByCmd(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 +} + +// validateRepoRoot returns an error if repoRoot does not seem to be +// a valid URL with scheme. +func validateRepoRoot(repoRoot string) error { + url, err := url.Parse(repoRoot) + if err != nil { + return err + } + if url.Scheme == "" { + return errors.New("no scheme") + } + return nil +} + +// metaImport represents the parsed 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 imports[match], nil +} + +// expand rewrites s to replace {k} with match[k] for each key k in match. +func expand(match map[string]string, s string) string { + for k, v := range match { + s = strings.Replace(s, "{"+k+"}", v, -1) + } + return s +} + +// vcsPaths lists the known vcs paths. +var vcsPaths = []*vcsPath{ + // go.googlesource.com + { + prefix: "go.googlesource.com", + re: `^(?Pgo\.googlesource\.com/[A-Za-z0-9_.\-]+/?)$`, + vcs: "git", + repo: "https://{root}", + check: noVCSSuffix, + }, + + // Github + { + prefix: "github.com/", + re: `^(?Pgithub\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[\p{L}0-9_.\-]+)*$`, + vcs: "git", + repo: "https://{root}", + check: noVCSSuffix, + }, + + // Bitbucket + { + prefix: "bitbucket.org/", + re: `^(?Pbitbucket\.org/(?P[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`, + repo: "https://{root}", + check: bitbucketVCS, + }, + + // Launchpad + { + prefix: "launchpad.net/", + re: `^(?Plaunchpad\.net/((?P[A-Za-z0-9_.\-]+)(?P/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`, + vcs: "bzr", + repo: "https://{root}", + check: launchpadVCS, + }, + + // Git at OpenStack + { + prefix: "git.openstack.org", + re: `^(?Pgit\.openstack\.org/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(\.git)?(/[A-Za-z0-9_.\-]+)*$`, + vcs: "git", + repo: "https://{root}", + check: noVCSSuffix, + }, + + // General syntax for any server. + { + re: `^(?P(?P([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?Pbzr|git|hg|svn))(/[A-Za-z0-9_.\-]+)*$`, + ping: true, + }, +} + +func init() { + // fill in cached regexps. + // Doing this eagerly discovers invalid regexp syntax + // without having to run a command that needs that regexp. + for _, srv := range vcsPaths { + srv.regexp = regexp.MustCompile(srv.re) + } +} + +// noVCSSuffix checks that the repository name does not +// end in .foo for any version control system foo. +// The usual culprit is ".git". +func noVCSSuffix(match map[string]string) error { + repo := match["repo"] + for _, vcs := range vcsList { + if strings.HasSuffix(repo, "."+vcs.Cmd) { + return fmt.Errorf("invalid version control suffix in %s path", match["prefix"]) + } + } + return nil +} + +// bitbucketVCS determines the version control system for a +// Bitbucket repository, by using the Bitbucket API. +func bitbucketVCS(match map[string]string) error { + if err := noVCSSuffix(match); err != nil { + return err + } + + var resp struct { + SCM string `json:"scm"` + } + url := expand(match, "https://api.bitbucket.org/1.0/repositories/{bitname}") + data, err := httpGET(url) + if err != nil { + return err + } + if err := json.Unmarshal(data, &resp); err != nil { + return fmt.Errorf("decoding %s: %v", url, err) + } + + if ByCmd(resp.SCM) != nil { + match["vcs"] = resp.SCM + if resp.SCM == "git" { + match["repo"] += ".git" + } + return nil + } + + return fmt.Errorf("unable to detect version control system for bitbucket.org/ path") +} + +// launchpadVCS solves the ambiguity for "lp.net/project/foo". In this case, +// "foo" could be a series name registered in Launchpad with its own branch, +// and it could also be the name of a directory within the main project +// branch one level up. +func launchpadVCS(match map[string]string) error { + if match["project"] == "" || match["series"] == "" { + return nil + } + _, err := httpGET(expand(match, "https://code.launchpad.net/{project}{series}/.bzr/branch-format")) + if err != nil { + match["root"] = expand(match, "launchpad.net/{project}") + match["repo"] = expand(match, "https://{root}") + } + return nil +} diff --git a/dev-tools/vendor/vendor.json b/dev-tools/vendor/vendor.json index 8daf6125eea..28a187422a0 100644 --- a/dev-tools/vendor/vendor.json +++ b/dev-tools/vendor/vendor.json @@ -75,6 +75,10 @@ "revisionTime": "2017-03-30T16:02:45Z" }, { + "checksumSHA1": "DnH0WldhYTUVJnsx3vlNLP0Yh/A=", + "path": "golang.org/x/tools/go/vcs", + "revision": "a5b4c53f6e8bdcafa95a94671bf2d1203365858b", + "revisionTime": "2018-05-20T07:57:22Z" } ], "rootPath": "github.com/elastic/beats/dev-tools"