Imported Upstream version 2.3.4
[scm/test.git] / lfs / diff_index_scanner.go
1 package lfs
2
3 import (
4         "bufio"
5         "fmt"
6         "strconv"
7         "strings"
8
9         "github.com/git-lfs/git-lfs/errors"
10         "github.com/git-lfs/git-lfs/git"
11 )
12
13 // Status represents the status of a file that appears in the output of `git
14 // diff-index`.
15 //
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
19
20 const (
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'
29 )
30
31 // String implements fmt.Stringer by returning a human-readable name for each
32 // status.
33 func (s DiffIndexStatus) String() string {
34         switch s {
35         case StatusAddition:
36                 return "addition"
37         case StatusCopy:
38                 return "copy"
39         case StatusDeletion:
40                 return "deletion"
41         case StatusModification:
42                 return "modification"
43         case StatusRename:
44                 return "rename"
45         case StatusTypeChange:
46                 return "change"
47         case StatusUnmerged:
48                 return "unmerged"
49         case StatusUnknown:
50                 return "unknown"
51         }
52         return "<unknown>"
53 }
54
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",
57 // "deletion", etc.
58 //
59 // If the '+' flag is not given, the shorthand will be used instead: 'A', 'C',
60 // and 'D', respectively.
61 //
62 // If any other format verb is given, this function will panic().
63 func (s DiffIndexStatus) Format(state fmt.State, c rune) {
64         switch c {
65         case 'd', 's', 'v':
66                 if state.Flag('+') {
67                         state.Write([]byte(s.String()))
68                 } else {
69                         state.Write([]byte{byte(rune(s))})
70                 }
71         default:
72                 panic(fmt.Sprintf("cannot format %v for DiffIndexStatus", c))
73         }
74 }
75
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
80         // octal.
81         SrcMode string
82         // DstMode is the file mode of the "dst" file, stored as a string-based
83         // octal.
84         DstMode string
85         // SrcSha is the Git blob ID of the "src" file.
86         SrcSha string
87         // DstSha is the Git blob ID of the "dst" file.
88         DstSha string
89         // Status is the status of the file in the index.
90         Status DiffIndexStatus
91         // StatusScore is the optional "score" associated with a particular
92         // status.
93         StatusScore int
94         // SrcName is the name of the file in its "src" state as it appears in
95         // the index.
96         SrcName string
97         // DstName is the name of the file in its "dst" state as it appears in
98         // the index.
99         DstName string
100 }
101
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.
105         next *DiffIndexEntry
106         // err is any error that the Scanner encountered while scanning.
107         err error
108
109         // from is the underlying scanner, scanning the `git diff-index`
110         // command's stdout.
111         from *bufio.Scanner
112 }
113
114 // NewDiffIndexScanner initializes a new `DiffIndexScanner` scanning at the
115 // given ref, "ref".
116 //
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
120 // tree.
121 //
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)
127         if err != nil {
128                 return nil, err
129         }
130         return &DiffIndexScanner{
131                 from: scanner,
132         }, nil
133 }
134
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
137 // more entries.
138 func (s *DiffIndexScanner) Scan() bool {
139         if !s.prepareScan() {
140                 return false
141         }
142
143         s.next, s.err = s.scan(s.from.Text())
144         if s.err != nil {
145                 s.err = errors.Wrap(s.err, "scan")
146         }
147
148         return s.err == nil
149 }
150
151 // Entry returns the last entry that was Scan()'d by the DiffIndexScanner.
152 func (s *DiffIndexScanner) Entry() *DiffIndexEntry { return s.next }
153
154 // Entry returns the last error that was encountered by the DiffIndexScanner.
155 func (s *DiffIndexScanner) Err() error { return s.err }
156
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
161         if !s.from.Scan() {
162                 s.err = s.from.Err()
163                 return false
164         }
165
166         return true
167 }
168
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) {
172         // Format is:
173         //   :100644 100644 c5b3d83a7542255ec7856487baa5e83d65b1624c 9e82ac1b514be060945392291b5b3108c22f6fe3 M foo.gif
174         //   :<old mode> <new mode> <old sha1> <new sha1> <status>\t<file name>[\t<file name>]
175
176         parts := strings.Split(line, "\t")
177         if len(parts) < 2 {
178                 return nil, errors.Errorf("invalid line: %s", line)
179         }
180
181         desc := strings.Fields(parts[0])
182         if len(desc) < 5 {
183                 return nil, errors.Errorf("invalid description: %s", parts[0])
184         }
185
186         entry := &DiffIndexEntry{
187                 SrcMode: strings.TrimPrefix(desc[0], ":"),
188                 DstMode: desc[1],
189                 SrcSha:  desc[2],
190                 DstSha:  desc[3],
191                 Status:  DiffIndexStatus(rune(desc[4][0])),
192                 SrcName: parts[1],
193         }
194
195         if score, err := strconv.Atoi(desc[4][1:]); err != nil {
196                 entry.StatusScore = score
197         }
198
199         if len(parts) > 2 {
200                 entry.DstName = parts[2]
201         }
202
203         return entry, nil
204 }