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}