github.com/x-oss-byte/git-lfs@v2.5.2+incompatible/commands/command_smudge.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  
     8  	"github.com/git-lfs/git-lfs/errors"
     9  	"github.com/git-lfs/git-lfs/filepathfilter"
    10  	"github.com/git-lfs/git-lfs/git"
    11  	"github.com/git-lfs/git-lfs/lfs"
    12  	"github.com/git-lfs/git-lfs/tools"
    13  	"github.com/git-lfs/git-lfs/tools/humanize"
    14  	"github.com/git-lfs/git-lfs/tq"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  var (
    19  	// smudgeSkip is a command-line flag belonging to the "git-lfs smudge"
    20  	// command specifying whether to skip the smudge process.
    21  	smudgeSkip = false
    22  )
    23  
    24  // delayedSmudge performs a 'delayed' smudge, adding the LFS pointer to the
    25  // `*tq.TransferQueue` "q" if the file is not present locally, passes the given
    26  // filepathfilter, and is not skipped. If the pointer is malformed, or already
    27  // exists, it streams the contents to be written into the working copy to "to".
    28  //
    29  // delayedSmudge returns the number of bytes written, whether the checkout was
    30  // delayed, the *lfs.Pointer that was smudged, and an error, if one occurred.
    31  func delayedSmudge(gf *lfs.GitFilter, s *git.FilterProcessScanner, to io.Writer, from io.Reader, q *tq.TransferQueue, filename string, skip bool, filter *filepathfilter.Filter) (int64, bool, *lfs.Pointer, error) {
    32  	ptr, pbuf, perr := lfs.DecodeFrom(from)
    33  	if perr != nil {
    34  		// Write 'statusFromErr(nil)', even though 'perr != nil', since
    35  		// we are about to write non-delayed smudged contents to "to".
    36  		if err := s.WriteStatus(statusFromErr(nil)); err != nil {
    37  			return 0, false, nil, err
    38  		}
    39  
    40  		n, err := tools.Spool(to, pbuf, cfg.TempDir())
    41  		if err != nil {
    42  			return n, false, nil, errors.Wrap(err, perr.Error())
    43  		}
    44  
    45  		if n != 0 {
    46  			return 0, false, nil, errors.NewNotAPointerError(errors.Errorf(
    47  				"Unable to parse pointer at: %q", filename,
    48  			))
    49  		}
    50  		return 0, false, nil, nil
    51  	}
    52  
    53  	lfs.LinkOrCopyFromReference(cfg, ptr.Oid, ptr.Size)
    54  
    55  	path, err := cfg.Filesystem().ObjectPath(ptr.Oid)
    56  	if err != nil {
    57  		return 0, false, nil, err
    58  	}
    59  
    60  	if !skip && filter.Allows(filename) {
    61  		if _, statErr := os.Stat(path); statErr != nil {
    62  			q.Add(filename, path, ptr.Oid, ptr.Size)
    63  			return 0, true, ptr, nil
    64  		}
    65  
    66  		// Write 'statusFromErr(nil)', since the object is already
    67  		// present in the local cache, we will write the object's
    68  		// contents without delaying.
    69  		if err := s.WriteStatus(statusFromErr(nil)); err != nil {
    70  			return 0, false, nil, err
    71  		}
    72  
    73  		n, err := gf.Smudge(to, ptr, filename, false, nil, nil)
    74  		return n, false, ptr, err
    75  	}
    76  
    77  	if err := s.WriteStatus(statusFromErr(nil)); err != nil {
    78  		return 0, false, nil, err
    79  	}
    80  
    81  	n, err := ptr.Encode(to)
    82  	return int64(n), false, ptr, err
    83  }
    84  
    85  // smudge smudges the given `*lfs.Pointer`, "ptr", and writes its objects
    86  // contents to the `io.Writer`, "to".
    87  //
    88  // If the encoded LFS pointer is not parse-able as a pointer, the contents of
    89  // that file will instead be spooled to a temporary location on disk and then
    90  // copied out back to Git. If the pointer file is empty, an empty file will be
    91  // written with no error.
    92  //
    93  // If the smudged object did not "pass" the include and exclude filterset, it
    94  // will not be downloaded, and the object will remain a pointer on disk, as if
    95  // the smudge filter had not been applied at all.
    96  //
    97  // Any errors encountered along the way will be returned immediately if they
    98  // were non-fatal, otherwise execution will halt and the process will be
    99  // terminated by using the `commands.Panic()` func.
   100  func smudge(gf *lfs.GitFilter, to io.Writer, from io.Reader, filename string, skip bool, filter *filepathfilter.Filter) (int64, error) {
   101  	ptr, pbuf, perr := lfs.DecodeFrom(from)
   102  	if perr != nil {
   103  		n, err := tools.Spool(to, pbuf, cfg.TempDir())
   104  		if err != nil {
   105  			return 0, errors.Wrap(err, perr.Error())
   106  		}
   107  
   108  		if n != 0 {
   109  			return 0, errors.NewNotAPointerError(errors.Errorf(
   110  				"Unable to parse pointer at: %q", filename,
   111  			))
   112  		}
   113  		return 0, nil
   114  	}
   115  
   116  	lfs.LinkOrCopyFromReference(cfg, ptr.Oid, ptr.Size)
   117  	cb, file, err := gf.CopyCallbackFile("download", filename, 1, 1)
   118  	if err != nil {
   119  		return 0, err
   120  	}
   121  
   122  	download := !skip
   123  	if download {
   124  		download = filter.Allows(filename)
   125  	}
   126  
   127  	n, err := gf.Smudge(to, ptr, filename, download, getTransferManifest(), cb)
   128  	if file != nil {
   129  		file.Close()
   130  	}
   131  
   132  	if err != nil {
   133  		ptr.Encode(to)
   134  		// Download declined error is ok to skip if we weren't requesting download
   135  		if !(errors.IsDownloadDeclinedError(err) && !download) {
   136  			var oid string = ptr.Oid
   137  			if len(oid) >= 7 {
   138  				oid = oid[:7]
   139  			}
   140  
   141  			LoggedError(err, "Error downloading object: %s (%s): %s", filename, oid, err)
   142  			if !cfg.SkipDownloadErrors() {
   143  				os.Exit(2)
   144  			}
   145  		}
   146  	}
   147  
   148  	return n, nil
   149  }
   150  
   151  func smudgeCommand(cmd *cobra.Command, args []string) {
   152  	requireStdin("This command should be run by the Git 'smudge' filter")
   153  	installHooks(false)
   154  
   155  	if !smudgeSkip && cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) {
   156  		smudgeSkip = true
   157  	}
   158  	filter := filepathfilter.New(cfg.FetchIncludePaths(), cfg.FetchExcludePaths())
   159  	gitfilter := lfs.NewGitFilter(cfg)
   160  
   161  	if n, err := smudge(gitfilter, os.Stdout, os.Stdin, smudgeFilename(args), smudgeSkip, filter); err != nil {
   162  		if errors.IsNotAPointerError(err) {
   163  			fmt.Fprintln(os.Stderr, err.Error())
   164  		} else {
   165  			Error(err.Error())
   166  		}
   167  	} else if possiblyMalformedObjectSize(n) {
   168  		fmt.Fprintln(os.Stderr, "Possibly malformed smudge on Windows: see `git lfs help smudge` for more info.")
   169  	}
   170  }
   171  
   172  func smudgeFilename(args []string) string {
   173  	if len(args) > 0 {
   174  		return args[0]
   175  	}
   176  	return "<unknown file>"
   177  }
   178  
   179  func possiblyMalformedObjectSize(n int64) bool {
   180  	return n > 4*humanize.Gigabyte
   181  }
   182  
   183  func init() {
   184  	RegisterCommand("smudge", smudgeCommand, func(cmd *cobra.Command) {
   185  		cmd.Flags().BoolVarP(&smudgeSkip, "skip", "s", false, "")
   186  	})
   187  }