fork of go-gitdiff with jj support
at v0.7.0 4.1 kB view raw
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}