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.
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}