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