mirror of
https://github.com/golang/go
synced 2024-11-20 00:04:43 -07:00
goinstall: support googlecode subrepos and add repo match tests
goinstall: don't hit network unless a checkout or update is required R=rsc, rogpeppe CC=golang-dev https://golang.org/cl/5343042
This commit is contained in:
parent
7c161b05aa
commit
86c08e9611
@ -79,6 +79,10 @@ Goinstall recognizes packages from a few common code hosting sites:
|
|||||||
import "project.googlecode.com/svn/trunk"
|
import "project.googlecode.com/svn/trunk"
|
||||||
import "project.googlecode.com/svn/trunk/sub/directory"
|
import "project.googlecode.com/svn/trunk/sub/directory"
|
||||||
|
|
||||||
|
Google Code Project Hosting sub-repositories:
|
||||||
|
|
||||||
|
import "code.google.com/p/project.subrepo/sub/directory
|
||||||
|
|
||||||
Launchpad (Bazaar)
|
Launchpad (Bazaar)
|
||||||
|
|
||||||
import "launchpad.net/project"
|
import "launchpad.net/project"
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -55,10 +56,14 @@ type vcs struct {
|
|||||||
check string
|
check string
|
||||||
protocols []string
|
protocols []string
|
||||||
suffix string
|
suffix string
|
||||||
defaultHosts []host
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hg = vcs{
|
func (v *vcs) String() string {
|
||||||
|
return v.name
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcsMap = map[string]*vcs{
|
||||||
|
"hg": &vcs{
|
||||||
name: "Mercurial",
|
name: "Mercurial",
|
||||||
cmd: "hg",
|
cmd: "hg",
|
||||||
metadir: ".hg",
|
metadir: ".hg",
|
||||||
@ -71,9 +76,9 @@ var hg = vcs{
|
|||||||
check: "identify",
|
check: "identify",
|
||||||
protocols: []string{"https", "http"},
|
protocols: []string{"https", "http"},
|
||||||
suffix: ".hg",
|
suffix: ".hg",
|
||||||
}
|
},
|
||||||
|
|
||||||
var git = vcs{
|
"git": &vcs{
|
||||||
name: "Git",
|
name: "Git",
|
||||||
cmd: "git",
|
cmd: "git",
|
||||||
metadir: ".git",
|
metadir: ".git",
|
||||||
@ -86,9 +91,9 @@ var git = vcs{
|
|||||||
check: "ls-remote",
|
check: "ls-remote",
|
||||||
protocols: []string{"git", "https", "http"},
|
protocols: []string{"git", "https", "http"},
|
||||||
suffix: ".git",
|
suffix: ".git",
|
||||||
}
|
},
|
||||||
|
|
||||||
var svn = vcs{
|
"svn": &vcs{
|
||||||
name: "Subversion",
|
name: "Subversion",
|
||||||
cmd: "svn",
|
cmd: "svn",
|
||||||
metadir: ".svn",
|
metadir: ".svn",
|
||||||
@ -98,9 +103,9 @@ var svn = vcs{
|
|||||||
check: "info",
|
check: "info",
|
||||||
protocols: []string{"https", "http", "svn"},
|
protocols: []string{"https", "http", "svn"},
|
||||||
suffix: ".svn",
|
suffix: ".svn",
|
||||||
}
|
},
|
||||||
|
|
||||||
var bzr = vcs{
|
"bzr": &vcs{
|
||||||
name: "Bazaar",
|
name: "Bazaar",
|
||||||
cmd: "bzr",
|
cmd: "bzr",
|
||||||
metadir: ".bzr",
|
metadir: ".bzr",
|
||||||
@ -115,202 +120,343 @@ var bzr = vcs{
|
|||||||
check: "info",
|
check: "info",
|
||||||
protocols: []string{"https", "http", "bzr"},
|
protocols: []string{"https", "http", "bzr"},
|
||||||
suffix: ".bzr",
|
suffix: ".bzr",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var vcsList = []*vcs{&git, &hg, &bzr, &svn}
|
type RemoteRepo interface {
|
||||||
|
// IsCheckedOut returns whether this repository is checked
|
||||||
|
// out inside the given srcDir (eg, $GOPATH/src).
|
||||||
|
IsCheckedOut(srcDir string) bool
|
||||||
|
|
||||||
|
// Repo returns the information about this repository: its url,
|
||||||
|
// the part of the import path that forms the repository root,
|
||||||
|
// and the version control system it uses. It may discover this
|
||||||
|
// information by using the supplied client to make HTTP requests.
|
||||||
|
Repo(_ *http.Client) (url, root string, vcs *vcs, err error)
|
||||||
|
}
|
||||||
|
|
||||||
type host struct {
|
type host struct {
|
||||||
pattern *regexp.Regexp
|
pattern *regexp.Regexp
|
||||||
getVcs func(repo, path string) (*vcsMatch, error)
|
repo func(repo string) (RemoteRepo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var knownHosts = []host{
|
var knownHosts = []host{
|
||||||
{
|
{
|
||||||
regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|git|hg))(/[a-z0-9A-Z_.\-/]*)?$`),
|
regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|git|hg))(/[a-z0-9A-Z_.\-/]+)?$`),
|
||||||
googleVcs,
|
matchGoogleRepo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
|
regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+\.[a-z0-9\-]+)(/[a-z0-9A-Z_.\-/]+)?$`),
|
||||||
githubVcs,
|
matchGoogleSubrepo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
|
regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$`),
|
||||||
bitbucketVcs,
|
matchGithubRepo,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$`),
|
||||||
|
matchBitbucketRepo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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_.\-/]+)?$`),
|
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,
|
matchLaunchpadRepo,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type vcsMatch struct {
|
// baseRepo is the base implementation of RemoteRepo.
|
||||||
*vcs
|
type baseRepo struct {
|
||||||
prefix, repo string
|
url, root string
|
||||||
|
vcs *vcs
|
||||||
}
|
}
|
||||||
|
|
||||||
func googleVcs(repo, path string) (*vcsMatch, error) {
|
func (r *baseRepo) Repo(_ *http.Client) (url, root string, vcs *vcs, err error) {
|
||||||
parts := strings.SplitN(repo, "/", 2)
|
return r.url, r.root, r.vcs, nil
|
||||||
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, errors.New("unsupported googlecode vcs: " + parts[1])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func githubVcs(repo, path string) (*vcsMatch, error) {
|
// IsCheckedOut reports whether the repo root inside srcDir contains a
|
||||||
if strings.HasSuffix(repo, ".git") {
|
// repository metadir. It updates the baseRepo's vcs field if necessary.
|
||||||
|
func (r *baseRepo) IsCheckedOut(srcDir string) bool {
|
||||||
|
pkgPath := filepath.Join(srcDir, r.root)
|
||||||
|
if r.vcs == nil {
|
||||||
|
for _, vcs := range vcsMap {
|
||||||
|
if isDir(filepath.Join(pkgPath, vcs.metadir)) {
|
||||||
|
r.vcs = vcs
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isDir(filepath.Join(pkgPath, r.vcs.metadir))
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchGoogleRepo handles matches of the form "repo.googlecode.com/vcs/path".
|
||||||
|
func matchGoogleRepo(root string) (RemoteRepo, error) {
|
||||||
|
p := strings.SplitN(root, "/", 2)
|
||||||
|
if vcs := vcsMap[p[1]]; vcs != nil {
|
||||||
|
return &baseRepo{"https://" + root, root, vcs}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unsupported googlecode vcs: " + p[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchGithubRepo handles matches for github.com repositories.
|
||||||
|
func matchGithubRepo(root string) (RemoteRepo, error) {
|
||||||
|
if strings.HasSuffix(root, ".git") {
|
||||||
return nil, errors.New("path must not include .git suffix")
|
return nil, errors.New("path must not include .git suffix")
|
||||||
}
|
}
|
||||||
return &vcsMatch{&git, repo, "http://" + repo + ".git"}, nil
|
return &baseRepo{"http://" + root + ".git", root, vcsMap["git"]}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bitbucketVcs(repo, path string) (*vcsMatch, error) {
|
// matchLaunchpadRepo handles matches for launchpad.net repositories.
|
||||||
const bitbucketApiUrl = "https://api.bitbucket.org/1.0/repositories/"
|
func matchLaunchpadRepo(root string) (RemoteRepo, error) {
|
||||||
|
return &baseRepo{"https://" + root, root, vcsMap["bzr"]}, nil
|
||||||
if strings.HasSuffix(repo, ".git") {
|
|
||||||
return nil, errors.New("path must not include .git suffix")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(repo, "/", 2)
|
// matchGoogleSubrepo matches repos like "code.google.com/p/repo.subrepo/path".
|
||||||
|
// Note that it doesn't match primary Google Code repositories,
|
||||||
|
// which should use the "foo.googlecode.com" form only. (for now)
|
||||||
|
func matchGoogleSubrepo(id string) (RemoteRepo, error) {
|
||||||
|
root := "code.google.com/p/" + id
|
||||||
|
return &googleSubrepo{baseRepo{"https://" + root, root, nil}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Ask the bitbucket API what kind of repository this is.
|
// googleSubrepo implements a RemoteRepo that discovers a Google Code
|
||||||
r, err := http.Get(bitbucketApiUrl + parts[1])
|
// repository's VCS type by scraping the code.google.com source checkout page.
|
||||||
|
type googleSubrepo struct{ baseRepo }
|
||||||
|
|
||||||
|
var googleSubrepoRe = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)
|
||||||
|
|
||||||
|
func (r *googleSubrepo) Repo(client *http.Client) (url, root string, vcs *vcs, err error) {
|
||||||
|
if r.vcs != nil {
|
||||||
|
return r.url, r.root, r.vcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the code.google.com source checkout page to find the VCS type.
|
||||||
|
const prefix = "code.google.com/p/"
|
||||||
|
p := strings.SplitN(r.root[len(prefix):], ".", 2)
|
||||||
|
u := fmt.Sprintf("https://%s%s/source/checkout?repo=%s", prefix, p[0], p[1])
|
||||||
|
resp, err := client.Get(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error querying BitBucket API: %v", err)
|
return "", "", nil, err
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
// Did we get a useful response?
|
return "", "", nil, fmt.Errorf("fetching %s: %v", u, resp.Status)
|
||||||
if r.StatusCode != 200 {
|
}
|
||||||
return nil, fmt.Errorf("error querying BitBucket API: %v", r.Status)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("fetching %s: %v", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scrape result for vcs details.
|
||||||
|
m := googleSubrepoRe.FindSubmatch(b)
|
||||||
|
if len(m) == 2 {
|
||||||
|
if v := vcsMap[string(m[1])]; v != nil {
|
||||||
|
r.vcs = v
|
||||||
|
return r.url, r.root, r.vcs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", nil, errors.New("could not detect googlecode vcs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchBitbucketRepo handles matches for all bitbucket.org repositories.
|
||||||
|
func matchBitbucketRepo(root string) (RemoteRepo, error) {
|
||||||
|
if strings.HasSuffix(root, ".git") {
|
||||||
|
return nil, errors.New("path must not include .git suffix")
|
||||||
|
}
|
||||||
|
return &bitbucketRepo{baseRepo{root: root}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitbucketRepo implements a RemoteRepo that uses the BitBucket API to
|
||||||
|
// discover the repository's VCS type.
|
||||||
|
type bitbucketRepo struct{ baseRepo }
|
||||||
|
|
||||||
|
func (r *bitbucketRepo) Repo(client *http.Client) (url, root string, vcs *vcs, err error) {
|
||||||
|
if r.vcs != nil && r.url != "" {
|
||||||
|
return r.url, r.root, r.vcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the BitBucket API to find which kind of repository this is.
|
||||||
|
const apiUrl = "https://api.bitbucket.org/1.0/repositories/"
|
||||||
|
resp, err := client.Get(apiUrl + strings.SplitN(r.root, "/", 2)[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("BitBucket API: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", "", nil, fmt.Errorf("BitBucket API: %v", resp.Status)
|
||||||
|
}
|
||||||
var response struct {
|
var response struct {
|
||||||
Vcs string `json:"scm"`
|
Vcs string `json:"scm"`
|
||||||
}
|
}
|
||||||
err = json.NewDecoder(r.Body).Decode(&response)
|
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error querying BitBucket API: %v", err)
|
return "", "", nil, fmt.Errorf("BitBucket API: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we should be able to construct a vcsMatch structure
|
|
||||||
switch response.Vcs {
|
switch response.Vcs {
|
||||||
case "git":
|
case "git":
|
||||||
return &vcsMatch{&git, repo, "http://" + repo + ".git"}, nil
|
r.url = "http://" + r.root + ".git"
|
||||||
case "hg":
|
case "hg":
|
||||||
return &vcsMatch{&hg, repo, "http://" + repo}, nil
|
r.url = "http://" + r.root
|
||||||
|
default:
|
||||||
|
return "", "", nil, errors.New("unsupported bitbucket vcs: " + response.Vcs)
|
||||||
|
}
|
||||||
|
if r.vcs = vcsMap[response.Vcs]; r.vcs == nil {
|
||||||
|
panic("vcs is nil when it should not be")
|
||||||
|
}
|
||||||
|
return r.url, r.root, r.vcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("unsupported bitbucket vcs: " + response.Vcs)
|
// findPublicRepo checks whether importPath is a well-formed path for one of
|
||||||
}
|
// the supported code hosting sites and, if so, returns a RemoteRepo.
|
||||||
|
func findPublicRepo(importPath string) (RemoteRepo, error) {
|
||||||
func launchpadVcs(repo, path string) (*vcsMatch, error) {
|
|
||||||
return &vcsMatch{&bzr, repo, "https://" + repo}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findPublicRepo checks whether pkg is located at one of
|
|
||||||
// the supported code hosting sites and, if so, returns a match.
|
|
||||||
func findPublicRepo(pkg string) (*vcsMatch, error) {
|
|
||||||
for _, host := range knownHosts {
|
for _, host := range knownHosts {
|
||||||
if hm := host.pattern.FindStringSubmatch(pkg); hm != nil {
|
if hm := host.pattern.FindStringSubmatch(importPath); hm != nil {
|
||||||
return host.getVcs(hm[1], hm[2])
|
return host.repo(hm[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findAnyRepo looks for a vcs suffix in pkg (.git, etc) and returns a match.
|
// findAnyRepo matches import paths with a repo suffix (.git, etc).
|
||||||
func findAnyRepo(pkg string) (*vcsMatch, error) {
|
func findAnyRepo(importPath string) RemoteRepo {
|
||||||
for _, v := range vcsList {
|
for _, v := range vcsMap {
|
||||||
i := strings.Index(pkg+"/", v.suffix+"/")
|
i := strings.Index(importPath+"/", v.suffix+"/")
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.Contains(pkg[:i], "/") {
|
if !strings.Contains(importPath[:i], "/") {
|
||||||
continue // don't match vcs suffix in the host name
|
continue // don't match vcs suffix in the host name
|
||||||
}
|
}
|
||||||
if m := v.find(pkg[:i]); m != nil {
|
return &anyRepo{
|
||||||
return m, nil
|
baseRepo{
|
||||||
}
|
root: importPath[:i] + v.suffix,
|
||||||
return nil, fmt.Errorf("couldn't find %s repository", v.name)
|
vcs: v,
|
||||||
}
|
},
|
||||||
return nil, nil
|
importPath[:i],
|
||||||
}
|
|
||||||
|
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isRemote returns true if the first part of the package name looks like a
|
// anyRepo implements an discoverable remote repo with a suffix (.git, etc).
|
||||||
// hostname - i.e. contains at least one '.' and the last part is at least 2
|
type anyRepo struct {
|
||||||
// characters.
|
baseRepo
|
||||||
func isRemote(pkg string) bool {
|
rootWithoutSuffix string
|
||||||
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 (r *anyRepo) Repo(_ *http.Client) (url, root string, vcs *vcs, err error) {
|
||||||
func download(pkg, srcDir string) (public bool, err error) {
|
if r.url != "" {
|
||||||
if strings.Contains(pkg, "..") {
|
return r.url, r.root, r.vcs, nil
|
||||||
|
}
|
||||||
|
url, err = r.vcs.findURL(r.rootWithoutSuffix)
|
||||||
|
if url == "" && err == nil {
|
||||||
|
err = fmt.Errorf("couldn't find %s repository", r.vcs.name)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
r.url = url
|
||||||
|
return r.url, r.root, r.vcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findURL finds the URL for a given repo root by trying each combination of
|
||||||
|
// protocol and suffix in series.
|
||||||
|
func (v *vcs) findURL(root string) (string, error) {
|
||||||
|
for _, proto := range v.protocols {
|
||||||
|
for _, suffix := range []string{"", v.suffix} {
|
||||||
|
url := proto + "://" + root + suffix
|
||||||
|
out, err := exec.Command(v.cmd, v.check, url).CombinedOutput()
|
||||||
|
if err == nil {
|
||||||
|
printf("find %s: found %s\n", root, url)
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
printf("findURL(%s): %s %s %s: %v\n%s\n", root, v.cmd, v.check, url, err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// download checks out or updates the specified package from the remote server.
|
||||||
|
func download(importPath, srcDir string) (public bool, err error) {
|
||||||
|
if strings.Contains(importPath, "..") {
|
||||||
err = errors.New("invalid path (contains ..)")
|
err = errors.New("invalid path (contains ..)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m, err := findPublicRepo(pkg)
|
|
||||||
|
repo, err := findPublicRepo(importPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false, err
|
||||||
}
|
}
|
||||||
if m != nil {
|
if repo != nil {
|
||||||
public = true
|
public = true
|
||||||
} else {
|
} else {
|
||||||
m, err = findAnyRepo(pkg)
|
repo = findAnyRepo(importPath)
|
||||||
|
}
|
||||||
|
if repo == nil {
|
||||||
|
err = errors.New("cannot download: " + importPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = checkoutRepo(srcDir, repo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkoutRepo checks out repo into srcDir (if it's not checked out already)
|
||||||
|
// and, if the -u flag is set, updates the repository.
|
||||||
|
func checkoutRepo(srcDir string, repo RemoteRepo) error {
|
||||||
|
if !repo.IsCheckedOut(srcDir) {
|
||||||
|
// do checkout
|
||||||
|
url, root, vcs, err := repo.Repo(http.DefaultClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
|
}
|
||||||
|
repoPath := filepath.Join(srcDir, root)
|
||||||
|
parent, _ := filepath.Split(repoPath)
|
||||||
|
if err = os.MkdirAll(parent, 0777); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, url, repoPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return vcs.updateRepo(repoPath)
|
||||||
|
}
|
||||||
|
if *update {
|
||||||
|
// do update
|
||||||
|
_, root, vcs, err := repo.Repo(http.DefaultClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoPath := filepath.Join(srcDir, root)
|
||||||
|
// 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(repoPath, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err = run(repoPath, nil, vcs.cmd, vcs.pull); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m == nil {
|
// Update to release or latest revision
|
||||||
err = errors.New("cannot download: " + pkg)
|
return vcs.updateRepo(repoPath)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = m.checkoutRepo(srcDir, m.prefix, m.repo)
|
return nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateRepo gets a list of tags in the repository and
|
// updateRepo gets a list of tags in the repository and
|
||||||
// checks out the tag closest to the current runtime.Version.
|
// checks out the tag closest to the current runtime.Version.
|
||||||
// If no matching tag is found, it just updates to tip.
|
// If no matching tag is found, it just updates to tip.
|
||||||
func (v *vcs) updateRepo(dst string) error {
|
func (v *vcs) updateRepo(repoPath string) error {
|
||||||
if v.tagList == "" || v.tagListRe == nil {
|
if v.tagList == "" || v.tagListRe == nil {
|
||||||
// TODO(adg): fix for svn
|
// TODO(adg): fix for svn
|
||||||
return run(dst, nil, v.cmd, v.update)
|
return run(repoPath, nil, v.cmd, v.update)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tag list.
|
// Get tag list.
|
||||||
stderr := new(bytes.Buffer)
|
stderr := new(bytes.Buffer)
|
||||||
cmd := exec.Command(v.cmd, v.tagList)
|
cmd := exec.Command(v.cmd, v.tagList)
|
||||||
cmd.Dir = dst
|
cmd.Dir = repoPath
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
b, err := cmd.Output()
|
b, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -328,12 +474,12 @@ func (v *vcs) updateRepo(dst string) error {
|
|||||||
// Select tag.
|
// Select tag.
|
||||||
if tag := selectTag(ver, tags); tag != "" {
|
if tag := selectTag(ver, tags); tag != "" {
|
||||||
printf("selecting revision %q\n", tag)
|
printf("selecting revision %q\n", tag)
|
||||||
return run(dst, nil, v.cmd, v.checkout, v.updateRevFlag+tag)
|
return run(repoPath, nil, v.cmd, v.checkout, v.updateRevFlag+tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No matching tag found, make default selection.
|
// No matching tag found, make default selection.
|
||||||
printf("selecting tip\n")
|
printf("selecting tip\n")
|
||||||
return run(dst, nil, v.cmd, v.update)
|
return run(repoPath, nil, v.cmd, v.update)
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectTag returns the closest matching tag for a given version.
|
// selectTag returns the closest matching tag for a given version.
|
||||||
@ -378,41 +524,7 @@ func selectTag(goVersion string, tags []string) (match string) {
|
|||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkoutRepo checks out repo into dst using vcs.
|
func isDir(dir string) bool {
|
||||||
// It tries to check out (or update, if the dst already
|
fi, err := os.Stat(dir)
|
||||||
// exists and -u was specified on the command line)
|
return err == nil && fi.IsDirectory()
|
||||||
// the repository at tag/branch "release". If there is no
|
|
||||||
// such tag or branch, it falls back to the repository tip.
|
|
||||||
func (vcs *vcs) checkoutRepo(srcDir, pkgprefix, repo string) error {
|
|
||||||
dst := filepath.Join(srcDir, filepath.FromSlash(pkgprefix))
|
|
||||||
dir, err := os.Stat(filepath.Join(dst, vcs.metadir))
|
|
||||||
if err == nil && !dir.IsDirectory() {
|
|
||||||
return errors.New("not a directory: " + dst)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
parent, _ := filepath.Split(dst)
|
|
||||||
if err = os.MkdirAll(parent, 0777); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return vcs.updateRepo(dst)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} else if err = run(dst, nil, vcs.cmd, vcs.pull); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update to release or latest revision
|
|
||||||
return vcs.updateRepo(dst)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
140
src/cmd/goinstall/download_test.go
Normal file
140
src/cmd/goinstall/download_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2011 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FindPublicRepoTests = []struct {
|
||||||
|
pkg string
|
||||||
|
vcs, root, url string
|
||||||
|
transport *testTransport
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"repo.googlecode.com/hg/path/foo",
|
||||||
|
"hg",
|
||||||
|
"repo.googlecode.com/hg",
|
||||||
|
"https://repo.googlecode.com/hg",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"repo.googlecode.com/svn/path",
|
||||||
|
"svn",
|
||||||
|
"repo.googlecode.com/svn",
|
||||||
|
"https://repo.googlecode.com/svn",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"repo.googlecode.com/git",
|
||||||
|
"git",
|
||||||
|
"repo.googlecode.com/git",
|
||||||
|
"https://repo.googlecode.com/git",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code.google.com/p/repo.sub/path",
|
||||||
|
"hg",
|
||||||
|
"code.google.com/p/repo.sub",
|
||||||
|
"https://code.google.com/p/repo.sub",
|
||||||
|
&testTransport{
|
||||||
|
"https://code.google.com/p/repo/source/checkout?repo=sub",
|
||||||
|
`<tt id="checkoutcmd">hg clone https://...`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitbucket.org/user/repo/path/foo",
|
||||||
|
"hg",
|
||||||
|
"bitbucket.org/user/repo",
|
||||||
|
"http://bitbucket.org/user/repo",
|
||||||
|
&testTransport{
|
||||||
|
"https://api.bitbucket.org/1.0/repositories/user/repo",
|
||||||
|
`{"scm": "hg"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitbucket.org/user/repo/path/foo",
|
||||||
|
"git",
|
||||||
|
"bitbucket.org/user/repo",
|
||||||
|
"http://bitbucket.org/user/repo.git",
|
||||||
|
&testTransport{
|
||||||
|
"https://api.bitbucket.org/1.0/repositories/user/repo",
|
||||||
|
`{"scm": "git"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/user/repo/path/foo",
|
||||||
|
"git",
|
||||||
|
"github.com/user/repo",
|
||||||
|
"http://github.com/user/repo.git",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"launchpad.net/project/series/path",
|
||||||
|
"bzr",
|
||||||
|
"launchpad.net/project/series",
|
||||||
|
"https://launchpad.net/project/series",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"launchpad.net/~user/project/branch/path",
|
||||||
|
"bzr",
|
||||||
|
"launchpad.net/~user/project/branch",
|
||||||
|
"https://launchpad.net/~user/project/branch",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindPublicRepo(t *testing.T) {
|
||||||
|
for _, test := range FindPublicRepoTests {
|
||||||
|
client := http.DefaultClient
|
||||||
|
if test.transport != nil {
|
||||||
|
client = &http.Client{Transport: test.transport}
|
||||||
|
}
|
||||||
|
repo, err := findPublicRepo(test.pkg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("findPublicRepo(%s): error: %v", test.pkg, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if repo == nil {
|
||||||
|
t.Errorf("%s: got nil match", test.pkg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
url, root, vcs, err := repo.Repo(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: repo.Repo error: %v", test.pkg, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v := vcsMap[test.vcs]; vcs != v {
|
||||||
|
t.Errorf("%s: got vcs=%v, want %v", test.pkg, vcs, v)
|
||||||
|
}
|
||||||
|
if root != test.root {
|
||||||
|
t.Errorf("%s: got root=%v, want %v", test.pkg, root, test.root)
|
||||||
|
}
|
||||||
|
if url != test.url {
|
||||||
|
t.Errorf("%s: got url=%v, want %v", test.pkg, url, test.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testTransport struct {
|
||||||
|
expectURL string
|
||||||
|
responseBody string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if g, e := req.URL.String(), t.expectURL; g != e {
|
||||||
|
return nil, errors.New("want " + e)
|
||||||
|
}
|
||||||
|
body := ioutil.NopCloser(bytes.NewBufferString(t.responseBody))
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: body,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -218,8 +218,9 @@ func install(pkg, parent string) {
|
|||||||
} else {
|
} else {
|
||||||
// Test if this is a public repository
|
// Test if this is a public repository
|
||||||
// (for reporting to dashboard).
|
// (for reporting to dashboard).
|
||||||
m, _ := findPublicRepo(pkg)
|
repo, e := findPublicRepo(pkg)
|
||||||
public = m != nil
|
public = repo != nil
|
||||||
|
err = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -334,3 +335,18 @@ func genRun(dir string, stdin []byte, arg []string, quiet bool) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user