2016-11-14 18:56:06 +00:00
|
|
|
package lfs
|
|
|
|
|
|
|
|
import (
|
2016-11-15 22:14:52 +00:00
|
|
|
"bufio"
|
2016-11-14 18:56:06 +00:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"strconv"
|
|
|
|
|
2016-11-15 22:14:52 +00:00
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
2016-11-14 18:56:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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 elegible LFS pointers are sent to
|
|
|
|
// pointerCh. Any errors are sent to errCh. An error is returned if the 'git
|
|
|
|
// cat-file' command fails to start.
|
2016-11-16 18:39:08 +00:00
|
|
|
func runCatFileBatch(pointerCh chan *WrappedPointer, revs *StringChannelWrapper, errCh chan error) error {
|
2016-11-14 18:56:06 +00:00
|
|
|
cmd, err := startCommand("git", "cat-file", "--batch")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
go catFileBatchOutput(pointerCh, cmd, errCh)
|
2016-11-16 18:39:08 +00:00
|
|
|
go catFileBatchInput(cmd, revs, errCh)
|
2016-11-14 18:56:06 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-15 22:15:37 +00:00
|
|
|
func catFileBatchOutput(pointerCh chan *WrappedPointer, cmd *wrappedCmd, errCh chan error) {
|
|
|
|
scanner := &catFileBatchScanner{r: cmd.Stdout}
|
|
|
|
for scanner.Scan() {
|
|
|
|
pointerCh <- scanner.Pointer()
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
errCh <- err
|
|
|
|
}
|
|
|
|
|
|
|
|
stderr, _ := ioutil.ReadAll(cmd.Stderr)
|
|
|
|
err := cmd.Wait()
|
|
|
|
if err != nil {
|
|
|
|
errCh <- fmt.Errorf("Error in git cat-file --batch: %v %v", err, string(stderr))
|
|
|
|
}
|
|
|
|
|
|
|
|
close(pointerCh)
|
|
|
|
close(errCh)
|
|
|
|
}
|
|
|
|
|
2016-11-16 18:39:08 +00:00
|
|
|
func catFileBatchInput(cmd *wrappedCmd, revs *StringChannelWrapper, errCh chan error) {
|
|
|
|
for r := range revs.Results {
|
2016-11-15 22:15:37 +00:00
|
|
|
cmd.Stdin.Write([]byte(r + "\n"))
|
|
|
|
}
|
2016-11-16 18:39:08 +00:00
|
|
|
err := revs.Wait()
|
|
|
|
if err != nil {
|
|
|
|
// We can share errchan with other goroutine since that won't close it
|
|
|
|
// until we close the stdin below
|
|
|
|
errCh <- err
|
|
|
|
}
|
2016-11-15 22:15:37 +00:00
|
|
|
cmd.Stdin.Close()
|
|
|
|
}
|
|
|
|
|
2016-11-15 22:14:52 +00:00
|
|
|
type catFileBatchScanner struct {
|
|
|
|
r *bufio.Reader
|
|
|
|
pointer *WrappedPointer
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *catFileBatchScanner) Pointer() *WrappedPointer {
|
|
|
|
return s.pointer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *catFileBatchScanner) Err() error {
|
|
|
|
return s.err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *catFileBatchScanner) Scan() bool {
|
|
|
|
s.pointer, s.err = nil, nil
|
|
|
|
p, err := scanPointer(s.r)
|
2016-11-16 18:04:28 +00:00
|
|
|
if err != nil {
|
|
|
|
// EOF halts scanning, but isn't a reportable error
|
|
|
|
if err != io.EOF {
|
|
|
|
s.err = err
|
|
|
|
}
|
2016-11-15 22:14:52 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
s.pointer = p
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanPointer(r *bufio.Reader) (*WrappedPointer, error) {
|
|
|
|
var pointer *WrappedPointer
|
|
|
|
|
|
|
|
for pointer == nil {
|
|
|
|
l, err := r.ReadBytes('\n')
|
2016-11-16 18:04:28 +00:00
|
|
|
if err != nil {
|
2016-11-15 22:14:52 +00:00
|
|
|
return nil, err
|
2016-11-14 18:56:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Line is formatted:
|
|
|
|
// <sha1> <type> <size>
|
|
|
|
fields := bytes.Fields(l)
|
2016-11-15 21:39:48 +00:00
|
|
|
if len(fields) < 3 {
|
2016-11-16 18:04:28 +00:00
|
|
|
return nil, errors.Wrap(fmt.Errorf("Invalid: %q", string(l)), "git cat-file --batch:")
|
2016-11-14 18:56:06 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 22:14:52 +00:00
|
|
|
size, _ := strconv.Atoi(string(fields[2]))
|
2016-11-18 16:09:52 +00:00
|
|
|
buf := make([]byte, size)
|
|
|
|
read, err := io.ReadFull(r, buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if size != read {
|
|
|
|
return nil, fmt.Errorf("expected %d bytes, read %d bytes", size, read)
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := DecodePointer(bytes.NewBuffer(buf[0:read]))
|
2016-11-14 18:56:06 +00:00
|
|
|
if err == nil {
|
2016-11-15 22:14:52 +00:00
|
|
|
pointer = &WrappedPointer{
|
2016-11-14 18:56:06 +00:00
|
|
|
Sha1: string(fields[0]),
|
|
|
|
Pointer: p,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-15 22:14:52 +00:00
|
|
|
_, err = r.ReadBytes('\n') // Extra \n inserted by cat-file
|
2016-11-16 18:04:28 +00:00
|
|
|
if err != nil {
|
2016-11-15 22:14:52 +00:00
|
|
|
return nil, err
|
2016-11-14 18:56:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-15 22:14:52 +00:00
|
|
|
return pointer, nil
|
|
|
|
}
|