package lfs
import (
"bufio"
"bytes"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"time"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/filepathfilter"
"github.com/git-lfs/git-lfs/v3/git"
"github.com/git-lfs/git-lfs/v3/subprocess"
"github.com/git-lfs/git-lfs/v3/tr"
"github.com/rubyist/tracerx"
)
type LogDiffDirection byte
const (
LogDiffAdditions = LogDiffDirection('+')
LogDiffDeletions = LogDiffDirection('-')
)
var (
logLfsSearchArgs = []string{
"--no-ext-diff",
"--no-textconv",
"--color=never",
"-G", "oid sha256:",
"-p",
"-U12",
`--format=lfs-commit-sha: %H %P`,
}
)
type gitscannerResult struct {
Pointer *WrappedPointer
Err error
}
func scanUnpushed(cb GitScannerFoundPointer, remote string) error {
logArgs := []string{
"--branches", "--tags",
"--not"}
if len(remote) == 0 {
logArgs = append(logArgs, "--remotes")
} else {
logArgs = append(logArgs, fmt.Sprintf("--remotes=%v", remote))
}
logArgs = append(logArgs, logLfsSearchArgs...)
cmd, err := git.Log(logArgs...)
if err != nil {
return err
}
parseScannerLogOutput(cb, LogDiffAdditions, cmd, nil)
return nil
}
func scanStashed(cb GitScannerFoundPointer) error {
logArgs := []string{"-g", "--format=%h", "refs/stash", "--"}
cmd, err := git.Log(logArgs...)
if err != nil {
return err
}
scanner := bufio.NewScanner(cmd.Stdout)
var stashMergeShas []string
for scanner.Scan() {
stashMergeSha := strings.TrimSpace(scanner.Text())
stashMergeShas = append(stashMergeShas, fmt.Sprintf("%v^..%v", stashMergeSha, stashMergeSha))
}
if err := scanner.Err(); err != nil {
errors.New(tr.Tr.Get("error while scanning `git log` for stashed refs: %v", err))
}
err = cmd.Wait()
if err != nil {
return nil
}
stashMergeLogArgs := [][]string{{"-m", "--first-parent"}, {}}
for _, logArgs := range stashMergeLogArgs {
logArgs = append(logArgs, logLfsSearchArgs...)
logArgs = append(logArgs, stashMergeShas...)
cmd, err = git.Log(logArgs...)
if err != nil {
return err
}
parseScannerLogOutput(cb, LogDiffAdditions, cmd, nil)
}
return nil
}
func parseScannerLogOutput(cb GitScannerFoundPointer, direction LogDiffDirection, cmd *subprocess.BufferedCmd, filter *filepathfilter.Filter) {
ch := make(chan gitscannerResult, chanBufSize)
cherr := make(chan []byte)
go func() {
stderr, _ := io.ReadAll(cmd.Stderr)
cherr <- stderr
close(cherr)
}()
go func() {
scanner := newLogScanner(direction, cmd.Stdout)
scanner.Filter = filter
for scanner.Scan() {
if p := scanner.Pointer(); p != nil {
ch <- gitscannerResult{Pointer: p}
}
}
if err := scanner.Err(); err != nil {
io.ReadAll(cmd.Stdout)
ch <- gitscannerResult{Err: errors.New(tr.Tr.Get("error while scanning `git log`: %v", err))}
}
stderr := <-cherr
err := cmd.Wait()
if err != nil {
ch <- gitscannerResult{Err: errors.New(tr.Tr.Get("error in `git log`: %v %v", err, string(stderr)))}
}
close(ch)
}()
cmd.Stdin.Close()
for result := range ch {
cb(result.Pointer, result.Err)
}
}
func logPreviousSHAs(cb GitScannerFoundPointer, ref string, filter *filepathfilter.Filter, since time.Time) error {
logArgs := []string{
fmt.Sprintf("--since=%v", git.FormatGitDate(since)),
}
logArgs = append(logArgs, logLfsSearchArgs...)
logArgs = append(logArgs, ref)
cmd, err := git.Log(logArgs...)
if err != nil {
return err
}
parseScannerLogOutput(cb, LogDiffDeletions, cmd, filter)
return nil
}
type logScanner struct {
Filter *filepathfilter.Filter
r *bufio.Reader
err error
dir LogDiffDirection
pointer *WrappedPointer
pointerData *bytes.Buffer
currentFilename string
currentFileIncluded bool
commitHeaderRegex *regexp.Regexp
fileHeaderRegex *regexp.Regexp
fileMergeHeaderRegex *regexp.Regexp
pointerDataRegex *regexp.Regexp
}
func newLogScanner(dir LogDiffDirection, r io.Reader) *logScanner {
return &logScanner{
r: bufio.NewReader(r),
dir: dir,
pointerData: &bytes.Buffer{},
currentFileIncluded: true,
commitHeaderRegex: regexp.MustCompile(fmt.Sprintf(`^lfs-commit-sha: (%s)(?: (%s))*`, git.ObjectIDRegex, git.ObjectIDRegex)),
fileHeaderRegex: regexp.MustCompile(`^diff --git "?a\/(.+?)\s+"?b\/(.+)`),
fileMergeHeaderRegex: regexp.MustCompile(`^diff --cc (.+)`),
pointerDataRegex: regexp.MustCompile(`^([\+\- ])(version https://git-lfs|oid sha256|size|ext-).*$`),
}
}
func (s *logScanner) Pointer() *WrappedPointer {
return s.pointer
}
func (s *logScanner) Err() error {
return s.err
}
func (s *logScanner) Scan() bool {
s.pointer = nil
p, canScan := s.scan()
s.pointer = p
return canScan
}
func (s *logScanner) finishLastPointer() *WrappedPointer {
if s.pointerData.Len() == 0 || !s.currentFileIncluded {
return nil
}
p, err := DecodePointer(s.pointerData)
s.pointerData.Reset()
if err == nil {
return &WrappedPointer{Name: s.currentFilename, Pointer: p}
} else {
tracerx.Printf("Unable to parse pointer from log: %v", err)
return nil
}
}
lfs-commit-sha: 60fde3d23553e10a55e2a32ed18c20f65edd91e7 e2eaf1c10b57da7b98eb5d722ec5912ddeb53ea1
diff --git a/1D_Noise.png b/1D_Noise.png
new file mode 100644
index 0000000..2622b4a
--- /dev/null
+++ b/1D_Noise.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5d84da40ab1f6aa28df2b2bf1ade2cdcd4397133f903c12b4106641b10e1ed6
+size 1289
*/
func (s *logScanner) scan() (*WrappedPointer, bool) {
for {
line, err := s.r.ReadString('\n')
if err != nil && err != io.EOF {
s.err = err
return nil, false
}
line = strings.TrimSuffix(strings.TrimRight(line, "\n"), "\r")
if match := s.commitHeaderRegex.FindStringSubmatch(line); match != nil {
if p := s.finishLastPointer(); p != nil {
return p, true
}
} else if match := s.fileHeaderRegex.FindStringSubmatch(line); match != nil {
p := s.finishLastPointer()
if s.dir == LogDiffAdditions {
s.setFilename(match[2])
} else {
s.setFilename(match[1])
}
if p != nil {
return p, true
}
} else if match := s.fileMergeHeaderRegex.FindStringSubmatch(line); match != nil {
p := s.finishLastPointer()
s.setFilename(match[1])
if p != nil {
return p, true
}
} else if s.currentFileIncluded {
if match := s.pointerDataRegex.FindStringSubmatch(line); match != nil {
changeType := match[1][0]
if LogDiffDirection(changeType) == s.dir || changeType == ' ' {
s.pointerData.WriteString(line[1:])
s.pointerData.WriteString("\n")
}
}
}
if err == io.EOF {
break
}
}
if p := s.finishLastPointer(); p != nil {
return p, true
}
return nil, false
}
func (s *logScanner) setFilename(name string) {
if len(name) > 0 && name[len(name)-1] == '"' {
name = name[:len(name)-1]
}
unquotedName, err := strconv.Unquote(`"` + name + `"`)
if err == nil {
name = unquotedName
}
s.currentFilename = name
s.currentFileIncluded = s.Filter.Allows(name)
}