func PointerSmudgeToFile(filename string, ptr *Pointer, download bool, manifest *transfer.Manifest, cb progress.CopyCallback) error { os.MkdirAll(filepath.Dir(filename), 0755) file, err := os.Create(filename) if err != nil { return fmt.Errorf("Could not create working directory file: %v", err) } defer file.Close() if err := PointerSmudge(file, ptr, filename, download, manifest, cb); err != nil { if errors.IsDownloadDeclinedError(err) { // write placeholder data instead file.Seek(0, os.SEEK_SET) ptr.Encode(file) return err } else { return fmt.Errorf("Could not write working directory file: %v", err) } } return nil }
func smudgeCommand(cmd *cobra.Command, args []string) { requireStdin("This command should be run by the Git 'smudge' filter") lfs.InstallHooks(false) // keeps the initial buffer from lfs.DecodePointer b := &bytes.Buffer{} r := io.TeeReader(os.Stdin, b) ptr, err := lfs.DecodePointer(r) if err != nil { mr := io.MultiReader(b, os.Stdin) _, err := io.Copy(os.Stdout, mr) if err != nil { Panic(err, "Error writing data to stdout:") } return } lfs.LinkOrCopyFromReference(ptr.Oid, ptr.Size) if smudgeInfo { localPath, err := lfs.LocalMediaPath(ptr.Oid) if err != nil { Exit(err.Error()) } stat, err := os.Stat(localPath) if err != nil { Print("%d --", ptr.Size) } else { Print("%d %s", stat.Size(), localPath) } return } filename := smudgeFilename(args, err) cb, file, err := lfs.CopyCallbackFile("smudge", filename, 1, 1) if err != nil { Error(err.Error()) } download := lfs.FilenamePassesIncludeExcludeFilter(filename, cfg.FetchIncludePaths(), cfg.FetchExcludePaths()) if smudgeSkip || cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) { download = false } err = ptr.Smudge(os.Stdout, filename, download, TransferManifest(), cb) if file != nil { file.Close() } if err != nil { ptr.Encode(os.Stdout) // Download declined error is ok to skip if we weren't requesting download if !(errors.IsDownloadDeclinedError(err) && !download) { LoggedError(err, "Error downloading object: %s (%s)", filename, ptr.Oid) if !cfg.SkipDownloadErrors() { os.Exit(2) } } } }
// Populate the working copy with the real content of objects where the file is // either missing, or contains a matching pointer placeholder, from a list of pointers. // If the file exists but has other content it is left alone // Callers of this function MUST NOT Panic or otherwise exit the process // without waiting for this function to shut down. If the process exits while // update-index is in the middle of processing a file the git index can be left // in a locked state. func checkoutWithChan(in <-chan *lfs.WrappedPointer) { // Get a converter from repo-relative to cwd-relative // Since writing data & calling git update-index must be relative to cwd repopathchan := make(chan string, 1) cwdpathchan, err := lfs.ConvertRepoFilesRelativeToCwd(repopathchan) if err != nil { Panic(err, "Could not convert file paths") } // Don't fire up the update-index command until we have at least one file to // give it. Otherwise git interprets the lack of arguments to mean param-less update-index // which can trigger entire working copy to be re-examined, which triggers clean filters // and which has unexpected side effects (e.g. downloading filtered-out files) var cmd *exec.Cmd var updateIdxStdin io.WriteCloser var updateIdxOut bytes.Buffer // From this point on, git update-index is running. Code in this loop MUST // NOT Panic() or otherwise cause the process to exit. If the process exits // while update-index is in the middle of updating, the index can remain in a // locked state. // As files come in, write them to the wd and update the index manifest := TransferManifest() for pointer := range in { // Check the content - either missing or still this pointer (not exist is ok) filepointer, err := lfs.DecodePointerFromFile(pointer.Name) if err != nil && !os.IsNotExist(err) { if errors.IsNotAPointerError(err) { // File has non-pointer content, leave it alone continue } LoggedError(err, "Problem accessing %v", pointer.Name) continue } if filepointer != nil && filepointer.Oid != pointer.Oid { // User has probably manually reset a file to another commit // while leaving it a pointer; don't mess with this continue } repopathchan <- pointer.Name cwdfilepath := <-cwdpathchan err = lfs.PointerSmudgeToFile(cwdfilepath, pointer.Pointer, false, manifest, nil) if err != nil { if errors.IsDownloadDeclinedError(err) { // acceptable error, data not local (fetch not run or include/exclude) LoggedError(err, "Skipped checkout for %v, content not local. Use fetch to download.", pointer.Name) } else { LoggedError(err, "Could not checkout file") continue } } if cmd == nil { // Fire up the update-index command cmd = exec.Command("git", "update-index", "-q", "--refresh", "--stdin") cmd.Stdout = &updateIdxOut cmd.Stderr = &updateIdxOut updateIdxStdin, err = cmd.StdinPipe() if err != nil { Panic(err, "Could not update the index") } if err := cmd.Start(); err != nil { Panic(err, "Could not update the index") } } updateIdxStdin.Write([]byte(cwdfilepath + "\n")) } close(repopathchan) if cmd != nil && updateIdxStdin != nil { updateIdxStdin.Close() if err := cmd.Wait(); err != nil { LoggedError(err, "Error updating the git index:\n%s", updateIdxOut.String()) } } }