Skip to main content

znippy_plugin_git/
delta.rs

1//! **Computing a git delta**, for the one entry shape this engine cannot copy.
2//!
3//! # Why an engine whose whole claim is "it copies" computes anything at all
4//!
5//! [`crate::git_ops::GitStore::emit_set`] copies stored pack entries byte for
6//! byte, and that is the product. One shape defeats it: a stored delta whose
7//! base falls **outside** the request. The base cannot be added — a base pulled
8//! into a narrowed clone drags an object the client never asked for, and if it
9//! is a tree it arrives owing children the pack does not carry, which is the
10//! `did not receive expected object` defect §2 of the standing report records.
11//! Naming the base by oid in a `REF_DELTA` works for a *fetch*, where the
12//! receiver already holds it, and cannot work for a *clone*, where the receiver
13//! holds nothing.
14//!
15//! That left exactly one answer for a clone: **send the object whole**. Measured
16//! on `h2h-linear-sha1-2048c-1024f-16k`, that is 961 of 31 805 entries — 3.0 % —
17//! and it doubled the wire: 11 613 666 bytes where stock git sends 5 820 000 for
18//! the identical object set. Three per cent of the entries were essentially all
19//! of a 2× gap, because the 961 are 16 KiB blobs whose stored delta is 284 bytes.
20//!
21//! So this module exists to answer the third option: **re-delta the boundary
22//! entry against something the pack DOES carry.** It is the only place in this
23//! crate that computes a delta, `crate::git_ops::GitStore::whole_entry` is the
24//! only other place that deflates on a serving path, and both run on the same
25//! boundary fraction and nowhere else.
26//!
27//! # The receipt stays a fact
28//!
29//! An entry this produces is [`crate::pack_walk::EmitEntry::recompressed`], same
30//! as the whole rebuild it replaces — its payload really was inflated and
31//! re-deflated, and `copied + recompressed == objects` is unchanged. What is new
32//! is [`crate::pack_walk::EmitEntry::deltified`], a strict subset of
33//! `recompressed`: *of the entries that had to be rebuilt, this many went out as
34//! a computed delta rather than whole*. A full clone measures all three as zero,
35//! because a whole-repository request contains every base.
36//!
37//! # The format
38//!
39//! git's delta stream, which [`crate::resolve`] already applies: the base's size
40//! and the target's size as LEB128, then instructions. A byte with the top bit
41//! set is a **copy** — the low nibble selects which of four offset bytes follow,
42//! bits 4-6 which of three size bytes, all little-endian and all absent when
43//! zero. A byte with the top bit clear is an **insert** of that many literal
44//! bytes, 1..=127.
45//!
46//! Two encoding hazards, both avoided by construction rather than by comment:
47//!
48//! * a copy whose three size bytes are all zero means **65536**, not zero, so a
49//!   zero-length copy must never be emitted — [`Delta::copy`] is only ever
50//!   called with `len >= MIN_MATCH`;
51//! * a copy offset is four bytes, so a base at or past 4 GiB cannot be named.
52//!   [`delta`] refuses such a base up front rather than truncating one.
53//!
54//! # The search, and what it deliberately does not do
55//!
56//! `git pack-objects` sorts every object by type, path hash and size and then
57//! tries a sliding window of 10 candidates per object, at `pack.depth` 50. That
58//! is a *global* search and it is the expensive half of packing.
59//!
60//! This does none of it. The caller arrives with **one** candidate base — the
61//! nearest ancestor of the entry's own stored delta chain that is inside the
62//! request — and this module only has to encode against it. The candidate is
63//! free: it is read out of the archive's own back-references, which are the
64//! packer's original similarity judgement, already made and already stored.
65//!
66//! # What it produced, end to end
67//!
68//! One server process, one seeded store, oden 2026-08-11, `git fsck --full
69//! --strict` exit 0 on every arm. The only difference between the columns is
70//! [`enabled`]:
71//!
72//! ```text
73//! arm                      objects   whole (off)   re-delta (on)
74//! full clone                36 172     6 200 252     6 200 252    unchanged
75//! narrowed clone `base`     31 805    11 613 666     6 424 726    1.81x
76//! --filter=blob:none        16 491     1 566 022     1 566 022    unchanged
77//! incremental fetch          4 367     1 339 879     1 339 879    unchanged
78//! ```
79//!
80//! The narrowed pack's own entry shapes say where it went: **3 153 whole
81//! entries become 2 294**, so exactly **859** of the 961 found an in-request
82//! ancestor — the number this module's search predicted before it was written —
83//! and 5 188 940 bytes came off, 6 041 per entry. Stock git sends 5 820 000 for
84//! the same 31 805 objects, so what was **2.00× git** is now **1.10×**.
85//!
86//! # Cost
87//!
88//! One pass over the base to index it and one over the target to encode, both
89//! linear, with a 16-byte block hash and a bounded collision chain, plus one
90//! inflate of the base — which the single-slot cache spares for a run of
91//! entries sharing one ancestor.
92//!
93//! Against that, the *deflate* it feeds is strictly cheaper than the one it
94//! replaces: a ~1 KB delta instead of a 16 KiB object. The two roughly cancel.
95//! Measured on the server's own `utime + stime` over 10 narrowed clones, three
96//! interleaved rounds, CPU pressure gated below 2.00: **139 ms per clone with
97//! it on, 137 ms with it off**, against a within-arm spread of 133-145. So the
98//! 1.81× on the wire costs nothing measurable in CPU, which is the trade this
99//! engine had to be able to make and could not be assumed.
100
101use anyhow::Result;
102
103/// **Is the boundary re-delta on?** `true` unless `ZNIPPY_GIT_BOUNDARY_DELTA` is
104/// set to `0`, `off` or `false`.
105///
106/// It exists so the trade this module makes — wire bytes against clone CPU — can
107/// be measured as an A/B on **one binary against one seeded store**, rather than
108/// against two builds whose difference nobody can pin down afterwards. Read once
109/// and cached: this is on the path of every request, and a `getenv` per served
110/// object is exactly the kind of cost an engine that claims CPU should not add.
111///
112/// Off, every boundary entry is shipped whole, which is the behaviour before
113/// this module existed and is always correct.
114pub fn enabled() -> bool {
115    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
116    *ON.get_or_init(|| match crate::arms::read_env(crate::arms::ENV_BOUNDARY_DELTA) {
117        Some(v) => !matches!(v.to_ascii_lowercase().as_str(), "0" | "off" | "false"),
118        None => true,
119    })
120}
121
122/// The block size the base is indexed at, and the shortest match worth a copy
123/// instruction.
124///
125/// git's `diff-delta.c` uses 16 for the same two purposes. Below it a copy
126/// instruction (1-8 bytes) stops being cheaper than the literal it replaces.
127const MIN_MATCH: usize = 16;
128
129/// How many index entries sharing one hash bucket are probed before giving up
130/// on the position.
131///
132/// A bound and not a completeness property: a base full of one repeated block
133/// would otherwise make the encoder quadratic. git bounds the same walk.
134const MAX_PROBE: usize = 4;
135
136/// A copy instruction names its base offset in **four** bytes.
137const MAX_BASE: usize = u32::MAX as usize;
138
139/// A copy instruction names its length in **three** bytes. `0` is not zero, it
140/// is 65536, so lengths are capped below the point where that could be emitted.
141const MAX_COPY: usize = 0xff_ffff;
142
143/// **A git delta that rebuilds `target` out of `base`**, or `None` when there is
144/// no point sending one.
145///
146/// `None` — not an error — for every case where the whole object is the better
147/// answer, and the caller's fallback is exactly that. Two conditions produce it,
148/// and only two:
149///
150/// * a base at or past 4 GiB, which a copy offset cannot name. Checked up
151///   front, because nothing downstream could notice a truncated offset;
152/// * **a delta that came out no smaller than the target itself.** This is the
153///   guard that makes a badly chosen candidate merely useless rather than
154///   harmful, and it is also what answers every degenerate input — an empty
155///   base, an empty target, a base shorter than one block — without a special
156///   case for any of them, because all of them produce a literal-only encoding
157///   that is longer than what it encodes. An earlier revision checked the
158///   empties separately; removing that check changed no outcome on any input,
159///   which is the definition of a guard that could never fail.
160///
161/// The size comparison is against the **raw** target length rather than the
162/// deflated one, because the delta is deflated afterwards by the same encoder at
163/// the same level: comparing the two before compression compares like with like
164/// and costs no extra deflate to decide.
165pub fn delta(base: &[u8], target: &[u8]) -> Result<Option<Vec<u8>>> {
166    if base.len() > MAX_BASE {
167        return Ok(None);
168    }
169
170    let index = BlockIndex::build(base);
171    let mut d = Delta::new(base.len(), target.len());
172
173    let mut i = 0usize;
174    let mut literal_from = 0usize;
175    while i < target.len() {
176        let m = if i + MIN_MATCH <= target.len() {
177            index.longest_match(base, target, i)
178        } else {
179            None
180        };
181        match m {
182            Some((at, len)) => {
183                d.literal(&target[literal_from..i]);
184                d.copy(at, len);
185                i += len;
186                literal_from = i;
187            }
188            None => i += 1,
189        }
190        // A delta that has already outgrown its target cannot win, and every
191        // further byte of search is spent for nothing.
192        if d.out.len() >= target.len() {
193            return Ok(None);
194        }
195    }
196    d.literal(&target[literal_from..]);
197
198    if d.out.len() >= target.len() {
199        return Ok(None);
200    }
201    Ok(Some(d.out))
202}
203
204/// The delta being built, and the two instruction encoders.
205struct Delta {
206    out: Vec<u8>,
207}
208
209impl Delta {
210    fn new(base_len: usize, target_len: usize) -> Self {
211        // The delta will be a fraction of the target for the shape this runs on;
212        // a quarter is a starting point, not a bound.
213        let mut out = Vec::with_capacity(target_len / 4 + 32);
214        varint(&mut out, base_len as u64);
215        varint(&mut out, target_len as u64);
216        Delta { out }
217    }
218
219    /// Literal bytes, in runs of at most 127 — the largest an insert opcode can
220    /// name, because the opcode *is* the length and its top bit means copy.
221    fn literal(&mut self, bytes: &[u8]) {
222        for chunk in bytes.chunks(0x7f) {
223            self.out.push(chunk.len() as u8);
224            self.out.extend_from_slice(chunk);
225        }
226    }
227
228    /// `base[at..at + len]`, as one or more copy instructions.
229    ///
230    /// **`len` must be non-zero**: three zero size bytes decode as 65536, so an
231    /// empty copy is not encodable and is not a thing this is ever asked for —
232    /// the only caller has already matched at least [`MIN_MATCH`] bytes.
233    fn copy(&mut self, at: usize, len: usize) {
234        debug_assert!(len > 0, "a zero-length copy encodes as 65536");
235        let mut at = at;
236        let mut left = len;
237        while left > 0 {
238            let n = left.min(MAX_COPY);
239            let opcode_at = self.out.len();
240            self.out.push(0x80);
241            let mut op = 0x80u8;
242            for shift in 0..4 {
243                let b = ((at >> (shift * 8)) & 0xff) as u8;
244                if b != 0 {
245                    op |= 1 << shift;
246                    self.out.push(b);
247                }
248            }
249            for shift in 0..3 {
250                let b = ((n >> (shift * 8)) & 0xff) as u8;
251                if b != 0 {
252                    op |= 0x10 << shift;
253                    self.out.push(b);
254                }
255            }
256            self.out[opcode_at] = op;
257            at += n;
258            left -= n;
259        }
260    }
261}
262
263/// git's delta-header size encoding: LEB128, little end first.
264fn varint(out: &mut Vec<u8>, mut n: u64) {
265    loop {
266        let mut b = (n & 0x7f) as u8;
267        n >>= 7;
268        if n > 0 {
269            b |= 0x80;
270        }
271        out.push(b);
272        if n == 0 {
273            return;
274        }
275    }
276}
277
278/// Where each 16-byte block of the base starts, keyed by a hash of its content.
279///
280/// Open buckets in one flat `Vec` with a power-of-two mask, and a per-bucket
281/// chain held as a second `Vec` of "next" links — one allocation each rather
282/// than a `HashMap<u64, Vec<u32>>`, which is one allocation *per distinct
283/// block* on a path that runs per object served.
284struct BlockIndex {
285    /// `mask + 1` buckets, each the head of a chain or `NONE`.
286    head: Vec<u32>,
287    /// For block `k`, the next block in its bucket, or `NONE`.
288    next: Vec<u32>,
289    mask: usize,
290}
291
292const NONE: u32 = u32::MAX;
293
294impl BlockIndex {
295    fn build(base: &[u8]) -> Self {
296        let blocks = base.len() / MIN_MATCH;
297        let mut buckets = 1usize;
298        while buckets < blocks.max(1) * 2 {
299            buckets <<= 1;
300        }
301        let mut idx = BlockIndex {
302            head: vec![NONE; buckets],
303            next: vec![NONE; blocks],
304            mask: buckets - 1,
305        };
306        // Inserted **last block first**, so each bucket's chain runs
307        // lowest-offset-first afterwards. Two blocks with identical content are
308        // then probed in base order, which keeps the encoder's output a function
309        // of its inputs and not of the insertion order.
310        for k in (0..blocks).rev() {
311            let at = k * MIN_MATCH;
312            let b = (block_hash(&base[at..at + MIN_MATCH]) as usize) & idx.mask;
313            idx.next[k] = idx.head[b];
314            idx.head[b] = k as u32;
315        }
316        idx
317    }
318
319    /// The longest run of `target` from `from` that also appears in `base`, and
320    /// where in `base` it starts — or `None` if nothing reaches [`MIN_MATCH`].
321    fn longest_match(
322        &self,
323        base: &[u8],
324        target: &[u8],
325        from: usize,
326    ) -> Option<(usize, usize)> {
327        let probe = &target[from..from + MIN_MATCH];
328        let b = (block_hash(probe) as usize) & self.mask;
329        let mut best: Option<(usize, usize)> = None;
330        let mut k = self.head[b];
331        let mut tried = 0usize;
332        while k != NONE && tried < MAX_PROBE {
333            let at = k as usize * MIN_MATCH;
334            k = self.next[k as usize];
335            // The hash is a hint. Two different blocks share a bucket often
336            // enough that skipping this comparison would emit a copy of bytes
337            // the base does not hold — a pack that `index-pack` accepts and
338            // whose objects hash to something else.
339            if &base[at..at + MIN_MATCH] != probe {
340                tried += 1;
341                continue;
342            }
343            tried += 1;
344            let mut len = MIN_MATCH;
345            while at + len < base.len()
346                && from + len < target.len()
347                && base[at + len] == target[from + len]
348                && len < MAX_COPY
349            {
350                len += 1;
351            }
352            if best.is_none_or(|(_, prev)| len > prev) {
353                best = Some((at, len));
354            }
355        }
356        best
357    }
358}
359
360/// A cheap, well-mixed hash of exactly [`MIN_MATCH`] bytes.
361///
362/// Two `u64` reads and a multiply rather than a byte loop: this runs once per
363/// **byte** of every target that does not match, so a 16-iteration loop here is
364/// a 16× cost on the encoder's hot path.
365#[inline]
366fn block_hash(b: &[u8]) -> u64 {
367    debug_assert_eq!(b.len(), MIN_MATCH);
368    let lo = u64::from_le_bytes(b[0..8].try_into().expect("16 bytes"));
369    let hi = u64::from_le_bytes(b[8..16].try_into().expect("16 bytes"));
370    (lo ^ hi.rotate_left(29)).wrapping_mul(0x9E37_79B9_7F4A_7C15)
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    /// A minimal, independent applier — **not** this module's, and not
378    /// `crate::resolve`'s either.
379    ///
380    /// The oracle that actually matters is stock git, and
381    /// `crate::serve::tests::a_narrowed_clone_reuses_an_in_request_base_instead_of_shipping_whole`
382    /// runs `index-pack --strict` over a pack built out of these deltas. This
383    /// one exists for the cases that are awkward to reach through a whole pack —
384    /// the 65536 copy, the 4 GiB refusal — and it is written from the format
385    /// description rather than from the encoder, which is the only way a decoder
386    /// can catch an encoder rather than agree with it.
387    fn apply(base: &[u8], d: &[u8]) -> Vec<u8> {
388        let mut i = 0usize;
389        let n = |i: &mut usize| {
390            let mut v = 0u64;
391            let mut s = 0;
392            loop {
393                let b = d[*i];
394                *i += 1;
395                v |= u64::from(b & 0x7f) << s;
396                s += 7;
397                if b & 0x80 == 0 {
398                    return v;
399                }
400            }
401        };
402        let base_size = n(&mut i);
403        assert_eq!(base_size as usize, base.len(), "declared base size");
404        let target_size = n(&mut i);
405        let mut out = Vec::new();
406        while i < d.len() {
407            let op = d[i];
408            i += 1;
409            if op & 0x80 != 0 {
410                let mut off = 0usize;
411                for bit in 0..4 {
412                    if op & (1 << bit) != 0 {
413                        off |= (d[i] as usize) << (bit * 8);
414                        i += 1;
415                    }
416                }
417                let mut size = 0usize;
418                for bit in 0..3 {
419                    if op & (0x10 << bit) != 0 {
420                        size |= (d[i] as usize) << (bit * 8);
421                        i += 1;
422                    }
423                }
424                if size == 0 {
425                    size = 0x1_0000;
426                }
427                out.extend_from_slice(&base[off..off + size]);
428            } else {
429                let k = op as usize;
430                assert_ne!(k, 0, "a zero-length insert is not a valid instruction");
431                out.extend_from_slice(&d[i..i + k]);
432                i += k;
433            }
434        }
435        assert_eq!(out.len() as u64, target_size, "declared target size");
436        out
437    }
438
439    /// Pseudo-random bytes, so a "delta" that quietly shipped literals would
440    /// show up as a size rather than passing on compressible filler.
441    fn noise(n: usize, seed: u64) -> Vec<u8> {
442        let mut s = seed | 1;
443        (0..n)
444            .map(|_| {
445                s ^= s << 13;
446                s ^= s >> 7;
447                s ^= s << 17;
448                (s >> 33) as u8
449            })
450            .collect()
451    }
452
453    /// 🔴 **The delta rebuilds the target exactly, and is much smaller than it.**
454    ///
455    /// Both halves are load-bearing and neither alone is worth anything: an
456    /// encoder that emits one giant insert round-trips perfectly and saves
457    /// nothing, and one that emits a wrong copy offset is small and produces an
458    /// object with a different hash. The corpus is incompressible noise with one
459    /// 64-byte splice, which is the boundary blob's real shape — 16 KiB that
460    /// differs from its neighbour in a few hundred bytes.
461    #[test]
462    fn a_computed_delta_round_trips_and_is_a_fraction_of_the_target() {
463        let base = noise(16 * 1024, 0x1234);
464        let mut target = base.clone();
465        target[4096..4160].copy_from_slice(&noise(64, 0x99));
466        target.extend_from_slice(&noise(128, 0x77));
467
468        let d = delta(&base, &target).unwrap().expect("a near-copy deltas");
469        assert_eq!(apply(&base, &d), target, "the delta must rebuild the target");
470        assert!(
471            d.len() * 20 < target.len(),
472            "a 16 KiB target differing in ~200 bytes must delta to well under 5 % of it, got {} \
473             of {}",
474            d.len(),
475            target.len()
476        );
477    }
478
479    /// A copy longer than 65536 is split rather than encoded as a zero size,
480    /// which decodes as 65536 and would silently truncate.
481    #[test]
482    fn a_copy_of_exactly_65536_bytes_is_not_encoded_as_a_zero_size() {
483        let base = noise(200_000, 0xBEEF);
484        // Target = base[0..0x10000] and nothing else, so the encoder is forced
485        // to emit a copy of exactly the length that is unencodable as three
486        // zero bytes.
487        let target = base[..0x1_0000].to_vec();
488        let d = delta(&base, &target).unwrap().expect("a pure prefix deltas");
489        assert_eq!(apply(&base, &d), target, "the 65536 boundary must round trip");
490    }
491
492    /// Nothing in common: the encoder must **decline**, not ship a delta that is
493    /// longer than the object it replaces.
494    ///
495    /// This is what makes a badly chosen candidate base merely useless. Without
496    /// it a wrong base costs bytes *and* the re-deflate.
497    #[test]
498    fn an_unrelated_base_declines_rather_than_growing_the_entry() {
499        let base = noise(8192, 1);
500        let target = noise(8192, 2);
501        assert!(
502            delta(&base, &target).unwrap().is_none(),
503            "two unrelated buffers have no delta worth sending"
504        );
505    }
506
507    /// Degenerate inputs answer `None`, and the **grow guard** is what makes
508    /// them — there is no special case for an empty side and there must not be
509    /// one.
510    ///
511    /// 🔴 This is a rewrite. It used to assert the same two `is_none()`s against
512    /// an implementation that checked `is_empty()` up front, and deleting that
513    /// check left the test **green** — the grow guard was already producing the
514    /// answer. A guard that cannot fail is not a guard, so the check went and
515    /// the test now names the mechanism it is really pinning. Seen red by
516    /// removing the grow guard: `delta(b"", b"anything")` then returns an
517    /// 11-byte "delta" for 8 bytes of target.
518    ///
519    /// A base below one block is in here for the same reason: it exercises a
520    /// `BlockIndex` with zero blocks, which is the shape an index built by
521    /// division would panic or mis-size on.
522    #[test]
523    fn degenerate_inputs_decline_through_the_grow_guard_and_not_a_special_case() {
524        for (base, target) in [
525            (&b""[..], &b"anything"[..]),
526            (&b"anything"[..], &b""[..]),
527            (&b""[..], &b""[..]),
528            (&b"short"[..], &b"also short"[..]),
529        ] {
530            assert!(
531                delta(base, target).unwrap().is_none(),
532                "base {} bytes, target {} bytes: a delta no smaller than its target must decline",
533                base.len(),
534                target.len()
535            );
536        }
537    }
538
539    /// A target that is a **prefix** of its base, and one that is an extension:
540    /// the two shapes where an off-by-one in the match extension shows up as a
541    /// truncated or over-long copy rather than as a crash.
542    #[test]
543    fn prefixes_and_extensions_round_trip() {
544        let base = noise(4096, 7);
545        for target in [
546            base[..1000].to_vec(),
547            base[..4095].to_vec(),
548            {
549                let mut t = base.clone();
550                t.extend_from_slice(&noise(3, 9));
551                t
552            },
553            {
554                let mut t = noise(3, 11);
555                t.extend_from_slice(&base);
556                t
557            },
558        ] {
559            let d = delta(&base, &target)
560                .unwrap()
561                .unwrap_or_else(|| panic!("a {}-byte near-copy must delta", target.len()));
562            assert_eq!(apply(&base, &d), target, "{} bytes", target.len());
563        }
564    }
565}