fork of go-gitdiff with jj support
at v0.7.4 5.3 kB view raw
1package gitdiff 2 3import ( 4 "errors" 5 "fmt" 6 "io" 7) 8 9// BinaryApplier applies binary changes described in a fragment to source data. 10// The applier must be closed after use. 11type BinaryApplier struct { 12 dst io.Writer 13 src io.ReaderAt 14 15 closed bool 16 dirty bool 17} 18 19// NewBinaryApplier creates an BinaryApplier that reads data from src and 20// writes modified data to dst. 21func NewBinaryApplier(dst io.Writer, src io.ReaderAt) *BinaryApplier { 22 a := BinaryApplier{ 23 dst: dst, 24 src: src, 25 } 26 return &a 27} 28 29// ApplyFragment applies the changes in the fragment f and writes the result to 30// dst. ApplyFragment can be called at most once. 31// 32// If an error occurs while applying, ApplyFragment returns an *ApplyError that 33// annotates the error with additional information. If the error is because of 34// a conflict between a fragment and the source, the wrapped error will be a 35// *Conflict. 36func (a *BinaryApplier) ApplyFragment(f *BinaryFragment) error { 37 if f == nil { 38 return applyError(errors.New("nil fragment")) 39 } 40 if a.closed { 41 return applyError(errApplierClosed) 42 } 43 if a.dirty { 44 return applyError(errApplyInProgress) 45 } 46 47 // mark an apply as in progress, even if it fails before making changes 48 a.dirty = true 49 50 switch f.Method { 51 case BinaryPatchLiteral: 52 if _, err := a.dst.Write(f.Data); err != nil { 53 return applyError(err) 54 } 55 case BinaryPatchDelta: 56 if err := applyBinaryDeltaFragment(a.dst, a.src, f.Data); err != nil { 57 return applyError(err) 58 } 59 default: 60 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method)) 61 } 62 return nil 63} 64 65// Close writes any data following the last applied fragment and prevents 66// future calls to ApplyFragment. 67func (a *BinaryApplier) Close() (err error) { 68 if a.closed { 69 return nil 70 } 71 72 a.closed = true 73 if !a.dirty { 74 _, err = copyFrom(a.dst, a.src, 0) 75 } else { 76 // do nothing, applying a binary fragment copies all data 77 } 78 return err 79} 80 81func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error { 82 srcSize, delta := readBinaryDeltaSize(frag) 83 if err := checkBinarySrcSize(src, srcSize); err != nil { 84 return err 85 } 86 87 dstSize, delta := readBinaryDeltaSize(delta) 88 89 for len(delta) > 0 { 90 op := delta[0] 91 if op == 0 { 92 return errors.New("invalid delta opcode 0") 93 } 94 95 var n int64 96 var err error 97 switch op & 0x80 { 98 case 0x80: 99 n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src) 100 case 0x00: 101 n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:]) 102 } 103 if err != nil { 104 return err 105 } 106 dstSize -= n 107 } 108 109 if dstSize != 0 { 110 return errors.New("corrupt binary delta: insufficient or extra data") 111 } 112 return nil 113} 114 115// readBinaryDeltaSize reads a variable length size from a delta-encoded binary 116// fragment, returing the size and the unused data. Data is encoded as: 117// 118// [[1xxxxxxx]...] [0xxxxxxx] 119// 120// in little-endian order, with 7 bits of the value per byte. 121func readBinaryDeltaSize(d []byte) (size int64, rest []byte) { 122 shift := uint(0) 123 for i, b := range d { 124 size |= int64(b&0x7F) << shift 125 shift += 7 126 if b <= 0x7F { 127 return size, d[i+1:] 128 } 129 } 130 return size, nil 131} 132 133// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary 134// fragment, returning the amount of data written and the usused part of the 135// fragment. An add operation takes the form: 136// 137// [0xxxxxx][[data1]...] 138// 139// where the lower seven bits of the opcode is the number of data bytes 140// following the opcode. See also pack-format.txt in the Git source. 141func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) { 142 size := int(op) 143 if len(delta) < size { 144 return 0, delta, errors.New("corrupt binary delta: incomplete add") 145 } 146 _, err = w.Write(delta[:size]) 147 return int64(size), delta[size:], err 148} 149 150// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary 151// fragment, returing the amount of data written and the unused part of the 152// fragment. A copy operation takes the form: 153// 154// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3] 155// 156// where the lower seven bits of the opcode determine which non-zero offset and 157// size bytes are present in little-endian order: if bit 0 is set, offset1 is 158// present, etc. If no offset or size bytes are present, offset is 0 and size 159// is 0x10000. See also pack-format.txt in the Git source. 160func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) { 161 const defaultSize = 0x10000 162 163 unpack := func(start, bits uint) (v int64) { 164 for i := uint(0); i < bits; i++ { 165 mask := byte(1 << (i + start)) 166 if op&mask > 0 { 167 if len(delta) == 0 { 168 err = errors.New("corrupt binary delta: incomplete copy") 169 return 170 } 171 v |= int64(delta[0]) << (8 * i) 172 delta = delta[1:] 173 } 174 } 175 return 176 } 177 178 offset := unpack(0, 4) 179 size := unpack(4, 3) 180 if err != nil { 181 return 0, delta, err 182 } 183 if size == 0 { 184 size = defaultSize 185 } 186 187 // TODO(bkeyes): consider pooling these buffers 188 b := make([]byte, size) 189 if _, err := src.ReadAt(b, offset); err != nil { 190 return 0, delta, err 191 } 192 193 _, err = w.Write(b) 194 return size, delta, err 195} 196 197func checkBinarySrcSize(r io.ReaderAt, size int64) error { 198 ok, err := isLen(r, size) 199 if err != nil { 200 return err 201 } 202 if !ok { 203 return &Conflict{"fragment src size does not match actual src size"} 204 } 205 return nil 206}