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