fork of go-gitdiff with jj support
at v0.2.0 3.5 kB view raw
1// Package gitdiff parses and applies patches generated by Git. It supports 2// line-oriented text patches, binary patches, and can also parse standard 3// unified diffs generated by other tools. 4package gitdiff 5 6import ( 7 "bufio" 8 "fmt" 9 "io" 10) 11 12// Parse parses a patch with changes to one or more files. Any content before 13// the first file is returned as the second value. If an error occurs while 14// parsing, it returns all files parsed before the error. 15func Parse(r io.Reader) ([]*File, string, error) { 16 p := newParser(r) 17 18 if err := p.Next(); err != nil { 19 if err == io.EOF { 20 return nil, "", nil 21 } 22 return nil, "", err 23 } 24 25 var preamble string 26 var files []*File 27 for { 28 file, pre, err := p.ParseNextFileHeader() 29 if err != nil { 30 return files, preamble, err 31 } 32 if file == nil { 33 break 34 } 35 36 for _, fn := range []func(*File) (int, error){ 37 p.ParseTextFragments, 38 p.ParseBinaryFragments, 39 } { 40 n, err := fn(file) 41 if err != nil { 42 return files, preamble, err 43 } 44 if n > 0 { 45 break 46 } 47 } 48 49 if len(files) == 0 { 50 preamble = pre 51 } 52 files = append(files, file) 53 } 54 55 return files, preamble, nil 56} 57 58// TODO(bkeyes): consider exporting the parser type with configuration 59// this would enable OID validation, p-value guessing, and prefix stripping 60// by allowing users to set or override defaults 61 62// parser invariants: 63// - methods that parse objects: 64// - start with the parser on the first line of the first object 65// - if returning nil, do not advance 66// - if returning an error, do not advance past the object 67// - if returning an object, advance to the first line after the object 68// - any exported parsing methods must initialize the parser by calling Next() 69 70type stringReader interface { 71 ReadString(delim byte) (string, error) 72} 73 74type parser struct { 75 r stringReader 76 77 eof bool 78 lineno int64 79 lines [3]string 80} 81 82func newParser(r io.Reader) *parser { 83 if r, ok := r.(stringReader); ok { 84 return &parser{r: r} 85 } 86 return &parser{r: bufio.NewReader(r)} 87} 88 89// Next advances the parser by one line. It returns any error encountered while 90// reading the line, including io.EOF when the end of stream is reached. 91func (p *parser) Next() error { 92 if p.eof { 93 return io.EOF 94 } 95 96 if p.lineno == 0 { 97 // on first call to next, need to shift in all lines 98 for i := 0; i < len(p.lines)-1; i++ { 99 if err := p.shiftLines(); err != nil && err != io.EOF { 100 return err 101 } 102 } 103 } 104 105 err := p.shiftLines() 106 if err != nil && err != io.EOF { 107 return err 108 } 109 110 p.lineno++ 111 if p.lines[0] == "" { 112 p.eof = true 113 return io.EOF 114 } 115 return nil 116} 117 118func (p *parser) shiftLines() (err error) { 119 for i := 0; i < len(p.lines)-1; i++ { 120 p.lines[i] = p.lines[i+1] 121 } 122 p.lines[len(p.lines)-1], err = p.r.ReadString('\n') 123 return 124} 125 126// Line returns a line from the parser without advancing it. A delta of 0 127// returns the current line, while higher deltas return read-ahead lines. It 128// returns an empty string if the delta is higher than the available lines, 129// either because of the buffer size or because the parser reached the end of 130// the input. Valid lines always contain at least a newline character. 131func (p *parser) Line(delta uint) string { 132 return p.lines[delta] 133} 134 135// Errorf generates an error and appends the current line information. 136func (p *parser) Errorf(delta int64, msg string, args ...interface{}) error { 137 return fmt.Errorf("gitdiff: line %d: %s", p.lineno+delta, fmt.Sprintf(msg, args...)) 138}