blob: 08af2b161189bad57675a4096a1a6b01d6875bec [file] [log] [blame]
package main
import (
"flag"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/FiloSottile/gvt/fileutils"
"github.com/FiloSottile/gvt/gbvendor"
)
var (
branch string
revision string // revision (commit)
tag string
noRecurse bool
insecure bool // Allow the use of insecure protocols
tests bool
all bool
)
func addFetchFlags(fs *flag.FlagSet) {
fs.StringVar(&branch, "branch", "", "branch of the package")
fs.StringVar(&revision, "revision", "", "revision of the package")
fs.StringVar(&tag, "tag", "", "tag of the package")
fs.BoolVar(&noRecurse, "no-recurse", false, "do not fetch recursively")
fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols")
fs.BoolVar(&tests, "t", false, "fetch _test.go files and testdata")
fs.BoolVar(&all, "a", false, "fetch all files and subfolders")
}
var cmdFetch = &Command{
Name: "fetch",
UsageLine: "fetch [-branch branch] [-revision rev | -tag tag] [-precaire] [-no-recurse] [-t|-a] importpath",
Short: "fetch a remote dependency",
Long: `fetch vendors an upstream import path.
Recursive dependencies are fetched (at their master/tip/HEAD revision), unless they
or their parent package are already present.
If a subpackage of a dependency being fetched is already present, it will be deleted.
The import path may include a url scheme. This may be useful when fetching dependencies
from private repositories that cannot be probed.
Flags:
-t
fetch also _test.go files and testdata.
-a
fetch all files and subfolders, ignoring ONLY .git, .hg and .bzr.
-branch branch
fetch from the named branch. Will also be used by gvt update.
If not supplied the default upstream branch will be used.
-no-recurse
do not fetch recursively.
-tag tag
fetch the specified tag.
-revision rev
fetch the specific revision from the branch or repository.
If no revision supplied, the latest available will be fetched.
-precaire
allow the use of insecure protocols.
`,
Run: func(args []string) error {
switch len(args) {
case 0:
return fmt.Errorf("fetch: import path missing")
case 1:
path := args[0]
return fetch(path)
default:
return fmt.Errorf("more than one import path supplied")
}
},
AddFlags: addFetchFlags,
}
var (
fetchRoot string // where the current session started
rootRepoURL string // the url of the repo from which the root comes from
fetchedToday []string // packages fetched during this session
)
func fetch(path string) error {
m, err := vendor.ReadManifest(manifestFile)
if err != nil {
return fmt.Errorf("could not load manifest: %v", err)
}
fetchRoot = stripscheme(path)
return fetchRecursive(m, path, 0)
}
func fetchRecursive(m *vendor.Manifest, fullPath string, level int) error {
path := stripscheme(fullPath)
// Don't even bother the user about skipping packages we just fetched
for _, p := range fetchedToday {
if contains(p, path) {
return nil
}
}
// First, check if this or a parent is already vendored
if m.HasImportpath(path) {
if level == 0 {
return fmt.Errorf("%s or a parent of it is already vendored", path)
} else {
// TODO: print a different message for packages fetched during this session
logIndent(level, "Skipping (existing):", path)
return nil
}
}
// Next, check if we are trying to vendor from the same repository we are in
if importPath != "" && contains(importPath, path) {
if level == 0 {
return fmt.Errorf("refusing to vendor a subpackage of \".\"")
} else {
logIndent(level, "Skipping (subpackage of \".\"):", path)
return nil
}
}
if level == 0 {
log.Println("Fetching:", path)
} else {
logIndent(level, "Fetching recursive dependency:", path)
}
// Finally, check if we already vendored a subpackage and remove it
for _, subp := range m.GetSubpackages(path) {
if !contains(subp.Importpath, fetchRoot) { // ignore parents of the root
ignore := false
for _, d := range fetchedToday {
if contains(d, subp.Importpath) {
ignore = true // No need to warn the user if we just downloaded it
}
}
if !ignore {
logIndent(level, "Deleting existing subpackage to prevent overlap:", subp.Importpath)
}
}
if err := m.RemoveDependency(subp); err != nil {
return fmt.Errorf("failed to remove subpackage: %v", err)
}
}
if err := fileutils.RemoveAll(filepath.Join(vendorDir, path)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove existing folder: %v", err)
}
// Find and download the repository
repo, extra, err := GlobalDownloader.DeduceRemoteRepo(fullPath, insecure)
if err != nil {
return err
}
if level == 0 {
rootRepoURL = repo.URL()
}
var wc vendor.WorkingCopy
if repo.URL() == rootRepoURL {
wc, err = GlobalDownloader.Get(repo, branch, tag, revision)
} else {
wc, err = GlobalDownloader.Get(repo, "", "", "")
}
if err != nil {
return err
}
// Add the dependency to the manifest
rev, err := wc.Revision()
if err != nil {
return err
}
b, err := wc.Branch()
if err != nil {
return err
}
dep := vendor.Dependency{
Importpath: path,
Repository: repo.URL(),
VCS: repo.Type(),
Revision: rev,
Branch: b,
Path: extra,
NoTests: !tests,
AllFiles: all,
}
if err := m.AddDependency(dep); err != nil {
return err
}
// Copy the code to the vendor folder
dst := filepath.Join(vendorDir, dep.Importpath)
src := filepath.Join(wc.Dir(), dep.Path)
if err := fileutils.Copypath(dst, src, !dep.NoTests, dep.AllFiles); err != nil {
return err
}
if err := fileutils.CopyLicense(dst, wc.Dir()); err != nil {
return err
}
if err := vendor.WriteManifest(manifestFile, m); err != nil {
return err
}
// Recurse
fetchedToday = append(fetchedToday, path)
if !noRecurse {
// Look for dependencies in src, not going past wc.Dir() when looking for /vendor/,
// knowing that wc.Dir() corresponds to rootRepoPath
if !strings.HasSuffix(dep.Importpath, dep.Path) {
return fmt.Errorf("unable to derive the root repo import path")
}
rootRepoPath := strings.TrimRight(strings.TrimSuffix(dep.Importpath, dep.Path), "/")
deps, err := vendor.ParseImports(src, wc.Dir(), rootRepoPath, tests, all)
if err != nil {
return fmt.Errorf("failed to parse imports: %s", err)
}
for d := range deps {
if strings.Index(d, ".") == -1 { // TODO: replace this silly heuristic
continue
}
if err := fetchRecursive(m, d, level+1); err != nil {
if strings.HasPrefix(err.Error(), "error fetching") { // I know, ok?
return err
} else {
return fmt.Errorf("error fetching %s: %s", d, err)
}
}
}
}
return nil
}
func logIndent(level int, v ...interface{}) {
prefix := strings.Repeat("ยท", level)
v = append([]interface{}{prefix}, v...)
log.Println(v...)
}
// stripscheme removes any scheme components from url like paths.
func stripscheme(path string) string {
u, err := url.Parse(path)
if err != nil {
panic(err)
}
return u.Host + u.Path
}
// Package a contains package b?
func contains(a, b string) bool {
return a == b || strings.HasPrefix(b, a+"/")
}