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