git-lfs/lfs/gitscanner_catfilebatchcheck.go
Chris Darroch ac73c0a587 lfs/gitscanner_catfilebatchcheck.go: close channel
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.
2023-06-07 15:57:35 -07:00

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
}