From 4c6b1e19bdf1205cf2577e2fe2d434e7e7d1535a Mon Sep 17 00:00:00 2001 From: Billy Keyes Date: Sat, 18 Jul 2026 12:24:35 -0400 Subject: [PATCH 1/2] Limit binary fragment size during applies Applying a binary fragment can use a lot of memory in two ways: 1. Inflating a large amount of literal data 2. Processing a delta instruction stream with many copy operations Clients could theoretically protect against the first by checking fragment sizes for all binary fragments before the apply, but there's no way to protect against the second case except by decoding the delta instruction stream. And because the first case used to happen at parse time, there are no existing clients that check this before applying. Fix both issues by enforcing a maximum binary fragment size. The default value is 100MiB, which matches GitHub.com's default maximum file size. Clients can adjust or disabled this limit by passing new option functions to Apply, NewTextApplier, and NewBinaryApplier. Finally, fix an encoding issue in the bin.go helper utility by using the standard BinaryFragment string representation instead of implementing the encoding directly with bad wrapping logic. This was required to generate the test patches for applies over the limit. The `.hex` files are not used directly by the test, but provide the source input for the patches as processed by `bin.go`. --- gitdiff/apply.go | 6 +- gitdiff/apply_binary.go | 66 ++++++++--- gitdiff/apply_options.go | 43 +++++++ gitdiff/apply_test.go | 59 ++++++++-- gitdiff/apply_text.go | 8 +- gitdiff/testdata/apply/bin.go | 109 +++++++++--------- ...in_fragment_delta_error_dst_size_limit.hex | 9 ++ ..._fragment_delta_error_dst_size_limit.patch | 5 + ...fragment_delta_error_dst_size_short.patch} | 0 .../bin_fragment_delta_error_limit_add.hex | 28 +++++ .../bin_fragment_delta_error_limit_add.patch | 5 + .../bin_fragment_delta_error_limit_copy.hex | 18 +++ .../bin_fragment_delta_error_limit_copy.patch | 5 + .../bin_fragment_literal_error_limit.patch | 13 +++ 14 files changed, 290 insertions(+), 84 deletions(-) create mode 100644 gitdiff/apply_options.go create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.hex create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.patch rename gitdiff/testdata/apply/{bin_fragment_delta_error_dst_size.patch => bin_fragment_delta_error_dst_size_short.patch} (100%) create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.hex create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.patch create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.hex create mode 100644 gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.patch create mode 100644 gitdiff/testdata/apply/bin_fragment_literal_error_limit.patch diff --git a/gitdiff/apply.go b/gitdiff/apply.go index eb01c19..e1c7209 100644 --- a/gitdiff/apply.go +++ b/gitdiff/apply.go @@ -97,7 +97,7 @@ var ( // If an error occurs while applying, Apply returns an *ApplyError that // annotates the error with additional information. If the error is because of // a conflict with the source, the wrapped error will be a *Conflict. -func Apply(dst io.Writer, src io.ReaderAt, f *File) error { +func Apply(dst io.Writer, src io.ReaderAt, f *File, opts ...ApplyOption) error { if f.IsBinary { if len(f.TextFragments) > 0 { return applyError(errors.New("binary file contains text fragments")) @@ -113,7 +113,7 @@ func Apply(dst io.Writer, src io.ReaderAt, f *File) error { switch { case f.BinaryFragment != nil: - applier := NewBinaryApplier(dst, src) + applier := NewBinaryApplier(dst, src, opts...) if err := applier.ApplyFragment(f.BinaryFragment); err != nil { return err } @@ -131,7 +131,7 @@ func Apply(dst io.Writer, src io.ReaderAt, f *File) error { // right now, the application fails if fragments overlap, but it should be // possible to precompute the result of applying them in order - applier := NewTextApplier(dst, src) + applier := NewTextApplier(dst, src, opts...) for i, frag := range frags { if err := applier.ApplyFragment(frag); err != nil { return applyError(err, fragNum(i)) diff --git a/gitdiff/apply_binary.go b/gitdiff/apply_binary.go index 1668aca..d3552c2 100644 --- a/gitdiff/apply_binary.go +++ b/gitdiff/apply_binary.go @@ -9,8 +9,9 @@ import ( // BinaryApplier applies binary changes described in a fragment to source data. // The applier must be closed after use. type BinaryApplier struct { - dst io.Writer - src io.ReaderAt + dst io.Writer + src io.ReaderAt + opts applyOptions closed bool dirty bool @@ -18,10 +19,11 @@ type BinaryApplier struct { // NewBinaryApplier creates an BinaryApplier that reads data from src and // writes modified data to dst. -func NewBinaryApplier(dst io.Writer, src io.ReaderAt) *BinaryApplier { +func NewBinaryApplier(dst io.Writer, src io.ReaderAt, opts ...ApplyOption) *BinaryApplier { a := BinaryApplier{ - dst: dst, - src: src, + dst: dst, + src: src, + opts: collectApplyOptions(opts), } return &a } @@ -44,9 +46,17 @@ func (a *BinaryApplier) ApplyFragment(f *BinaryFragment) error { return applyError(errApplyInProgress) } - // mark an apply as in progress, even if it fails before making changes + // Mark an apply as in progress, even if it fails before making changes a.dirty = true + // Verify the binary data does not exceed the limit before decompressing + // it. The reader from Data() will not read more that f.Size+1 bytes, so we + // only need to check the expected size against the limit. + limit := a.opts.maxBinaryFragmentBytes + if limit >= 0 && f.Size > limit { + return applyError(fmt.Errorf("binary fragment size of %d exceeds %d byte limit", f.Size, limit)) + } + switch f.Method { case BinaryPatchLiteral: if _, err := io.Copy(a.dst, f.Data()); err != nil { @@ -57,7 +67,7 @@ func (a *BinaryApplier) ApplyFragment(f *BinaryFragment) error { if err != nil { return applyError(err) } - if err := applyBinaryDeltaFragment(a.dst, a.src, data); err != nil { + if err := applyBinaryDeltaFragment(a.dst, a.src, data, limit); err != nil { return applyError(err) } default: @@ -82,13 +92,26 @@ func (a *BinaryApplier) Close() (err error) { return err } -func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error { +func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte, limit int64) error { srcSize, delta := readBinaryDeltaSize(frag) if err := checkBinarySrcSize(src, srcSize); err != nil { return err } + // As a form of compression, delta instructures can create a lot of data in + // the destination from a small instruction set. First, check the expected + // size against the limit. Then, while executing instructions, stop as soon + // as a write exceeds the expected limit. dstSize, delta := readBinaryDeltaSize(delta) + if limit >= 0 && dstSize > limit { + return fmt.Errorf("binary delta size of %d exceeds %d byte limit", dstSize, limit) + } + + dstLimit := &limitWriter{ + w: dst, + limit: dstSize, + limitErr: errors.New("corrupt binary delta: extra data"), + } for len(delta) > 0 { op := delta[0] @@ -96,22 +119,20 @@ func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error return errors.New("invalid delta opcode 0") } - var n int64 var err error switch op & 0x80 { case 0x80: - n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src) + _, delta, err = applyBinaryDeltaCopy(dstLimit, op, delta[1:], src) case 0x00: - n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:]) + _, delta, err = applyBinaryDeltaAdd(dstLimit, op, delta[1:]) } if err != nil { return err } - dstSize -= n } - if dstSize != 0 { - return errors.New("corrupt binary delta: insufficient or extra data") + if dstLimit.limit > 0 { + return errors.New("corrupt binary delta: insufficient data") } return nil } @@ -208,3 +229,20 @@ func checkBinarySrcSize(r io.ReaderAt, size int64) error { } return nil } + +// limitWriter is an io.Writer that writes at most limit bytes to the wrapped +// io.Writer, then returns limitErr for any Write calls that exceed the limit. +type limitWriter struct { + w io.Writer + limit int64 + limitErr error +} + +func (w *limitWriter) Write(p []byte) (n int, err error) { + if int64(len(p)) > w.limit { + return 0, w.limitErr + } + n, err = w.w.Write(p) + w.limit -= int64(n) + return +} diff --git a/gitdiff/apply_options.go b/gitdiff/apply_options.go new file mode 100644 index 0000000..d454ab2 --- /dev/null +++ b/gitdiff/apply_options.go @@ -0,0 +1,43 @@ +package gitdiff + +const ( + // This default size is arbitrary, but 100MiB matches the largest allowed + // size of a file in GitHub.com as of July 2026. That suggests that 100MiB + // should cover most practical patches the library will encounter. + defaultMaxBinaryFragmentBytes = 100 * 1024 * 1024 +) + +type applyOptions struct { + maxBinaryFragmentBytes int64 +} + +// An ApplyOption modifies the behavior of [TextApplier] and [BinaryApplier]. +type ApplyOption func(*applyOptions) + +// WithMaxBinaryFragmentBytes sets the maximum size in bytes of any individual +// binary fragment in a patch. Passing a negative value disables the limit, +// allowing fragments of any size. +// +// When applying BinaryPatchLiteral fragments, this limits the size of the +// decompressed fragment content. When applying BinaryPatchDelta fragments, +// this limits both the size of the decompressed delta instruction stream and +// the size of the data generated by executing the instructions. +// +// Passing this option to [TextApplier] has no effect. +// +// The default value if unspecified is 100MiB. +func WithMaxBinaryFragmentBytes(b int64) ApplyOption { + return func(opts *applyOptions) { + opts.maxBinaryFragmentBytes = b + } +} + +func collectApplyOptions(allOpts []ApplyOption) applyOptions { + opts := applyOptions{ + maxBinaryFragmentBytes: defaultMaxBinaryFragmentBytes, + } + for _, opt := range allOpts { + opt(&opts) + } + return opts +} diff --git a/gitdiff/apply_test.go b/gitdiff/apply_test.go index 7edf507..c43e9c6 100644 --- a/gitdiff/apply_test.go +++ b/gitdiff/apply_test.go @@ -83,7 +83,7 @@ func TestApplyTextFragment(t *testing.T) { if len(file.TextFragments) != 1 { t.Fatalf("patch should contain exactly one fragment, but it has %d", len(file.TextFragments)) } - applier := NewTextApplier(dst, src) + applier := NewTextApplier(dst, src, test.Options...) return applier.ApplyFragment(file.TextFragments[0]) }) }) @@ -97,6 +97,16 @@ func TestApplyBinaryFragment(t *testing.T) { "deltaModify": {Files: getApplyFiles("bin_fragment_delta_modify")}, "deltaModifyLarge": {Files: getApplyFiles("bin_fragment_delta_modify_large")}, + "errorLiteralExceedsLimit": { + Files: applyFiles{ + Src: "bin_fragment_literal_create.src", + Patch: "bin_fragment_literal_error_limit.patch", + }, + Options: []ApplyOption{ + WithMaxBinaryFragmentBytes(200), + }, + Err: "exceeds 200 byte limit", + }, "errorIncompleteAdd": { Files: applyFiles{ Src: "bin_fragment_delta_error.src", @@ -111,26 +121,56 @@ func TestApplyBinaryFragment(t *testing.T) { }, Err: "incomplete copy", }, - "errorSrcSize": { + "errorDeltaIncorrectSrcSize": { Files: applyFiles{ Src: "bin_fragment_delta_error.src", Patch: "bin_fragment_delta_error_src_size.patch", }, Err: &Conflict{}, }, - "errorDstSize": { + "errorDeltaDstSizeShort": { + Files: applyFiles{ + Src: "bin_fragment_delta_error.src", + Patch: "bin_fragment_delta_error_dst_size_short.patch", + }, + Err: "insufficient", + }, + "errorDeltaDstSizeExceedsLimit": { Files: applyFiles{ Src: "bin_fragment_delta_error.src", - Patch: "bin_fragment_delta_error_dst_size.patch", + Patch: "bin_fragment_delta_error_dst_size_limit.patch", + }, + Options: []ApplyOption{ + WithMaxBinaryFragmentBytes(200), + }, + Err: "exceeds 200 byte limit", + }, + "errorDeltaExceedsLimitOnAdd": { + Files: applyFiles{ + Src: "bin_fragment_delta_error.src", + Patch: "bin_fragment_delta_error_limit_add.patch", + }, + Options: []ApplyOption{ + WithMaxBinaryFragmentBytes(1024), + }, + Err: "extra data", + }, + "errorDeltaExceedsLimitOnCopy": { + Files: applyFiles{ + Src: "bin_fragment_delta_error.src", + Patch: "bin_fragment_delta_error_limit_copy.patch", + }, + Options: []ApplyOption{ + WithMaxBinaryFragmentBytes(1024), }, - Err: "insufficient or extra data", + Err: "extra data", }, } for name, test := range tests { t.Run(name, func(t *testing.T) { test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error { - applier := NewBinaryApplier(dst, src) + applier := NewBinaryApplier(dst, src, test.Options...) return applier.ApplyFragment(file.BinaryFragment) }) }) @@ -171,15 +211,16 @@ func TestApplyFile(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error { - return Apply(dst, src, file) + return Apply(dst, src, file, test.Options...) }) }) } } type applyTest struct { - Files applyFiles - Err any + Files applyFiles + Options []ApplyOption + Err any } func (at applyTest) run(t *testing.T, apply func(io.Writer, io.ReaderAt, *File) error) { diff --git a/gitdiff/apply_text.go b/gitdiff/apply_text.go index 3e307b7..0948178 100644 --- a/gitdiff/apply_text.go +++ b/gitdiff/apply_text.go @@ -17,6 +17,7 @@ type TextApplier struct { src io.ReaderAt lineSrc LineReaderAt nextLine int64 + opts applyOptions closed bool dirty bool @@ -24,10 +25,11 @@ type TextApplier struct { // NewTextApplier creates a TextApplier that reads data from src and writes // modified data to dst. If src implements LineReaderAt, it is used directly. -func NewTextApplier(dst io.Writer, src io.ReaderAt) *TextApplier { +func NewTextApplier(dst io.Writer, src io.ReaderAt, opts ...ApplyOption) *TextApplier { a := TextApplier{ - dst: dst, - src: src, + dst: dst, + src: src, + opts: collectApplyOptions(opts), } if lineSrc, ok := src.(LineReaderAt); ok { diff --git a/gitdiff/testdata/apply/bin.go b/gitdiff/testdata/apply/bin.go index 49dfd27..a705542 100644 --- a/gitdiff/testdata/apply/bin.go +++ b/gitdiff/testdata/apply/bin.go @@ -10,49 +10,20 @@ package main import ( "bytes" "compress/zlib" - "encoding/binary" + "encoding/hex" "flag" "io" - "io/ioutil" "log" "os" - "strings" + "unicode" "github.com/bluekeyes/go-gitdiff/gitdiff" ) -var ( - b85Powers = []uint32{52200625, 614125, 7225, 85, 1} - b85Alpha = []byte( - "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "!#$%&()*+-;<=>?@^_`{|}~", - ) -) - var mode string var compression bool - -func base85Encode(data []byte) []byte { - chunks, remaining := len(data)/4, len(data)%4 - if remaining > 0 { - data = append(data, make([]byte, 4-remaining)...) - chunks++ - } - - var n int - out := make([]byte, 5*chunks) - - for i := 0; i < len(data); i += 4 { - v := binary.BigEndian.Uint32(data[i : i+4]) - for j := 0; j < 5; j++ { - p := v / b85Powers[j] - out[n+j] = b85Alpha[p] - v -= b85Powers[j] * p - } - n += 5 - } - - return out -} +var hexInput bool +var method string func compress(data []byte) ([]byte, error) { var b bytes.Buffer @@ -68,29 +39,11 @@ func compress(data []byte) ([]byte, error) { return b.Bytes(), nil } -func wrap(data []byte) string { - var s strings.Builder - for i := 0; i < len(data); i += 52 { - c := 52 - if c > len(data)-i { - c = len(data) - i - } - b := (c / 5) * 4 - - if b <= 26 { - s.WriteByte(byte('A' + b - 1)) - } else { - s.WriteByte(byte('a' + b - 27)) - } - s.Write(data[i : i+c]) - s.WriteByte('\n') - } - return s.String() -} - func init() { flag.StringVar(&mode, "mode", "parse", "operation mode, one of 'parse' or 'encode'") flag.BoolVar(&compression, "compression", true, "if true, decompress data in 'parse' mode and compress it in 'encode' mode") + flag.BoolVar(&hexInput, "hex", false, "in encode mode, treat the input as a hexidecimal string (removing whitespace and comments)") + flag.StringVar(&method, "method", "literal", "in encode mode, the method of the binary fragment, one of 'literal' or 'delta'") } func main() { @@ -121,19 +74,65 @@ func main() { } case "encode": - data, err := ioutil.ReadAll(os.Stdin) + var frag gitdiff.BinaryFragment + switch method { + case "literal": + frag.Method = gitdiff.BinaryPatchLiteral + case "delta": + frag.Method = gitdiff.BinaryPatchDelta + default: + log.Fatalf("invalid method: %s", method) + } + + data, err := io.ReadAll(os.Stdin) if err != nil { log.Fatalf("failed to read input: %v", err) } + + if hexInput { + data, err = decodeHexInput(data) + if err != nil { + log.Fatalf("input contains invalid hex data: %v", err) + } + } + + // Size is always for the uncompressed data, set it before compressing + frag.Size = int64(len(data)) + if compression { data, err = compress(data) if err != nil { log.Fatalf("failed to compress data: %v", err) } } - os.Stdout.WriteString(wrap(base85Encode(data))) + + frag.RawData = data + os.Stdout.WriteString(frag.String()) default: log.Fatalf("unknown mode: %s", mode) } } + +func decodeHexInput(input []byte) ([]byte, error) { + var err error + var data []byte + for line := range bytes.Lines(input) { + line := bytes.Map(dropSpaces, line) + if len(line) == 0 || line[0] == '#' { + continue + } + data, err = hex.AppendDecode(data, line) + if err != nil { + return data, err + } + } + return data, nil +} + +func dropSpaces(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r +} diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.hex b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.hex new file mode 100644 index 0000000..692cc9f --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.hex @@ -0,0 +1,9 @@ +# source size - 130 bytes +82 01 + +# result size - 260 bytes +88 02 + +# 8x delta copy operations of 130 bytes at offset 0 +90 82 +90 82 diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.patch new file mode 100644 index 0000000..41256b7 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_limit.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 8 +Tc${it>|mPEG@%I)0096048a33 + diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_short.patch similarity index 100% rename from gitdiff/testdata/apply/bin_fragment_delta_error_dst_size.patch rename to gitdiff/testdata/apply/bin_fragment_delta_error_dst_size_short.patch diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.hex b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.hex new file mode 100644 index 0000000..2613e12 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.hex @@ -0,0 +1,28 @@ +# source size - 130 bytes +82 01 + +# result size - claimed 1024 bytes, actually 1100 +80 08 + +# 10x delta copy operations of 100 bytes at offset 0 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 + +# 1x delta add operation of 64 bytes +40 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 +01 00 01 01 00 01 00 01 diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.patch new file mode 100644 index 0000000..e9cd087 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_add.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 89 +cc${itY~YxXf(acM85kKEAOu-7AOHaW|3VN7`~Uy| + diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.hex b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.hex new file mode 100644 index 0000000..df1db6c --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.hex @@ -0,0 +1,18 @@ +# source size - 130 bytes +82 01 + +# result size - claimed 1024 bytes, actually 1100 +80 08 + +# 11x delta copy operations of 100 bytes at offset 0 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 +90 64 diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.patch b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.patch new file mode 100644 index 0000000..bf2029c --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_delta_error_limit_copy.patch @@ -0,0 +1,5 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_delta_error.src b/gitdiff/testdata/apply/bin_fragment_delta_error.src +GIT binary patch +delta 26 +Tc${itY~YxXf(-%!00960lcNiW + diff --git a/gitdiff/testdata/apply/bin_fragment_literal_error_limit.patch b/gitdiff/testdata/apply/bin_fragment_literal_error_limit.patch new file mode 100644 index 0000000..2058854 --- /dev/null +++ b/gitdiff/testdata/apply/bin_fragment_literal_error_limit.patch @@ -0,0 +1,13 @@ +diff --git a/gitdiff/testdata/apply/bin_fragment_literal_create.src b/gitdiff/testdata/apply/bin_fragment_literal_create.src +GIT binary patch +literal 256 +zc$@$L0ssE~1Zc?U@Z18zeVql2Z+OX2?`MB@-)6s=B?NrLd25lQZhkW#HnOm(w0tfJ +z>=*tf;y#agZO%Q(CSMaN?#=h&z+H8}v%mK;&V%{q- +z#~YUBr(*NYPyXm6eE5@%8bmnOcbJI{!cu;m +z_n39~?nd`D4N>H9^iLk%q@5IeH?n=m$0x6i&aQ(_}%~74=_ZSSHP8 +zXz-tr>k<7|C`CWed_ms*mu6QUUkhV1KYx&5e-E7vsRc`8H*SO-xiFOB8d-UVD4$Z{ +LN|d+(00960;hlnx + +literal 0 +HcmV?d00001 + From 97fb6db46890758ebfff3a2a6dc72f39a9ca88de Mon Sep 17 00:00:00 2001 From: Billy Keyes Date: Sat, 18 Jul 2026 14:43:45 -0400 Subject: [PATCH 2/2] Improve some comments --- gitdiff/apply_binary.go | 2 +- gitdiff/apply_options.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gitdiff/apply_binary.go b/gitdiff/apply_binary.go index d3552c2..4864b69 100644 --- a/gitdiff/apply_binary.go +++ b/gitdiff/apply_binary.go @@ -98,7 +98,7 @@ func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte, limit return err } - // As a form of compression, delta instructures can create a lot of data in + // As a form of compression, delta instructions can create a lot of data in // the destination from a small instruction set. First, check the expected // size against the limit. Then, while executing instructions, stop as soon // as a write exceeds the expected limit. diff --git a/gitdiff/apply_options.go b/gitdiff/apply_options.go index d454ab2..de60165 100644 --- a/gitdiff/apply_options.go +++ b/gitdiff/apply_options.go @@ -20,8 +20,9 @@ type ApplyOption func(*applyOptions) // // When applying BinaryPatchLiteral fragments, this limits the size of the // decompressed fragment content. When applying BinaryPatchDelta fragments, -// this limits both the size of the decompressed delta instruction stream and -// the size of the data generated by executing the instructions. +// this independently limits both the size of the decompressed delta +// instruction stream and the size of the data generated by executing the +// instructions. // // Passing this option to [TextApplier] has no effect. //