fork of go-gitdiff with jj support
at v0.2.0 12 kB view raw
1package gitdiff 2 3import ( 4 "errors" 5 "fmt" 6 "io" 7 "sort" 8) 9 10// Conflict indicates an apply failed due to a conflict between the patch and 11// the source content. 12// 13// Users can test if an error was caused by a conflict by using errors.Is with 14// an empty Conflict: 15// 16// if errors.Is(err, &Conflict{}) { 17// // handle conflict 18// } 19// 20type Conflict struct { 21 msg string 22} 23 24func (c *Conflict) Error() string { 25 return "conflict: " + c.msg 26} 27 28// Is implements error matching for Conflict. Passing an empty instance of 29// Conflict always returns true. 30func (c *Conflict) Is(other error) bool { 31 if other, ok := other.(*Conflict); ok { 32 return other.msg == "" || other.msg == c.msg 33 } 34 return false 35} 36 37// ApplyError wraps an error that occurs during patch application with 38// additional location information, if it is available. 39type ApplyError struct { 40 // Line is the one-indexed line number in the source data 41 Line int64 42 // Fragment is the one-indexed fragment number in the file 43 Fragment int 44 // FragmentLine is the one-indexed line number in the fragment 45 FragmentLine int 46 47 err error 48} 49 50// Unwrap returns the wrapped error. 51func (e *ApplyError) Unwrap() error { 52 return e.err 53} 54 55func (e *ApplyError) Error() string { 56 return fmt.Sprintf("%v", e.err) 57} 58 59type lineNum int 60type fragNum int 61type fragLineNum int 62 63// applyError creates a new *ApplyError wrapping err or augments the information 64// in err with args if it is already an *ApplyError. Returns nil if err is nil. 65func applyError(err error, args ...interface{}) error { 66 if err == nil { 67 return nil 68 } 69 70 e, ok := err.(*ApplyError) 71 if !ok { 72 if err == io.EOF { 73 err = io.ErrUnexpectedEOF 74 } 75 e = &ApplyError{err: err} 76 } 77 for _, arg := range args { 78 switch v := arg.(type) { 79 case lineNum: 80 e.Line = int64(v) + 1 81 case fragNum: 82 e.Fragment = int(v) + 1 83 case fragLineNum: 84 e.FragmentLine = int(v) + 1 85 } 86 } 87 return e 88} 89 90var ( 91 errApplyInProgress = errors.New("gitdiff: incompatible apply in progress") 92) 93 94const ( 95 applyInitial = iota 96 applyText 97 applyBinary 98 applyFile 99) 100 101// Applier applies changes described in fragments to source data. If changes 102// are described in multiple fragments, those fragments must be applied in 103// order, usually by calling ApplyFile. 104// 105// By default, Applier operates in "strict" mode, where fragment content and 106// positions must exactly match those of the source. 107// 108// If an error occurs while applying, methods on Applier return instances of 109// *ApplyError that annotate the wrapped error with additional information 110// when available. If the error is because of a conflict between a fragment and 111// the source, the wrapped error will be a *Conflict. 112// 113// While an Applier can apply both text and binary fragments, only one fragment 114// type can be used without resetting the Applier. The first fragment applied 115// sets the type for the Applier. Mixing fragment types or mixing 116// fragment-level and file-level applies results in an error. 117type Applier struct { 118 src io.ReaderAt 119 lineSrc LineReaderAt 120 nextLine int64 121 applyType int 122} 123 124// NewApplier creates an Applier that reads data from src. If src is a 125// LineReaderAt, it is used directly to apply text fragments. 126func NewApplier(src io.ReaderAt) *Applier { 127 a := new(Applier) 128 a.Reset(src) 129 return a 130} 131 132// Reset resets the input and internal state of the Applier. If src is nil, the 133// existing source is reused. 134func (a *Applier) Reset(src io.ReaderAt) { 135 if src != nil { 136 a.src = src 137 if lineSrc, ok := src.(LineReaderAt); ok { 138 a.lineSrc = lineSrc 139 } else { 140 a.lineSrc = &lineReaderAt{r: src} 141 } 142 } 143 a.nextLine = 0 144 a.applyType = applyInitial 145} 146 147// ApplyFile applies the changes in all of the fragments of f and writes the 148// result to dst. 149func (a *Applier) ApplyFile(dst io.Writer, f *File) error { 150 if a.applyType != applyInitial { 151 return applyError(errApplyInProgress) 152 } 153 defer func() { a.applyType = applyFile }() 154 155 if f.IsBinary && len(f.TextFragments) > 0 { 156 return applyError(errors.New("binary file contains text fragments")) 157 } 158 if !f.IsBinary && f.BinaryFragment != nil { 159 return applyError(errors.New("text file contains binary fragment")) 160 } 161 162 switch { 163 case f.BinaryFragment != nil: 164 return a.ApplyBinaryFragment(dst, f.BinaryFragment) 165 166 case len(f.TextFragments) > 0: 167 frags := make([]*TextFragment, len(f.TextFragments)) 168 copy(frags, f.TextFragments) 169 170 sort.Slice(frags, func(i, j int) bool { 171 return frags[i].OldPosition < frags[j].OldPosition 172 }) 173 174 // TODO(bkeyes): consider merging overlapping fragments 175 // right now, the application fails if fragments overlap, but it should be 176 // possible to precompute the result of applying them in order 177 178 for i, frag := range frags { 179 if err := a.ApplyTextFragment(dst, frag); err != nil { 180 return applyError(err, fragNum(i)) 181 } 182 } 183 } 184 185 return applyError(a.Flush(dst)) 186} 187 188// ApplyTextFragment applies the changes in the fragment f and writes unwritten 189// data before the start of the fragment and the result to dst. If multiple 190// text fragments apply to the same source, ApplyTextFragment must be called in 191// order of increasing start position. As a result, each fragment can be 192// applied at most once before a call to Reset. 193func (a *Applier) ApplyTextFragment(dst io.Writer, f *TextFragment) error { 194 if a.applyType != applyInitial && a.applyType != applyText { 195 return applyError(errApplyInProgress) 196 } 197 defer func() { a.applyType = applyText }() 198 199 // application code assumes fragment fields are consistent 200 if err := f.Validate(); err != nil { 201 return applyError(err) 202 } 203 204 // lines are 0-indexed, positions are 1-indexed (but new files have position = 0) 205 fragStart := f.OldPosition - 1 206 if fragStart < 0 { 207 fragStart = 0 208 } 209 fragEnd := fragStart + f.OldLines 210 211 start := a.nextLine 212 if fragStart < start { 213 return applyError(&Conflict{"fragment overlaps with an applied fragment"}) 214 } 215 216 if f.OldPosition == 0 { 217 ok, err := isLen(a.src, 0) 218 if err != nil { 219 return applyError(err) 220 } 221 if !ok { 222 return applyError(&Conflict{"cannot create new file from non-empty src"}) 223 } 224 } 225 226 preimage := make([][]byte, fragEnd-start) 227 n, err := a.lineSrc.ReadLinesAt(preimage, start) 228 switch { 229 case err == nil: 230 case err == io.EOF && n == len(preimage): // last line of frag has no newline character 231 default: 232 return applyError(err, lineNum(start+int64(n))) 233 } 234 235 // copy leading data before the fragment starts 236 for i, line := range preimage[:fragStart-start] { 237 if _, err := dst.Write(line); err != nil { 238 a.nextLine = start + int64(i) 239 return applyError(err, lineNum(a.nextLine)) 240 } 241 } 242 preimage = preimage[fragStart-start:] 243 244 // apply the changes in the fragment 245 used := int64(0) 246 for i, line := range f.Lines { 247 if err := applyTextLine(dst, line, preimage, used); err != nil { 248 a.nextLine = fragStart + used 249 return applyError(err, lineNum(a.nextLine), fragLineNum(i)) 250 } 251 if line.Old() { 252 used++ 253 } 254 } 255 a.nextLine = fragStart + used 256 return nil 257} 258 259func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) { 260 if line.Old() && string(preimage[i]) != line.Line { 261 return &Conflict{"fragment line does not match src line"} 262 } 263 if line.New() { 264 _, err = io.WriteString(dst, line.Line) 265 } 266 return err 267} 268 269// Flush writes any data following the last applied fragment to dst. 270func (a *Applier) Flush(dst io.Writer) (err error) { 271 switch a.applyType { 272 case applyInitial: 273 _, err = copyFrom(dst, a.src, 0) 274 case applyText: 275 _, err = copyLinesFrom(dst, a.lineSrc, a.nextLine) 276 case applyBinary: 277 // nothing to flush, binary apply "consumes" full source 278 } 279 return err 280} 281 282// ApplyBinaryFragment applies the changes in the fragment f and writes the 283// result to dst. At most one binary fragment can be applied before a call to 284// Reset. 285func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error { 286 if a.applyType != applyInitial { 287 return applyError(errApplyInProgress) 288 } 289 defer func() { a.applyType = applyBinary }() 290 291 if f == nil { 292 return applyError(errors.New("nil fragment")) 293 } 294 295 switch f.Method { 296 case BinaryPatchLiteral: 297 if _, err := dst.Write(f.Data); err != nil { 298 return applyError(err) 299 } 300 case BinaryPatchDelta: 301 if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil { 302 return applyError(err) 303 } 304 default: 305 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method)) 306 } 307 return nil 308} 309 310func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error { 311 srcSize, delta := readBinaryDeltaSize(frag) 312 if err := checkBinarySrcSize(src, srcSize); err != nil { 313 return err 314 } 315 316 dstSize, delta := readBinaryDeltaSize(delta) 317 318 for len(delta) > 0 { 319 op := delta[0] 320 if op == 0 { 321 return errors.New("invalid delta opcode 0") 322 } 323 324 var n int64 325 var err error 326 switch op & 0x80 { 327 case 0x80: 328 n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src) 329 case 0x00: 330 n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:]) 331 } 332 if err != nil { 333 return err 334 } 335 dstSize -= n 336 } 337 338 if dstSize != 0 { 339 return errors.New("corrupt binary delta: insufficient or extra data") 340 } 341 return nil 342} 343 344// readBinaryDeltaSize reads a variable length size from a delta-encoded binary 345// fragment, returing the size and the unused data. Data is encoded as: 346// 347// [[1xxxxxxx]...] [0xxxxxxx] 348// 349// in little-endian order, with 7 bits of the value per byte. 350func readBinaryDeltaSize(d []byte) (size int64, rest []byte) { 351 shift := uint(0) 352 for i, b := range d { 353 size |= int64(b&0x7F) << shift 354 shift += 7 355 if b <= 0x7F { 356 return size, d[i+1:] 357 } 358 } 359 return size, nil 360} 361 362// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary 363// fragment, returning the amount of data written and the usused part of the 364// fragment. An add operation takes the form: 365// 366// [0xxxxxx][[data1]...] 367// 368// where the lower seven bits of the opcode is the number of data bytes 369// following the opcode. See also pack-format.txt in the Git source. 370func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) { 371 size := int(op) 372 if len(delta) < size { 373 return 0, delta, errors.New("corrupt binary delta: incomplete add") 374 } 375 _, err = w.Write(delta[:size]) 376 return int64(size), delta[size:], err 377} 378 379// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary 380// fragment, returing the amount of data written and the unused part of the 381// fragment. A copy operation takes the form: 382// 383// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3] 384// 385// where the lower seven bits of the opcode determine which non-zero offset and 386// size bytes are present in little-endian order: if bit 0 is set, offset1 is 387// present, etc. If no offset or size bytes are present, offset is 0 and size 388// is 0x10000. See also pack-format.txt in the Git source. 389func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) { 390 const defaultSize = 0x10000 391 392 unpack := func(start, bits uint) (v int64) { 393 for i := uint(0); i < bits; i++ { 394 mask := byte(1 << (i + start)) 395 if op&mask > 0 { 396 if len(delta) == 0 { 397 err = errors.New("corrupt binary delta: incomplete copy") 398 return 399 } 400 v |= int64(delta[0]) << (8 * i) 401 delta = delta[1:] 402 } 403 } 404 return 405 } 406 407 offset := unpack(0, 4) 408 size := unpack(4, 3) 409 if err != nil { 410 return 0, delta, err 411 } 412 if size == 0 { 413 size = defaultSize 414 } 415 416 // TODO(bkeyes): consider pooling these buffers 417 b := make([]byte, size) 418 if _, err := src.ReadAt(b, offset); err != nil { 419 return 0, delta, err 420 } 421 422 _, err = w.Write(b) 423 return size, delta, err 424} 425 426func checkBinarySrcSize(r io.ReaderAt, size int64) error { 427 ok, err := isLen(r, size) 428 if err != nil { 429 return err 430 } 431 if !ok { 432 return &Conflict{"fragment src size does not match actual src size"} 433 } 434 return nil 435}