fork of go-gitdiff with jj support
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}