Commit d066e02a authored by Julian Phillips's avatar Julian Phillips Committed by Andrew Gerrand

goinstall: More intelligent vcs selection for common sites

goinstall has built in support for a few common code hosting sites.  The
identification of which vcs tool should be used was based purely on a
regex match against the provided import path.  The problem with this
approach is that it requires distinct import paths for different vcs
tools on the same site.

Since bitbucket has recently starting hosting Git repositories under the
same bitbucket.org/user/project scheme as it already hosts Mercurial
repositories, now would seem a good time to take a more flexible
approach.

We still match the import path against a list of regexes, but now the
match is purely to distinguish the different hosting sites.  Once the
site is identified, the specified function is called with the repo and
path matched out of the import string.  This function is responsible for
creating the vcsMatch structure that tells us what we need to download
the code.

For github and launchpad, only one vcs tool is currently supported, so
these functions can simply return a vcsMatch structure.  For googlecode,
we retain the behaviour of determing the vcs from the import path - but
now it is done by the function instead of the regex.  For bitbucket, we
use api.bitbucket.org to find out what sort of repository the specified
import path corresponds to - and then construct the appropriate vcsMatch
structure.

R=golang-dev, adg
CC=golang-dev, rsc
https://golang.org/cl/5306069
parent 1e900b0d
...@@ -58,7 +58,7 @@ download the code if necessary. ...@@ -58,7 +58,7 @@ download the code if necessary.
Goinstall recognizes packages from a few common code hosting sites: Goinstall recognizes packages from a few common code hosting sites:
BitBucket (Mercurial) BitBucket (Git, Mercurial)
import "bitbucket.org/user/project" import "bitbucket.org/user/project"
import "bitbucket.org/user/project/sub/directory" import "bitbucket.org/user/project/sub/directory"
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"exec" "exec"
"fmt" "fmt"
"http" "http"
"json"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
...@@ -56,12 +57,6 @@ type vcs struct { ...@@ -56,12 +57,6 @@ type vcs struct {
defaultHosts []host defaultHosts []host
} }
type host struct {
pattern *regexp.Regexp
protocol string
suffix string
}
var hg = vcs{ var hg = vcs{
name: "Mercurial", name: "Mercurial",
cmd: "hg", cmd: "hg",
...@@ -75,10 +70,6 @@ var hg = vcs{ ...@@ -75,10 +70,6 @@ var hg = vcs{
check: "identify", check: "identify",
protocols: []string{"https", "http"}, protocols: []string{"https", "http"},
suffix: ".hg", 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{ var git = vcs{
...@@ -94,10 +85,6 @@ var git = vcs{ ...@@ -94,10 +85,6 @@ var git = vcs{
check: "ls-remote", check: "ls-remote",
protocols: []string{"git", "https", "http"}, protocols: []string{"git", "https", "http"},
suffix: ".git", suffix: ".git",
defaultHosts: []host{
{regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/git)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""},
{regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ".git"},
},
} }
var svn = vcs{ var svn = vcs{
...@@ -110,9 +97,6 @@ var svn = vcs{ ...@@ -110,9 +97,6 @@ var svn = vcs{
check: "info", check: "info",
protocols: []string{"https", "http", "svn"}, protocols: []string{"https", "http", "svn"},
suffix: ".svn", suffix: ".svn",
defaultHosts: []host{
{regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/svn)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""},
},
} }
var bzr = vcs{ var bzr = vcs{
...@@ -130,30 +114,110 @@ var bzr = vcs{ ...@@ -130,30 +114,110 @@ var bzr = vcs{
check: "info", check: "info",
protocols: []string{"https", "http", "bzr"}, protocols: []string{"https", "http", "bzr"},
suffix: ".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} var vcsList = []*vcs{&git, &hg, &bzr, &svn}
type host struct {
pattern *regexp.Regexp
getVcs func(repo, path string) (*vcsMatch, os.Error)
}
var knownHosts = []host{
{
regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|git|hg))(/[a-z0-9A-Z_.\-/]*)?$`),
googleVcs,
},
{
regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
githubVcs,
},
{
regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
bitbucketVcs,
},
{
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_.\-/]+)?$`),
launchpadVcs,
},
}
type vcsMatch struct { type vcsMatch struct {
*vcs *vcs
prefix, repo string prefix, repo string
} }
func googleVcs(repo, path string) (*vcsMatch, os.Error) {
parts := strings.SplitN(repo, "/", 2)
url := "https://" + repo
switch parts[1] {
case "svn":
return &vcsMatch{&svn, repo, url}, nil
case "git":
return &vcsMatch{&git, repo, url}, nil
case "hg":
return &vcsMatch{&hg, repo, url}, nil
}
return nil, os.NewError("unsupported googlecode vcs: " + parts[1])
}
func githubVcs(repo, path string) (*vcsMatch, os.Error) {
if strings.HasSuffix(repo, ".git") {
return nil, os.NewError("path must not include .git suffix")
}
return &vcsMatch{&git, repo, "http://" + repo + ".git"}, nil
}
func bitbucketVcs(repo, path string) (*vcsMatch, os.Error) {
const bitbucketApiUrl = "https://api.bitbucket.org/1.0/repositories/"
if strings.HasSuffix(repo, ".git") {
return nil, os.NewError("path must not include .git suffix")
}
parts := strings.SplitN(repo, "/", 2)
// Ask the bitbucket API what kind of repository this is.
r, err := http.Get(bitbucketApiUrl + parts[1])
if err != nil {
return nil, fmt.Errorf("error querying BitBucket API: %v", err)
}
defer r.Body.Close()
// Did we get a useful response?
if r.StatusCode != 200 {
return nil, fmt.Errorf("error querying BitBucket API: %v", r.Status)
}
var response struct {
Vcs string `json:"scm"`
}
err = json.NewDecoder(r.Body).Decode(&response)
if err != nil {
return nil, fmt.Errorf("error querying BitBucket API: %v", err)
}
// Now we should be able to construct a vcsMatch structure
switch response.Vcs {
case "git":
return &vcsMatch{&git, repo, "http://" + repo + ".git"}, nil
case "hg":
return &vcsMatch{&hg, repo, "http://" + repo}, nil
}
return nil, os.NewError("unsupported bitbucket vcs: " + response.Vcs)
}
func launchpadVcs(repo, path string) (*vcsMatch, os.Error) {
return &vcsMatch{&bzr, repo, "https://" + repo}, nil
}
// findPublicRepo checks whether pkg is located at one of // findPublicRepo checks whether pkg is located at one of
// the supported code hosting sites and, if so, returns a match. // the supported code hosting sites and, if so, returns a match.
func findPublicRepo(pkg string) (*vcsMatch, os.Error) { func findPublicRepo(pkg string) (*vcsMatch, os.Error) {
for _, v := range vcsList { for _, host := range knownHosts {
for _, host := range v.defaultHosts {
if hm := host.pattern.FindStringSubmatch(pkg); hm != nil { if hm := host.pattern.FindStringSubmatch(pkg); hm != nil {
if host.suffix != "" && strings.HasSuffix(hm[1], host.suffix) { return host.getVcs(hm[1], hm[2])
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 return nil, nil
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment