git-lfs/lfs/gitscanner_catfilebatch.go

192 lines
4.0 KiB
Go
Raw Normal View History

package lfs
import (
2016-11-15 22:14:52 +00:00
"bufio"
"bytes"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"strconv"
2016-11-15 22:14:52 +00:00
"github.com/git-lfs/git-lfs/errors"
)
// runCatFileBatch uses 'git cat-file --batch' to get the object contents of a
// git object, given its sha1. The contents will be decoded into a Git LFS
// pointer. Git Blob SHA1s are read from the sha1Ch channel and fed to STDIN.
// Results are parsed from STDOUT, and any eligible LFS pointers are sent to
// pointerCh. If a Git Blob is not an LFS pointer, check the lockableSet to see
// if that blob is for a locked file. Any errors are sent to errCh. An error is
// returned if the 'git cat-file' command fails to start.
func runCatFileBatch(pointerCh chan *WrappedPointer, lockableCh chan string, lockableSet *lockableNameSet, revs *StringChannelWrapper, errCh chan error) error {
2017-03-22 21:15:02 +00:00
scanner, err := NewCatFileBatchScanner()
if err != nil {
return err
}
go func() {
for r := range revs.Results {
2017-03-22 21:15:02 +00:00
canScan := scanner.Scan([]byte(r))
2016-11-19 00:00:57 +00:00
if err := scanner.Err(); err != nil {
errCh <- err
} else if p := scanner.Pointer(); p != nil {
pointerCh <- p
} else if b := scanner.BlobSHA(); len(b) == 40 {
if name, ok := lockableSet.Check(b); ok {
lockableCh <- name
}
}
2016-11-21 16:20:18 +00:00
if !canScan {
break
}
}
if err := revs.Wait(); err != nil {
errCh <- err
}
2017-03-22 21:15:02 +00:00
if err := scanner.Close(); err != nil {
errCh <- err
}
2016-11-15 22:15:37 +00:00
close(pointerCh)
close(errCh)
close(lockableCh)
}()
2016-11-15 22:15:37 +00:00
return nil
2016-11-15 22:15:37 +00:00
}
2017-03-22 21:15:02 +00:00
type CatFileBatchScanner struct {
2016-11-15 22:14:52 +00:00
r *bufio.Reader
2017-03-22 21:15:02 +00:00
w io.Writer
closeFn func() error
blobSha string
contentsSha string
pointer *WrappedPointer
err error
2016-11-15 22:14:52 +00:00
}
2017-03-22 21:15:02 +00:00
func NewCatFileBatchScanner() (*CatFileBatchScanner, error) {
cmd, err := startCommand("git", "cat-file", "--batch")
if err != nil {
return nil, err
}
closeFn := func() error {
if err := cmd.Stdin.Close(); err != nil {
return err
}
stderr, _ := ioutil.ReadAll(cmd.Stderr)
if err := cmd.Wait(); err != nil {
return errors.Errorf("Error in git cat-file --batch: %v %v", err, string(stderr))
}
return nil
}
return &CatFileBatchScanner{
r: cmd.Stdout,
w: cmd.Stdin,
closeFn: closeFn,
}, nil
}
func (s *CatFileBatchScanner) BlobSHA() string {
return s.blobSha
}
func (s *CatFileBatchScanner) ContentsSha() string {
return s.contentsSha
}
2017-03-22 21:15:02 +00:00
func (s *CatFileBatchScanner) Pointer() *WrappedPointer {
2016-11-19 00:00:57 +00:00
return s.pointer
}
2017-03-22 21:15:02 +00:00
func (s *CatFileBatchScanner) Err() error {
2016-11-19 00:00:57 +00:00
return s.err
}
2017-03-22 21:15:02 +00:00
func (s *CatFileBatchScanner) Scan(sha []byte) bool {
2016-11-19 00:00:57 +00:00
s.pointer, s.err = nil, nil
s.blobSha, s.contentsSha = "", ""
2017-03-22 21:15:02 +00:00
if s.w != nil && sha != nil {
if _, err := fmt.Fprintf(s.w, "%s\n", sha); err != nil {
s.err = err
return false
}
}
b, c, p, err := s.next()
s.blobSha = b
s.contentsSha = c
2016-11-19 00:00:57 +00:00
s.pointer = p
if err != nil {
if err != io.EOF {
s.err = err
}
return false
2016-11-15 22:14:52 +00:00
}
2016-11-19 00:00:57 +00:00
return true
2016-11-15 22:14:52 +00:00
}
2017-03-22 21:15:02 +00:00
func (s *CatFileBatchScanner) Close() error {
if s.closeFn == nil {
return nil
}
return s.closeFn()
}
func (s *CatFileBatchScanner) next() (string, string, *WrappedPointer, error) {
l, err := s.r.ReadBytes('\n')
2016-11-18 21:20:51 +00:00
if err != nil {
return "", "", nil, err
2016-11-18 21:20:51 +00:00
}
2016-11-15 22:14:52 +00:00
2016-11-18 21:20:51 +00:00
// Line is formatted:
// <sha1> <type> <size>
fields := bytes.Fields(l)
if len(fields) < 3 {
return "", "", nil, errors.Wrap(fmt.Errorf("Invalid: %q", string(l)), "git cat-file --batch")
2016-11-18 21:20:51 +00:00
}
blobSha := string(fields[0])
2016-11-18 21:20:51 +00:00
size, _ := strconv.Atoi(string(fields[2]))
sha := sha256.New()
2016-11-18 21:20:51 +00:00
buf := make([]byte, size)
read, err := io.ReadFull(io.TeeReader(s.r, sha), buf)
2016-11-18 21:20:51 +00:00
if err != nil {
return blobSha, "", nil, err
2016-11-18 21:20:51 +00:00
}
2016-11-18 21:20:51 +00:00
if size != read {
return blobSha, "", nil, fmt.Errorf("expected %d bytes, read %d bytes", size, read)
2016-11-18 21:20:51 +00:00
}
2016-11-18 16:09:52 +00:00
2016-11-21 16:20:18 +00:00
p, err := DecodePointer(bytes.NewBuffer(buf[:read]))
2016-11-18 21:20:51 +00:00
var pointer *WrappedPointer
var contentsSha string
2016-11-18 21:20:51 +00:00
if err == nil {
contentsSha = p.Oid
2016-11-18 21:20:51 +00:00
pointer = &WrappedPointer{
Sha1: blobSha,
2016-11-18 21:20:51 +00:00
Pointer: p,
2016-11-18 16:09:52 +00:00
}
} else {
contentsSha = fmt.Sprintf("%x", sha.Sum(nil))
2016-11-18 21:20:51 +00:00
}
2016-11-18 16:09:52 +00:00
_, err = s.r.ReadBytes('\n') // Extra \n inserted by cat-file
2017-03-22 21:15:02 +00:00
return blobSha, contentsSha, pointer, err
2016-11-18 21:20:51 +00:00
}