blob: dc0b1e3b1d33d49997db76e781e4e52edc547fd2 [file] [log] [blame]
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"sync/atomic"
"github.com/FiloSottile/gvt/fileutils"
"github.com/FiloSottile/gvt/gbvendor"
)
var (
rbInsecure bool // Allow the use of insecure protocols
rbConnections uint // Count of concurrent download connections
)
func addRestoreFlags(fs *flag.FlagSet) {
fs.BoolVar(&rbInsecure, "precaire", false, "allow the use of insecure protocols")
fs.UintVar(&rbConnections, "connections", 8, "count of parallel download connections")
}
var cmdRestore = &Command{
Name: "restore",
UsageLine: "restore [-precaire] [-connections N]",
Short: "restore dependencies from manifest",
Long: `restore fetches the dependencies listed in the manifest.
It's meant for workflows that don't include checking in to VCS the vendored
source, for example if .gitignore includes lines like
vendor/**
!vendor/manifest
Note that such a setup requires "gvt restore" to build the source, relies on
the availability of the dependencies repositories and breaks "go get".
Flags:
-precaire
allow the use of insecure protocols.
-connections
count of parallel download connections.
`,
Run: func(args []string) error {
switch len(args) {
case 0:
return restore(manifestFile)
default:
return fmt.Errorf("restore takes no arguments")
}
},
AddFlags: addRestoreFlags,
}
func restore(manFile string) error {
m, err := vendor.ReadManifest(manFile)
if err != nil {
return fmt.Errorf("could not load manifest: %v", err)
}
var errors uint32
var wg sync.WaitGroup
depC := make(chan vendor.Dependency)
for i := 0; i < int(rbConnections); i++ {
wg.Add(1)
go func() {
defer wg.Done()
for d := range depC {
if err := downloadDependency(d, &errors, vendorDir, false); err != nil {
log.Printf("%s: %v", d.Importpath, err)
atomic.AddUint32(&errors, 1)
}
}
}()
}
for _, dep := range m.Dependencies {
depC <- dep
}
close(depC)
wg.Wait()
if errors > 0 {
return fmt.Errorf("failed to fetch %d dependencies", errors)
}
return nil
}
func downloadDependency(dep vendor.Dependency, errors *uint32, vendorDir string, recursive bool) error {
extraMsg := ""
if !dep.NoTests {
extraMsg = "(including tests)"
}
if dep.AllFiles {
extraMsg = "(without file exclusions)"
}
if recursive {
log.Printf("fetching recursive %s %s", dep.Importpath, extraMsg)
} else {
log.Printf("fetching %s %s", dep.Importpath, extraMsg)
}
repo, err := vendor.NewRemoteRepo(dep.Repository, dep.VCS, rbInsecure)
if err != nil {
return fmt.Errorf("dependency could not be processed: %s", err)
}
// We can't pass the branch here, and benefit from narrow clones, as the
// revision might not be in the branch tree anymore. Thanks rebase.
wc, err := GlobalDownloader.Get(repo, "", "", dep.Revision)
if err != nil {
return fmt.Errorf("dependency could not be fetched: %s", err)
}
dst := filepath.Join(vendorDir, dep.Importpath)
src := filepath.Join(wc.Dir(), dep.Path)
if _, err := os.Stat(dst); err == nil {
if err := fileutils.RemoveAll(dst); err != nil {
return fmt.Errorf("dependency could not be deleted: %v", err)
}
}
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
}
// Check for for manifests in dependencies
man := filepath.Join(dst, "vendor", "manifest")
venDir := filepath.Join(dst, "vendor")
if _, err := os.Stat(man); err == nil {
m, err := vendor.ReadManifest(man)
if err != nil {
return fmt.Errorf("could not load manifest: %v", err)
}
for _, d := range m.Dependencies {
if err := downloadDependency(d, errors, venDir, true); err != nil {
log.Printf("%s: %v", d.Importpath, err)
atomic.AddUint32(errors, 1)
}
}
}
return nil
}