fork of go-gitdiff with jj support
at v0.6.1 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 if err != nil { 235 return applyError(err, lineNum(start+int64(n))) 236 } 237 238 // copy leading data before the fragment starts 239 for i, line := range preimage[:fragStart-start] { 240 if _, err := dst.Write(line); err != nil { 241 a.nextLine = start + int64(i) 242 return applyError(err, lineNum(a.nextLine)) 243 } 244 } 245 preimage = preimage[fragStart-start:] 246 247 // apply the changes in the fragment 248 used := int64(0) 249 for i, line := range f.Lines { 250 if err := applyTextLine(dst, line, preimage, used); err != nil { 251 a.nextLine = fragStart + used 252 return applyError(err, lineNum(a.nextLine), fragLineNum(i)) 253 } 254 if line.Old() { 255 used++ 256 } 257 } 258 a.nextLine = fragStart + used 259 260 // new position of +0,0 mean a full delete, so check for leftovers 261 if f.NewPosition == 0 && f.NewLines == 0 { 262 var b [1][]byte 263 n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine) 264 if err != nil && err != io.EOF { 265 return applyError(err, lineNum(a.nextLine)) 266 } 267 if n > 0 { 268 return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine)) 269 } 270 } 271 272 return nil 273} 274 275func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) { 276 if line.Old() && string(preimage[i]) != line.Line { 277 return &Conflict{"fragment line does not match src line"} 278 } 279 if line.New() { 280 _, err = io.WriteString(dst, line.Line) 281 } 282 return err 283} 284 285// Flush writes any data following the last applied fragment to dst. 286func (a *Applier) Flush(dst io.Writer) (err error) { 287 switch a.applyType { 288 case applyInitial: 289 _, err = copyFrom(dst, a.src, 0) 290 case applyText: 291 _, err = copyLinesFrom(dst, a.lineSrc, a.nextLine) 292 case applyBinary: 293 // nothing to flush, binary apply "consumes" full source 294 } 295 return err 296} 297 298// ApplyBinaryFragment applies the changes in the fragment f and writes the 299// result to dst. At most one binary fragment can be applied before a call to 300// Reset. 301func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error { 302 if a.applyType != applyInitial { 303 return applyError(errApplyInProgress) 304 } 305 defer func() { a.applyType = applyBinary }() 306 307 if f == nil { 308 return applyError(errors.New("nil fragment")) 309 } 310 311 switch f.Method { 312 case BinaryPatchLiteral: 313 if _, err := dst.Write(f.Data); err != nil { 314 return applyError(err) 315 } 316 case BinaryPatchDelta: 317 if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil { 318 return applyError(err) 319 } 320 default: 321 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method)) 322 } 323 return nil 324} 325 326func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error { 327 srcSize, delta := readBinaryDeltaSize(frag) 328 if err := checkBinarySrcSize(src, srcSize); err != nil { 329 return err 330 } 331 332 dstSize, delta := readBinaryDeltaSize(delta) 333 334 for len(delta) > 0 { 335 op := delta[0] 336 if op == 0 { 337 return errors.New("invalid delta opcode 0") 338 } 339 340 var n int64 341 var err error 342 switch op & 0x80 { 343 case 0x80: 344 n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src) 345 case 0x00: 346 n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:]) 347 } 348 if err != nil { 349 return err 350 } 351 dstSize -= n 352 } 353 354 if dstSize != 0 { 355 return errors.New("corrupt binary delta: insufficient or extra data") 356 } 357 return nil 358} 359 360// readBinaryDeltaSize reads a variable length size from a delta-encoded binary 361// fragment, returing the size and the unused data. Data is encoded as: 362// 363// [[1xxxxxxx]...] [0xxxxxxx] 364// 365// in little-endian order, with 7 bits of the value per byte. 366func readBinaryDeltaSize(d []byte) (size int64, rest []byte) { 367 shift := uint(0) 368 for i, b := range d { 369 size |= int64(b&0x7F) << shift 370 shift += 7 371 if b <= 0x7F { 372 return size, d[i+1:] 373 } 374 } 375 return size, nil 376} 377 378// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary 379// fragment, returning the amount of data written and the usused part of the 380// fragment. An add operation takes the form: 381// 382// [0xxxxxx][[data1]...] 383// 384// where the lower seven bits of the opcode is the number of data bytes 385// following the opcode. See also pack-format.txt in the Git source. 386func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) { 387 size := int(op) 388 if len(delta) < size { 389 return 0, delta, errors.New("corrupt binary delta: incomplete add") 390 } 391 _, err = w.Write(delta[:size]) 392 return int64(size), delta[size:], err 393} 394 395// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary 396// fragment, returing the amount of data written and the unused part of the 397// fragment. A copy operation takes the form: 398// 399// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3] 400// 401// where the lower seven bits of the opcode determine which non-zero offset and 402// size bytes are present in little-endian order: if bit 0 is set, offset1 is 403// present, etc. If no offset or size bytes are present, offset is 0 and size 404// is 0x10000. See also pack-format.txt in the Git source. 405func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) { 406 const defaultSize = 0x10000 407 408 unpack := func(start, bits uint) (v int64) { 409 for i := uint(0); i < bits; i++ { 410 mask := byte(1 << (i + start)) 411 if op&mask > 0 { 412 if len(delta) == 0 { 413 err = errors.New("corrupt binary delta: incomplete copy") 414 return 415 } 416 v |= int64(delta[0]) << (8 * i) 417 delta = delta[1:] 418 } 419 } 420 return 421 } 422 423 offset := unpack(0, 4) 424 size := unpack(4, 3) 425 if err != nil { 426 return 0, delta, err 427 } 428 if size == 0 { 429 size = defaultSize 430 } 431 432 // TODO(bkeyes): consider pooling these buffers 433 b := make([]byte, size) 434 if _, err := src.ReadAt(b, offset); err != nil { 435 return 0, delta, err 436 } 437 438 _, err = w.Write(b) 439 return size, delta, err 440} 441 442func checkBinarySrcSize(r io.ReaderAt, size int64) error { 443 ok, err := isLen(r, size) 444 if err != nil { 445 return err 446 } 447 if !ok { 448 return &Conflict{"fragment src size does not match actual src size"} 449 } 450 return nil 451}