fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "io"
5)
6
7// TextApplier applies changes described in text fragments to source data. If
8// changes are described in multiple fragments, those fragments must be applied
9// in order. The applier must be closed after use.
10//
11// By default, TextApplier operates in "strict" mode, where fragment content
12// and positions must exactly match those of the source.
13type TextApplier struct {
14 dst io.Writer
15 src io.ReaderAt
16 lineSrc LineReaderAt
17 nextLine int64
18
19 closed bool
20 dirty bool
21}
22
23// NewTextApplier creates a TextApplier that reads data from src and writes
24// modified data to dst. If src implements LineReaderAt, it is used directly.
25func NewTextApplier(dst io.Writer, src io.ReaderAt) *TextApplier {
26 a := TextApplier{
27 dst: dst,
28 src: src,
29 }
30
31 if lineSrc, ok := src.(LineReaderAt); ok {
32 a.lineSrc = lineSrc
33 } else {
34 a.lineSrc = &lineReaderAt{r: src}
35 }
36
37 return &a
38}
39
40// ApplyFragment applies the changes in the fragment f, writing unwritten data
41// before the start of the fragment and any changes from the fragment. If
42// multiple text fragments apply to the same content, ApplyFragment must be
43// called in order of increasing start position. As a result, each fragment can
44// be applied at most once.
45//
46// If an error occurs while applying, ApplyFragment returns an *ApplyError that
47// annotates the error with additional information. If the error is because of
48// a conflict between the fragment and the source, the wrapped error will be a
49// *Conflict.
50func (a *TextApplier) ApplyFragment(f *TextFragment) error {
51 if a.closed {
52 return applyError(errApplierClosed)
53 }
54
55 // mark an apply as in progress, even if it fails before making changes
56 a.dirty = true
57
58 // application code assumes fragment fields are consistent
59 if err := f.Validate(); err != nil {
60 return applyError(err)
61 }
62
63 // lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
64 fragStart := f.OldPosition - 1
65 if fragStart < 0 {
66 fragStart = 0
67 }
68 fragEnd := fragStart + f.OldLines
69
70 start := a.nextLine
71 if fragStart < start {
72 return applyError(&Conflict{"fragment overlaps with an applied fragment"})
73 }
74
75 if f.OldPosition == 0 {
76 ok, err := isLen(a.src, 0)
77 if err != nil {
78 return applyError(err)
79 }
80 if !ok {
81 return applyError(&Conflict{"cannot create new file from non-empty src"})
82 }
83 }
84
85 preimage := make([][]byte, fragEnd-start)
86 n, err := a.lineSrc.ReadLinesAt(preimage, start)
87 if err != nil {
88 return applyError(err, lineNum(start+int64(n)))
89 }
90
91 // copy leading data before the fragment starts
92 for i, line := range preimage[:fragStart-start] {
93 if _, err := a.dst.Write(line); err != nil {
94 a.nextLine = start + int64(i)
95 return applyError(err, lineNum(a.nextLine))
96 }
97 }
98 preimage = preimage[fragStart-start:]
99
100 // apply the changes in the fragment
101 used := int64(0)
102 for i, line := range f.Lines {
103 if err := applyTextLine(a.dst, line, preimage, used); err != nil {
104 a.nextLine = fragStart + used
105 return applyError(err, lineNum(a.nextLine), fragLineNum(i))
106 }
107 if line.Old() {
108 used++
109 }
110 }
111 a.nextLine = fragStart + used
112
113 // new position of +0,0 mean a full delete, so check for leftovers
114 if f.NewPosition == 0 && f.NewLines == 0 {
115 var b [1][]byte
116 n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine)
117 if err != nil && err != io.EOF {
118 return applyError(err, lineNum(a.nextLine))
119 }
120 if n > 0 {
121 return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine))
122 }
123 }
124
125 return nil
126}
127
128func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
129 if line.Old() && string(preimage[i]) != line.Line {
130 return &Conflict{"fragment line does not match src line"}
131 }
132 if line.New() {
133 _, err = io.WriteString(dst, line.Line)
134 }
135 return err
136}
137
138// Close writes any data following the last applied fragment and prevents
139// future calls to ApplyFragment.
140func (a *TextApplier) Close() (err error) {
141 if a.closed {
142 return nil
143 }
144
145 a.closed = true
146 if !a.dirty {
147 _, err = copyFrom(a.dst, a.src, 0)
148 } else {
149 _, err = copyLinesFrom(a.dst, a.lineSrc, a.nextLine)
150 }
151 return err
152}