github.com/git-lfs/git-lfs@v2.5.2+incompatible/lfs/diff_index_scanner.go (about) 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 }