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  }