github.com/git-lfs/git-lfs@v2.5.2+incompatible/lfs/gitfilter_smudge.go (about)

     1  package lfs
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/git-lfs/git-lfs/config"
    10  	"github.com/git-lfs/git-lfs/errors"
    11  	"github.com/git-lfs/git-lfs/tools"
    12  	"github.com/git-lfs/git-lfs/tools/humanize"
    13  	"github.com/git-lfs/git-lfs/tq"
    14  	"github.com/rubyist/tracerx"
    15  )
    16  
    17  func (f *GitFilter) SmudgeToFile(filename string, ptr *Pointer, download bool, manifest *tq.Manifest, cb tools.CopyCallback) error {
    18  	os.MkdirAll(filepath.Dir(filename), 0755)
    19  
    20  	if stat, _ := os.Stat(filename); stat != nil && stat.Mode()&0200 == 0 {
    21  		if err := os.Chmod(filename, stat.Mode()|0200); err != nil {
    22  			return errors.Wrap(err,
    23  				"Could not restore write permission")
    24  		}
    25  
    26  		// When we're done, return the file back to its normal
    27  		// permission bits.
    28  		defer os.Chmod(filename, stat.Mode())
    29  	}
    30  
    31  	file, err := os.Create(filename)
    32  	if err != nil {
    33  		return fmt.Errorf("Could not create working directory file: %v", err)
    34  	}
    35  	defer file.Close()
    36  	if _, err := f.Smudge(file, ptr, filename, download, manifest, cb); err != nil {
    37  		if errors.IsDownloadDeclinedError(err) {
    38  			// write placeholder data instead
    39  			file.Seek(0, os.SEEK_SET)
    40  			ptr.Encode(file)
    41  			return err
    42  		} else {
    43  			return fmt.Errorf("Could not write working directory file: %v", err)
    44  		}
    45  	}
    46  	return nil
    47  }
    48  
    49  func (f *GitFilter) Smudge(writer io.Writer, ptr *Pointer, workingfile string, download bool, manifest *tq.Manifest, cb tools.CopyCallback) (int64, error) {
    50  	mediafile, err := f.ObjectPath(ptr.Oid)
    51  	if err != nil {
    52  		return 0, err
    53  	}
    54  
    55  	LinkOrCopyFromReference(f.cfg, ptr.Oid, ptr.Size)
    56  
    57  	stat, statErr := os.Stat(mediafile)
    58  	if statErr == nil && stat != nil {
    59  		fileSize := stat.Size()
    60  		if fileSize == 0 || fileSize != ptr.Size {
    61  			tracerx.Printf("Removing %s, size %d is invalid", mediafile, fileSize)
    62  			os.RemoveAll(mediafile)
    63  			stat = nil
    64  		}
    65  	}
    66  
    67  	var n int64
    68  
    69  	if statErr != nil || stat == nil {
    70  		if download {
    71  			n, err = f.downloadFile(writer, ptr, workingfile, mediafile, manifest, cb)
    72  		} else {
    73  			return 0, errors.NewDownloadDeclinedError(statErr, "smudge")
    74  		}
    75  	} else {
    76  		n, err = f.readLocalFile(writer, ptr, mediafile, workingfile, cb)
    77  	}
    78  
    79  	if err != nil {
    80  		return 0, errors.NewSmudgeError(err, ptr.Oid, mediafile)
    81  	}
    82  
    83  	return n, nil
    84  }
    85  
    86  func (f *GitFilter) downloadFile(writer io.Writer, ptr *Pointer, workingfile, mediafile string, manifest *tq.Manifest, cb tools.CopyCallback) (int64, error) {
    87  	fmt.Fprintf(os.Stderr, "Downloading %s (%s)\n", workingfile, humanize.FormatBytes(uint64(ptr.Size)))
    88  
    89  	// NOTE: if given, "cb" is a tools.CopyCallback which writes updates
    90  	// to the logpath specified by GIT_LFS_PROGRESS.
    91  	//
    92  	// Either way, forward it into the *tq.TransferQueue so that updates are
    93  	// sent over correctly.
    94  
    95  	q := tq.NewTransferQueue(tq.Download, manifest, f.cfg.Remote(),
    96  		tq.WithProgressCallback(cb),
    97  		tq.RemoteRef(f.RemoteRef()),
    98  	)
    99  	q.Add(filepath.Base(workingfile), mediafile, ptr.Oid, ptr.Size)
   100  	q.Wait()
   101  
   102  	if errs := q.Errors(); len(errs) > 0 {
   103  		var multiErr error
   104  		for _, e := range errs {
   105  			if multiErr != nil {
   106  				multiErr = fmt.Errorf("%v\n%v", multiErr, e)
   107  			} else {
   108  				multiErr = e
   109  			}
   110  			return 0, errors.Wrapf(multiErr, "Error downloading %s (%s)", workingfile, ptr.Oid)
   111  		}
   112  	}
   113  
   114  	return f.readLocalFile(writer, ptr, mediafile, workingfile, nil)
   115  }
   116  
   117  func (f *GitFilter) readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile string, cb tools.CopyCallback) (int64, error) {
   118  	reader, err := os.Open(mediafile)
   119  	if err != nil {
   120  		return 0, errors.Wrapf(err, "Error opening media file.")
   121  	}
   122  	defer reader.Close()
   123  
   124  	if ptr.Size == 0 {
   125  		if stat, _ := os.Stat(mediafile); stat != nil {
   126  			ptr.Size = stat.Size()
   127  		}
   128  	}
   129  
   130  	if len(ptr.Extensions) > 0 {
   131  		registeredExts := f.cfg.Extensions()
   132  		extensions := make(map[string]config.Extension)
   133  		for _, ptrExt := range ptr.Extensions {
   134  			ext, ok := registeredExts[ptrExt.Name]
   135  			if !ok {
   136  				err := fmt.Errorf("Extension '%s' is not configured.", ptrExt.Name)
   137  				return 0, errors.Wrap(err, "smudge")
   138  			}
   139  			ext.Priority = ptrExt.Priority
   140  			extensions[ext.Name] = ext
   141  		}
   142  		exts, err := config.SortExtensions(extensions)
   143  		if err != nil {
   144  			return 0, errors.Wrap(err, "smudge")
   145  		}
   146  
   147  		// pipe extensions in reverse order
   148  		var extsR []config.Extension
   149  		for i := range exts {
   150  			ext := exts[len(exts)-1-i]
   151  			extsR = append(extsR, ext)
   152  		}
   153  
   154  		request := &pipeRequest{"smudge", reader, workingfile, extsR}
   155  
   156  		response, err := pipeExtensions(f.cfg, request)
   157  		if err != nil {
   158  			return 0, errors.Wrap(err, "smudge")
   159  		}
   160  
   161  		actualExts := make(map[string]*pipeExtResult)
   162  		for _, result := range response.results {
   163  			actualExts[result.name] = result
   164  		}
   165  
   166  		// verify name, order, and oids
   167  		oid := response.results[0].oidIn
   168  		if ptr.Oid != oid {
   169  			err = fmt.Errorf("Actual oid %s during smudge does not match expected %s", oid, ptr.Oid)
   170  			return 0, errors.Wrap(err, "smudge")
   171  		}
   172  
   173  		for _, expected := range ptr.Extensions {
   174  			actual := actualExts[expected.Name]
   175  			if actual.name != expected.Name {
   176  				err = fmt.Errorf("Actual extension name '%s' does not match expected '%s'", actual.name, expected.Name)
   177  				return 0, errors.Wrap(err, "smudge")
   178  			}
   179  			if actual.oidOut != expected.Oid {
   180  				err = fmt.Errorf("Actual oid %s for extension '%s' does not match expected %s", actual.oidOut, expected.Name, expected.Oid)
   181  				return 0, errors.Wrap(err, "smudge")
   182  			}
   183  		}
   184  
   185  		// setup reader
   186  		reader, err = os.Open(response.file.Name())
   187  		if err != nil {
   188  			return 0, errors.Wrapf(err, "Error opening smudged file: %s", err)
   189  		}
   190  		defer reader.Close()
   191  	}
   192  
   193  	n, err := tools.CopyWithCallback(writer, reader, ptr.Size, cb)
   194  	if err != nil {
   195  		return n, errors.Wrapf(err, "Error reading from media file: %s", err)
   196  	}
   197  
   198  	return n, nil
   199  }