GoPLS Viewer

Home|gopls/go/vcs/vcs.go
1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package vcs exposes functions for resolving import paths
6// and using version control systems, which can be used to
7// implement behavior similar to the standard "go get" command.
8//
9// This package is a copy of internal code in package cmd/go/internal/get,
10// modified to make the identifiers exported. It's provided here
11// for developers who want to write tools with similar semantics.
12// It needs to be manually kept in sync with upstream when changes are
13// made to cmd/go/internal/get; see https://golang.org/issue/11490.
14package vcs // import "golang.org/x/tools/go/vcs"
15
16import (
17    "bytes"
18    "encoding/json"
19    "errors"
20    "fmt"
21    exec "golang.org/x/sys/execabs"
22    "log"
23    "net/url"
24    "os"
25    "path/filepath"
26    "regexp"
27    "strconv"
28    "strings"
29)
30
31// Verbose enables verbose operation logging.
32var Verbose bool
33
34// ShowCmd controls whether VCS commands are printed.
35var ShowCmd bool
36
37// A Cmd describes how to use a version control system
38// like Mercurial, Git, or Subversion.
39type Cmd struct {
40    Name string
41    Cmd  string // name of binary to invoke command
42
43    CreateCmd   string // command to download a fresh copy of a repository
44    DownloadCmd string // command to download updates into an existing repository
45
46    TagCmd         []TagCmd // commands to list tags
47    TagLookupCmd   []TagCmd // commands to lookup tags before running tagSyncCmd
48    TagSyncCmd     string   // command to sync to specific tag
49    TagSyncDefault string   // command to sync to default tag
50
51    LogCmd string // command to list repository changelogs in an XML format
52
53    Scheme  []string
54    PingCmd string
55}
56
57// A TagCmd describes a command to list available tags
58// that can be passed to Cmd.TagSyncCmd.
59type TagCmd struct {
60    Cmd     string // command to list tags
61    Pattern string // regexp to extract tags from list
62}
63
64// vcsList lists the known version control systems
65var vcsList = []*Cmd{
66    vcsHg,
67    vcsGit,
68    vcsSvn,
69    vcsBzr,
70}
71
72// ByCmd returns the version control system for the given
73// command name (hg, git, svn, bzr).
74func ByCmd(cmd string) *Cmd {
75    for _vcs := range vcsList {
76        if vcs.Cmd == cmd {
77            return vcs
78        }
79    }
80    return nil
81}
82
83// vcsHg describes how to use Mercurial.
84var vcsHg = &Cmd{
85    Name"Mercurial",
86    Cmd:  "hg",
87
88    CreateCmd:   "clone -U {repo} {dir}",
89    DownloadCmd"pull",
90
91    // We allow both tag and branch names as 'tags'
92    // for selecting a version.  This lets people have
93    // a go.release.r60 branch and a go1 branch
94    // and make changes in both, without constantly
95    // editing .hgtags.
96    TagCmd: []TagCmd{
97        {"tags"`^(\S+)`},
98        {"branches"`^(\S+)`},
99    },
100    TagSyncCmd:     "update -r {tag}",
101    TagSyncDefault"update default",
102
103    LogCmd"log --encoding=utf-8 --limit={limit} --template={template}",
104
105    Scheme:  []string{"https""http""ssh"},
106    PingCmd"identify {scheme}://{repo}",
107}
108
109// vcsGit describes how to use Git.
110var vcsGit = &Cmd{
111    Name"Git",
112    Cmd:  "git",
113
114    CreateCmd:   "clone {repo} {dir}",
115    DownloadCmd"pull --ff-only",
116
117    TagCmd: []TagCmd{
118        // tags/xxx matches a git tag named xxx
119        // origin/xxx matches a git branch named xxx on the default remote repository
120        {"show-ref"`(?:tags|origin)/(\S+)$`},
121    },
122    TagLookupCmd: []TagCmd{
123        {"show-ref tags/{tag} origin/{tag}"`((?:tags|origin)/\S+)$`},
124    },
125    TagSyncCmd:     "checkout {tag}",
126    TagSyncDefault"checkout master",
127
128    Scheme:  []string{"git""https""http""git+ssh"},
129    PingCmd"ls-remote {scheme}://{repo}",
130}
131
132// vcsBzr describes how to use Bazaar.
133var vcsBzr = &Cmd{
134    Name"Bazaar",
135    Cmd:  "bzr",
136
137    CreateCmd"branch {repo} {dir}",
138
139    // Without --overwrite bzr will not pull tags that changed.
140    // Replace by --overwrite-tags after http://pad.lv/681792 goes in.
141    DownloadCmd"pull --overwrite",
142
143    TagCmd:         []TagCmd{{"tags"`^(\S+)`}},
144    TagSyncCmd:     "update -r {tag}",
145    TagSyncDefault"update -r revno:-1",
146
147    Scheme:  []string{"https""http""bzr""bzr+ssh"},
148    PingCmd"info {scheme}://{repo}",
149}
150
151// vcsSvn describes how to use Subversion.
152var vcsSvn = &Cmd{
153    Name"Subversion",
154    Cmd:  "svn",
155
156    CreateCmd:   "checkout {repo} {dir}",
157    DownloadCmd"update",
158
159    // There is no tag command in subversion.
160    // The branch information is all in the path names.
161
162    LogCmd"log --xml --limit={limit}",
163
164    Scheme:  []string{"https""http""svn""svn+ssh"},
165    PingCmd"info {scheme}://{repo}",
166}
167
168func (v *CmdString() string {
169    return v.Name
170}
171
172// run runs the command line cmd in the given directory.
173// keyval is a list of key, value pairs.  run expands
174// instances of {key} in cmd into value, but only after
175// splitting cmd into individual arguments.
176// If an error occurs, run prints the command line and the
177// command's combined stdout+stderr to standard error.
178// Otherwise run discards the command's output.
179func (v *Cmdrun(dir stringcmd stringkeyval ...stringerror {
180    _err := v.run1(dircmdkeyvaltrue)
181    return err
182}
183
184// runVerboseOnly is like run but only generates error output to standard error in verbose mode.
185func (v *CmdrunVerboseOnly(dir stringcmd stringkeyval ...stringerror {
186    _err := v.run1(dircmdkeyvalfalse)
187    return err
188}
189
190// runOutput is like run but returns the output of the command.
191func (v *CmdrunOutput(dir stringcmd stringkeyval ...string) ([]byteerror) {
192    return v.run1(dircmdkeyvaltrue)
193}
194
195// run1 is the generalized implementation of run and runOutput.
196func (v *Cmdrun1(dir stringcmdline stringkeyval []stringverbose bool) ([]byteerror) {
197    m := make(map[string]string)
198    for i := 0i < len(keyval); i += 2 {
199        m[keyval[i]] = keyval[i+1]
200    }
201    args := strings.Fields(cmdline)
202    for iarg := range args {
203        args[i] = expand(marg)
204    }
205
206    _err := exec.LookPath(v.Cmd)
207    if err != nil {
208        fmt.Fprintf(os.Stderr,
209            "go: missing %s command. See http://golang.org/s/gogetcmd\n",
210            v.Name)
211        return nilerr
212    }
213
214    cmd := exec.Command(v.Cmdargs...)
215    cmd.Dir = dir
216    cmd.Env = envForDir(cmd.Dir)
217    if ShowCmd {
218        fmt.Printf("cd %s\n"dir)
219        fmt.Printf("%s %s\n"v.Cmdstrings.Join(args" "))
220    }
221    var buf bytes.Buffer
222    cmd.Stdout = &buf
223    cmd.Stderr = &buf
224    err = cmd.Run()
225    out := buf.Bytes()
226    if err != nil {
227        if verbose || Verbose {
228            fmt.Fprintf(os.Stderr"# cd %s; %s %s\n"dirv.Cmdstrings.Join(args" "))
229            os.Stderr.Write(out)
230        }
231        return nilerr
232    }
233    return outnil
234}
235
236// Ping pings the repo to determine if scheme used is valid.
237// This repo must be pingable with this scheme and VCS.
238func (v *CmdPing(schemerepo stringerror {
239    return v.runVerboseOnly("."v.PingCmd"scheme"scheme"repo"repo)
240}
241
242// Create creates a new copy of repo in dir.
243// The parent of dir must exist; dir must not.
244func (v *CmdCreate(dirrepo stringerror {
245    return v.run("."v.CreateCmd"dir"dir"repo"repo)
246}
247
248// CreateAtRev creates a new copy of repo in dir at revision rev.
249// The parent of dir must exist; dir must not.
250// rev must be a valid revision in repo.
251func (v *CmdCreateAtRev(dirreporev stringerror {
252    if err := v.Create(dirrepo); err != nil {
253        return err
254    }
255    return v.run(dirv.TagSyncCmd"tag"rev)
256}
257
258// Download downloads any new changes for the repo in dir.
259// dir must be a valid VCS repo compatible with v.
260func (v *CmdDownload(dir stringerror {
261    return v.run(dirv.DownloadCmd)
262}
263
264// Tags returns the list of available tags for the repo in dir.
265// dir must be a valid VCS repo compatible with v.
266func (v *CmdTags(dir string) ([]stringerror) {
267    var tags []string
268    for _tc := range v.TagCmd {
269        outerr := v.runOutput(dirtc.Cmd)
270        if err != nil {
271            return nilerr
272        }
273        re := regexp.MustCompile(`(?m-s)` + tc.Pattern)
274        for _m := range re.FindAllStringSubmatch(string(out), -1) {
275            tags = append(tagsm[1])
276        }
277    }
278    return tagsnil
279}
280
281// TagSync syncs the repo in dir to the named tag, which is either a
282// tag returned by Tags or the empty string (the default tag).
283// dir must be a valid VCS repo compatible with v and the tag must exist.
284func (v *CmdTagSync(dirtag stringerror {
285    if v.TagSyncCmd == "" {
286        return nil
287    }
288    if tag != "" {
289        for _tc := range v.TagLookupCmd {
290            outerr := v.runOutput(dirtc.Cmd"tag"tag)
291            if err != nil {
292                return err
293            }
294            re := regexp.MustCompile(`(?m-s)` + tc.Pattern)
295            m := re.FindStringSubmatch(string(out))
296            if len(m) > 1 {
297                tag = m[1]
298                break
299            }
300        }
301    }
302    if tag == "" && v.TagSyncDefault != "" {
303        return v.run(dirv.TagSyncDefault)
304    }
305    return v.run(dirv.TagSyncCmd"tag"tag)
306}
307
308// Log logs the changes for the repo in dir.
309// dir must be a valid VCS repo compatible with v.
310func (v *CmdLog(dirlogTemplate string) ([]byteerror) {
311    if err := v.Download(dir); err != nil {
312        return []byte{}, err
313    }
314
315    const N = 50 // how many revisions to grab
316    return v.runOutput(dirv.LogCmd"limit"strconv.Itoa(N), "template"logTemplate)
317}
318
319// LogAtRev logs the change for repo in dir at the rev revision.
320// dir must be a valid VCS repo compatible with v.
321// rev must be a valid revision for the repo in dir.
322func (v *CmdLogAtRev(dirrevlogTemplate string) ([]byteerror) {
323    if err := v.Download(dir); err != nil {
324        return []byte{}, err
325    }
326
327    // Append revision flag to LogCmd.
328    logAtRevCmd := v.LogCmd + " --rev=" + rev
329    return v.runOutput(dirlogAtRevCmd"limit"strconv.Itoa(1), "template"logTemplate)
330}
331
332// A vcsPath describes how to convert an import path into a
333// version control system and repository name.
334type vcsPath struct {
335    prefix string                              // prefix this description applies to
336    re     string                              // pattern for import path
337    repo   string                              // repository to use (expand with match of re)
338    vcs    string                              // version control system to use (expand with match of re)
339    check  func(match map[string]stringerror // additional checks
340    ping   bool                                // ping for scheme to use to download repo
341
342    regexp *regexp.Regexp // cached compiled form of re
343}
344
345// FromDir inspects dir and its parents to determine the
346// version control system and code repository to use.
347// On return, root is the import path
348// corresponding to the root of the repository.
349func FromDir(dirsrcRoot string) (vcs *Cmdroot stringerr error) {
350    // Clean and double-check that dir is in (a subdirectory of) srcRoot.
351    dir = filepath.Clean(dir)
352    srcRoot = filepath.Clean(srcRoot)
353    if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
354        return nil""fmt.Errorf("directory %q is outside source root %q"dirsrcRoot)
355    }
356
357    var vcsRet *Cmd
358    var rootRet string
359
360    origDir := dir
361    for len(dir) > len(srcRoot) {
362        for _vcs := range vcsList {
363            if _err := os.Stat(filepath.Join(dir"."+vcs.Cmd)); err == nil {
364                root := filepath.ToSlash(dir[len(srcRoot)+1:])
365                // Record first VCS we find, but keep looking,
366                // to detect mistakes like one kind of VCS inside another.
367                if vcsRet == nil {
368                    vcsRet = vcs
369                    rootRet = root
370                    continue
371                }
372                // Allow .git inside .git, which can arise due to submodules.
373                if vcsRet == vcs && vcs.Cmd == "git" {
374                    continue
375                }
376                // Otherwise, we have one VCS inside a different VCS.
377                return nil""fmt.Errorf("directory %q uses %s, but parent %q uses %s",
378                    filepath.Join(srcRootrootRet), vcsRet.Cmdfilepath.Join(srcRootroot), vcs.Cmd)
379            }
380        }
381
382        // Move to parent.
383        ndir := filepath.Dir(dir)
384        if len(ndir) >= len(dir) {
385            // Shouldn't happen, but just in case, stop.
386            break
387        }
388        dir = ndir
389    }
390
391    if vcsRet != nil {
392        return vcsRetrootRetnil
393    }
394
395    return nil""fmt.Errorf("directory %q is not using a known version control system"origDir)
396}
397
398// RepoRoot represents a version control system, a repo, and a root of
399// where to put it on disk.
400type RepoRoot struct {
401    VCS *Cmd
402
403    // Repo is the repository URL, including scheme.
404    Repo string
405
406    // Root is the import path corresponding to the root of the
407    // repository.
408    Root string
409}
410
411// RepoRootForImportPath analyzes importPath to determine the
412// version control system, and code repository to use.
413func RepoRootForImportPath(importPath stringverbose bool) (*RepoRooterror) {
414    rrerr := RepoRootForImportPathStatic(importPath"")
415    if err == errUnknownSite {
416        rrerr = RepoRootForImportDynamic(importPathverbose)
417
418        // RepoRootForImportDynamic returns error detail
419        // that is irrelevant if the user didn't intend to use a
420        // dynamic import in the first place.
421        // Squelch it.
422        if err != nil {
423            if Verbose {
424                log.Printf("import %q: %v"importPatherr)
425            }
426            err = fmt.Errorf("unrecognized import path %q"importPath)
427        }
428    }
429
430    if err == nil && strings.Contains(importPath"...") && strings.Contains(rr.Root"...") {
431        // Do not allow wildcards in the repo root.
432        rr = nil
433        err = fmt.Errorf("cannot expand ... in %q"importPath)
434    }
435    return rrerr
436}
437
438var errUnknownSite = errors.New("dynamic lookup required to find mapping")
439
440// RepoRootForImportPathStatic attempts to map importPath to a
441// RepoRoot using the commonly-used VCS hosting sites in vcsPaths
442// (github.com/user/dir), or from a fully-qualified importPath already
443// containing its VCS type (foo.com/repo.git/dir)
444//
445// If scheme is non-empty, that scheme is forced.
446func RepoRootForImportPathStatic(importPathscheme string) (*RepoRooterror) {
447    if strings.Contains(importPath"://") {
448        return nilfmt.Errorf("invalid import path %q"importPath)
449    }
450    for _srv := range vcsPaths {
451        if !strings.HasPrefix(importPathsrv.prefix) {
452            continue
453        }
454        m := srv.regexp.FindStringSubmatch(importPath)
455        if m == nil {
456            if srv.prefix != "" {
457                return nilfmt.Errorf("invalid %s import path %q"srv.prefiximportPath)
458            }
459            continue
460        }
461
462        // Build map of named subexpression matches for expand.
463        match := map[string]string{
464            "prefix"srv.prefix,
465            "import"importPath,
466        }
467        for iname := range srv.regexp.SubexpNames() {
468            if name != "" && match[name] == "" {
469                match[name] = m[i]
470            }
471        }
472        if srv.vcs != "" {
473            match["vcs"] = expand(matchsrv.vcs)
474        }
475        if srv.repo != "" {
476            match["repo"] = expand(matchsrv.repo)
477        }
478        if srv.check != nil {
479            if err := srv.check(match); err != nil {
480                return nilerr
481            }
482        }
483        vcs := ByCmd(match["vcs"])
484        if vcs == nil {
485            return nilfmt.Errorf("unknown version control system %q"match["vcs"])
486        }
487        if srv.ping {
488            if scheme != "" {
489                match["repo"] = scheme + "://" + match["repo"]
490            } else {
491                for _scheme := range vcs.Scheme {
492                    if vcs.Ping(schemematch["repo"]) == nil {
493                        match["repo"] = scheme + "://" + match["repo"]
494                        break
495                    }
496                }
497            }
498        }
499        rr := &RepoRoot{
500            VCS:  vcs,
501            Repomatch["repo"],
502            Rootmatch["root"],
503        }
504        return rrnil
505    }
506    return nilerrUnknownSite
507}
508
509// RepoRootForImportDynamic finds a *RepoRoot for a custom domain that's not
510// statically known by RepoRootForImportPathStatic.
511//
512// This handles custom import paths like "name.tld/pkg/foo" or just "name.tld".
513func RepoRootForImportDynamic(importPath stringverbose bool) (*RepoRooterror) {
514    slash := strings.Index(importPath"/")
515    if slash < 0 {
516        slash = len(importPath)
517    }
518    host := importPath[:slash]
519    if !strings.Contains(host".") {
520        return nilerrors.New("import path doesn't contain a hostname")
521    }
522    urlStrbodyerr := httpsOrHTTP(importPath)
523    if err != nil {
524        return nilfmt.Errorf("http/https fetch: %v"err)
525    }
526    defer body.Close()
527    importserr := parseMetaGoImports(body)
528    if err != nil {
529        return nilfmt.Errorf("parsing %s: %v"importPatherr)
530    }
531    metaImporterr := matchGoImport(importsimportPath)
532    if err != nil {
533        if err != errNoMatch {
534            return nilfmt.Errorf("parse %s: %v"urlStrerr)
535        }
536        return nilfmt.Errorf("parse %s: no go-import meta tags"urlStr)
537    }
538    if verbose {
539        log.Printf("get %q: found meta tag %#v at %s"importPathmetaImporturlStr)
540    }
541    // If the import was "uni.edu/bob/project", which said the
542    // prefix was "uni.edu" and the RepoRoot was "evilroot.com",
543    // make sure we don't trust Bob and check out evilroot.com to
544    // "uni.edu" yet (possibly overwriting/preempting another
545    // non-evil student).  Instead, first verify the root and see
546    // if it matches Bob's claim.
547    if metaImport.Prefix != importPath {
548        if verbose {
549            log.Printf("get %q: verifying non-authoritative meta tag"importPath)
550        }
551        urlStr0 := urlStr
552        urlStrbodyerr = httpsOrHTTP(metaImport.Prefix)
553        if err != nil {
554            return nilfmt.Errorf("fetch %s: %v"urlStrerr)
555        }
556        importserr := parseMetaGoImports(body)
557        if err != nil {
558            return nilfmt.Errorf("parsing %s: %v"importPatherr)
559        }
560        if len(imports) == 0 {
561            return nilfmt.Errorf("fetch %s: no go-import meta tag"urlStr)
562        }
563        metaImport2err := matchGoImport(importsimportPath)
564        if err != nil || metaImport != metaImport2 {
565            return nilfmt.Errorf("%s and %s disagree about go-import for %s"urlStr0urlStrmetaImport.Prefix)
566        }
567    }
568
569    if err := validateRepoRoot(metaImport.RepoRoot); err != nil {
570        return nilfmt.Errorf("%s: invalid repo root %q: %v"urlStrmetaImport.RepoRooterr)
571    }
572    rr := &RepoRoot{
573        VCS:  ByCmd(metaImport.VCS),
574        RepometaImport.RepoRoot,
575        RootmetaImport.Prefix,
576    }
577    if rr.VCS == nil {
578        return nilfmt.Errorf("%s: unknown vcs %q"urlStrmetaImport.VCS)
579    }
580    return rrnil
581}
582
583// validateRepoRoot returns an error if repoRoot does not seem to be
584// a valid URL with scheme.
585func validateRepoRoot(repoRoot stringerror {
586    urlerr := url.Parse(repoRoot)
587    if err != nil {
588        return err
589    }
590    if url.Scheme == "" {
591        return errors.New("no scheme")
592    }
593    return nil
594}
595
596// metaImport represents the parsed <meta name="go-import"
597// content="prefix vcs reporoot" /> tags from HTML files.
598type metaImport struct {
599    PrefixVCSRepoRoot string
600}
601
602// errNoMatch is returned from matchGoImport when there's no applicable match.
603var errNoMatch = errors.New("no import match")
604
605// pathPrefix reports whether sub is a prefix of s,
606// only considering entire path components.
607func pathPrefix(ssub stringbool {
608    // strings.HasPrefix is necessary but not sufficient.
609    if !strings.HasPrefix(ssub) {
610        return false
611    }
612    // The remainder after the prefix must either be empty or start with a slash.
613    rem := s[len(sub):]
614    return rem == "" || rem[0] == '/'
615}
616
617// matchGoImport returns the metaImport from imports matching importPath.
618// An error is returned if there are multiple matches.
619// errNoMatch is returned if none match.
620func matchGoImport(imports []metaImportimportPath string) (_ metaImporterr error) {
621    match := -1
622    for iim := range imports {
623        if !pathPrefix(importPathim.Prefix) {
624            continue
625        }
626
627        if match != -1 {
628            err = fmt.Errorf("multiple meta tags match import path %q"importPath)
629            return
630        }
631        match = i
632    }
633    if match == -1 {
634        err = errNoMatch
635        return
636    }
637    return imports[match], nil
638}
639
640// expand rewrites s to replace {k} with match[k] for each key k in match.
641func expand(match map[string]strings stringstring {
642    for kv := range match {
643        s = strings.Replace(s"{"+k+"}"v, -1)
644    }
645    return s
646}
647
648// vcsPaths lists the known vcs paths.
649var vcsPaths = []*vcsPath{
650    // Github
651    {
652        prefix"github.com/",
653        re:     `^(?P<root>github\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[\p{L}0-9_.\-]+)*$`,
654        vcs:    "git",
655        repo:   "https://{root}",
656        check:  noVCSSuffix,
657    },
658
659    // Bitbucket
660    {
661        prefix"bitbucket.org/",
662        re:     `^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
663        repo:   "https://{root}",
664        check:  bitbucketVCS,
665    },
666
667    // Launchpad
668    {
669        prefix"launchpad.net/",
670        re:     `^(?P<root>launchpad\.net/((?P<project>[A-Za-z0-9_.\-]+)(?P<series>/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
671        vcs:    "bzr",
672        repo:   "https://{root}",
673        check:  launchpadVCS,
674    },
675
676    // Git at OpenStack
677    {
678        prefix"git.openstack.org",
679        re:     `^(?P<root>git\.openstack\.org/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(\.git)?(/[A-Za-z0-9_.\-]+)*$`,
680        vcs:    "git",
681        repo:   "https://{root}",
682        check:  noVCSSuffix,
683    },
684
685    // General syntax for any server.
686    {
687        re:   `^(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?P<vcs>bzr|git|hg|svn))(/[A-Za-z0-9_.\-]+)*$`,
688        pingtrue,
689    },
690}
691
692func init() {
693    // fill in cached regexps.
694    // Doing this eagerly discovers invalid regexp syntax
695    // without having to run a command that needs that regexp.
696    for _srv := range vcsPaths {
697        srv.regexp = regexp.MustCompile(srv.re)
698    }
699}
700
701// noVCSSuffix checks that the repository name does not
702// end in .foo for any version control system foo.
703// The usual culprit is ".git".
704func noVCSSuffix(match map[string]stringerror {
705    repo := match["repo"]
706    for _vcs := range vcsList {
707        if strings.HasSuffix(repo"."+vcs.Cmd) {
708            return fmt.Errorf("invalid version control suffix in %s path"match["prefix"])
709        }
710    }
711    return nil
712}
713
714// bitbucketVCS determines the version control system for a
715// Bitbucket repository, by using the Bitbucket API.
716func bitbucketVCS(match map[string]stringerror {
717    if err := noVCSSuffix(match); err != nil {
718        return err
719    }
720
721    var resp struct {
722        SCM string `json:"scm"`
723    }
724    url := expand(match"https://api.bitbucket.org/2.0/repositories/{bitname}?fields=scm")
725    dataerr := httpGET(url)
726    if err != nil {
727        return err
728    }
729    if err := json.Unmarshal(data, &resp); err != nil {
730        return fmt.Errorf("decoding %s: %v"urlerr)
731    }
732
733    if ByCmd(resp.SCM) != nil {
734        match["vcs"] = resp.SCM
735        if resp.SCM == "git" {
736            match["repo"] += ".git"
737        }
738        return nil
739    }
740
741    return fmt.Errorf("unable to detect version control system for bitbucket.org/ path")
742}
743
744// launchpadVCS solves the ambiguity for "lp.net/project/foo". In this case,
745// "foo" could be a series name registered in Launchpad with its own branch,
746// and it could also be the name of a directory within the main project
747// branch one level up.
748func launchpadVCS(match map[string]stringerror {
749    if match["project"] == "" || match["series"] == "" {
750        return nil
751    }
752    _err := httpGET(expand(match"https://code.launchpad.net/{project}{series}/.bzr/branch-format"))
753    if err != nil {
754        match["root"] = expand(match"launchpad.net/{project}")
755        match["repo"] = expand(match"https://{root}")
756    }
757    return nil
758}
759
MembersX
Cmd.run1._
Cmd.Ping
expand.RangeStmt_19028.k
init
Cmd.run1.m
Cmd.TagSync.dir
RepoRoot.VCS
bitbucketVCS.err
vcsPath.vcs
FromDir.vcs
RepoRootForImportPathStatic.scheme
Cmd.runVerboseOnly.err
Cmd.CreateAtRev
Cmd.Tags.RangeStmt_7551.BlockStmt.out
matchGoImport.RangeStmt_18605.im
bitbucketVCS
strconv
Cmd.Name
Cmd.run.dir
Cmd.run1.cmd
vcsPath
RepoRoot.Root
RepoRootForImportDynamic.BlockStmt.urlStr0
RepoRootForImportDynamic.BlockStmt.err
errors
Cmd.run1.i
Cmd.run1.RangeStmt_5708.arg
metaImport
Cmd.Create.dir
vcsPath.prefix
FromDir.srcRoot
RepoRootForImportPath.err
matchGoImport
Cmd.CreateCmd
Cmd.PingCmd
Cmd.runVerboseOnly.dir
RepoRootForImportPathStatic
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.rr
RepoRootForImportDynamic.slash
RepoRootForImportDynamic.body
Cmd.Log.v
Cmd.LogAtRev.rev
RepoRootForImportPath.rr
FromDir.dir
FromDir.BlockStmt.RangeStmt_10725.vcs
RepoRootForImportDynamic.urlStr
RepoRootForImportDynamic.BlockStmt.imports
ByCmd
Cmd.run1.out
Cmd.Ping.v
RepoRootForImportPath
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.RangeStmt_13934.i
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.BlockStmt.BlockStmt.RangeStmt_14518.scheme
Cmd.run1.RangeStmt_5708.i
Cmd.Download
vcsPath.re
Cmd.CreateAtRev.err
Cmd.TagSync.BlockStmt.RangeStmt_8144.BlockStmt.out
pathPrefix.s
matchGoImport.imports
noVCSSuffix.match
json
TagCmd.Pattern
Cmd.run
Cmd.LogAtRev
bitbucketVCS.match
ByCmd.cmd
Cmd.Tags.dir
Cmd.TagSync.BlockStmt.RangeStmt_8144.BlockStmt.err
Cmd.String
Cmd.Tags.tags
Cmd.LogAtRev.err
FromDir.rootRet
RepoRootForImportDynamic.verbose
ShowCmd
Cmd.DownloadCmd
ByCmd.RangeStmt_2175.vcs
validateRepoRoot.url
Cmd.run1.err
Cmd.run1.buf
exec
regexp
Cmd.run1.dir
Cmd.Log.logTemplate
Cmd.LogAtRev.v
FromDir.origDir
Cmd.TagSyncDefault
TagCmd
Cmd.runVerboseOnly.keyval
Cmd.TagSync.tag
bitbucketVCS.url
filepath
Cmd.Create
Cmd.Tags.RangeStmt_7551.BlockStmt.RangeStmt_7713.m
vcsPath.regexp
FromDir.BlockStmt.RangeStmt_10725.BlockStmt._
validateRepoRoot
pathPrefix.sub
init.RangeStmt_20481.srv
Cmd
Cmd.run1.cmdline
Cmd.Log.N
noVCSSuffix.RangeStmt_20773.vcs
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.RangeStmt_13934.name
noVCSSuffix
TagCmd.Cmd
Cmd.run.keyval
Cmd.Log.dir
Cmd.TagSync.BlockStmt.RangeStmt_8144.tc
Cmd.LogAtRev.logTemplate
RepoRootForImportPathStatic.importPath
Cmd.runOutput.keyval
Cmd.run1.keyval
Cmd.Download.dir
RepoRoot
RepoRootForImportPathStatic.RangeStmt_13501.srv
RepoRootForImportDynamic
RepoRootForImportDynamic.metaImport
Cmd.runOutput
Cmd.run1.v
Cmd.Tags.RangeStmt_7551.BlockStmt.err
Cmd.CreateAtRev.repo
expand.s
Cmd.runOutput.v
Cmd.run1
FromDir.BlockStmt.ndir
RepoRoot.Repo
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.vcs
Verbose
Cmd.TagSyncCmd
Cmd.run.v
matchGoImport.err
expand.RangeStmt_19028.v
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.m
validateRepoRoot.repoRoot
Cmd.Create.v
Cmd.Tags
FromDir.vcsRet
RepoRootForImportDynamic.imports
metaImport.RepoRoot
bitbucketVCS.data
Cmd.run._
Cmd.runOutput.cmd
Cmd.TagSync
pathPrefix
expand
launchpadVCS._
Cmd.CreateAtRev.rev
Cmd.Tags.v
metaImport.Prefix
RepoRootForImportDynamic.BlockStmt.metaImport2
launchpadVCS.err
Cmd.LogCmd
Cmd.Ping.repo
Cmd.Log
Cmd.TagSync.v
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.BlockStmt.err
launchpadVCS
Cmd.Scheme
Cmd.runVerboseOnly._
Cmd.CreateAtRev.v
RepoRootForImportPath.verbose
RepoRootForImportDynamic.importPath
metaImport.VCS
Cmd.Create.repo
Cmd.Log.err
FromDir.BlockStmt.RangeStmt_10725.BlockStmt.BlockStmt.root
Cmd.TagSync.BlockStmt.RangeStmt_8144.BlockStmt.m
Cmd.LogAtRev.dir
vcsPath.repo
Cmd.TagLookupCmd
Cmd.String.v
Cmd.runVerboseOnly.v
Cmd.runVerboseOnly.cmd
Cmd.Download.v
FromDir
launchpadVCS.match
Cmd.Cmd
Cmd.TagCmd
Cmd.runVerboseOnly
Cmd.run.cmd
Cmd.CreateAtRev.dir
FromDir.err
Cmd.Ping.scheme
Cmd.Tags.RangeStmt_7551.BlockStmt.re
vcsPath.check
vcsPath.ping
FromDir.root
bytes
Cmd.run.err
Cmd.runOutput.dir
RepoRootForImportDynamic.rr
matchGoImport.importPath
FromDir.BlockStmt.RangeStmt_10725.BlockStmt.err
RepoRootForImportPathStatic.RangeStmt_13501.BlockStmt.match
RepoRootForImportDynamic.err
Cmd.TagSync.BlockStmt.RangeStmt_8144.BlockStmt.re
RepoRootForImportPath.importPath
validateRepoRoot.err
matchGoImport.RangeStmt_18605.i
expand.match
Cmd.run1.verbose
Cmd.run1.args
Cmd.Tags.RangeStmt_7551.tc
Members
X