| package vendor | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"io/ioutil" | 
 | 	"log" | 
 | 	"net/url" | 
 | 	"os" | 
 | 	"os/exec" | 
 | 	"path/filepath" | 
 | 	"regexp" | 
 | 	"strings" | 
 |  | 
 | 	"github.com/constabulary/gb/fileutils" | 
 | ) | 
 |  | 
 | // RemoteRepo describes a remote dvcs repository. | 
 | type RemoteRepo interface { | 
 |  | 
 | 	// Checkout checks out a specific branch, tag, or revision. | 
 | 	// The interpretation of these three values is impementation | 
 | 	// specific. | 
 | 	Checkout(branch, tag, revision string) (WorkingCopy, error) | 
 |  | 
 | 	// URL returns the URL the clone was taken from. It should | 
 | 	// only be called after Clone. | 
 | 	URL() string | 
 | } | 
 |  | 
 | // WorkingCopy represents a local copy of a remote dvcs repository. | 
 | type WorkingCopy interface { | 
 |  | 
 | 	// Dir is the root of this working copy. | 
 | 	Dir() string | 
 |  | 
 | 	// Revision returns the revision of this working copy. | 
 | 	Revision() (string, error) | 
 |  | 
 | 	// Branch returns the branch to which this working copy belongs. | 
 | 	Branch() (string, error) | 
 |  | 
 | 	// Destroy removes the working copy and cleans path to the working copy. | 
 | 	Destroy() error | 
 | } | 
 |  | 
 | var ( | 
 | 	ghregex   = regexp.MustCompile(`^(?P<root>github\.com/([A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`) | 
 | 	bbregex   = regexp.MustCompile(`^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`) | 
 | 	lpregex   = regexp.MustCompile(`^launchpad.net/([A-Za-z0-9-._]+)(/[A-Za-z0-9-._]+)?(/.+)?`) | 
 | 	gcregex   = regexp.MustCompile(`^(?P<root>code\.google\.com/[pr]/(?P<project>[a-z0-9\-]+)(\.(?P<subrepo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`) | 
 | 	genericre = regexp.MustCompile(`^(?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_.\-~]+)*$`) | 
 | ) | 
 |  | 
 | // DeduceRemoteRepo takes a potential import path and returns a RemoteRepo | 
 | // representing the remote location of the source of an import path. | 
 | // Remote repositories can be bare import paths, or urls including a checkout scheme. | 
 | // If deduction would cause traversal of an insecure host, a message will be | 
 | // printed and the travelsal path will be ignored. | 
 | func DeduceRemoteRepo(path string, insecure bool) (RemoteRepo, string, error) { | 
 | 	u, err := url.Parse(path) | 
 | 	if err != nil { | 
 | 		return nil, "", fmt.Errorf("%q is not a valid import path", path) | 
 | 	} | 
 |  | 
 | 	var schemes []string | 
 | 	if u.Scheme != "" { | 
 | 		schemes = append(schemes, u.Scheme) | 
 | 	} | 
 |  | 
 | 	path = u.Host + u.Path | 
 | 	if !regexp.MustCompile(`^([A-Za-z0-9-]+)(\.[A-Za-z0-9-]+)+(/[A-Za-z0-9-_.~]+)*$`).MatchString(path) { | 
 | 		return nil, "", fmt.Errorf("%q is not a valid import path", path) | 
 | 	} | 
 |  | 
 | 	switch { | 
 | 	case ghregex.MatchString(path): | 
 | 		v := ghregex.FindStringSubmatch(path) | 
 | 		url := &url.URL{ | 
 | 			Host: "github.com", | 
 | 			Path: v[2], | 
 | 		} | 
 | 		repo, err := Gitrepo(url, insecure, schemes...) | 
 | 		return repo, v[0][len(v[1]):], err | 
 | 	case bbregex.MatchString(path): | 
 | 		v := bbregex.FindStringSubmatch(path) | 
 | 		url := &url.URL{ | 
 | 			Host: "bitbucket.org", | 
 | 			Path: v[2], | 
 | 		} | 
 | 		repo, err := Gitrepo(url, insecure, schemes...) | 
 | 		if err == nil { | 
 | 			return repo, v[0][len(v[1]):], nil | 
 | 		} | 
 | 		repo, err = Hgrepo(url, insecure) | 
 | 		if err == nil { | 
 | 			return repo, v[0][len(v[1]):], nil | 
 | 		} | 
 | 		return nil, "", fmt.Errorf("unknown repository type") | 
 | 	case gcregex.MatchString(path): | 
 | 		v := gcregex.FindStringSubmatch(path) | 
 | 		url := &url.URL{ | 
 | 			Host: "code.google.com", | 
 | 			Path: "p/" + v[2], | 
 | 		} | 
 | 		repo, err := Hgrepo(url, insecure, schemes...) | 
 | 		if err == nil { | 
 | 			return repo, v[0][len(v[1]):], nil | 
 | 		} | 
 | 		repo, err = Gitrepo(url, insecure, schemes...) | 
 | 		if err == nil { | 
 | 			return repo, v[0][len(v[1]):], nil | 
 | 		} | 
 | 		return nil, "", fmt.Errorf("unknown repository type") | 
 | 	case lpregex.MatchString(path): | 
 | 		v := lpregex.FindStringSubmatch(path) | 
 | 		v = append(v, "", "") | 
 | 		if v[2] == "" { | 
 | 			// launchpad.net/project" | 
 | 			repo, err := Bzrrepo(fmt.Sprintf("https://launchpad.net/%v", v[1])) | 
 | 			return repo, "", err | 
 | 		} | 
 | 		// launchpad.net/project/series" | 
 | 		repo, err := Bzrrepo(fmt.Sprintf("https://launchpad.net/%s/%s", v[1], v[2])) | 
 | 		return repo, v[3], err | 
 | 	} | 
 |  | 
 | 	// try the general syntax | 
 | 	if genericre.MatchString(path) { | 
 | 		v := genericre.FindStringSubmatch(path) | 
 | 		switch v[5] { | 
 | 		case "git": | 
 | 			x := strings.SplitN(v[1], "/", 2) | 
 | 			url := &url.URL{ | 
 | 				Host: x[0], | 
 | 				Path: x[1], | 
 | 			} | 
 | 			repo, err := Gitrepo(url, insecure, schemes...) | 
 | 			return repo, v[6], err | 
 | 		case "hg": | 
 | 			x := strings.SplitN(v[1], "/", 2) | 
 | 			url := &url.URL{ | 
 | 				Host: x[0], | 
 | 				Path: x[1], | 
 | 			} | 
 | 			repo, err := Hgrepo(url, insecure, schemes...) | 
 | 			return repo, v[6], err | 
 | 		case "bzr": | 
 | 			repo, err := Bzrrepo("https://" + v[1]) | 
 | 			return repo, v[6], err | 
 | 		default: | 
 | 			return nil, "", fmt.Errorf("unknown repository type: %q", v[5]) | 
 |  | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// no idea, try to resolve as a vanity import | 
 | 	importpath, vcs, reporoot, err := ParseMetadata(path, insecure) | 
 | 	if err != nil { | 
 | 		return nil, "", err | 
 | 	} | 
 | 	u, err = url.Parse(reporoot) | 
 | 	if err != nil { | 
 | 		return nil, "", err | 
 | 	} | 
 | 	extra := path[len(importpath):] | 
 | 	switch vcs { | 
 | 	case "git": | 
 | 		u.Path = u.Path[1:] | 
 | 		repo, err := Gitrepo(u, insecure, u.Scheme) | 
 | 		return repo, extra, err | 
 | 	case "hg": | 
 | 		u.Path = u.Path[1:] | 
 | 		repo, err := Hgrepo(u, insecure, u.Scheme) | 
 | 		return repo, extra, err | 
 | 	case "bzr": | 
 | 		repo, err := Bzrrepo(reporoot) | 
 | 		return repo, extra, err | 
 | 	default: | 
 | 		return nil, "", fmt.Errorf("unknown repository type: %q", vcs) | 
 | 	} | 
 | } | 
 |  | 
 | // Gitrepo returns a RemoteRepo representing a remote git repository. | 
 | func Gitrepo(url *url.URL, insecure bool, schemes ...string) (RemoteRepo, error) { | 
 | 	if len(schemes) == 0 { | 
 | 		schemes = []string{"https", "git", "ssh", "http"} | 
 | 	} | 
 | 	u, err := probeGitUrl(url, insecure, schemes) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	return &gitrepo{ | 
 | 		url: u, | 
 | 	}, nil | 
 | } | 
 |  | 
 | func probeGitUrl(u *url.URL, insecure bool, schemes []string) (string, error) { | 
 | 	git := func(url *url.URL) error { | 
 | 		out, err := run("git", "ls-remote", url.String(), "HEAD") | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 |  | 
 | 		if !bytes.Contains(out, []byte("HEAD")) { | 
 | 			return fmt.Errorf("not a git repo") | 
 | 		} | 
 | 		return nil | 
 | 	} | 
 | 	return probe(git, u, insecure, schemes...) | 
 | } | 
 |  | 
 | func probeHgUrl(u *url.URL, insecure bool, schemes []string) (string, error) { | 
 | 	hg := func(url *url.URL) error { | 
 | 		_, err := run("hg", "identify", url.String()) | 
 | 		return err | 
 | 	} | 
 | 	return probe(hg, u, insecure, schemes...) | 
 | } | 
 |  | 
 | func probeBzrUrl(u string) error { | 
 | 	bzr := func(url *url.URL) error { | 
 | 		_, err := run("bzr", "info", url.String()) | 
 | 		return err | 
 | 	} | 
 | 	url, err := url.Parse(u) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	_, err = probe(bzr, url, false, "https") | 
 | 	return err | 
 | } | 
 |  | 
 | // probe calls the supplied vcs function to probe a variety of url constructions. | 
 | // If vcs returns non nil, it is assumed that the url is not a valid repo. | 
 | func probe(vcs func(*url.URL) error, url *url.URL, insecure bool, schemes ...string) (string, error) { | 
 | 	var unsuccessful []string | 
 | 	for _, scheme := range schemes { | 
 |  | 
 | 		// make copy of url and apply scheme | 
 | 		url := *url | 
 | 		url.Scheme = scheme | 
 |  | 
 | 		switch url.Scheme { | 
 | 		case "https", "ssh": | 
 | 			if err := vcs(&url); err == nil { | 
 | 				return url.String(), nil | 
 | 			} | 
 | 		case "http", "git": | 
 | 			if !insecure { | 
 | 				log.Printf("skipping insecure protocol: %s", url.String()) | 
 | 				continue | 
 | 			} | 
 | 			if err := vcs(&url); err == nil { | 
 | 				return url.String(), nil | 
 | 			} | 
 | 		default: | 
 | 			return "", fmt.Errorf("unsupported scheme: %v", url.Scheme) | 
 | 		} | 
 | 		unsuccessful = append(unsuccessful, url.String()) | 
 | 	} | 
 | 	return "", fmt.Errorf("vcs probe failed, tried: %s", strings.Join(unsuccessful, ",")) | 
 | } | 
 |  | 
 | // gitrepo is a git RemoteRepo. | 
 | type gitrepo struct { | 
 |  | 
 | 	// remote repository url, see man 1 git-clone | 
 | 	url string | 
 | } | 
 |  | 
 | func (g *gitrepo) URL() string { | 
 | 	return g.url | 
 | } | 
 |  | 
 | // Checkout fetchs the remote branch, tag, or revision. If the branch is blank, | 
 | // then the default remote branch will be used. If the branch is "HEAD" and | 
 | // revision is empty, an impossible update is assumed. | 
 | func (g *gitrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) { | 
 | 	if branch == "HEAD" && revision == "" { | 
 | 		return nil, fmt.Errorf("cannot update %q as it has been previously fetched with -tag or -revision. Please use gvt delete then fetch again.", g.url) | 
 | 	} | 
 | 	if !atMostOne(tag, revision) { | 
 | 		return nil, fmt.Errorf("only one of tag or revision may be supplied") | 
 | 	} | 
 | 	if !atMostOne(branch, tag) { | 
 | 		return nil, fmt.Errorf("only one of branch or tag may be supplied") | 
 | 	} | 
 | 	dir, err := mktmp() | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	wc := workingcopy{ | 
 | 		path: dir, | 
 | 	} | 
 |  | 
 | 	quiet := false | 
 | 	args := []string{ | 
 | 		"clone", | 
 | 		"-q", // silence progress report to stderr | 
 | 		g.url, | 
 | 		dir, | 
 | 	} | 
 | 	if branch != "" && branch != "HEAD" { | 
 | 		args = append(args, "--branch", branch, "--single-branch") | 
 | 	} | 
 | 	if tag != "" { | 
 | 		quiet = true // git REALLY wants to tell you how awesome 'detached HEAD' is... | 
 | 		args = append(args, "--branch", tag, "--single-branch") | 
 | 		args = append(args, "--depth", "1") | 
 | 	} | 
 | 	if revision == "" { | 
 | 		args = append(args, "--depth", "1") | 
 | 	} | 
 |  | 
 | 	if quiet { | 
 | 		err = runQuiet("git", args...) | 
 | 	} else { | 
 | 		_, err = run("git", args...) | 
 | 	} | 
 | 	if err != nil { | 
 | 		wc.Destroy() | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	if revision != "" { | 
 | 		if err := runOutPath(os.Stderr, dir, "git", "checkout", "-q", revision); err != nil { | 
 | 			wc.Destroy() | 
 | 			return nil, err | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return &GitClone{wc}, nil | 
 | } | 
 |  | 
 | type workingcopy struct { | 
 | 	path string | 
 | } | 
 |  | 
 | func (w workingcopy) Dir() string { return w.path } | 
 |  | 
 | func (w workingcopy) Destroy() error { | 
 | 	if err := fileutils.RemoveAll(w.path); err != nil { | 
 | 		return err | 
 | 	} | 
 | 	parent := filepath.Dir(w.path) | 
 | 	return cleanPath(parent) | 
 | } | 
 |  | 
 | // GitClone is a git WorkingCopy. | 
 | type GitClone struct { | 
 | 	workingcopy | 
 | } | 
 |  | 
 | func (g *GitClone) Revision() (string, error) { | 
 | 	rev, err := runPath(g.path, "git", "rev-parse", "HEAD") | 
 | 	return strings.TrimSpace(string(rev)), err | 
 | } | 
 |  | 
 | func (g *GitClone) Branch() (string, error) { | 
 | 	rev, err := runPath(g.path, "git", "rev-parse", "--abbrev-ref", "HEAD") | 
 | 	return strings.TrimSpace(string(rev)), err | 
 | } | 
 |  | 
 | // Hgrepo returns a RemoteRepo representing a remote git repository. | 
 | func Hgrepo(u *url.URL, insecure bool, schemes ...string) (RemoteRepo, error) { | 
 | 	if len(schemes) == 0 { | 
 | 		schemes = []string{"https", "http"} | 
 | 	} | 
 | 	url, err := probeHgUrl(u, insecure, schemes) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	return &hgrepo{ | 
 | 		url: url, | 
 | 	}, nil | 
 | } | 
 |  | 
 | // hgrepo is a Mercurial repo. | 
 | type hgrepo struct { | 
 |  | 
 | 	// remote repository url, see man 1 hg | 
 | 	url string | 
 | } | 
 |  | 
 | func (h *hgrepo) URL() string { return h.url } | 
 |  | 
 | func (h *hgrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) { | 
 | 	if !atMostOne(tag, revision) { | 
 | 		return nil, fmt.Errorf("only one of tag or revision may be supplied") | 
 | 	} | 
 | 	dir, err := mktmp() | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	args := []string{ | 
 | 		"clone", | 
 | 		h.url, | 
 | 		dir, | 
 | 		"--noninteractive", | 
 | 	} | 
 |  | 
 | 	if branch != "" { | 
 | 		args = append(args, "--branch", branch) | 
 | 	} | 
 | 	if err := runOut(os.Stderr, "hg", args...); err != nil { | 
 | 		fileutils.RemoveAll(dir) | 
 | 		return nil, err | 
 | 	} | 
 | 	if revision != "" { | 
 | 		if err := runOut(os.Stderr, "hg", "--cwd", dir, "update", "-r", revision); err != nil { | 
 | 			fileutils.RemoveAll(dir) | 
 | 			return nil, err | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return &HgClone{ | 
 | 		workingcopy{ | 
 | 			path: dir, | 
 | 		}, | 
 | 	}, nil | 
 | } | 
 |  | 
 | // HgClone is a mercurial WorkingCopy. | 
 | type HgClone struct { | 
 | 	workingcopy | 
 | } | 
 |  | 
 | func (h *HgClone) Revision() (string, error) { | 
 | 	rev, err := run("hg", "--cwd", h.path, "id", "-i") | 
 | 	return strings.TrimSpace(string(rev)), err | 
 | } | 
 |  | 
 | func (h *HgClone) Branch() (string, error) { | 
 | 	rev, err := run("hg", "--cwd", h.path, "branch") | 
 | 	return strings.TrimSpace(string(rev)), err | 
 | } | 
 |  | 
 | // Bzrrepo returns a RemoteRepo representing a remote bzr repository. | 
 | func Bzrrepo(url string) (RemoteRepo, error) { | 
 | 	if err := probeBzrUrl(url); err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	return &bzrrepo{ | 
 | 		url: url, | 
 | 	}, nil | 
 | } | 
 |  | 
 | // bzrrepo is a bzr RemoteRepo. | 
 | type bzrrepo struct { | 
 |  | 
 | 	// remote repository url | 
 | 	url string | 
 | } | 
 |  | 
 | func (b *bzrrepo) URL() string { | 
 | 	return b.url | 
 | } | 
 |  | 
 | func (b *bzrrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) { | 
 | 	if !atMostOne(tag, revision) { | 
 | 		return nil, fmt.Errorf("only one of tag or revision may be supplied") | 
 | 	} | 
 | 	dir, err := mktmp() | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	wc := filepath.Join(dir, "wc") | 
 | 	if err := runOut(os.Stderr, "bzr", "branch", b.url, wc); err != nil { | 
 | 		fileutils.RemoveAll(dir) | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	return &BzrClone{ | 
 | 		workingcopy{ | 
 | 			path: wc, | 
 | 		}, | 
 | 	}, nil | 
 | } | 
 |  | 
 | // BzrClone is a bazaar WorkingCopy. | 
 | type BzrClone struct { | 
 | 	workingcopy | 
 | } | 
 |  | 
 | func (b *BzrClone) Revision() (string, error) { | 
 | 	return "1", nil | 
 | } | 
 |  | 
 | func (b *BzrClone) Branch() (string, error) { | 
 | 	return "master", nil | 
 | } | 
 |  | 
 | func cleanPath(path string) error { | 
 | 	if files, _ := ioutil.ReadDir(path); len(files) > 0 || filepath.Base(path) == "vendor" { | 
 | 		return nil | 
 | 	} | 
 | 	parent := filepath.Dir(path) | 
 | 	if err := fileutils.RemoveAll(path); err != nil { | 
 | 		return err | 
 | 	} | 
 | 	return cleanPath(parent) | 
 | } | 
 |  | 
 | func mkdir(path string) error { | 
 | 	return os.MkdirAll(path, 0755) | 
 | } | 
 |  | 
 | func mktmp() (string, error) { | 
 | 	return ioutil.TempDir("", "gvt-") | 
 | } | 
 |  | 
 | func run(c string, args ...string) ([]byte, error) { | 
 | 	var buf bytes.Buffer | 
 | 	err := runOut(&buf, c, args...) | 
 | 	return buf.Bytes(), err | 
 | } | 
 |  | 
 | func runOut(w io.Writer, c string, args ...string) error { | 
 | 	cmd := exec.Command(c, args...) | 
 | 	cmd.Stdin = nil | 
 | 	cmd.Stdout = w | 
 | 	cmd.Stderr = os.Stderr | 
 | 	return cmd.Run() | 
 | } | 
 |  | 
 | func runQuiet(c string, args ...string) error { | 
 | 	cmd := exec.Command(c, args...) | 
 | 	cmd.Stdin = nil | 
 | 	cmd.Stdout = nil | 
 | 	cmd.Stderr = nil | 
 | 	return cmd.Run() | 
 | } | 
 |  | 
 | func runPath(path string, c string, args ...string) ([]byte, error) { | 
 | 	var buf bytes.Buffer | 
 | 	err := runOutPath(&buf, path, c, args...) | 
 | 	return buf.Bytes(), err | 
 | } | 
 |  | 
 | func runOutPath(w io.Writer, path string, c string, args ...string) error { | 
 | 	cmd := exec.Command(c, args...) | 
 | 	cmd.Dir = path | 
 | 	cmd.Stdin = nil | 
 | 	cmd.Stdout = w | 
 | 	cmd.Stderr = os.Stderr | 
 | 	return cmd.Run() | 
 | } | 
 |  | 
 | // atMostOne returns true if no more than one string supplied is not empty. | 
 | func atMostOne(args ...string) bool { | 
 | 	var c int | 
 | 	for _, arg := range args { | 
 | 		if arg != "" { | 
 | 			c++ | 
 | 		} | 
 | 	} | 
 | 	return c < 2 | 
 | } | 
 |  | 
 | // oneof returns the first non empty string | 
 | func oneOf(args ...string) string { | 
 | 	for _, arg := range args { | 
 | 		if arg != "" { | 
 | 			return arg | 
 | 		} | 
 | 	} | 
 | 	return "" | 
 | } |