git-lfs/lfs/diff_index_scanner.go

211 lines
6.1 KiB
Go
Raw Normal View History

2017-03-15 02:07:18 +00:00
package lfs
import (
"bufio"
"fmt"
2017-03-15 02:07:18 +00:00
"strconv"
"strings"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/git"
2021-12-14 16:05:11 +00:00
"github.com/git-lfs/git-lfs/v3/tr"
2017-03-15 02:07:18 +00:00
)
// Status represents the status of a file that appears in the output of `git
// diff-index`.
//
// More information about each of its valid instances can be found:
// https://git-scm.com/docs/git-diff-index
type DiffIndexStatus rune
2017-03-15 02:07:18 +00:00
const (
StatusAddition DiffIndexStatus = 'A'
StatusCopy DiffIndexStatus = 'C'
StatusDeletion DiffIndexStatus = 'D'
StatusModification DiffIndexStatus = 'M'
StatusRename DiffIndexStatus = 'R'
StatusTypeChange DiffIndexStatus = 'T'
StatusUnmerged DiffIndexStatus = 'U'
StatusUnknown DiffIndexStatus = 'X'
2017-03-15 02:07:18 +00:00
)
2017-03-16 15:42:42 +00:00
// String implements fmt.Stringer by returning a human-readable name for each
// status.
func (s DiffIndexStatus) String() string {
2017-03-15 02:07:18 +00:00
switch s {
case StatusAddition:
return "addition"
case StatusCopy:
return "copy"
case StatusDeletion:
return "deletion"
case StatusModification:
return "modification"
case StatusRename:
return "rename"
case StatusTypeChange:
return "change"
case StatusUnmerged:
return "unmerged"
case StatusUnknown:
return "unknown"
}
return "<unknown>"
}
// Format implements fmt.Formatter. If printed as "%+d", "%+s", or "%+v", the
// status will be written out as an English word: i.e., "addition", "copy",
// "deletion", etc.
//
// If the '+' flag is not given, the shorthand will be used instead: 'A', 'C',
// and 'D', respectively.
//
// If any other format verb is given, this function will panic().
func (s DiffIndexStatus) Format(state fmt.State, c rune) {
switch c {
case 'd', 's', 'v':
if state.Flag('+') {
state.Write([]byte(s.String()))
} else {
state.Write([]byte{byte(rune(s))})
}
default:
avoid extra message format parsing where possible Because the tr.Tr.Get() family of methods insert arguments into printf(3)-style format strings after translating the format string, we can in a few cases drop a surrounding call to fmt.Sprintf() or a similar method, as those now take no arguments and so are redundant. Moreover, this will help us avoid situations where either the translated string or the argument values interpolated by tr.Tr.Get() produce an output string which itself happens to contain character sequences that resemble Go format specifiers (e.g., "%s", "%d", etc.) In such cases passing the string at runtime to a method such as fmt.Fprintf() will result in the output containing a warning such as "%!s(MISSING)", which is not ideal. Note that in one case, in lfs/attribute.go, we can now also simplify the format string to use standard format specifiers instead of double-escaped ones (e.g., "%%q") since we can just allow tr.Tr.Get() to do the interpolation. We also take the opportunity to remove explicit leading or trailing newlines from translation messages wherever it is possible to convert the surrounding call to fmt.Print(), fmt.Fprint(), fmt.Println(), or fmt.Fprintln(). Finally, in the commands/run.go file, we can replace two calls to fmt.Fprintf() with fmt.Println() because they are just printing output to os.Stdout, not os.Stderr, and in the lfs/extension.go file, we can make better use of fmt.Errorf(). Note that at least one of these messages is not yet actually passed as a translation string, but we will address that issue in a subsequent commit.
2022-01-27 02:00:03 +00:00
panic(tr.Tr.Get("cannot format %v for DiffIndexStatus", c))
}
}
// DiffIndexEntry holds information about a single item in the results of a `git
// diff-index` command.
2017-03-15 02:07:18 +00:00
type DiffIndexEntry struct {
// SrcMode is the file mode of the "src" file, stored as a string-based
// octal.
SrcMode string
// DstMode is the file mode of the "dst" file, stored as a string-based
// octal.
DstMode string
// SrcSha is the Git blob ID of the "src" file.
SrcSha string
// DstSha is the Git blob ID of the "dst" file.
DstSha string
// Status is the status of the file in the index.
Status DiffIndexStatus
2017-03-16 15:42:42 +00:00
// StatusScore is the optional "score" associated with a particular
// status.
2017-03-15 02:07:18 +00:00
StatusScore int
2017-03-16 15:42:42 +00:00
// SrcName is the name of the file in its "src" state as it appears in
// the index.
SrcName string
2017-03-16 15:42:42 +00:00
// DstName is the name of the file in its "dst" state as it appears in
// the index.
DstName string
2017-03-15 02:07:18 +00:00
}
// DiffIndexScanner scans the output of the `git diff-index` command.
2017-03-15 02:07:18 +00:00
type DiffIndexScanner struct {
// next is the next entry scanned by the Scanner.
2017-03-15 02:07:18 +00:00
next *DiffIndexEntry
// err is any error that the Scanner encountered while scanning.
err error
2017-03-15 02:07:18 +00:00
// from is the underlying scanner, scanning the `git diff-index`
// command's stdout.
2017-03-15 02:07:18 +00:00
from *bufio.Scanner
}
// NewDiffIndexScanner initializes a new `DiffIndexScanner` scanning at the
// given ref, "ref".
//
// If "cache" is given, the DiffIndexScanner will scan for differences between
// the given ref and the index. If "cache" is _not_ given, DiffIndexScanner will
// scan for differences between the given ref and the currently checked out
// tree.
//
// If "refresh" is given, the DiffIndexScanner will refresh the index. This is
// probably what you want in all cases except fsck, where invoking a filtering
// operation would be undesirable due to the possibility of corruption. It can
// also be disabled where another operation will have refreshed the index.
//
// If any error was encountered in starting the command or closing its `stdin`,
// that error will be returned immediately. Otherwise, a `*DiffIndexScanner`
// will be returned with a `nil` error.
func NewDiffIndexScanner(ref string, cached bool, refresh bool) (*DiffIndexScanner, error) {
scanner, err := git.DiffIndex(ref, cached, refresh)
2017-03-15 02:07:18 +00:00
if err != nil {
return nil, err
2017-03-15 02:07:18 +00:00
}
return &DiffIndexScanner{
from: scanner,
}, nil
2017-03-15 02:07:18 +00:00
}
// Scan advances the scan line and yields either a new value for Entry(), or an
// Err(). It returns true or false, whether or not it can continue scanning for
// more entries.
2017-03-15 02:07:18 +00:00
func (s *DiffIndexScanner) Scan() bool {
if !s.prepareScan() {
return false
}
s.next, s.err = s.scan(s.from.Text())
2017-03-15 20:59:43 +00:00
if s.err != nil {
s.err = errors.Wrap(s.err, tr.Tr.Get("`git diff-index` scan"))
2017-03-15 20:59:43 +00:00
}
2017-03-15 02:07:18 +00:00
return s.err == nil
}
// Entry returns the last entry that was Scan()'d by the DiffIndexScanner.
2017-03-15 02:07:18 +00:00
func (s *DiffIndexScanner) Entry() *DiffIndexEntry { return s.next }
// Entry returns the last error that was encountered by the DiffIndexScanner.
func (s *DiffIndexScanner) Err() error { return s.err }
// prepareScan clears out the results from the last Scan() loop, and advances
// the internal scanner to fetch a new line of Text().
2017-03-15 02:07:18 +00:00
func (s *DiffIndexScanner) prepareScan() bool {
s.next, s.err = nil, nil
if !s.from.Scan() {
s.err = s.from.Err()
return false
}
return true
}
// scan parses the given line and returns a `*DiffIndexEntry` or an error,
// depending on whether or not the parse was successful.
2017-03-15 02:07:18 +00:00
func (s *DiffIndexScanner) scan(line string) (*DiffIndexEntry, error) {
// Format is:
// :100644 100644 c5b3d83a7542255ec7856487baa5e83d65b1624c 9e82ac1b514be060945392291b5b3108c22f6fe3 M foo.gif
// :<old mode> <new mode> <old sha1> <new sha1> <status>\t<file name>[\t<file name>]
2017-03-15 02:07:18 +00:00
parts := strings.Split(line, "\t")
if len(parts) < 2 {
2021-12-14 16:05:11 +00:00
return nil, errors.Errorf(tr.Tr.Get("invalid line: %s", line))
2017-03-15 02:07:18 +00:00
}
desc := strings.Fields(parts[0])
if len(desc) < 5 {
2021-12-14 16:05:11 +00:00
return nil, errors.Errorf(tr.Tr.Get("invalid description: %s", parts[0]))
2017-03-15 02:07:18 +00:00
}
entry := &DiffIndexEntry{
SrcMode: strings.TrimPrefix(desc[0], ":"),
DstMode: desc[1],
SrcSha: desc[2],
DstSha: desc[3],
Status: DiffIndexStatus(rune(desc[4][0])),
2017-03-15 02:07:18 +00:00
SrcName: parts[1],
}
if score, err := strconv.Atoi(desc[4][1:]); err != nil {
entry.StatusScore = score
}
if len(parts) > 2 {
entry.DstName = parts[2]
}
return entry, nil
}