github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/command_fetch.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "os" 6 "time" 7 8 "github.com/git-lfs/git-lfs/filepathfilter" 9 "github.com/git-lfs/git-lfs/git" 10 "github.com/git-lfs/git-lfs/lfs" 11 "github.com/git-lfs/git-lfs/tasklog" 12 "github.com/git-lfs/git-lfs/tq" 13 "github.com/rubyist/tracerx" 14 "github.com/spf13/cobra" 15 ) 16 17 var ( 18 fetchRecentArg bool 19 fetchAllArg bool 20 fetchPruneArg bool 21 ) 22 23 func getIncludeExcludeArgs(cmd *cobra.Command) (include, exclude *string) { 24 includeFlag := cmd.Flag("include") 25 excludeFlag := cmd.Flag("exclude") 26 if includeFlag.Changed { 27 include = &includeArg 28 } 29 if excludeFlag.Changed { 30 exclude = &excludeArg 31 } 32 33 return 34 } 35 36 func fetchCommand(cmd *cobra.Command, args []string) { 37 requireInRepo() 38 39 var refs []*git.Ref 40 41 if len(args) > 0 { 42 // Remote is first arg 43 if err := cfg.SetValidRemote(args[0]); err != nil { 44 Exit("Invalid remote name %q: %s", args[0], err) 45 } 46 } 47 48 if len(args) > 1 { 49 resolvedrefs, err := git.ResolveRefs(args[1:]) 50 if err != nil { 51 Panic(err, "Invalid ref argument: %v", args[1:]) 52 } 53 refs = resolvedrefs 54 } else if !fetchAllArg { 55 ref, err := git.CurrentRef() 56 if err != nil { 57 Panic(err, "Could not fetch") 58 } 59 refs = []*git.Ref{ref} 60 } 61 62 success := true 63 gitscanner := lfs.NewGitScanner(nil) 64 defer gitscanner.Close() 65 66 include, exclude := getIncludeExcludeArgs(cmd) 67 fetchPruneCfg := lfs.NewFetchPruneConfig(cfg.Git) 68 69 if fetchAllArg { 70 if fetchRecentArg || len(args) > 1 { 71 Exit("Cannot combine --all with ref arguments or --recent") 72 } 73 if include != nil || exclude != nil { 74 Exit("Cannot combine --all with --include or --exclude") 75 } 76 if len(cfg.FetchIncludePaths()) > 0 || len(cfg.FetchExcludePaths()) > 0 { 77 Print("Ignoring global include / exclude paths to fulfil --all") 78 } 79 success = fetchAll() 80 81 } else { // !all 82 filter := buildFilepathFilter(cfg, include, exclude) 83 84 // Fetch refs sequentially per arg order; duplicates in later refs will be ignored 85 for _, ref := range refs { 86 Print("fetch: Fetching reference %s", ref.Refspec()) 87 s := fetchRef(ref.Sha, filter) 88 success = success && s 89 } 90 91 if fetchRecentArg || fetchPruneCfg.FetchRecentAlways { 92 s := fetchRecent(fetchPruneCfg, refs, filter) 93 success = success && s 94 } 95 } 96 97 if fetchPruneArg { 98 verify := fetchPruneCfg.PruneVerifyRemoteAlways 99 // no dry-run or verbose options in fetch, assume false 100 prune(fetchPruneCfg, verify, false, false) 101 } 102 103 if !success { 104 c := getAPIClient() 105 e := c.Endpoints.Endpoint("download", cfg.Remote()) 106 Exit("error: failed to fetch some objects from '%s'", e.Url) 107 } 108 } 109 110 func pointersToFetchForRef(ref string, filter *filepathfilter.Filter) ([]*lfs.WrappedPointer, error) { 111 var pointers []*lfs.WrappedPointer 112 var multiErr error 113 tempgitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) { 114 if err != nil { 115 if multiErr != nil { 116 multiErr = fmt.Errorf("%v\n%v", multiErr, err) 117 } else { 118 multiErr = err 119 } 120 return 121 } 122 123 pointers = append(pointers, p) 124 }) 125 126 tempgitscanner.Filter = filter 127 128 if err := tempgitscanner.ScanTree(ref); err != nil { 129 return nil, err 130 } 131 132 tempgitscanner.Close() 133 return pointers, multiErr 134 } 135 136 // Fetch all binaries for a given ref (that we don't have already) 137 func fetchRef(ref string, filter *filepathfilter.Filter) bool { 138 pointers, err := pointersToFetchForRef(ref, filter) 139 if err != nil { 140 Panic(err, "Could not scan for Git LFS files") 141 } 142 return fetchAndReportToChan(pointers, filter, nil) 143 } 144 145 // Fetch all previous versions of objects from since to ref (not including final state at ref) 146 // So this will fetch all the '-' sides of the diff from since to ref 147 func fetchPreviousVersions(ref string, since time.Time, filter *filepathfilter.Filter) bool { 148 var pointers []*lfs.WrappedPointer 149 150 tempgitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) { 151 if err != nil { 152 Panic(err, "Could not scan for Git LFS previous versions") 153 return 154 } 155 156 pointers = append(pointers, p) 157 }) 158 159 tempgitscanner.Filter = filter 160 161 if err := tempgitscanner.ScanPreviousVersions(ref, since, nil); err != nil { 162 ExitWithError(err) 163 } 164 165 tempgitscanner.Close() 166 return fetchAndReportToChan(pointers, filter, nil) 167 } 168 169 // Fetch recent objects based on config 170 func fetchRecent(fetchconf lfs.FetchPruneConfig, alreadyFetchedRefs []*git.Ref, filter *filepathfilter.Filter) bool { 171 if fetchconf.FetchRecentRefsDays == 0 && fetchconf.FetchRecentCommitsDays == 0 { 172 return true 173 } 174 175 ok := true 176 // Make a list of what unique commits we've already fetched for to avoid duplicating work 177 uniqueRefShas := make(map[string]string, len(alreadyFetchedRefs)) 178 for _, ref := range alreadyFetchedRefs { 179 uniqueRefShas[ref.Sha] = ref.Name 180 } 181 // First find any other recent refs 182 if fetchconf.FetchRecentRefsDays > 0 { 183 Print("fetch: Fetching recent branches within %v days", fetchconf.FetchRecentRefsDays) 184 refsSince := time.Now().AddDate(0, 0, -fetchconf.FetchRecentRefsDays) 185 refs, err := git.RecentBranches(refsSince, fetchconf.FetchRecentRefsIncludeRemotes, cfg.Remote()) 186 if err != nil { 187 Panic(err, "Could not scan for recent refs") 188 } 189 for _, ref := range refs { 190 // Don't fetch for the same SHA twice 191 if prevRefName, ok := uniqueRefShas[ref.Sha]; ok { 192 if ref.Name != prevRefName { 193 tracerx.Printf("Skipping fetch for %v, already fetched via %v", ref.Name, prevRefName) 194 } 195 } else { 196 uniqueRefShas[ref.Sha] = ref.Name 197 Print("fetch: Fetching reference %s", ref.Name) 198 k := fetchRef(ref.Sha, filter) 199 ok = ok && k 200 } 201 } 202 } 203 // For every unique commit we've fetched, check recent commits too 204 if fetchconf.FetchRecentCommitsDays > 0 { 205 for commit, refName := range uniqueRefShas { 206 // We measure from the last commit at the ref 207 summ, err := git.GetCommitSummary(commit) 208 if err != nil { 209 Error("Couldn't scan commits at %v: %v", refName, err) 210 continue 211 } 212 Print("fetch: Fetching changes within %v days of %v", fetchconf.FetchRecentCommitsDays, refName) 213 commitsSince := summ.CommitDate.AddDate(0, 0, -fetchconf.FetchRecentCommitsDays) 214 k := fetchPreviousVersions(commit, commitsSince, filter) 215 ok = ok && k 216 } 217 218 } 219 return ok 220 } 221 222 func fetchAll() bool { 223 pointers := scanAll() 224 Print("fetch: Fetching all references...") 225 return fetchAndReportToChan(pointers, nil, nil) 226 } 227 228 func scanAll() []*lfs.WrappedPointer { 229 // This could be a long process so use the chan version & report progress 230 task := tasklog.NewSimpleTask() 231 defer task.Complete() 232 233 logger := tasklog.NewLogger(OutputWriter) 234 logger.Enqueue(task) 235 var numObjs int64 236 237 // use temp gitscanner to collect pointers 238 var pointers []*lfs.WrappedPointer 239 var multiErr error 240 tempgitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) { 241 if err != nil { 242 if multiErr != nil { 243 multiErr = fmt.Errorf("%v\n%v", multiErr, err) 244 } else { 245 multiErr = err 246 } 247 return 248 } 249 250 numObjs++ 251 task.Logf("fetch: %d object(s) found", numObjs) 252 pointers = append(pointers, p) 253 }) 254 255 if err := tempgitscanner.ScanAll(nil); err != nil { 256 Panic(err, "Could not scan for Git LFS files") 257 } 258 259 tempgitscanner.Close() 260 261 if multiErr != nil { 262 Panic(multiErr, "Could not scan for Git LFS files") 263 } 264 265 return pointers 266 } 267 268 // Fetch and report completion of each OID to a channel (optional, pass nil to skip) 269 // Returns true if all completed with no errors, false if errors were written to stderr/log 270 func fetchAndReportToChan(allpointers []*lfs.WrappedPointer, filter *filepathfilter.Filter, out chan<- *lfs.WrappedPointer) bool { 271 ready, pointers, meter := readyAndMissingPointers(allpointers, filter) 272 q := newDownloadQueue( 273 getTransferManifestOperationRemote("download", cfg.Remote()), 274 cfg.Remote(), tq.WithProgress(meter), 275 ) 276 277 if out != nil { 278 // If we already have it, or it won't be fetched 279 // report it to chan immediately to support pull/checkout 280 for _, p := range ready { 281 out <- p 282 } 283 284 dlwatch := q.Watch() 285 286 go func() { 287 // fetch only reports single OID, but OID *might* be referenced by multiple 288 // WrappedPointers if same content is at multiple paths, so map oid->slice 289 oidToPointers := make(map[string][]*lfs.WrappedPointer, len(pointers)) 290 for _, pointer := range pointers { 291 plist := oidToPointers[pointer.Oid] 292 oidToPointers[pointer.Oid] = append(plist, pointer) 293 } 294 295 for t := range dlwatch { 296 plist, ok := oidToPointers[t.Oid] 297 if !ok { 298 continue 299 } 300 for _, p := range plist { 301 out <- p 302 } 303 } 304 close(out) 305 }() 306 } 307 308 for _, p := range pointers { 309 tracerx.Printf("fetch %v [%v]", p.Name, p.Oid) 310 311 q.Add(downloadTransfer(p)) 312 } 313 314 processQueue := time.Now() 315 q.Wait() 316 tracerx.PerformanceSince("process queue", processQueue) 317 318 ok := true 319 for _, err := range q.Errors() { 320 ok = false 321 FullError(err) 322 } 323 return ok 324 } 325 326 func readyAndMissingPointers(allpointers []*lfs.WrappedPointer, filter *filepathfilter.Filter) ([]*lfs.WrappedPointer, []*lfs.WrappedPointer, *tq.Meter) { 327 logger := tasklog.NewLogger(os.Stdout) 328 meter := buildProgressMeter(false, tq.Download) 329 logger.Enqueue(meter) 330 331 seen := make(map[string]bool, len(allpointers)) 332 missing := make([]*lfs.WrappedPointer, 0, len(allpointers)) 333 ready := make([]*lfs.WrappedPointer, 0, len(allpointers)) 334 335 for _, p := range allpointers { 336 // no need to download the same object multiple times 337 if seen[p.Oid] { 338 continue 339 } 340 341 seen[p.Oid] = true 342 343 // no need to download objects that exist locally already 344 lfs.LinkOrCopyFromReference(cfg, p.Oid, p.Size) 345 if cfg.LFSObjectExists(p.Oid, p.Size) { 346 ready = append(ready, p) 347 continue 348 } 349 350 missing = append(missing, p) 351 meter.Add(p.Size) 352 } 353 354 return ready, missing, meter 355 } 356 357 func init() { 358 RegisterCommand("fetch", fetchCommand, func(cmd *cobra.Command) { 359 cmd.Flags().StringVarP(&includeArg, "include", "I", "", "Include a list of paths") 360 cmd.Flags().StringVarP(&excludeArg, "exclude", "X", "", "Exclude a list of paths") 361 cmd.Flags().BoolVarP(&fetchRecentArg, "recent", "r", false, "Fetch recent refs & commits") 362 cmd.Flags().BoolVarP(&fetchAllArg, "all", "a", false, "Fetch all LFS files ever referenced") 363 cmd.Flags().BoolVarP(&fetchPruneArg, "prune", "p", false, "After fetching, prune old data") 364 }) 365 }