34
var vcsPatterns = map[string]*regexp.Regexp{
35
"googlecode": regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|hg))(/[a-z0-9A-Z_.\-/]*)?$`),
36
"github": regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
37
"bitbucket": regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
38
"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_.\-/]+)?$`),
41
// isRemote returns true if the provided package path
42
// matches one of the supported remote repositories.
43
func isRemote(pkg string) bool {
44
for _, r := range vcsPatterns {
45
if r.MatchString(pkg) {
52
// download checks out or updates pkg from the remote server.
53
func download(pkg, srcDir string) os.Error {
54
if strings.Contains(pkg, "..") {
55
return os.ErrorString("invalid path (contains ..)")
57
if m := vcsPatterns["bitbucket"].FindStringSubmatch(pkg); m != nil {
58
if err := vcsCheckout(&hg, srcDir, m[1], "http://"+m[1], m[1]); err != nil {
63
if m := vcsPatterns["googlecode"].FindStringSubmatch(pkg); m != nil {
71
// regexp only allows hg, svn to get through
72
panic("missing case in download: " + pkg)
74
if err := vcsCheckout(v, srcDir, m[1], "https://"+m[1], m[1]); err != nil {
79
if m := vcsPatterns["github"].FindStringSubmatch(pkg); m != nil {
80
if strings.HasSuffix(m[1], ".git") {
81
return os.ErrorString("repository " + pkg + " should not have .git suffix")
83
if err := vcsCheckout(&git, srcDir, m[1], "http://"+m[1]+".git", m[1]); err != nil {
88
if m := vcsPatterns["launchpad"].FindStringSubmatch(pkg); m != nil {
89
// Either lp.net/<project>[/<series>[/<path>]]
90
// or lp.net/~<user or team>/<project>/<branch>[/<path>]
91
if err := vcsCheckout(&bzr, srcDir, m[1], "https://"+m[1], m[1]); err != nil {
96
return os.ErrorString("unknown repository: " + pkg)
99
36
// a vcs represents a version control system
100
37
// like Mercurial, Git, or Subversion.
163
134
logLimitFlag: "-l1",
164
135
logReleaseFlag: "-rrelease",
137
protocols: []string{"https", "http", "bzr"},
139
defaultHosts: []host{
140
{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", ""},
144
var vcsList = []*vcs{&git, &hg, &bzr, &svn}
146
type vcsMatch struct {
151
// findHostedRepo checks whether pkg is located at one of
152
// the supported code hosting sites and, if so, returns a match.
153
func findHostedRepo(pkg string) (*vcsMatch, os.Error) {
154
for _, v := range vcsList {
155
for _, host := range v.defaultHosts {
156
if hm := host.pattern.FindStringSubmatch(pkg); hm != nil {
157
if host.suffix != "" && strings.HasSuffix(hm[1], host.suffix) {
158
return nil, os.NewError("repository " + pkg + " should not have " + v.suffix + " suffix")
160
repo := host.protocol + "://" + hm[1] + host.suffix
161
return &vcsMatch{v, hm[1], repo}, nil
168
// findAnyRepo looks for a vcs suffix in pkg (.git, etc) and returns a match.
169
func findAnyRepo(pkg string) (*vcsMatch, os.Error) {
170
for _, v := range vcsList {
171
i := strings.Index(pkg+"/", v.suffix+"/")
175
if !strings.Contains(pkg[:i], "/") {
176
continue // don't match vcs suffix in the host name
178
if m := v.find(pkg[:i]); m != nil {
181
return nil, fmt.Errorf("couldn't find %s repository", v.name)
186
func (v *vcs) find(pkg string) *vcsMatch {
187
for _, proto := range v.protocols {
188
for _, suffix := range []string{"", v.suffix} {
189
repo := proto + "://" + pkg + suffix
190
out, err := exec.Command(v.cmd, v.check, repo).CombinedOutput()
192
printf("find %s: found %s\n", pkg, repo)
193
return &vcsMatch{v, pkg + v.suffix, repo}
195
printf("find %s: %s %s %s: %v\n%s\n", pkg, v.cmd, v.check, repo, err, out)
201
// isRemote returns true if the first part of the package name looks like a
202
// hostname - i.e. contains at least one '.' and the last part is at least 2
204
func isRemote(pkg string) bool {
205
parts := strings.SplitN(pkg, "/", 2)
209
parts = strings.Split(parts[0], ".")
210
if len(parts) < 2 || len(parts[len(parts)-1]) < 2 {
216
// download checks out or updates pkg from the remote server.
217
func download(pkg, srcDir string) (dashReport bool, err os.Error) {
218
if strings.Contains(pkg, "..") {
219
err = os.NewError("invalid path (contains ..)")
222
m, err := findHostedRepo(pkg)
227
dashReport = true // only report public code hosting sites
229
m, err = findAnyRepo(pkg)
235
err = os.NewError("cannot download: " + pkg)
238
installed, err := m.checkoutRepo(srcDir, m.prefix, m.repo)
167
248
// Try to detect if a "release" tag exists. If it does, update
183
// vcsCheckout checks out repo into dst using vcs.
264
// checkoutRepo checks out repo into dst using vcs.
184
265
// It tries to check out (or update, if the dst already
185
266
// exists and -u was specified on the command line)
186
267
// the repository at tag/branch "release". If there is no
187
268
// such tag or branch, it falls back to the repository tip.
188
func vcsCheckout(vcs *vcs, srcDir, pkgprefix, repo, dashpath string) os.Error {
269
func (vcs *vcs) checkoutRepo(srcDir, pkgprefix, repo string) (installed bool, err os.Error) {
189
270
dst := filepath.Join(srcDir, filepath.FromSlash(pkgprefix))
190
271
dir, err := os.Stat(filepath.Join(dst, vcs.metadir))
191
272
if err == nil && !dir.IsDirectory() {
192
return os.ErrorString("not a directory: " + dst)
273
err = os.NewError("not a directory: " + dst)
195
277
parent, _ := filepath.Split(dst)
196
if err := os.MkdirAll(parent, 0777); err != nil {
199
if err := run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil {
202
if err := vcs.updateRepo(dst); err != nil {
205
// success on first installation - report
206
maybeReportToDashboard(dashpath)
278
if err = os.MkdirAll(parent, 0777); err != nil {
281
if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil {
284
if err = vcs.updateRepo(dst); err != nil {
207
288
} else if *update {
208
289
// Retrieve new revisions from the remote branch, if the VCS
209
290
// supports this operation independently (e.g. svn doesn't)
210
291
if vcs.pull != "" {
211
292
if vcs.pullForceFlag != "" {
212
if err := run(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
293
if err = run(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
215
} else if err := run(dst, nil, vcs.cmd, vcs.pull); err != nil {
296
} else if err = run(dst, nil, vcs.cmd, vcs.pull); err != nil {
220
300
// Update to release or latest revision
221
if err := vcs.updateRepo(dst); err != nil {
301
if err = vcs.updateRepo(dst); err != nil {