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