summaryrefslogtreecommitdiff
path: root/src/cmd/goinstall/download.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/goinstall/download.go')
-rw-r--r--src/cmd/goinstall/download.go248
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
}