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 }