ac73c0a587
In commit 08c5ae6c6482b6dd161bb30ea4312caa77de2326 of PR #1953 the runCatFileBatchCheck() and runCatFileBatch() functions of the "lfs" package were updated to write the pathspecs of locked files which are not Git LFS pointers to a dedicated channel created by the catFileBatchCheck() and catFileBatch() wrapper functions, respectively. The scanRefsToChan() function, which calls both of these with a potentially non-nil *lockableNameSet paramter (and is the only caller to do so), starts a goroutine with an anonymous function to read any events on the channel returned by catFileBatchCheck(), and then reads all events on the channel returned by catFileBatch() directly. In the latter case, this would cause scanRefsToChan() to stall indefinitely unless the channel is closed, so the anonymous function started by the runCatFileBatch() function that writes to the channel always closes the channel upon exit. However, the anonymous function started by the runCatFileBatchCheck() function that writes to its lock path channel does not do the same. While this does not cause a stalled program because scanRefsToChan() creates its own anonymous function to read from the channel, that function will not exit until the progam stops. By adding an explicit close() of the channel at the end of the anonymous function started by runCatFileBatchCheck(), we can ensure the anonymous function which reads that channel will also exit as soon as possible.
120 lines
2.7 KiB
Go
120 lines
2.7 KiB
Go
package lfs
|
|
|
|
import (
|
|
"bufio"
|
|
"io/ioutil"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/errors"
|
|
"github.com/git-lfs/git-lfs/v3/git"
|
|
"github.com/git-lfs/git-lfs/v3/tr"
|
|
)
|
|
|
|
// runCatFileBatchCheck uses 'git cat-file --batch-check' to get the type and
|
|
// size of a git object. Any object that isn't of type blob and under the
|
|
// blobSizeCutoff will be ignored, unless it's a locked file. revs is a channel
|
|
// over which strings containing git sha1s will be sent. It returns a channel
|
|
// from which sha1 strings can be read.
|
|
func runCatFileBatchCheck(smallRevCh chan string, lockableCh chan string, lockableSet *lockableNameSet, revs *StringChannelWrapper, errCh chan error) error {
|
|
cmd, err := git.CatFile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
scanner := &catFileBatchCheckScanner{s: bufio.NewScanner(cmd.Stdout), limit: blobSizeCutoff}
|
|
for r := range revs.Results {
|
|
cmd.Stdin.Write([]byte(r + "\n"))
|
|
hasNext := scanner.Scan()
|
|
if err := scanner.Err(); err != nil {
|
|
errCh <- err
|
|
} else if b := scanner.LFSBlobOID(); len(b) > 0 {
|
|
smallRevCh <- b
|
|
} else if b := scanner.GitBlobOID(); len(b) > 0 {
|
|
if name, ok := lockableSet.Check(b); ok {
|
|
lockableCh <- name
|
|
}
|
|
}
|
|
|
|
if !hasNext {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := revs.Wait(); err != nil {
|
|
errCh <- err
|
|
}
|
|
cmd.Stdin.Close()
|
|
|
|
stderr, _ := ioutil.ReadAll(cmd.Stderr)
|
|
err := cmd.Wait()
|
|
if err != nil {
|
|
errCh <- errors.New(tr.Tr.Get("error in `git cat-file --batch-check`: %v %v", err, string(stderr)))
|
|
}
|
|
close(smallRevCh)
|
|
close(errCh)
|
|
close(lockableCh)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
type catFileBatchCheckScanner struct {
|
|
s *bufio.Scanner
|
|
limit int
|
|
lfsBlobOID string
|
|
gitBlobOID string
|
|
}
|
|
|
|
func (s *catFileBatchCheckScanner) LFSBlobOID() string {
|
|
return s.lfsBlobOID
|
|
}
|
|
|
|
func (s *catFileBatchCheckScanner) GitBlobOID() string {
|
|
return s.gitBlobOID
|
|
}
|
|
|
|
func (s *catFileBatchCheckScanner) Err() error {
|
|
return s.s.Err()
|
|
}
|
|
|
|
func (s *catFileBatchCheckScanner) Scan() bool {
|
|
lfsBlobSha, gitBlobSha, hasNext := s.next()
|
|
s.lfsBlobOID = lfsBlobSha
|
|
s.gitBlobOID = gitBlobSha
|
|
return hasNext
|
|
}
|
|
|
|
func (s *catFileBatchCheckScanner) next() (string, string, bool) {
|
|
hasNext := s.s.Scan()
|
|
line := s.s.Text()
|
|
lineLen := len(line)
|
|
|
|
oidLen := strings.IndexByte(line, ' ')
|
|
|
|
// Format is:
|
|
// <hash> <type> <size>
|
|
// type is at a fixed spot, if we see that it's "blob", we can avoid
|
|
// splitting the line just to get the size.
|
|
if oidLen == -1 || lineLen < oidLen+6 {
|
|
return "", "", hasNext
|
|
}
|
|
|
|
if line[oidLen+1:oidLen+5] != "blob" {
|
|
return "", "", hasNext
|
|
}
|
|
|
|
size, err := strconv.Atoi(line[oidLen+6 : lineLen])
|
|
if err != nil {
|
|
return "", "", hasNext
|
|
}
|
|
|
|
blobSha := line[0:oidLen]
|
|
if size >= s.limit {
|
|
return "", blobSha, hasNext
|
|
}
|
|
|
|
return blobSha, "", hasNext
|
|
}
|