github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/pull.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "sync" 9 10 "github.com/git-lfs/git-lfs/config" 11 "github.com/git-lfs/git-lfs/errors" 12 "github.com/git-lfs/git-lfs/git" 13 "github.com/git-lfs/git-lfs/lfs" 14 "github.com/git-lfs/git-lfs/subprocess" 15 "github.com/git-lfs/git-lfs/tq" 16 ) 17 18 // Handles the process of checking out a single file, and updating the git 19 // index. 20 func newSingleCheckout(gitEnv config.Environment, remote string) abstractCheckout { 21 manifest := getTransferManifestOperationRemote("download", remote) 22 23 clean, ok := gitEnv.Get("filter.lfs.clean") 24 if !ok || len(clean) == 0 { 25 return &noOpCheckout{manifest: manifest} 26 } 27 28 // Get a converter from repo-relative to cwd-relative 29 // Since writing data & calling git update-index must be relative to cwd 30 pathConverter, err := lfs.NewRepoToCurrentPathConverter(cfg) 31 if err != nil { 32 Panic(err, "Could not convert file paths") 33 } 34 35 return &singleCheckout{ 36 gitIndexer: &gitIndexer{}, 37 pathConverter: pathConverter, 38 manifest: manifest, 39 } 40 } 41 42 type abstractCheckout interface { 43 Manifest() *tq.Manifest 44 Skip() bool 45 Run(*lfs.WrappedPointer) 46 Close() 47 } 48 49 type singleCheckout struct { 50 gitIndexer *gitIndexer 51 pathConverter lfs.PathConverter 52 manifest *tq.Manifest 53 } 54 55 func (c *singleCheckout) Manifest() *tq.Manifest { 56 return c.manifest 57 } 58 59 func (c *singleCheckout) Skip() bool { 60 return false 61 } 62 63 func (c *singleCheckout) Run(p *lfs.WrappedPointer) { 64 cwdfilepath := c.pathConverter.Convert(p.Name) 65 66 // Check the content - either missing or still this pointer (not exist is ok) 67 filepointer, err := lfs.DecodePointerFromFile(cwdfilepath) 68 if err != nil && !os.IsNotExist(err) { 69 if errors.IsNotAPointerError(err) { 70 // File has non-pointer content, leave it alone 71 return 72 } 73 74 LoggedError(err, "Checkout error: %s", err) 75 return 76 } 77 78 if filepointer != nil && filepointer.Oid != p.Oid { 79 // User has probably manually reset a file to another commit 80 // while leaving it a pointer; don't mess with this 81 return 82 } 83 84 gitfilter := lfs.NewGitFilter(cfg) 85 err = gitfilter.SmudgeToFile(cwdfilepath, p.Pointer, false, c.manifest, nil) 86 if err != nil { 87 if errors.IsDownloadDeclinedError(err) { 88 // acceptable error, data not local (fetch not run or include/exclude) 89 LoggedError(err, "Skipped checkout for %q, content not local. Use fetch to download.", p.Name) 90 } else { 91 FullError(fmt.Errorf("Could not check out %q", p.Name)) 92 } 93 return 94 } 95 96 // errors are only returned when the gitIndexer is starting a new cmd 97 if err := c.gitIndexer.Add(cwdfilepath); err != nil { 98 Panic(err, "Could not update the index") 99 } 100 } 101 102 func (c *singleCheckout) Close() { 103 if err := c.gitIndexer.Close(); err != nil { 104 LoggedError(err, "Error updating the git index:\n%s", c.gitIndexer.Output()) 105 } 106 } 107 108 type noOpCheckout struct { 109 manifest *tq.Manifest 110 } 111 112 func (c *noOpCheckout) Manifest() *tq.Manifest { 113 return c.manifest 114 } 115 116 func (c *noOpCheckout) Skip() bool { 117 return true 118 } 119 120 func (c *noOpCheckout) Run(p *lfs.WrappedPointer) {} 121 func (c *noOpCheckout) Close() {} 122 123 // Don't fire up the update-index command until we have at least one file to 124 // give it. Otherwise git interprets the lack of arguments to mean param-less update-index 125 // which can trigger entire working copy to be re-examined, which triggers clean filters 126 // and which has unexpected side effects (e.g. downloading filtered-out files) 127 type gitIndexer struct { 128 cmd *subprocess.Cmd 129 input io.WriteCloser 130 output bytes.Buffer 131 mu sync.Mutex 132 } 133 134 func (i *gitIndexer) Add(path string) error { 135 i.mu.Lock() 136 defer i.mu.Unlock() 137 138 if i.cmd == nil { 139 // Fire up the update-index command 140 cmd := git.UpdateIndexFromStdin() 141 cmd.Stdout = &i.output 142 cmd.Stderr = &i.output 143 stdin, err := cmd.StdinPipe() 144 if err != nil { 145 return err 146 } 147 err = cmd.Start() 148 if err != nil { 149 return err 150 } 151 i.cmd = cmd 152 i.input = stdin 153 } 154 155 i.input.Write([]byte(path + "\n")) 156 return nil 157 } 158 159 func (i *gitIndexer) Output() string { 160 return i.output.String() 161 } 162 163 func (i *gitIndexer) Close() error { 164 i.mu.Lock() 165 defer i.mu.Unlock() 166 167 if i.input != nil { 168 i.input.Close() 169 } 170 171 if i.cmd != nil { 172 return i.cmd.Wait() 173 } 174 175 return nil 176 }