Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(837)

Delta Between Two Patch Sets: src/cmd/go/vcs.go

Issue 5660051: code review 5660051: cmd/go: allow go get with arbitrary URLs (Closed)
Left Patch Set: diff -r 116b2ccf6c88 https://go.googlecode.com/hg/ Created 12 years, 1 month ago
Right Patch Set: diff -r f46217216512 https://go.googlecode.com/hg/ Created 12 years ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « src/cmd/go/http.go ('k') | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 // Copyright 2012 The Go Authors. All rights reserved. 1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 package main 5 package main
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "encoding/json" 9 "encoding/json"
10 "encoding/xml"
11 "errors" 10 "errors"
12 "fmt" 11 "fmt"
13 "io"
14 "log" 12 "log"
15 "os" 13 "os"
16 "os/exec" 14 "os/exec"
15 "path/filepath"
17 "regexp" 16 "regexp"
18 "strings" 17 "strings"
19 ) 18 )
20 19
21 // A vcsCmd describes how to use a version control system 20 // A vcsCmd describes how to use a version control system
22 // like Mercurial, Git, or Subversion. 21 // like Mercurial, Git, or Subversion.
23 type vcsCmd struct { 22 type vcsCmd struct {
24 name string 23 name string
25 cmd string // name of binary to invoke command 24 cmd string // name of binary to invoke command
26 25
(...skipping 239 matching lines...) Expand 10 before | Expand all | Expand 10 after
266 prefix string // prefix this description ap plies to 265 prefix string // prefix this description ap plies to
267 re string // pattern for import path 266 re string // pattern for import path
268 repo string // repository to use (expand with match of re) 267 repo string // repository to use (expand with match of re)
269 vcs string // version control system to use (expand with match of re) 268 vcs string // version control system to use (expand with match of re)
270 check func(match map[string]string) error // additional checks 269 check func(match map[string]string) error // additional checks
271 ping bool // ping for scheme to use to download repo 270 ping bool // ping for scheme to use to download repo
272 271
273 regexp *regexp.Regexp // cached compiled form of re 272 regexp *regexp.Regexp // cached compiled form of re
274 } 273 }
275 274
275 // vcsForDir inspects dir and its parents to determine the
276 // version control system and code repository to use.
277 // On return, root is the import path
278 // corresponding to the root of the repository
279 // (thus root is a prefix of importPath).
280 func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) {
281 // Clean and double-check that dir is in (a subdirectory of) srcRoot.
282 dir := filepath.Clean(p.Dir)
283 srcRoot := filepath.Clean(p.build.SrcRoot)
284 if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
285 return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
286 }
287
288 for len(dir) > len(srcRoot) {
289 for _, vcs := range vcsList {
290 if fi, err := os.Stat(filepath.Join(dir, "."+vcs.cmd)); err == nil && fi.IsDir() {
291 return vcs, dir[len(srcRoot)+1:], nil
292 }
293 }
294
295 // Move to parent.
296 ndir := filepath.Dir(dir)
297 if len(ndir) >= len(dir) {
298 // Shouldn't happen, but just in case, stop.
299 break
300 }
301 dir = ndir
302 }
303
304 return nil, "", fmt.Errorf("directory %q is not using a known version co ntrol system", dir)
305 }
306
276 // repoRoot represents a version control system, a repo, and a root of 307 // repoRoot represents a version control system, a repo, and a root of
277 // where to put it on disk. 308 // where to put it on disk.
278 type repoRoot struct { 309 type repoRoot struct {
279 vcs *vcsCmd 310 vcs *vcsCmd
280 311
281 // repo is the repository URL, including scheme 312 // repo is the repository URL, including scheme
282 repo string 313 repo string
283 314
284 » // root is the import path corresponding to the root of the repository 315 » // root is the import path corresponding to the root of the
285 » // (thus root is a prefix of importPath) 316 » // repository
niemeyer 2012/02/23 13:40:35 s/importPath/the import path/?
bradfitz 2012/02/23 22:26:58 Deleted. This was moved from an old comment. It no
286 root string 317 root string
287 } 318 }
288 319
289 // repoRootForImportPath analyzes importPath to determine the 320 // repoRootForImportPath analyzes importPath to determine the
290 // version control system, and code repository to use. 321 // version control system, and code repository to use.
291 func repoRootForImportPath(importPath string) (*repoRoot, error) { 322 func repoRootForImportPath(importPath string) (*repoRoot, error) {
292 rr, err := repoRootForImportPathStatic(importPath, "") 323 rr, err := repoRootForImportPathStatic(importPath, "")
293 if err == errUnknownSite { 324 if err == errUnknownSite {
294 » » rr, err = repoRootForImportCustom(importPath) 325 » » rr, err = repoRootForImportDynamic(importPath)
295 } 326 }
296 return rr, err 327 return rr, err
297 } 328 }
298 329
299 var errUnknownSite = errors.New("dynamic lookup required to find mapping") 330 var errUnknownSite = errors.New("dynamic lookup required to find mapping")
300 331
301 // repoRootForImportPathStatic attempts to map importPath to a 332 // repoRootForImportPathStatic attempts to map importPath to a
niemeyer 2012/02/23 13:40:35 On the nitpicky side, these names seem to be getti
bradfitz 2012/02/23 22:26:58 I fought with the names for a bit and I'm not a hu
niemeyer 2012/02/23 23:51:39 Sorry, it's just that it jumped in the eye. I gues
302 // repoRoot using the commonly-used VCS hosting sites in vcsPaths 333 // repoRoot using the commonly-used VCS hosting sites in vcsPaths
303 // (github.com/user/dir), or from a fully-qualified importPath already 334 // (github.com/user/dir), or from a fully-qualified importPath already
304 // containing its VCS type (foo.com/repo.git/dir) 335 // containing its VCS type (foo.com/repo.git/dir)
305 // 336 //
306 // If scheme is non-empty, that scheme is forced. 337 // If scheme is non-empty, that scheme is forced.
307 func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) { 338 func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
308 if strings.Contains(importPath, "://") { 339 if strings.Contains(importPath, "://") {
309 » » return nil, fmt.Errorf("scheme in import path %q", importPath) 340 » » return nil, fmt.Errorf("invalid import path %q", importPath)
310 } 341 }
311 for _, srv := range vcsPaths { 342 for _, srv := range vcsPaths {
312 if !strings.HasPrefix(importPath, srv.prefix) { 343 if !strings.HasPrefix(importPath, srv.prefix) {
313 continue 344 continue
314 } 345 }
315 m := srv.regexp.FindStringSubmatch(importPath) 346 m := srv.regexp.FindStringSubmatch(importPath)
316 if m == nil { 347 if m == nil {
317 if srv.prefix != "" { 348 if srv.prefix != "" {
318 return nil, fmt.Errorf("invalid %s import path % q", srv.prefix, importPath) 349 return nil, fmt.Errorf("invalid %s import path % q", srv.prefix, importPath)
319 } 350 }
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
360 rr := &repoRoot{ 391 rr := &repoRoot{
361 vcs: vcs, 392 vcs: vcs,
362 repo: match["repo"], 393 repo: match["repo"],
363 root: match["root"], 394 root: match["root"],
364 } 395 }
365 return rr, nil 396 return rr, nil
366 } 397 }
367 return nil, errUnknownSite 398 return nil, errUnknownSite
368 } 399 }
369 400
370 func repoRootForImportCustom(importPath string) (*repoRoot, error) { 401 // repoRootForImportDynamic finds a *repoRoot for a custom domain that's not
niemeyer 2012/02/23 13:40:35 A quick comment above this function regarding what
bradfitz 2012/02/23 22:26:58 Done.
402 // statically known by repoRootForImportPathStatic.
403 //
404 // This handles "vanity import paths" like "name.tld/pkg/foo".
405 func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
371 slash := strings.Index(importPath, "/") 406 slash := strings.Index(importPath, "/")
372 » if slash == -1 { 407 » if slash < 0 {
373 » » return nil, fmt.Errorf("no slash in import %q", importPath) 408 » » return nil, fmt.Errorf("missing / in import %q", importPath)
374 » } 409 » }
375 » body, err := httpsOrHTTP(importPath) 410 » urlStr, body, err := httpsOrHTTP(importPath)
376 if err != nil { 411 if err != nil {
377 » » return nil, fmt.Errorf("error fetching %q: %v", importPath, err) 412 » » return nil, fmt.Errorf("http/https fetch for import %q: %v", imp ortPath, err)
378 } 413 }
379 defer body.Close() 414 defer body.Close()
380 metaImport, err := matchGoImport(parseMetaGoImports(body), importPath) 415 metaImport, err := matchGoImport(parseMetaGoImports(body), importPath)
381 if err != nil { 416 if err != nil {
382 if err != errNoMatch { 417 if err != errNoMatch {
383 » » » return nil, fmt.Errorf("error parsing %q: %v", importPat h, err) 418 » » » return nil, fmt.Errorf("parse %s: %v", urlStr, err)
384 » » } 419 » » }
385 » » return nil, fmt.Errorf("no go-import meta tags fetching %q", imp ortPath) 420 » » return nil, fmt.Errorf("parse %s: no go-import meta tags", urlSt r)
386 » } 421 » }
387 422 » if buildV {
423 » » log.Printf("get %q: found meta tag %#v at %s", importPath, metaI mport, urlStr)
424 » }
388 // If the import was "uni.edu/bob/project", which said the 425 // If the import was "uni.edu/bob/project", which said the
389 // prefix was "uni.edu" and the RepoRoot was "evilroot.com", 426 // prefix was "uni.edu" and the RepoRoot was "evilroot.com",
390 // make sure we don't trust Bob and check out evilroot.com to 427 // make sure we don't trust Bob and check out evilroot.com to
391 // "uni.edu" yet (possibly overwriting/preempting another 428 // "uni.edu" yet (possibly overwriting/preempting another
392 // non-evil student). Instead, first verify the root and see 429 // non-evil student). Instead, first verify the root and see
393 // if it matches Bob's claim. 430 // if it matches Bob's claim.
394 if metaImport.Prefix != importPath { 431 if metaImport.Prefix != importPath {
395 » » log.Printf("discovery of %q finds prefix %q; verifying prefix... ", importPath, metaImport.Prefix) 432 » » if buildV {
396 » » body, err = httpsOrHTTP(metaImport.Prefix) 433 » » » log.Printf("get %q: verifying non-authoritative meta tag ", importPath)
434 » » }
435 » » urlStr0 := urlStr
436 » » urlStr, body, err = httpsOrHTTP(metaImport.Prefix)
397 if err != nil { 437 if err != nil {
398 » » » log.Printf("meta's prefix %q doesn't match desired impor t path %q, but fetching %q's meta to verify it failed", 438 » » » return nil, fmt.Errorf("fetch %s: %v", urlStr, err)
399 » » » » metaImport.Prefix, importPath, metaImport.Prefix )
400 » » » return nil, fmt.Errorf("error fetching %q: %v", metaImpo rt.Prefix, err)
401 } 439 }
402 imports := parseMetaGoImports(body) 440 imports := parseMetaGoImports(body)
403 if len(imports) == 0 { 441 if len(imports) == 0 {
404 » » » return nil, fmt.Errorf("no go-imports meta tags found pa rsing %q", metaImport.Prefix) 442 » » » return nil, fmt.Errorf("fetch %s: no go-import meta tag" , urlStr)
405 » » } 443 » » }
406 » » metaImport, err = matchGoImport(imports, importPath) 444 » » metaImport2, err := matchGoImport(imports, importPath)
407 » » if err != nil { 445 » » if err != nil || metaImport != metaImport2 {
408 » » » return nil, fmt.Errorf("meta's prefix %q doesn't match d esired import path %q, and parsing %q's HTML = %v", 446 » » » return nil, fmt.Errorf("%s and %s disagree about go-impo rt for %s", urlStr0, urlStr, metaImport.Prefix)
409 » » » » metaImport.Prefix, importPath, metaImport.Prefix , err) 447 » » }
410 » » } 448 » }
411 » } 449
412 450 » if !strings.Contains(metaImport.RepoRoot, "://") {
413 » repoScheme := "" // "http://", "https://", or "" 451 » » return nil, fmt.Errorf("%s: invalid repo root %q; no scheme", ur lStr, metaImport.RepoRoot)
414 » if i := strings.Index(metaImport.RepoRoot, "://"); i != -1 { 452 » }
415 » » repoScheme = metaImport.RepoRoot[:i] 453 » rr := &repoRoot{
416 » » metaImport.RepoRoot = metaImport.RepoRoot[i+3:] 454 » » vcs: vcsByCmd(metaImport.VCS),
417 » } 455 » » repo: metaImport.RepoRoot,
418 456 » » root: metaImport.Prefix,
419 » newImportPath := metaImport.RepoRoot 457 » }
420 » rr, err := repoRootForImportPathStatic(newImportPath, repoScheme) 458 » if rr.vcs == nil {
421 » if err != nil { 459 » » return nil, fmt.Errorf("%s: unknown vcs %q", urlStr, metaImport. VCS)
422 » » // Try again, with the ".vcs" suffix. 460 » }
niemeyer 2012/02/23 13:40:35 Is this necessary? If the static import path needs
bradfitz 2012/02/23 22:26:58 I thought so too, and was thinking along those lin
niemeyer 2012/02/23 23:51:39 We can easily not make an error to provide a URL i
423 » » newImportPath += "." + strings.ToLower(metaImport.VCS) 461 » return rr, nil
424 » » rr, err = repoRootForImportPathStatic(newImportPath, repoScheme)
425 » » if err != nil {
426 » » » return nil, err
427 » » }
428 » }
429 » rr.root = metaImport.Prefix
430 » return rr, err
431 } 462 }
432 463
433 // metaImport represents the parsed <meta name="go-import" 464 // metaImport represents the parsed <meta name="go-import"
434 // content="prefix vcs reporoot" /> tags from HTML files. 465 // content="prefix vcs reporoot" /> tags from HTML files.
435 type metaImport struct { 466 type metaImport struct {
436 Prefix, VCS, RepoRoot string 467 Prefix, VCS, RepoRoot string
437 } 468 }
438 469
439 // errNoMatch is returned from matchGoImport when there's no applicable match. 470 // errNoMatch is returned from matchGoImport when there's no applicable match.
440 var errNoMatch = errors.New("no import match") 471 var errNoMatch = errors.New("no import match")
(...skipping 11 matching lines...) Expand all
452 err = fmt.Errorf("multiple meta tags match import path % q", importPath) 483 err = fmt.Errorf("multiple meta tags match import path % q", importPath)
453 return 484 return
454 } 485 }
455 match = i 486 match = i
456 } 487 }
457 if match == -1 { 488 if match == -1 {
458 err = errNoMatch 489 err = errNoMatch
459 return 490 return
460 } 491 }
461 return imports[match], nil 492 return imports[match], nil
462 }
463
464 // parseMetaGoImports returns meta imports from the HTML in r.
465 // Parsing ends at the end of the <head> section or the beginning of the <body>.
466 func parseMetaGoImports(r io.Reader) (imports []metaImport) {
467 d := xml.NewDecoder(r)
468 d.Strict = false
469 for {
470 t, err := d.Token()
471 if err != nil {
472 return
473 }
474 if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name .Local, "body") {
475 return
476 }
477 if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.L ocal, "head") {
478 return
479 }
480 e, ok := t.(xml.StartElement)
481 if !ok || !strings.EqualFold(e.Name.Local, "meta") {
niemeyer 2012/02/23 13:40:35 Shouldn't this be checking for "go-import" in the
bradfitz 2012/02/23 22:26:58 Whoops, indeed. Fixed.
482 continue
483 }
484 if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
485 imports = append(imports, metaImport{
486 Prefix: f[0],
487 VCS: f[1],
488 RepoRoot: f[2],
489 })
490 }
491 }
492 return
493 }
494
495 // attrValue returns the attribute value for the case-insensitive key
496 // `name', or the empty string if nothing is found.
497 func attrValue(attrs []xml.Attr, name string) string {
498 for _, a := range attrs {
499 if strings.EqualFold(a.Name.Local, name) {
500 return a.Value
501 }
502 }
503 return ""
504 } 493 }
505 494
506 // expand rewrites s to replace {k} with match[k] for each key k in match. 495 // expand rewrites s to replace {k} with match[k] for each key k in match.
507 func expand(match map[string]string, s string) string { 496 func expand(match map[string]string, s string) string {
508 for k, v := range match { 497 for k, v := range match {
509 s = strings.Replace(s, "{"+k+"}", v, -1) 498 s = strings.Replace(s, "{"+k+"}", v, -1)
510 } 499 }
511 return s 500 return s
512 } 501 }
513 502
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
659 if match["project"] == "" || match["series"] == "" { 648 if match["project"] == "" || match["series"] == "" {
660 return nil 649 return nil
661 } 650 }
662 _, err := httpGET(expand(match, "https://code.launchpad.net/{project}{se ries}/.bzr/branch-format")) 651 _, err := httpGET(expand(match, "https://code.launchpad.net/{project}{se ries}/.bzr/branch-format"))
663 if err != nil { 652 if err != nil {
664 match["root"] = expand(match, "launchpad.net/{project}") 653 match["root"] = expand(match, "launchpad.net/{project}")
665 match["repo"] = expand(match, "https://{root}") 654 match["repo"] = expand(match, "https://{root}")
666 } 655 }
667 return nil 656 return nil
668 } 657 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b