diff options
Diffstat (limited to 'src/cmd/goinstall/download.go')
-rw-r--r-- | src/cmd/goinstall/download.go | 248 |
1 files changed, 164 insertions, 84 deletions
diff --git a/src/cmd/goinstall/download.go b/src/cmd/goinstall/download.go index 2edf85efd..da892a69d 100644 --- a/src/cmd/goinstall/download.go +++ b/src/cmd/goinstall/download.go @@ -7,6 +7,8 @@ package main import ( + "exec" + "fmt" "http" "os" "path/filepath" @@ -31,74 +33,10 @@ func maybeReportToDashboard(path string) { } } -var vcsPatterns = map[string]*regexp.Regexp{ - "googlecode": regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|hg))(/[a-z0-9A-Z_.\-/]*)?$`), - "github": regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), - "bitbucket": regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), - "launchpad": regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+))(/[a-z0-9A-Z_.\-/]+)?$`), -} - -// isRemote returns true if the provided package path -// matches one of the supported remote repositories. -func isRemote(pkg string) bool { - for _, r := range vcsPatterns { - if r.MatchString(pkg) { - return true - } - } - return false -} - -// download checks out or updates pkg from the remote server. -func download(pkg, srcDir string) os.Error { - if strings.Contains(pkg, "..") { - return os.ErrorString("invalid path (contains ..)") - } - if m := vcsPatterns["bitbucket"].FindStringSubmatch(pkg); m != nil { - if err := vcsCheckout(&hg, srcDir, m[1], "http://"+m[1], m[1]); err != nil { - return err - } - return nil - } - if m := vcsPatterns["googlecode"].FindStringSubmatch(pkg); m != nil { - var v *vcs - switch m[2] { - case "hg": - v = &hg - case "svn": - v = &svn - default: - // regexp only allows hg, svn to get through - panic("missing case in download: " + pkg) - } - if err := vcsCheckout(v, srcDir, m[1], "https://"+m[1], m[1]); err != nil { - return err - } - return nil - } - if m := vcsPatterns["github"].FindStringSubmatch(pkg); m != nil { - if strings.HasSuffix(m[1], ".git") { - return os.ErrorString("repository " + pkg + " should not have .git suffix") - } - if err := vcsCheckout(&git, srcDir, m[1], "http://"+m[1]+".git", m[1]); err != nil { - return err - } - return nil - } - if m := vcsPatterns["launchpad"].FindStringSubmatch(pkg); m != nil { - // Either lp.net/<project>[/<series>[/<path>]] - // or lp.net/~<user or team>/<project>/<branch>[/<path>] - if err := vcsCheckout(&bzr, srcDir, m[1], "https://"+m[1], m[1]); err != nil { - return err - } - return nil - } - return os.ErrorString("unknown repository: " + pkg) -} - // a vcs represents a version control system // like Mercurial, Git, or Subversion. type vcs struct { + name string cmd string metadir string checkout string @@ -110,9 +48,20 @@ type vcs struct { log string logLimitFlag string logReleaseFlag string + check string + protocols []string + suffix string + defaultHosts []host +} + +type host struct { + pattern *regexp.Regexp + protocol string + suffix string } var hg = vcs{ + name: "Mercurial", cmd: "hg", metadir: ".hg", checkout: "checkout", @@ -123,9 +72,17 @@ var hg = vcs{ log: "log", logLimitFlag: "-l1", logReleaseFlag: "-rrelease", + check: "identify", + protocols: []string{"https", "http"}, + suffix: ".hg", + defaultHosts: []host{ + {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/hg)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""}, + {regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ""}, + }, } var git = vcs{ + name: "Git", cmd: "git", metadir: ".git", checkout: "checkout", @@ -136,9 +93,16 @@ var git = vcs{ log: "show-ref", logLimitFlag: "", logReleaseFlag: "release", + check: "ls-remote", + protocols: []string{"git", "https", "http"}, + suffix: ".git", + defaultHosts: []host{ + {regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ".git"}, + }, } var svn = vcs{ + name: "Subversion", cmd: "svn", metadir: ".svn", checkout: "checkout", @@ -148,9 +112,16 @@ var svn = vcs{ log: "log", logLimitFlag: "-l1", logReleaseFlag: "release", + check: "info", + protocols: []string{"https", "http", "svn"}, + suffix: ".svn", + defaultHosts: []host{ + {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/svn)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""}, + }, } var bzr = vcs{ + name: "Bazaar", cmd: "bzr", metadir: ".bzr", checkout: "update", @@ -162,6 +133,116 @@ var bzr = vcs{ log: "log", logLimitFlag: "-l1", logReleaseFlag: "-rrelease", + check: "info", + protocols: []string{"https", "http", "bzr"}, + suffix: ".bzr", + defaultHosts: []host{ + {regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+))(/[a-z0-9A-Z_.\-/]+)?$`), "https", ""}, + }, +} + +var vcsList = []*vcs{&git, &hg, &bzr, &svn} + +type vcsMatch struct { + *vcs + prefix, repo string +} + +// findHostedRepo checks whether pkg is located at one of +// the supported code hosting sites and, if so, returns a match. +func findHostedRepo(pkg string) (*vcsMatch, os.Error) { + for _, v := range vcsList { + for _, host := range v.defaultHosts { + if hm := host.pattern.FindStringSubmatch(pkg); hm != nil { + if host.suffix != "" && strings.HasSuffix(hm[1], host.suffix) { + return nil, os.NewError("repository " + pkg + " should not have " + v.suffix + " suffix") + } + repo := host.protocol + "://" + hm[1] + host.suffix + return &vcsMatch{v, hm[1], repo}, nil + } + } + } + return nil, nil +} + +// findAnyRepo looks for a vcs suffix in pkg (.git, etc) and returns a match. +func findAnyRepo(pkg string) (*vcsMatch, os.Error) { + for _, v := range vcsList { + i := strings.Index(pkg+"/", v.suffix+"/") + if i < 0 { + continue + } + if !strings.Contains(pkg[:i], "/") { + continue // don't match vcs suffix in the host name + } + if m := v.find(pkg[:i]); m != nil { + return m, nil + } + return nil, fmt.Errorf("couldn't find %s repository", v.name) + } + return nil, nil +} + +func (v *vcs) find(pkg string) *vcsMatch { + for _, proto := range v.protocols { + for _, suffix := range []string{"", v.suffix} { + repo := proto + "://" + pkg + suffix + out, err := exec.Command(v.cmd, v.check, repo).CombinedOutput() + if err == nil { + printf("find %s: found %s\n", pkg, repo) + return &vcsMatch{v, pkg + v.suffix, repo} + } + printf("find %s: %s %s %s: %v\n%s\n", pkg, v.cmd, v.check, repo, err, out) + } + } + return nil +} + +// isRemote returns true if the first part of the package name looks like a +// hostname - i.e. contains at least one '.' and the last part is at least 2 +// characters. +func isRemote(pkg string) bool { + parts := strings.SplitN(pkg, "/", 2) + if len(parts) != 2 { + return false + } + parts = strings.Split(parts[0], ".") + if len(parts) < 2 || len(parts[len(parts)-1]) < 2 { + return false + } + return true +} + +// download checks out or updates pkg from the remote server. +func download(pkg, srcDir string) (dashReport bool, err os.Error) { + if strings.Contains(pkg, "..") { + err = os.NewError("invalid path (contains ..)") + return + } + m, err := findHostedRepo(pkg) + if err != nil { + return + } + if m != nil { + dashReport = true // only report public code hosting sites + } else { + m, err = findAnyRepo(pkg) + if err != nil { + return + } + } + if m == nil { + err = os.NewError("cannot download: " + pkg) + return + } + installed, err := m.checkoutRepo(srcDir, m.prefix, m.repo) + if err != nil { + return + } + if !installed { + dashReport = false + } + return } // Try to detect if a "release" tag exists. If it does, update @@ -180,47 +261,46 @@ func (v *vcs) updateRepo(dst string) os.Error { return nil } -// vcsCheckout checks out repo into dst using vcs. +// checkoutRepo checks out repo into dst using vcs. // It tries to check out (or update, if the dst already // exists and -u was specified on the command line) // the repository at tag/branch "release". If there is no // such tag or branch, it falls back to the repository tip. -func vcsCheckout(vcs *vcs, srcDir, pkgprefix, repo, dashpath string) os.Error { +func (vcs *vcs) checkoutRepo(srcDir, pkgprefix, repo string) (installed bool, err os.Error) { dst := filepath.Join(srcDir, filepath.FromSlash(pkgprefix)) dir, err := os.Stat(filepath.Join(dst, vcs.metadir)) if err == nil && !dir.IsDirectory() { - return os.ErrorString("not a directory: " + dst) + err = os.NewError("not a directory: " + dst) + return } if err != nil { parent, _ := filepath.Split(dst) - if err := os.MkdirAll(parent, 0777); err != nil { - return err + if err = os.MkdirAll(parent, 0777); err != nil { + return } - if err := run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil { - return err + if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil { + return } - if err := vcs.updateRepo(dst); err != nil { - return err + if err = vcs.updateRepo(dst); err != nil { + return } - // success on first installation - report - maybeReportToDashboard(dashpath) + installed = true } else if *update { // Retrieve new revisions from the remote branch, if the VCS // supports this operation independently (e.g. svn doesn't) if vcs.pull != "" { if vcs.pullForceFlag != "" { - if err := run(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil { - return err + if err = run(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil { + return } - } else if err := run(dst, nil, vcs.cmd, vcs.pull); err != nil { - return err + } else if err = run(dst, nil, vcs.cmd, vcs.pull); err != nil { + return } } - // Update to release or latest revision - if err := vcs.updateRepo(dst); err != nil { - return err + if err = vcs.updateRepo(dst); err != nil { + return } } - return nil + return } |