1package lfs 2 3import ( 4 "bytes" 5 "crypto/sha256" 6 "fmt" 7 "io" 8 9 "github.com/git-lfs/git-lfs/v3/config" 10 "github.com/git-lfs/git-lfs/v3/git" 11) 12 13// runCatFileBatch uses 'git cat-file --batch' to get the object contents of a 14// git object, given its sha1. The contents will be decoded into a Git LFS 15// pointer. Git Blob SHA1s are read from the sha1Ch channel and fed to STDIN. 16// Results are parsed from STDOUT, and any eligible LFS pointers are sent to 17// pointerCh. If a Git Blob is not an LFS pointer, check the lockableSet to see 18// if that blob is for a locked file. Any errors are sent to errCh. An error is 19// returned if the 'git cat-file' command fails to start. 20func runCatFileBatch(pointerCh chan *WrappedPointer, lockableCh chan string, lockableSet *lockableNameSet, revs *StringChannelWrapper, errCh chan error, gitEnv, osEnv config.Environment) error { 21 scanner, err := NewPointerScanner(gitEnv, osEnv) 22 if err != nil { 23 return err 24 } 25 26 go func() { 27 canScan := true 28 for r := range revs.Results { 29 canScan = scanner.Scan(r) 30 31 if err := scanner.Err(); err != nil { 32 errCh <- err 33 } else if p := scanner.Pointer(); p != nil { 34 pointerCh <- p 35 } else if b := scanner.BlobSHA(); git.HasValidObjectIDLength(b) { 36 if name, ok := lockableSet.Check(b); ok { 37 lockableCh <- name 38 } 39 } 40 41 if !canScan { 42 break 43 } 44 } 45 46 if canScan { 47 if err := revs.Wait(); err != nil { 48 errCh <- err 49 } 50 } 51 52 if err := scanner.Close(); err != nil { 53 errCh <- err 54 } 55 56 close(pointerCh) 57 close(errCh) 58 close(lockableCh) 59 }() 60 61 return nil 62} 63 64type PointerScanner struct { 65 scanner *git.ObjectScanner 66 67 blobSha string 68 contentsSha string 69 pointer *WrappedPointer 70 err error 71} 72 73func NewPointerScanner(gitEnv, osEnv config.Environment) (*PointerScanner, error) { 74 scanner, err := git.NewObjectScanner(gitEnv, osEnv) 75 if err != nil { 76 return nil, err 77 } 78 79 return &PointerScanner{scanner: scanner}, nil 80} 81 82func (s *PointerScanner) BlobSHA() string { 83 return s.blobSha 84} 85 86func (s *PointerScanner) ContentsSha() string { 87 return s.contentsSha 88} 89 90func (s *PointerScanner) Pointer() *WrappedPointer { 91 return s.pointer 92} 93 94func (s *PointerScanner) Err() error { 95 return s.err 96} 97 98func (s *PointerScanner) Scan(sha string) bool { 99 s.pointer, s.err = nil, nil 100 s.blobSha, s.contentsSha = "", "" 101 102 b, c, p, err := s.next(sha) 103 s.blobSha = b 104 s.contentsSha = c 105 s.pointer = p 106 107 if err != nil { 108 if err != io.EOF { 109 s.err = err 110 } 111 return false 112 } 113 114 return true 115} 116 117func (s *PointerScanner) Close() error { 118 return s.scanner.Close() 119} 120 121func (s *PointerScanner) next(blob string) (string, string, *WrappedPointer, error) { 122 if !s.scanner.Scan(blob) { 123 if err := s.scanner.Err(); err != nil { 124 return "", "", nil, err 125 } 126 return "", "", nil, io.EOF 127 } 128 129 blobSha := s.scanner.Sha1() 130 size := s.scanner.Size() 131 132 sha := sha256.New() 133 134 var buf *bytes.Buffer 135 var to io.Writer = sha 136 if size < blobSizeCutoff { 137 buf = bytes.NewBuffer(make([]byte, 0, size)) 138 to = io.MultiWriter(to, buf) 139 } 140 141 read, err := io.CopyN(to, s.scanner.Contents(), int64(size)) 142 if err != nil { 143 return blobSha, "", nil, err 144 } 145 146 if int64(size) != read { 147 return blobSha, "", nil, fmt.Errorf("expected %d bytes, read %d bytes", size, read) 148 } 149 150 var pointer *WrappedPointer 151 var contentsSha string 152 153 if size < blobSizeCutoff { 154 if p, err := DecodePointer(bytes.NewReader(buf.Bytes())); err != nil { 155 contentsSha = fmt.Sprintf("%x", sha.Sum(nil)) 156 } else { 157 pointer = &WrappedPointer{ 158 Sha1: blobSha, 159 Pointer: p, 160 } 161 contentsSha = p.Oid 162 } 163 } else { 164 contentsSha = fmt.Sprintf("%x", sha.Sum(nil)) 165 } 166 167 return blobSha, contentsSha, pointer, err 168} 169