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