github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/core/fetch.go (about) 1 package core 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/atlassian/git-lob/providers" 12 "github.com/atlassian/git-lob/util" 13 ) 14 15 // Implementation of fetch 16 func Fetch(provider providers.SyncProvider, remoteName string, refspecs []*GitRefSpec, dryRun, force bool, 17 callback util.ProgressCallback) error { 18 // We need to build a list of commits ranges at which we want to ensure binaries are present locally 19 // We can't build the list of binaries solely from the log, because not all binaries needed may have been 20 // modified in the range. Therefore we need 'git ls-tree' at the base ancestor in each range, followed by 21 // a 'git log -G' query for subsequent LOB changes. This is faster than doing ls-tree for every individual commit 22 // and eliminating duplicates. 23 24 util.LogDebugf("Fetching from %v via %v\n", remoteName, provider.TypeID()) 25 26 var fileLobsNeeded []*FileLOB 27 var fetchranges []*GitRefSpec 28 if len(refspecs) == 0 { 29 // No refs specified, use 'Recent' fetch algorithm 30 if util.GlobalOptions.Verbose { 31 callback(&util.ProgressCallbackData{util.ProgressCalculate, "Calculating recent commits...", 32 int64(0), int64(1), 0, 0}) 33 } 34 // Get HEAD LOBs first 35 headfilelobs, earliestCommit, err := GetGitAllFileLOBsToCheckoutAtCommitAndRecent("HEAD", util.GlobalOptions.FetchCommitsPeriodHEAD, 36 util.GlobalOptions.FetchIncludePaths, util.GlobalOptions.FetchExcludePaths) 37 if err != nil { 38 return errors.New(fmt.Sprintf("Error determining recent HEAD commits: %v", err.Error())) 39 } 40 if util.GlobalOptions.Verbose { 41 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf(" * HEAD: %d binary references", len(headfilelobs)), 42 0, 0, 0, 0}) 43 } 44 fileLobsNeeded = headfilelobs 45 headSHA, err := GitRefToFullSHA("HEAD") 46 if err != nil { 47 return errors.New(fmt.Sprintf("Error determining HEAD sha: %v", err.Error())) 48 } 49 fetchranges = append(fetchranges, &GitRefSpec{fmt.Sprintf("^%v", earliestCommit), "..", headSHA}) 50 if util.GlobalOptions.FetchRefsPeriodDays > 0 { 51 // Find recent other refs (only include remote branches for this remote) 52 recentrefs, err := GetGitRecentRefs(util.GlobalOptions.FetchRefsPeriodDays, true, remoteName) 53 if err != nil { 54 return errors.New(fmt.Sprintf("Error determining recent refs: %v", err.Error())) 55 } 56 // Now each other ref, they should be in reverse date order from GetGitRecentRefs so we're doing 57 // things by priority, HEAD first then most recent 58 refSHAsDone := util.NewStringSet() 59 refSHAsDone.Add(headSHA) 60 for i, ref := range recentrefs { 61 // Don't duplicate work when >1 ref has the same SHA 62 // Most common with HEAD if not detached but also tags 63 if refSHAsDone.Contains(ref.CommitSHA) { 64 continue 65 } 66 refSHAsDone.Add(ref.CommitSHA) 67 68 recentreflobs, earliestCommit, err := GetGitAllFileLOBsToCheckoutAtCommitAndRecent(ref.Name, util.GlobalOptions.FetchCommitsPeriodOther, 69 util.GlobalOptions.FetchIncludePaths, util.GlobalOptions.FetchExcludePaths) 70 if err != nil { 71 return errors.New(fmt.Sprintf("Error determining recent commits on %v: %v", ref, err.Error())) 72 } 73 if util.GlobalOptions.Verbose { 74 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf(" * %v: %d binary references", ref, len(recentreflobs)), 75 int64(i), int64(len(refspecs)), 0, 0}) 76 } 77 fileLobsNeeded = append(fileLobsNeeded, recentreflobs...) 78 79 fetchranges = append(fetchranges, &GitRefSpec{fmt.Sprintf("^%v", earliestCommit), "..", ref.CommitSHA}) 80 } 81 82 } 83 } else { 84 // Get LOBs directly from specified refs/ranges 85 for i, refspec := range refspecs { 86 if util.GlobalOptions.Verbose { 87 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf("Calculating data to fetch for %v", refspec), 88 int64(i), int64(len(refspecs)), 0, 0}) 89 } 90 reffileshas, err := GetGitAllFilesAndLOBsToCheckoutInRefSpec(refspec, util.GlobalOptions.FetchIncludePaths, util.GlobalOptions.FetchExcludePaths) 91 if err != nil { 92 return errors.New(fmt.Sprintf("Error determining LOBs to fetch for %v: %v", refspec, err.Error())) 93 } 94 if util.GlobalOptions.Verbose { 95 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf(" * %v: %d binary references", refspec, len(refspecs)), 96 int64(i), int64(len(refspecs)), 0, 0}) 97 } 98 fileLobsNeeded = append(fileLobsNeeded, reffileshas...) 99 100 if refspec.IsRange() { 101 fetchranges = append(fetchranges, refspec) 102 } else { 103 fetchranges = append(fetchranges, &GitRefSpec{fmt.Sprintf("^%v", refspec.Ref1), "..", refspec.Ref1}) 104 } 105 } 106 } 107 108 var commitsToMarkPushedAfterFetching []string 109 // Before we actually fetch anything, check if we can bulk mark things as pushed 110 // Common case - first fetch after clone, user hasn't done any local work 111 if !InitSuccessfullyPushedCacheIfAppropriate() { 112 // Otherwise, let's see whether we can move the pushed state forward as we fetch 113 // Remember, we can have 'gaps' in the history for LOBs if enough time passed since last fetch 114 // that the earliest fetch doesn't cover the whole period since the last fetch 115 // So the cases where we can move the push markers forward are: 116 // 1. There's nothing to push before the first commit we're fetching, OR 117 // 2. The intervening 'unpushed' commits already exist on the remote 118 for _, fetchrange := range fetchranges { 119 anyCommitsUnpushed := false 120 allUnpushedCommitsAreOnRemote := true 121 unpushedCallback := func(commit *CommitLOBRef) (quit bool, err error) { 122 anyCommitsUnpushed = true 123 for _, sha := range commit.LobSHAs { 124 // check remote 125 remoteerr := CheckRemoteLOBFilesForSHA(sha, provider, remoteName) 126 if remoteerr != nil { 127 // LOB doesn't exist on remote so this is genuinely unpushed 128 allUnpushedCommitsAreOnRemote = false 129 return true, remoteerr 130 } 131 } 132 return false, nil 133 } 134 // These are all ranges, Ref1 being exclusive so that's where we measure from 135 WalkGitCommitLOBsToPush(remoteName, fetchrange.Ref1, false, unpushedCallback) 136 if !anyCommitsUnpushed || allUnpushedCommitsAreOnRemote { 137 pushedsha := fetchrange.Ref2 138 if !GitRefIsFullSHA(pushedsha) { 139 // Was probably a manual ref, convert to SHA 140 pushedsha, _ = GitRefToFullSHA(pushedsha) 141 } 142 commitsToMarkPushedAfterFetching = append(commitsToMarkPushedAfterFetching, pushedsha) 143 util.LogDebugf("Will mark %v as pushed after fetch since there are no unpushed LOBs in ancestors that aren't on %v\n", fetchrange.Ref2, remoteName) 144 } else { 145 util.LogDebugf("%v will not be marked as pushed after fetch since there are unpushed LOBs in ancestors that aren't on %v\n", fetchrange.Ref2, remoteName) 146 } 147 } 148 149 } 150 151 fetchAnyNotFound := false 152 153 if len(fileLobsNeeded) == 0 { 154 callback(&util.ProgressCallbackData{util.ProgressCalculate, "No binaries to download.", 155 int64(len(refspecs)), int64(len(refspecs)), 0, 0}) 156 } else { 157 158 // Duplicates are not eliminated by methods we call, for efficiency 159 // We need to remove them though because otherwise we can report much higher download requirements 160 // than necessary when multiple refs include the same SHA 161 // We use this opportunity to build a straight list of unduplicated LOB shas which is also map to a filename 162 // which we may use in smart servers to determine other versions to generate deltas on 163 164 lobsToDownload := ConvertFileLOBSliceToMap(fileLobsNeeded) 165 if !force { 166 // Eliminate any that are OK locally 167 // It's safe to delete as you iterate in Go! refreshing :) 168 for sha, _ := range lobsToDownload { 169 if !IsLOBMissing(sha, false) { 170 delete(lobsToDownload, sha) 171 } 172 } 173 } 174 175 if len(lobsToDownload) == 0 { 176 callback(&util.ProgressCallbackData{util.ProgressCalculate, "No binaries to download.", 177 int64(len(refspecs)), int64(len(refspecs)), 0, 0}) 178 return nil 179 } else { 180 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf("%d binaries to download.", len(lobsToDownload)), 181 int64(len(refspecs)), int64(len(refspecs)), 0, 0}) 182 } 183 if !dryRun { 184 fetchCallback := func(data *util.ProgressCallbackData) (abort bool) { 185 if data.Type == util.ProgressNotFound { 186 fetchAnyNotFound = true 187 } 188 // passthrough to external callback 189 return callback(data) 190 } 191 192 err := fetchLOBs(lobsToDownload, provider, remoteName, force, fetchCallback) 193 if err != nil { 194 return err 195 } 196 197 } 198 } 199 200 util.LogDebugf("Successfully fetched from %v via %v\n", remoteName, provider.TypeID()) 201 202 // Now mark as pushed if appropriate 203 // If any files were not found on the remote, don't do this (we may get them locally later & need to push them) 204 if !fetchAnyNotFound && len(commitsToMarkPushedAfterFetching) > 0 { 205 for _, c := range commitsToMarkPushedAfterFetching { 206 err := MarkBinariesAsPushed(remoteName, c, "") 207 if err != nil { 208 util.LogErrorf("Error marking %v as pushed after fetch for %v: %v", c, remoteName, err.Error()) 209 } 210 } 211 // resolve any redundant state that creates 212 CleanupPushState(remoteName) 213 } 214 215 return nil 216 217 } 218 219 // Internal method for fetching 220 func fetchLOBs(lobshas map[string]string, provider providers.SyncProvider, remoteName string, force bool, callback util.ProgressCallback) error { 221 // Download metafiles first 222 // This will allow us to estimate the time required 223 callback(&util.ProgressCallbackData{util.ProgressCalculate, "Downloading metadata", 224 0, 0, 0, 0}) 225 err := fetchMetadata(lobshas, provider, remoteName, force, callback) 226 if err != nil { 227 return err 228 } 229 230 // So now we have all the metadata available locally, we can know what files to download 231 232 var filesTotalBytes int64 233 var files []string 234 var deltas []*LOBDelta 235 var deltaTotalBytes int64 236 var deltaSavings int64 237 smartProvider := providers.UpgradeToSmartSyncProvider(provider) 238 239 callback(&util.ProgressCallbackData{util.ProgressCalculate, "Calculating content files to download", 240 0, 0, 0, 0}) 241 for sha, filename := range lobshas { 242 info, err := GetLOBInfo(sha) 243 if err != nil { 244 // If we could not get the lob data, it means that we could not download the meta file 245 // it's OK for this to happen since the remote may not have all the data we need (e.g. 246 // local branch with changes that actually came from somewhere else, or remote hasn't been 247 // fully updated yet) 248 // We notified earlier 249 continue 250 } 251 // If this is a smart provider, try to download deltas where appropriate 252 if info.Size > util.GlobalOptions.FetchDeltasAboveSize && smartProvider != nil { 253 // This doesn't download, just prepares and gets size 254 delta := prepareFetchDelta(sha, filename, smartProvider, remoteName) 255 if delta != nil { 256 deltas = append(deltas, delta) 257 deltaTotalBytes += delta.DeltaSize 258 deltaSavings += info.Size - (delta.DeltaSize + ApproximateMetadataSize) 259 // We'll do a delta for this so don't continue to determine files 260 continue 261 } 262 } 263 // fallback to basic file download 264 filesTotalBytes += info.Size 265 for i := 0; i < info.NumChunks; i++ { 266 // get relative filename for download purposes 267 files = append(files, GetLOBChunkRelativePath(sha, i)) 268 } 269 } 270 totalBytes := filesTotalBytes + deltaTotalBytes 271 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf("Metadata done, downloading content (%v)", util.FormatSize(totalBytes)), 272 0, 0, 0, 0}) 273 if deltaSavings > 0 { 274 callback(&util.ProgressCallbackData{util.ProgressCalculate, fmt.Sprintf("Saving %v by fetching deltas", util.FormatSize(deltaSavings)), 275 0, 0, 0, 0}) 276 } 277 278 // Download content now 279 if smartProvider != nil && len(deltas) > 0 { 280 // First try deltas, if any fail fall back on regular download 281 failedDeltas := fetchDeltas(deltas, deltaTotalBytes, smartProvider, remoteName, force, callback) 282 if len(failedDeltas) > 0 { 283 for _, delta := range failedDeltas { 284 // Slightly costly but this should be rare for prepare to work and download not to 285 info, err := GetLOBInfo(delta.TargetSHA) 286 if err != nil { 287 return fmt.Errorf("LOB info for %v went missing, this should be impossible: %v", delta.TargetSHA, err.Error()) 288 } 289 filesTotalBytes += info.Size 290 for i := 0; i < info.NumChunks; i++ { 291 // get relative filename for download purposes 292 files = append(files, GetLOBChunkRelativePath(info.SHA, i)) 293 } 294 } 295 } 296 } 297 return fetchContentFiles(files, filesTotalBytes, provider, remoteName, force, callback) 298 299 } 300 301 func prepareFetchDelta(lobsha, filename string, provider providers.SmartSyncProvider, remoteName string) *LOBDelta { 302 othershas, err := GetGitAllLOBHistoryForFile(filename, lobsha) 303 if err != nil { 304 util.LogErrorf("Unable to prepare delta for %v(%v): %v\n", lobsha, filename, err.Error()) 305 return nil 306 } 307 // This is all the possible base shas, but we can only use ones we have locally too 308 // Right now we're not trying to cope with ordered downloads where we might have newer ones part way through fetch (too fiddly) 309 var localbaseshas []string 310 for _, sha := range othershas { 311 if !IsLOBMissing(sha, false) { 312 localbaseshas = append(localbaseshas, sha) 313 } 314 } 315 if len(localbaseshas) == 0 { 316 // no base shas, cannot do this 317 return nil 318 } 319 // Now ask the server to pick a sha, generate a delta, cache it and tell us how big it is 320 sz, chosenbasesha, err := provider.PrepareDeltaForDownload(remoteName, lobsha, localbaseshas) 321 if err != nil { 322 util.LogErrorf("Unable to prepare delta %v(%v): %v\n", lobsha, filename, err.Error()) 323 return nil 324 } 325 // No common base to use 326 if chosenbasesha == "" { 327 return nil 328 } 329 return &LOBDelta{ 330 BaseSHA: chosenbasesha, 331 TargetSHA: lobsha, 332 DeltaSize: sz, 333 } 334 } 335 336 // Internal method for fetching 337 func fetchMetadata(lobshas map[string]string, provider providers.SyncProvider, remoteName string, force bool, callback util.ProgressCallback) error { 338 // Use average metafile bytes as estimate of download, usually < 100 bytes of JSON 339 metaTotalBytes := int64(len(lobshas) * ApproximateMetadataSize) 340 var metafilesDone int 341 metacallback := func(fileInProgress string, progressType util.ProgressCallbackType, bytesDone, totalBytes int64) (abort bool) { 342 // Don't bother to track partial completion, only 100 bytes each 343 if progressType == util.ProgressSkip || progressType == util.ProgressNotFound { 344 metafilesDone++ 345 callback(&util.ProgressCallbackData{progressType, fileInProgress, totalBytes, totalBytes, 346 int64(metafilesDone * ApproximateMetadataSize), metaTotalBytes}) 347 // Remote did not have this file 348 } else { 349 if bytesDone == totalBytes { 350 // finished 351 metafilesDone++ 352 callback(&util.ProgressCallbackData{util.ProgressTransferBytes, fileInProgress, totalBytes, totalBytes, 353 int64(metafilesDone * ApproximateMetadataSize), metaTotalBytes}) 354 } 355 } 356 return false 357 } 358 // Download all meta files 359 var metafilesToDownload []string 360 for sha, _ := range lobshas { 361 // Note get relative file name 362 metafilesToDownload = append(metafilesToDownload, GetLOBMetaRelativePath(sha)) 363 } 364 // Download to shared if using shared area (we link later) 365 destDir := getFetchDestination() 366 err := provider.Download(remoteName, metafilesToDownload, destDir, force, metacallback) 367 368 // If shared store, link any metadata we downloaded into local 369 if IsUsingSharedStorage() { 370 for _, sha := range lobshas { 371 // filenames are relative (for download) 372 localfile := GetLocalLOBMetaPath(sha) 373 sharedfile := getSharedLOBMetaPath(sha) 374 if (force || !util.FileExists(localfile)) && util.FileExists(sharedfile) { 375 linkerr := linkSharedLOBFilename(sharedfile) 376 if linkerr != nil { 377 // we want to continue so don't return this 378 util.LogErrorf("Failed to link shared file %v into local repo: %v\n", sharedfile, linkerr.Error()) 379 } 380 } 381 } 382 } 383 // Deal with errors afterwards so we linked partial successes 384 if err != nil { 385 return err 386 } 387 388 return nil 389 } 390 391 func getFetchDestination() string { 392 // Download to shared if using shared area (we link later) 393 if IsUsingSharedStorage() { 394 return GetSharedLOBRoot() 395 } else { 396 return GetLocalLOBRoot() 397 } 398 } 399 400 func fetchContentFiles(files []string, filesTotalBytes int64, provider providers.SyncProvider, 401 remoteName string, force bool, callback util.ProgressCallback) error { 402 var lastFilename string 403 var lastFileBytes int64 404 var bytesFromFilesDoneSoFar int64 405 contentcallback := func(fileInProgress string, progressType util.ProgressCallbackType, bytesDone, totalBytes int64) (abort bool) { 406 407 var ret bool 408 if lastFilename != fileInProgress && lastFilename != "" { 409 // we obviously never got a 100% call for previous file 410 bytesFromFilesDoneSoFar += lastFileBytes 411 ret = callback(&util.ProgressCallbackData{util.ProgressTransferBytes, lastFilename, lastFileBytes, lastFileBytes, 412 bytesFromFilesDoneSoFar, filesTotalBytes}) 413 lastFilename = "" 414 } 415 if progressType == util.ProgressSkip || progressType == util.ProgressNotFound { 416 bytesFromFilesDoneSoFar += totalBytes 417 ret = callback(&util.ProgressCallbackData{progressType, fileInProgress, totalBytes, totalBytes, 418 bytesFromFilesDoneSoFar, filesTotalBytes}) 419 } else { 420 421 if bytesDone == totalBytes { 422 // finished 423 bytesFromFilesDoneSoFar += totalBytes 424 ret = callback(&util.ProgressCallbackData{util.ProgressTransferBytes, fileInProgress, bytesDone, totalBytes, 425 bytesFromFilesDoneSoFar, filesTotalBytes}) 426 lastFilename = "" 427 } else { 428 // partly progressed file 429 lastFilename = fileInProgress 430 lastFileBytes = totalBytes 431 ret = callback(&util.ProgressCallbackData{util.ProgressTransferBytes, fileInProgress, bytesDone, totalBytes, 432 bytesFromFilesDoneSoFar + bytesDone, filesTotalBytes}) 433 434 } 435 436 } 437 438 return ret 439 } 440 destDir := getFetchDestination() 441 err := provider.Download(remoteName, files, destDir, force, contentcallback) 442 if err == nil && lastFilename != "" { 443 // we obviously never got a 100% progress call for final file 444 callback(&util.ProgressCallbackData{util.ProgressTransferBytes, lastFilename, lastFileBytes, lastFileBytes, 445 filesTotalBytes, filesTotalBytes}) 446 lastFilename = "" 447 } 448 // Also if shared store, link meta into local 449 // Link any we successfully downloaded 450 if IsUsingSharedStorage() { 451 localroot := GetLocalLOBRoot() 452 sharedroot := GetSharedLOBRoot() 453 for _, relfile := range files { 454 // filenames are relative (for download) 455 localfile := filepath.Join(localroot, relfile) 456 sharedfile := filepath.Join(sharedroot, relfile) 457 if (force || !util.FileExists(localfile)) && util.FileExists(sharedfile) { 458 linkerr := linkSharedLOBFilename(sharedfile) 459 if linkerr != nil { 460 // we want to continue so don't return this 461 util.LogErrorf("Failed to link shared file %v into local repo: %v\n", sharedfile, linkerr.Error()) 462 } 463 } 464 } 465 } 466 467 return err 468 } 469 470 // Fetch via deltas which have already been picked & prepared on the server. Any that fail for any reason are added 471 // to the faileddeltas return list and will be re-tried using the standard download 472 func fetchDeltas(deltas []*LOBDelta, deltaTotalBytes int64, provider providers.SmartSyncProvider, remoteName string, 473 force bool, callback util.ProgressCallback) (faileddeltas []*LOBDelta) { 474 475 var failed []*LOBDelta 476 var bytesDoneSoFar int64 477 for _, delta := range deltas { 478 479 err := fetchSingleDelta(delta, bytesDoneSoFar, deltaTotalBytes, provider, remoteName, force, callback) 480 bytesDoneSoFar += delta.DeltaSize 481 if err != nil { 482 failed = append(failed, delta) 483 msg := fmt.Sprintf("Error applying %v: %v. Falling back to non-delta download", getDeltaProgressDesc(delta), err.Error()) 484 callback(&util.ProgressCallbackData{util.ProgressError, msg, delta.DeltaSize, delta.DeltaSize, 485 bytesDoneSoFar, deltaTotalBytes}) 486 } 487 488 } 489 return failed 490 491 } 492 493 func getDeltaProgressDesc(delta *LOBDelta) string { 494 return fmt.Sprintf("Delta %v..%v", delta.BaseSHA[:7], delta.TargetSHA[:7]) 495 } 496 497 func fetchSingleDelta(delta *LOBDelta, bytesSoFar int64, deltaTotalBytes int64, provider providers.SmartSyncProvider, remoteName string, 498 force bool, callback util.ProgressCallback) error { 499 500 // Description for progress 501 desc := getDeltaProgressDesc(delta) 502 503 // Initial 0% call 504 callback(&util.ProgressCallbackData{util.ProgressTransferBytes, desc, 0, delta.DeltaSize, 505 bytesSoFar, deltaTotalBytes}) 506 507 // We could pipe download output directly into ApplyDelta via a goroutine 508 // But for simplicity of fail states, use a temp file 509 tempf, err := ioutil.TempFile("", "deltadownload") 510 if err != nil { 511 return err 512 } 513 tempfilename := tempf.Name() 514 defer os.Remove(tempfilename) // ensure always removed 515 defer tempf.Close() // only used in panic cases, we close manually 516 localcallback := func(txt string, progressType util.ProgressCallbackType, bytesDone, totalBytes int64) (abort bool) { 517 518 var ret bool 519 if bytesDone != totalBytes { 520 // only do part progress in here, do final outside to ensure it always happens regardless 521 ret = callback(&util.ProgressCallbackData{util.ProgressTransferBytes, desc, bytesDone, totalBytes, 522 bytesSoFar + bytesDone, deltaTotalBytes}) 523 524 } 525 526 return ret 527 } 528 529 err = provider.DownloadDelta(remoteName, delta.BaseSHA, delta.TargetSHA, tempf, localcallback) 530 tempf.Close() // Close so available to read back 531 if err != nil { 532 return err 533 } 534 // finished download OK, now apply 535 deltain, err := os.OpenFile(tempfilename, os.O_RDONLY, 0644) 536 if err != nil { 537 return err 538 } 539 defer deltain.Close() 540 // Apply to shared or local 541 err = ApplyLOBDeltaInBaseDir(getFetchDestination(), delta.BaseSHA, delta.TargetSHA, deltain) 542 if err != nil { 543 return err 544 } 545 546 // Also if downloading to shared store, link into local 547 if IsUsingSharedStorage() { 548 ok := recoverLocalLOBFilesFromSharedStore(delta.TargetSHA) 549 if !ok { 550 return fmt.Errorf("%v was applied to shared store but linking to local failed", desc) 551 } 552 } 553 554 // yay, call final 100% 555 callback(&util.ProgressCallbackData{util.ProgressTransferBytes, desc, delta.DeltaSize, delta.DeltaSize, 556 bytesSoFar + delta.DeltaSize, deltaTotalBytes}) 557 558 return nil 559 560 } 561 562 // Fetch the files required for a single LOB 563 func FetchSingle(lobsha string, provider providers.SyncProvider, remoteName string, force bool, callback util.ProgressCallback) error { 564 565 var lobToDownload map[string]string 566 if force || IsLOBMissing(lobsha, false) { 567 // We don't know the filename, this is forced 568 lobToDownload[lobsha] = "" 569 } 570 571 if len(lobToDownload) > 0 { 572 return fetchLOBs(lobToDownload, provider, remoteName, force, callback) 573 } else { 574 return nil 575 } 576 } 577 578 // Auto-fetch a single LOB from the default locations 579 // If the required files are not found this won't cause an error 580 func AutoFetch(lobsha string, reportProgress bool) error { 581 remoteName := GetGitDefaultRemoteForPull() 582 util.LogDebugf("Trying to auto-fetch %v from %v\n", lobsha, remoteName) 583 // check the remote config to make sure it's valid 584 provider, err := providers.GetProviderForRemote(remoteName) 585 if err != nil { 586 return err 587 } 588 if err = provider.ValidateConfig(remoteName); err != nil { 589 return err 590 } 591 592 var fetcherr error 593 if reportProgress { 594 // We need to run this in a goroutine to report progress deterministically 595 // 100 items in the queue should be good enough, this means that it won't block 596 callbackChan := make(chan *util.ProgressCallbackData, 100) 597 go func(lobsha string, provider providers.SyncProvider, remoteName string, progresschan chan<- *util.ProgressCallbackData) { 598 599 // Progress callback just passes the result back to the channel 600 progress := func(data *util.ProgressCallbackData) (abort bool) { 601 progresschan <- data 602 603 return false 604 } 605 606 err := FetchSingle(lobsha, provider, remoteName, false, progress) 607 608 close(progresschan) 609 610 if err != nil { 611 fetcherr = err 612 } 613 614 }(lobsha, provider, remoteName, callbackChan) 615 616 // Report progress on operation every 0.5s 617 util.ReportProgressToConsole(callbackChan, "Fetch", time.Millisecond*500) 618 // Because no final newline from report progress 619 util.LogConsole("") 620 } else { 621 // no progress, just do it 622 fetcherr = FetchSingle(lobsha, provider, remoteName, false, func(data *util.ProgressCallbackData) (abort bool) { return false }) 623 } 624 625 if fetcherr == nil { 626 util.LogDebugf("Successfully fetched %v from %v\n", lobsha, remoteName) 627 } else { 628 util.LogDebugf("Failed to auto fetch %v from %v: %v\n", lobsha, remoteName, fetcherr) 629 } 630 631 return fetcherr 632 }