9 "github.com/git-lfs/git-lfs/errors"
10 "github.com/git-lfs/git-lfs/git"
13 // Status represents the status of a file that appears in the output of `git
16 // More information about each of its valid instances can be found:
17 // https://git-scm.com/docs/git-diff-index
18 type DiffIndexStatus rune
21 StatusAddition DiffIndexStatus = 'A'
22 StatusCopy DiffIndexStatus = 'C'
23 StatusDeletion DiffIndexStatus = 'D'
24 StatusModification DiffIndexStatus = 'M'
25 StatusRename DiffIndexStatus = 'R'
26 StatusTypeChange DiffIndexStatus = 'T'
27 StatusUnmerged DiffIndexStatus = 'U'
28 StatusUnknown DiffIndexStatus = 'X'
31 // String implements fmt.Stringer by returning a human-readable name for each
33 func (s DiffIndexStatus) String() string {
41 case StatusModification:
45 case StatusTypeChange:
55 // Format implements fmt.Formatter. If printed as "%+d", "%+s", or "%+v", the
56 // status will be written out as an English word: i.e., "addition", "copy",
59 // If the '+' flag is not given, the shorthand will be used instead: 'A', 'C',
60 // and 'D', respectively.
62 // If any other format verb is given, this function will panic().
63 func (s DiffIndexStatus) Format(state fmt.State, c rune) {
67 state.Write([]byte(s.String()))
69 state.Write([]byte{byte(rune(s))})
72 panic(fmt.Sprintf("cannot format %v for DiffIndexStatus", c))
76 // DiffIndexEntry holds information about a single item in the results of a `git
77 // diff-index` command.
78 type DiffIndexEntry struct {
79 // SrcMode is the file mode of the "src" file, stored as a string-based
82 // DstMode is the file mode of the "dst" file, stored as a string-based
85 // SrcSha is the Git blob ID of the "src" file.
87 // DstSha is the Git blob ID of the "dst" file.
89 // Status is the status of the file in the index.
90 Status DiffIndexStatus
91 // StatusScore is the optional "score" associated with a particular
94 // SrcName is the name of the file in its "src" state as it appears in
97 // DstName is the name of the file in its "dst" state as it appears in
102 // DiffIndexScanner scans the output of the `git diff-index` command.
103 type DiffIndexScanner struct {
104 // next is the next entry scanned by the Scanner.
106 // err is any error that the Scanner encountered while scanning.
109 // from is the underlying scanner, scanning the `git diff-index`
114 // NewDiffIndexScanner initializes a new `DiffIndexScanner` scanning at the
117 // If "cache" is given, the DiffIndexScanner will scan for differences between
118 // the given ref and the index. If "cache" is _not_ given, DiffIndexScanner will
119 // scan for differences between the given ref and the currently checked out
122 // If any error was encountered in starting the command or closing its `stdin`,
123 // that error will be returned immediately. Otherwise, a `*DiffIndexScanner`
124 // will be returned with a `nil` error.
125 func NewDiffIndexScanner(ref string, cached bool) (*DiffIndexScanner, error) {
126 scanner, err := git.DiffIndex(ref, cached)
130 return &DiffIndexScanner{
135 // Scan advances the scan line and yields either a new value for Entry(), or an
136 // Err(). It returns true or false, whether or not it can continue scanning for
138 func (s *DiffIndexScanner) Scan() bool {
139 if !s.prepareScan() {
143 s.next, s.err = s.scan(s.from.Text())
145 s.err = errors.Wrap(s.err, "scan")
151 // Entry returns the last entry that was Scan()'d by the DiffIndexScanner.
152 func (s *DiffIndexScanner) Entry() *DiffIndexEntry { return s.next }
154 // Entry returns the last error that was encountered by the DiffIndexScanner.
155 func (s *DiffIndexScanner) Err() error { return s.err }
157 // prepareScan clears out the results from the last Scan() loop, and advances
158 // the internal scanner to fetch a new line of Text().
159 func (s *DiffIndexScanner) prepareScan() bool {
160 s.next, s.err = nil, nil
169 // scan parses the given line and returns a `*DiffIndexEntry` or an error,
170 // depending on whether or not the parse was successful.
171 func (s *DiffIndexScanner) scan(line string) (*DiffIndexEntry, error) {
173 // :100644 100644 c5b3d83a7542255ec7856487baa5e83d65b1624c 9e82ac1b514be060945392291b5b3108c22f6fe3 M foo.gif
174 // :<old mode> <new mode> <old sha1> <new sha1> <status>\t<file name>[\t<file name>]
176 parts := strings.Split(line, "\t")
178 return nil, errors.Errorf("invalid line: %s", line)
181 desc := strings.Fields(parts[0])
183 return nil, errors.Errorf("invalid description: %s", parts[0])
186 entry := &DiffIndexEntry{
187 SrcMode: strings.TrimPrefix(desc[0], ":"),
191 Status: DiffIndexStatus(rune(desc[4][0])),
195 if score, err := strconv.Atoi(desc[4][1:]); err != nil {
196 entry.StatusScore = score
200 entry.DstName = parts[2]