Skip to main content

znippy_plugin_git/
pack_walk.rs

1//! The split: one pass over a pushed packfile's entries, and nothing else.
2//!
3//! This is what [`crate::store`]'s `put_pack` walks before it stores a byte, and
4//! it is the reason the **closure check costs nothing extra** (§13.7): the walk
5//! has to visit every entry anyway to find where the next one starts, so the set
6//! of entry boundaries and the set of delta bases fall out of it for free. No
7//! index is consulted to answer *is this pack self-contained*.
8//!
9//! # What it does NOT do
10//!
11//! **It does not compute oids.** An entry's oid is
12//! `sha1("<type> <size>\0" ‖ inflated content)`, and for a delta that content
13//! only exists after the chain is resolved — that is `git index-pack`'s job, it
14//! is the *indexer's* work by §13.9 ("the index is built after the ack, over a
15//! channel"), and it is deliberately not on the ack path. So a [`PackEntry`]
16//! carries the five facts the pack itself states — extent, type, declared
17//! size and delta base — and no more.
18//!
19//! # Why the bytes are inflated but the output is thrown away
20//!
21//! A pack entry has no length field. The only way to find entry *n+1* is to run
22//! the zlib stream of entry *n* to its end and ask the decompressor how many
23//! **input** bytes it consumed. So the walk pays one inflate over the pack — but
24//! it never materialises an object: the output goes into one reusable
25//! [`SCRATCH`]-sized buffer and is discarded, so peak memory is the scratch
26//! buffer and not the repository. `total_out` is still checked against the
27//! declared size, because a stream that inflates to a different length than its
28//! header claims is a corrupt pack and the honest answer is an error.
29//!
30//! # Whose grammar is this
31//!
32//! Ours, and §18 is the reason it is allowed to be: the plan records the choice
33//! between keeping gix as a grammar library and writing the two parsers
34//! ourselves as **open**, and it prescribes exactly how to settle it — "write the
35//! two parsers, and gate them behind a test that runs both ours and gix's over a
36//! real corpus and requires byte-identical output on every entry". That gate is
37//! [`tests::ours_and_gix_agree_on_every_entry_of_every_pack`], which drives
38//! `gix_pack::data::input::BytesToEntriesIter` (a **dev**-dependency, so no gix
39//! crate enters a shipped archiver) over the same bytes and requires the five
40//! facts to match entry for entry. Writing the parser is therefore executing the
41//! plan's decision procedure, not pre-empting its decision: if the gate ever
42//! fails, gix is the arbiter and this module is wrong.
43//!
44//! The one dependency it adds is `flate2` on its pure-Rust `zlib-rs` backend —
45//! the same crate and the same backend `ldeflate` already pulls in
46//! non-optionally through `znippy-common`, so the build graph is unchanged: no
47//! C, no cmake, no build-script network, and `--no-default-features` still
48//! builds in seconds.
49
50use anyhow::{anyhow, bail, Context as _, Result};
51use flate2::{Decompress, FlushDecompress, Status};
52
53use crate::index_layout::ObjType;
54use crate::object::GitHashKind;
55
56/// Bytes of inflated output the walk keeps around at once. The output is
57/// discarded, so this bounds the walk's memory over any pack of any size.
58const SCRATCH: usize = 64 * 1024;
59
60/// A pack header is `PACK`, a version and an object count.
61const HEADER_LEN: usize = 12;
62
63/// Where a delta entry's base is.
64///
65/// **The stored form is an OFFSET, never an ordinal** (§13, decided): an ordinal
66/// indexes the derived objects table, that table is rebuilt whenever the
67/// projection is rebuilt, and a rebuild re-derives every ordinal as the new
68/// oid-lexicographic rank. An ordinal written down before a rebuild therefore
69/// points at a different row afterwards — silently. An offset addresses the
70/// bytes, which append-only storage never moves.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum DeltaBase {
73    /// A whole object: no base.
74    None,
75    /// `OFS_DELTA` — the base is another entry, at this **absolute** offset.
76    /// Absolute in whatever coordinate space the walk was rebased into: within
77    /// the pack after [`walk`], within the archive after [`PackWalk::rebased`].
78    Offset(u64),
79    /// `REF_DELTA` — the base is named by oid and is usually *outside* the pack
80    /// (that is what a thin pack is). Resolving it is the one part of the check
81    /// that reads `objects.oid`, exactly as §13's table says receive-pack does.
82    Ref(Vec<u8>),
83}
84
85impl DeltaBase {
86    /// The offset form for the `objects.delta_base` column.
87    ///
88    /// `0` means *no offset base* and is a safe sentinel rather than a lie:
89    /// archive offset 0 is the first pack's `PACK` magic, so no object entry can
90    /// ever legitimately start there. A `Ref` base has no offset until the
91    /// indexer resolves its oid, and reports `0` for the same reason.
92    pub fn as_offset(&self) -> u64 {
93        match self {
94            DeltaBase::Offset(o) => *o,
95            DeltaBase::None | DeltaBase::Ref(_) => 0,
96        }
97    }
98}
99
100/// One entry, as the pack itself states it.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct PackEntry {
103    /// Start of the entry — its type/size header, not its zlib stream.
104    pub offset: u64,
105    /// Header **plus** compressed stream. `offset + len` is where the next entry
106    /// starts, which is what makes this the byte extent to store verbatim.
107    pub len: u64,
108    pub obj_type: ObjType,
109    /// The size the entry's header declares, and what its zlib stream really
110    /// inflated to — the walk checks the two agree.
111    ///
112    /// For `OfsDelta` / `RefDelta` this is the size of the **delta instruction
113    /// stream**, not of the object the chain resolves to. The resolved size is
114    /// unknowable without applying the chain and is the indexer's output; the
115    /// type column is what stops a caller mistaking one for the other.
116    pub uncompressed_size: u64,
117    pub delta_base: DeltaBase,
118}
119
120/// What one pass over a pack found.
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct PackWalk {
123    /// `2` or `3`.
124    pub version: u32,
125    /// Entries in pack order — which is also ascending offset order.
126    pub entries: Vec<PackEntry>,
127    /// The pack's own trailing checksum, as stored. Not verified here: this walk
128    /// reports what the bytes say, and the checksum of *what was stored* is
129    /// [`crate::indexer`]'s row.
130    pub trailer: Vec<u8>,
131}
132
133/// What the closure check found. **Derived from the walk alone** — no index, no
134/// disk, no oid resolution.
135#[derive(Debug, Clone, PartialEq, Eq, Default)]
136pub struct Closure {
137    /// `OFS_DELTA` bases that do not land on an entry boundary inside this pack.
138    /// A non-empty list is a corrupt pack, not a thin one.
139    pub broken_offsets: Vec<u64>,
140    /// `REF_DELTA` base oids. Each has to exist *somewhere* — in this pack under
141    /// an oid the walk cannot compute, or already in the store. Only these need
142    /// `objects.oid`, and there are usually very few.
143    pub external_refs: Vec<Vec<u8>>,
144}
145
146impl Closure {
147    /// Nothing to ask anybody about: every delta base is an offset inside this
148    /// pack.
149    pub fn is_self_contained(&self) -> bool {
150        self.broken_offsets.is_empty() && self.external_refs.is_empty()
151    }
152}
153
154impl PackWalk {
155    /// The same walk with every offset moved into the archive's coordinate
156    /// space, which is what the `objects` table stores.
157    ///
158    /// A delta base moves with it — that is the whole reason it is an offset and
159    /// not an ordinal: the shift is arithmetic on a known quantity, with no
160    /// table to consult and nothing to keep in step.
161    pub fn rebased(mut self, archive_offset: u64) -> Self {
162        for e in &mut self.entries {
163            e.offset += archive_offset;
164            if let DeltaBase::Offset(o) = &mut e.delta_base {
165                *o += archive_offset;
166            }
167        }
168        self
169    }
170
171    /// The closure check, out of the walk we already did.
172    pub fn closure(&self) -> Closure {
173        let boundaries: std::collections::HashSet<u64> =
174            self.entries.iter().map(|e| e.offset).collect();
175        let mut c = Closure::default();
176        for e in &self.entries {
177            match &e.delta_base {
178                DeltaBase::None => {}
179                DeltaBase::Offset(o) => {
180                    if !boundaries.contains(o) {
181                        c.broken_offsets.push(*o);
182                    }
183                }
184                DeltaBase::Ref(oid) => c.external_refs.push(oid.clone()),
185            }
186        }
187        c
188    }
189
190    /// Total inflated bytes the entries declare. Not the pack's own length.
191    pub fn declared_bytes(&self) -> u64 {
192        self.entries.iter().map(|e| e.uncompressed_size).sum()
193    }
194}
195
196/// Walk a pushed pack's entries.
197///
198/// `oid_len` is 20 for sha1 and 32 for sha256 — a `REF_DELTA`'s base oid is raw
199/// bytes with no length prefix, so the width has to be known from outside the
200/// pack. That is git's own situation.
201///
202/// **P-4: a malformed or hostile pack returns `Err`, never a panic and never a
203/// half-truth.** Every arithmetic step is checked against the buffer's end.
204pub fn walk(pack: &[u8], oid_len: usize) -> Result<PackWalk> {
205    walk_observed(pack, oid_len, &mut |_done, _total| {})
206}
207
208/// [`walk`], **telling somebody how far it has got, entry by entry.**
209///
210/// # This is the walk, not a second one
211///
212/// There is exactly one place on this system that knows how many objects of a
213/// pack have been taken in *while the taking is still happening*, and it is the
214/// loop below: a pack entry carries no length, so finding the boundaries **is**
215/// how the pack gets split at all, and the loop already inflates every entry to
216/// find where the next one starts. Counting the entries as they go past is
217/// therefore free — no extra pass, no extra inflate, not one byte decompressed
218/// that was not already being decompressed. Any other way of learning the same
219/// number would be a second pack decoder, which LAW 5 forbids and which
220/// `gunnar-wire`'s `fetch_pack` refuses for the same reason.
221///
222/// `on_entry` is called **once per entry**, after that entry is accounted for,
223/// with `(entries so far, the count the header declared)`. Per entry and not
224/// per batch because the loop is per entry: batching here would be inventing a
225/// coarseness the code does not have.
226///
227/// # `&mut dyn FnMut` and not a generic
228///
229/// [`walk`] and this are one function, and a generic parameter would
230/// monomorphise the whole 70-line body a second time for the no-op closure —
231/// the same machine code twice, on the push hot path, so that a call that
232/// publishes nothing can skip an indirect call it makes once per object. The
233/// dyn call is dwarfed by the inflate on the line above it.
234pub fn walk_observed(
235    pack: &[u8],
236    oid_len: usize,
237    on_entry: &mut dyn FnMut(u64, u64),
238) -> Result<PackWalk> {
239    if oid_len != 20 && oid_len != 32 {
240        bail!("oid width {oid_len} is neither sha1 (20) nor sha256 (32)");
241    }
242    if pack.len() < HEADER_LEN + oid_len {
243        bail!(
244            "a pack is at least {} bytes (header + trailer), this one is {}",
245            HEADER_LEN + oid_len,
246            pack.len()
247        );
248    }
249    if &pack[0..4] != b"PACK" {
250        bail!("not a packfile: it does not start with `PACK`");
251    }
252    let version = u32::from_be_bytes([pack[4], pack[5], pack[6], pack[7]]);
253    if version != 2 && version != 3 {
254        bail!("pack version {version} is not 2 or 3");
255    }
256    let count = u32::from_be_bytes([pack[8], pack[9], pack[10], pack[11]]) as usize;
257
258    let body_end = pack.len() - oid_len;
259    let mut pos = HEADER_LEN;
260    let mut entries = Vec::with_capacity(count);
261    let mut scratch = vec![0u8; SCRATCH];
262
263    for i in 0..count {
264        if pos >= body_end {
265            bail!(
266                "the pack header claims {count} objects but the bytes ran out after {i} — {} of \
267                 {} bytes consumed",
268                pos,
269                pack.len()
270            );
271        }
272        let start = pos;
273        let (obj_type, size, n) = type_and_size(&pack[pos..body_end])
274            .map_err(|e| anyhow!("entry {i} at offset {start}: {e}"))?;
275        pos += n;
276
277        let delta_base = match obj_type {
278            ObjType::OfsDelta => {
279                let (distance, n) = ofs_distance(&pack[pos..body_end])
280                    .map_err(|e| anyhow!("entry {i} at offset {start}: {e}"))?;
281                pos += n;
282                let start_u64 = start as u64;
283                if distance == 0 || distance > start_u64 {
284                    bail!(
285                        "entry {i} at offset {start} is an ofs-delta whose base is {distance} \
286                         bytes back, which is outside the pack"
287                    );
288                }
289                DeltaBase::Offset(start_u64 - distance)
290            }
291            ObjType::RefDelta => {
292                if pos + oid_len > body_end {
293                    bail!("entry {i} at offset {start}: a ref-delta base oid runs off the pack");
294                }
295                let oid = pack[pos..pos + oid_len].to_vec();
296                pos += oid_len;
297                DeltaBase::Ref(oid)
298            }
299            _ => DeltaBase::None,
300        };
301
302        let consumed = inflate_and_discard(&pack[pos..body_end], size, &mut scratch)
303            .map_err(|e| anyhow!("entry {i} at offset {start}: {e}"))?;
304        pos += consumed;
305
306        entries.push(PackEntry {
307            offset: start as u64,
308            len: (pos - start) as u64,
309            obj_type,
310            uncompressed_size: size,
311            delta_base,
312        });
313        // After the push, so the number published is the number of entries that
314        // are actually accounted for — never `i`, which would report an entry
315        // as done at the moment it was started.
316        on_entry(entries.len() as u64, count as u64);
317    }
318
319    if pos != body_end {
320        bail!(
321            "the pack's {count} entries end at {pos} but its trailer starts at {body_end} — \
322             {} bytes are unaccounted for",
323            body_end - pos.min(body_end)
324        );
325    }
326
327    Ok(PackWalk {
328        version,
329        entries,
330        trailer: pack[body_end..].to_vec(),
331    })
332}
333
334/// The type/size varint. `(type, uncompressed size, bytes consumed)`.
335///
336/// Byte 0 is `[continue:1][type:3][size low nibble:4]`; each continuation byte
337/// adds 7 more size bits, least significant group first.
338fn type_and_size(b: &[u8]) -> Result<(ObjType, u64, usize)> {
339    let first = *b.first().ok_or_else(|| anyhow!("no type/size header"))?;
340    let code = (first >> 4) & 0b111;
341    let obj_type = ObjType::from_code(code).ok_or_else(|| {
342        anyhow!("object type code {code} is not one git writes — refusing to guess it")
343    })?;
344    let mut size = u64::from(first & 0x0f);
345    let mut shift = 4u32;
346    let mut i = 1usize;
347    let mut cont = first & 0x80 != 0;
348    while cont {
349        let byte = *b
350            .get(i)
351            .ok_or_else(|| anyhow!("the type/size varint runs off the pack"))?;
352        if shift >= 64 {
353            bail!("the type/size varint is longer than a u64 can hold");
354        }
355        size |= u64::from(byte & 0x7f) << shift;
356        shift += 7;
357        cont = byte & 0x80 != 0;
358        i += 1;
359    }
360    Ok((obj_type, size, i))
361}
362
363/// The `OFS_DELTA` backwards distance. Git's own encoding, which is **not** the
364/// same varint as above: each continuation adds one before shifting, so the
365/// encoding has no redundant representations.
366fn ofs_distance(b: &[u8]) -> Result<(u64, usize)> {
367    let mut i = 0usize;
368    let mut byte = *b
369        .first()
370        .ok_or_else(|| anyhow!("no ofs-delta distance varint"))?;
371    i += 1;
372    let mut d = u64::from(byte & 0x7f);
373    while byte & 0x80 != 0 {
374        byte = *b
375            .get(i)
376            .ok_or_else(|| anyhow!("the ofs-delta distance varint runs off the pack"))?;
377        i += 1;
378        d = d
379            .checked_add(1)
380            .and_then(|d| d.checked_shl(7))
381            .ok_or_else(|| anyhow!("the ofs-delta distance overflows a u64"))?
382            | u64::from(byte & 0x7f);
383    }
384    Ok((d, i))
385}
386
387/// Run one zlib stream to its end, throwing the output away, and report how many
388/// **input** bytes it took. Checks the inflated length against `declared`.
389fn inflate_and_discard(input: &[u8], declared: u64, scratch: &mut [u8]) -> Result<usize> {
390    let mut d = Decompress::new(true);
391    loop {
392        let before_in = d.total_in();
393        let before_out = d.total_out();
394        let status = d
395            .decompress(&input[before_in as usize..], scratch, FlushDecompress::None)
396            .map_err(|e| anyhow!("zlib: {e}"))?;
397        match status {
398            Status::StreamEnd => break,
399            Status::Ok | Status::BufError => {
400                // No progress on either side and not at the end: the stream is
401                // truncated. Without this the loop would spin forever on a
402                // hostile pack.
403                if d.total_in() == before_in && d.total_out() == before_out {
404                    bail!("the zlib stream is truncated after {} bytes", d.total_in());
405                }
406            }
407        }
408    }
409    if d.total_out() != declared {
410        bail!(
411            "the entry header declares {declared} bytes but its stream inflates to {}",
412            d.total_out()
413        );
414    }
415    Ok(d.total_in() as usize)
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use flate2::{write::ZlibEncoder, Compression};
422    use std::io::Write;
423
424    fn deflate(bytes: &[u8]) -> Vec<u8> {
425        let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
426        e.write_all(bytes).unwrap();
427        e.finish().unwrap()
428    }
429
430    /// The type/size header git would write.
431    fn header(code: u8, mut size: u64) -> Vec<u8> {
432        let mut out = vec![(code << 4) | (size as u8 & 0x0f)];
433        size >>= 4;
434        while size > 0 {
435            let last = out.len() - 1;
436            out[last] |= 0x80;
437            out.push((size & 0x7f) as u8);
438            size >>= 7;
439        }
440        out
441    }
442
443    /// git's backwards-distance encoding.
444    fn ofs(mut d: u64) -> Vec<u8> {
445        let mut out = vec![(d & 0x7f) as u8];
446        d >>= 7;
447        while d > 0 {
448            d -= 1;
449            out.insert(0, 0x80 | (d & 0x7f) as u8);
450            d >>= 7;
451        }
452        out
453    }
454
455    /// A pack of three entries: a blob, a second blob, and an ofs-delta whose
456    /// base is the first. Returns `(bytes, expected entry offsets)`.
457    fn three_entry_pack() -> (Vec<u8>, Vec<u64>) {
458        let a = b"the quick brown fox jumps over the lazy dog".to_vec();
459        let b = vec![b'x'; 300];
460        // A delta stream's *content* is never parsed by the walk, so any bytes
461        // will do — but the declared size must be its real inflated length,
462        // because the walk checks that.
463        let delta = b"\x2b\x2b\x90\x01\x00".to_vec();
464
465        let mut pack = b"PACK".to_vec();
466        pack.extend_from_slice(&2u32.to_be_bytes());
467        pack.extend_from_slice(&3u32.to_be_bytes());
468
469        let mut offsets = Vec::new();
470        offsets.push(pack.len() as u64);
471        pack.extend_from_slice(&header(3, a.len() as u64));
472        pack.extend_from_slice(&deflate(&a));
473
474        offsets.push(pack.len() as u64);
475        pack.extend_from_slice(&header(3, b.len() as u64));
476        pack.extend_from_slice(&deflate(&b));
477
478        let third = pack.len() as u64;
479        offsets.push(third);
480        pack.extend_from_slice(&header(6, delta.len() as u64));
481        pack.extend_from_slice(&ofs(third - offsets[0]));
482        pack.extend_from_slice(&deflate(&delta));
483
484        pack.extend_from_slice(&[0u8; 20]); // trailer
485        (pack, offsets)
486    }
487
488    /// A pack with one `REF_DELTA` whose base is not in the pack — a thin pack.
489    fn thin_pack(base_oid: &[u8]) -> Vec<u8> {
490        let delta = b"\x0a\x0a\x91\x00\x0a".to_vec();
491        let mut pack = b"PACK".to_vec();
492        pack.extend_from_slice(&2u32.to_be_bytes());
493        pack.extend_from_slice(&1u32.to_be_bytes());
494        pack.extend_from_slice(&header(7, delta.len() as u64));
495        pack.extend_from_slice(base_oid);
496        pack.extend_from_slice(&deflate(&delta));
497        pack.extend_from_slice(&[0u8; 20]);
498        pack
499    }
500
501    /// The walk's whole output, asserted as **applied bytes**: every entry's
502    /// offset is where the previous one ended, every `len` covers header plus
503    /// stream, and `offset + len` of the last entry is exactly where the trailer
504    /// starts. A walk that got any length wrong cannot satisfy all three.
505    ///
506    /// Seen RED by changing `pos += consumed` to `pos += consumed + 1` in
507    /// [`walk`]: "a well-formed pack walks: entry 1 at offset 66: the entry
508    /// header declares 2 bytes but its stream inflates to 300" — one byte of
509    /// desync and the *next* entry's header is read out of the middle of a
510    /// deflate stream, which is why nothing downstream has to trust the length.
511    #[test]
512    fn every_entry_boundary_is_where_the_bytes_say_it_is() {
513        let (pack, expected) = three_entry_pack();
514        let w = walk(&pack, 20).expect("a well-formed pack walks");
515        assert_eq!(w.version, 2);
516        assert_eq!(w.entries.len(), 3);
517
518        let got: Vec<u64> = w.entries.iter().map(|e| e.offset).collect();
519        assert_eq!(got, expected, "entry offsets");
520
521        // Boundaries chain, and the last one ends at the trailer.
522        for (i, e) in w.entries.iter().enumerate() {
523            let next = w
524                .entries
525                .get(i + 1)
526                .map(|n| n.offset)
527                .unwrap_or((pack.len() - 20) as u64);
528            assert_eq!(
529                e.offset + e.len,
530                next,
531                "entry {i} claims to end at {} but the next starts at {next}",
532                e.offset + e.len
533            );
534        }
535
536        assert_eq!(w.entries[0].obj_type, ObjType::Blob);
537        assert_eq!(w.entries[0].uncompressed_size, 43);
538        assert_eq!(w.entries[1].uncompressed_size, 300);
539        assert_eq!(w.entries[2].obj_type, ObjType::OfsDelta);
540        assert_eq!(
541            w.entries[2].delta_base,
542            DeltaBase::Offset(expected[0]),
543            "the ofs-delta base must resolve to the first entry's offset"
544        );
545        assert_eq!(w.trailer.len(), 20);
546    }
547
548    /// The closure check, and that it is answered without an index: a
549    /// self-contained pack asks nobody anything, a thin one names exactly the
550    /// oid it needs, and a corrupt base offset is reported as corrupt rather
551    /// than as external.
552    ///
553    /// Seen RED by making `closure()` insert `e.offset + 1` into `boundaries`:
554    /// "a pack whose only delta is an ofs-delta into itself needs nobody:
555    /// Closure { broken_offsets: [12], external_refs: [] }".
556    #[test]
557    fn the_closure_check_falls_out_of_the_walk_and_consults_nothing() {
558        let (pack, offsets) = three_entry_pack();
559        let c = walk(&pack, 20).unwrap().closure();
560        assert!(
561            c.is_self_contained(),
562            "a pack whose only delta is an ofs-delta into itself needs nobody: {c:?}"
563        );
564        assert!(c.external_refs.is_empty());
565
566        let base = vec![0xab; 20];
567        let c = walk(&thin_pack(&base), 20).unwrap().closure();
568        assert!(!c.is_self_contained(), "a thin pack is not self-contained");
569        assert_eq!(c.external_refs, vec![base], "the one oid to ask about");
570        assert!(c.broken_offsets.is_empty(), "a thin pack is not corrupt");
571
572        // A base offset that lands mid-entry is corruption, and is named.
573        let mut w = walk(&pack, 20).unwrap();
574        w.entries[2].delta_base = DeltaBase::Offset(offsets[0] + 1);
575        let c = w.closure();
576        assert_eq!(c.broken_offsets, vec![offsets[0] + 1]);
577        assert!(c.external_refs.is_empty());
578    }
579
580    /// Rebasing moves the entry and its base together. This is the property that
581    /// makes `delta_base` an offset rather than an ordinal: one addition, no
582    /// table.
583    ///
584    /// Seen RED by leaving the `DeltaBase::Offset` arm out of
585    /// [`PackWalk::rebased`]: `left: Offset(12)`, `right: Offset(1000012)` — the
586    /// entry moved and its base did not, which is precisely the silent
587    /// mis-addressing the offset form exists to make impossible.
588    #[test]
589    fn rebasing_moves_an_entry_and_its_base_by_the_same_amount() {
590        let (pack, offsets) = three_entry_pack();
591        let base = 1_000_000u64;
592        let w = walk(&pack, 20).unwrap().rebased(base);
593        assert_eq!(w.entries[0].offset, offsets[0] + base);
594        assert_eq!(
595            w.entries[2].delta_base,
596            DeltaBase::Offset(offsets[0] + base)
597        );
598        assert!(
599            w.closure().is_self_contained(),
600            "a rebased pack is still self-contained — that is the point"
601        );
602        assert_eq!(DeltaBase::None.as_offset(), 0);
603        assert_eq!(DeltaBase::Ref(vec![1; 20]).as_offset(), 0);
604    }
605
606    /// P-4, on the shapes a hostile client can actually send. Every one is an
607    /// `Err` with a reason, and none of them panics or loops.
608    ///
609    /// Seen RED by replacing `if d.total_out() != declared` with `if false`:
610    /// "lies about its size must be refused, got Ok(PackWalk { version: 2,
611    /// entries: [PackEntry { offset: 12, len: 15, obj_type: Blob,
612    /// uncompressed_size: 999, delta_base: None }] … })" — a walk that believed
613    /// a header over the bytes.
614    #[test]
615    fn a_hostile_pack_is_an_error_with_a_reason_never_a_panic() {
616        let (good, _) = three_entry_pack();
617
618        let mut wrong_magic = good.clone();
619        wrong_magic[0] = b'N';
620
621        let mut wrong_version = good.clone();
622        wrong_version[7] = 9;
623
624        let mut too_many = good.clone();
625        too_many[11] = 99;
626
627        let truncated = good[..good.len() / 2].to_vec();
628
629        // An entry whose declared size is not what its stream inflates to.
630        let mut liar = b"PACK".to_vec();
631        liar.extend_from_slice(&2u32.to_be_bytes());
632        liar.extend_from_slice(&1u32.to_be_bytes());
633        liar.extend_from_slice(&header(3, 999));
634        liar.extend_from_slice(&deflate(b"short"));
635        liar.extend_from_slice(&[0u8; 20]);
636
637        // Type code 5, which git does not use.
638        let mut bad_type = b"PACK".to_vec();
639        bad_type.extend_from_slice(&2u32.to_be_bytes());
640        bad_type.extend_from_slice(&1u32.to_be_bytes());
641        bad_type.extend_from_slice(&header(5, 5));
642        bad_type.extend_from_slice(&deflate(b"hello"));
643        bad_type.extend_from_slice(&[0u8; 20]);
644
645        // An ofs-delta pointing before the start of the pack.
646        let mut bad_ofs = b"PACK".to_vec();
647        bad_ofs.extend_from_slice(&2u32.to_be_bytes());
648        bad_ofs.extend_from_slice(&1u32.to_be_bytes());
649        bad_ofs.extend_from_slice(&header(6, 5));
650        bad_ofs.extend_from_slice(&ofs(1_000_000));
651        bad_ofs.extend_from_slice(&deflate(b"delta"));
652        bad_ofs.extend_from_slice(&[0u8; 20]);
653
654        for (what, bytes) in [
655            ("wrong magic", wrong_magic),
656            ("wrong version", wrong_version),
657            ("more objects than bytes", too_many),
658            ("truncated", truncated),
659            ("lies about its size", liar),
660            ("unused type code", bad_type),
661            ("base before the pack", bad_ofs),
662            ("empty", Vec::new()),
663            ("header only", b"PACK\0\0\0\x02\0\0\0\x01".to_vec()),
664        ] {
665            let r = walk(&bytes, 20);
666            assert!(r.is_err(), "{what} must be refused, got {r:?}");
667        }
668
669        assert!(walk(&good, 21).is_err(), "an oid width of 21 is nonsense");
670    }
671
672    /// §18's decision procedure, run as a test: **ours against gix's, entry for
673    /// entry, on every pack we can find.**
674    ///
675    /// gix is a dev-dependency, so this costs a shipped archiver nothing. The
676    /// synthetic packs always run; the real ones are whatever `.pack` files this
677    /// machine happens to carry under `~/git`, capped so the test
678    /// stays a test. The count of both is printed, because a differential test
679    /// that silently compared nothing is exactly the hollow guard LAW 2 is
680    /// about.
681    ///
682    /// Seen RED by changing `shift += 7` to `shift += 8` in [`type_and_size`]:
683    /// "ours walks it: entry 18 at offset 10265: the entry header declares 4133
684    /// bytes but its stream inflates to 2085".
685    ///
686    /// **And that red proof is the argument for the real corpus.** `shift` is
687    /// first *used* at 4 and only then advanced, so the broken version still
688    /// decodes every two-byte varint correctly — every object under 2048 bytes.
689    /// Both synthetic packs passed it. It took entry 18 of a real repository's
690    /// pack to expose it, which is exactly why §18 says the gate has to run over
691    /// a corpus and not over a fixture.
692    #[test]
693    fn ours_and_gix_agree_on_every_entry_of_every_pack() {
694        let mut compared = 0usize;
695        let mut packs = 0usize;
696
697        let (synthetic, _) = three_entry_pack();
698        for p in [synthetic, thin_pack(&[0x7f; 20])] {
699            compared += agree(&p);
700            packs += 1;
701        }
702        assert!(
703            packs == 2 && compared >= 4,
704            "the synthetic packs must compare"
705        );
706
707        // 🔴 **And the generated corpus, which is what makes the red proof above
708        // reproducible off this box.** `shift += 8` decodes every two-byte
709        // varint correctly — every object under 2048 bytes — so both synthetic
710        // packs pass it, and the argument in this test's docs was that only a
711        // real repository's pack has entries big enough to expose it. That made
712        // the coverage a property of the machine. [`crate::pack_fixture`]'s
713        // blobs are 2048 bytes exactly and its trees are larger, so every entry
714        // in it carries a three-byte type/size varint and the same red is
715        // available on a fresh clone.
716        let corpus = crate::pack_fixture::corpus().pack.clone();
717        let n = agree(&corpus);
718        assert!(
719            n > 1000,
720            "the generated corpus compared only {n} entries"
721        );
722        compared += n;
723        packs += 1;
724
725        let mut real = 0usize;
726        for pack in real_packs(8) {
727            let bytes = std::fs::read(&pack).expect("reading a real pack");
728            let n = agree(&bytes);
729            eprintln!("{}: {n} entries agree", pack.display());
730            compared += n;
731            real += 1;
732        }
733        eprintln!(
734            "compared {compared} entries over {} packs ({real} real)",
735            packs + real
736        );
737    }
738
739    /// Both parsers over one pack; asserts the five facts and returns the entry
740    /// count.
741    fn agree(pack: &[u8]) -> usize {
742        let ours = walk(pack, 20).expect("ours walks it");
743        let theirs = gix_pack::data::input::BytesToEntriesIter::new_from_header(
744            std::io::BufReader::new(pack),
745            gix_pack::data::input::Mode::AsIs,
746            gix_pack::data::input::EntryDataMode::Ignore,
747            gix_hash::Kind::Sha1,
748        )
749        .expect("gix reads the header");
750
751        let mut n = 0usize;
752        for (i, entry) in theirs.enumerate() {
753            let g = entry.expect("gix walks it");
754            let o = &ours.entries[i];
755            assert_eq!(o.offset, g.pack_offset, "entry {i} offset");
756            assert_eq!(
757                o.len,
758                g.bytes_in_pack(),
759                "entry {i} length: ours {} vs gix {}",
760                o.len,
761                g.bytes_in_pack()
762            );
763            assert_eq!(
764                o.uncompressed_size, g.decompressed_size,
765                "entry {i} decompressed size"
766            );
767            let (gt, gbase) = match g.header {
768                gix_pack::data::entry::Header::Commit => (ObjType::Commit, DeltaBase::None),
769                gix_pack::data::entry::Header::Tree => (ObjType::Tree, DeltaBase::None),
770                gix_pack::data::entry::Header::Blob => (ObjType::Blob, DeltaBase::None),
771                gix_pack::data::entry::Header::Tag => (ObjType::Tag, DeltaBase::None),
772                gix_pack::data::entry::Header::OfsDelta { base_distance } => (
773                    ObjType::OfsDelta,
774                    DeltaBase::Offset(g.pack_offset - base_distance),
775                ),
776                gix_pack::data::entry::Header::RefDelta { base_id } => (
777                    ObjType::RefDelta,
778                    DeltaBase::Ref(base_id.as_slice().to_vec()),
779                ),
780            };
781            assert_eq!(o.obj_type, gt, "entry {i} type");
782            assert_eq!(o.delta_base, gbase, "entry {i} delta base");
783            n += 1;
784        }
785        assert_eq!(
786            n,
787            ours.entries.len(),
788            "gix found a different number of entries"
789        );
790        n
791    }
792
793    /// Up to `cap` real `.pack` files from this machine's own repositories.
794    fn real_packs(cap: usize) -> Vec<std::path::PathBuf> {
795        let mut out = Vec::new();
796        let root_buf = std::path::PathBuf::from(std::env::var_os("HOME").unwrap_or_default()).join("git");
797        let root = root_buf.as_path();
798        let Ok(repos) = std::fs::read_dir(root) else {
799            return out;
800        };
801        for repo in repos.flatten() {
802            let dir = repo.path().join(".git/objects/pack");
803            let Ok(files) = std::fs::read_dir(&dir) else {
804                continue;
805            };
806            for f in files.flatten() {
807                let p = f.path();
808                // Small ones only: this is a correctness gate, not a benchmark.
809                let small = f.metadata().map(|m| m.len() < 64 << 20).unwrap_or(false);
810                if small && p.extension().is_some_and(|e| e == "pack") {
811                    out.push(p);
812                    if out.len() >= cap {
813                        return out;
814                    }
815                }
816            }
817        }
818        out
819    }
820}
821
822// ── the inverses, for emitting a pack ────────────────────────────────────────
823//
824// Reading this grammar is what the walk above does. Writing it is what emitting
825// a pack for a selected object set needs, and it needs *only* this: a pack
826// entry's compressed payload is position-independent, so a subset of a pack is
827// the stored payloads byte for byte with their headers re-encoded. Nothing is
828// re-compressed and no delta is recomputed.
829//
830// These are deliberately in the same file as their decoders. The two must agree
831// bit for bit, and the way to keep them agreeing is to make disagreement
832// obvious to whoever edits one.
833
834/// The type/size varint, for a caller outside this module that needs to find
835/// where an entry's header ends — reading the grammar rather than assuming a
836/// width.
837pub fn type_and_size_of(b: &[u8]) -> Result<(ObjType, u64, usize)> {
838    type_and_size(b)
839}
840
841/// An `OFS_DELTA`'s **backwards distance** to its base, and how many bytes it
842/// occupied. `b` starts immediately after the type/size varint.
843///
844/// The pair to [`type_and_size_of`], exported for the same reason: a caller
845/// walking a delta chain by entry header — [`crate::serve`]'s type probe is the
846/// one — must read the grammar rather than re-derive it, or the two spellings
847/// drift and the second one is wrong somewhere nothing checks.
848pub fn ofs_distance_of(b: &[u8]) -> Result<(u64, usize)> {
849    ofs_distance(b)
850}
851
852/// Encode the type/size header — the inverse of [`type_and_size`].
853///
854/// Byte 0 is `[continue:1][type:3][size low nibble:4]`; each continuation byte
855/// carries 7 more size bits, least significant group first.
856pub fn encode_type_and_size(out: &mut Vec<u8>, obj_type: ObjType, size: u64) {
857    let mut byte = (obj_type.code() << 4) | ((size & 0x0f) as u8);
858    let mut rest = size >> 4;
859    while rest > 0 {
860        out.push(byte | 0x80);
861        byte = (rest & 0x7f) as u8;
862        rest >>= 7;
863    }
864    out.push(byte);
865}
866
867/// Encode an `OFS_DELTA` backwards distance — the inverse of [`ofs_distance`].
868///
869/// **Not the same varint as the one above**, and that is the whole reason this
870/// is written out rather than shared: each continuation subtracts one before
871/// shifting, so the encoding has no redundant representations. Getting this
872/// wrong by one produces a pack `git index-pack --strict` rejects — or worse,
873/// one it accepts and mis-reads, because a delta would then be applied against
874/// the wrong base.
875///
876/// The distance is emitted most-significant group first, which is why it is
877/// built backwards into a scratch buffer.
878pub fn encode_ofs_distance(out: &mut Vec<u8>, distance: u64) {
879    let mut buf = [0u8; 10];
880    let mut i = buf.len() - 1;
881    let mut d = distance;
882    buf[i] = (d & 0x7f) as u8;
883    while d >= 0x80 {
884        d >>= 7;
885        d -= 1;
886        i -= 1;
887        buf[i] = 0x80 | (d & 0x7f) as u8;
888    }
889    out.extend_from_slice(&buf[i..]);
890}
891
892#[cfg(test)]
893mod encode_tests {
894    use super::*;
895
896    /// **Every encoder here is checked against the decoder beside it**, over
897    /// the boundaries the varints actually turn on rather than a handful of
898    /// round numbers.
899    ///
900    /// The sizes are the ones where a group boundary falls: the 4-bit nibble in
901    /// byte 0, then every 7 bits after it. The distances are the ones where the
902    /// subtract-one encoding changes length, which is where an off-by-one in
903    /// either direction shows up and nowhere else.
904    #[test]
905    fn the_encoders_are_the_inverses_of_the_decoders() {
906        let sizes = [
907            0u64,
908            1,
909            15,
910            16,
911            17,
912            2047,
913            2048,
914            2049,
915            262_143,
916            262_144,
917            1 << 20,
918            1 << 31,
919            (1u64 << 57) - 1,
920        ];
921        for &size in &sizes {
922            for t in [
923                ObjType::Commit,
924                ObjType::Tree,
925                ObjType::Blob,
926                ObjType::Tag,
927                ObjType::OfsDelta,
928                ObjType::RefDelta,
929            ] {
930                let mut buf = Vec::new();
931                encode_type_and_size(&mut buf, t, size);
932                let (got_t, got_size, n) =
933                    type_and_size(&buf).expect("what we wrote must parse back");
934                assert_eq!(got_t, t, "type round trip at size {size}");
935                assert_eq!(got_size, size, "size round trip for {t:?}");
936                assert_eq!(
937                    n,
938                    buf.len(),
939                    "the decoder must consume exactly what was written"
940                );
941            }
942        }
943
944        // The subtract-one encoding: 127/128 and 16511/16512 are where the
945        // length changes, and are exactly where an off-by-one hides.
946        let distances = [
947            0u64,
948            1,
949            126,
950            127,
951            128,
952            129,
953            16_383,
954            16_511,
955            16_512,
956            16_513,
957            1 << 20,
958            1 << 40,
959            u32::MAX as u64,
960        ];
961        for &d in &distances {
962            let mut buf = Vec::new();
963            encode_ofs_distance(&mut buf, d);
964            let (got, n) = ofs_distance(&buf).expect("what we wrote must parse back");
965            assert_eq!(got, d, "distance round trip");
966            assert_eq!(
967                n,
968                buf.len(),
969                "the decoder must consume exactly what was written"
970            );
971        }
972    }
973}
974
975// ── emitting a subset as a packfile ──────────────────────────────────────────
976
977/// **Where an entry's bytes are** — an address into the archive, or bytes that
978/// exist nowhere else.
979///
980/// # Why this is not a `Vec<u8>`
981///
982/// It was one until 2026-08-14, and that single field is what made a clone hold
983/// the whole repository. [`crate::git_ops::GitStore::emit_set`] filled it with a
984/// `pread` into a fresh `Vec::with_capacity(len)` — one syscall, one allocation
985/// and one kernel→user copy **per object** — and every one of those `Vec`s was
986/// live at once, because the entries are all built before
987/// [`emit_pack`] writes a byte. On a `linux.git` clone: ~13.8 M syscalls, ~27.6 M
988/// allocations, 6.4 GB copied and held, and a peak RSS of **2314 MB** against
989/// gitea's 122 MB on the identical clone
990/// (`gunnar/.nornir/forge-bakeoff-benchmarks.md`, `vs_forge_clone`).
991///
992/// An [`Extent`](Self::Extent) is 16 bytes and addresses bytes that are already
993/// in the page cache. Resolving it is [`crate::archive_map::Mapped::get`] —
994/// pointer arithmetic and a bounds check — with a `pread` fallback for the one
995/// case a mapping cannot answer.
996#[derive(Debug, Clone, PartialEq, Eq)]
997pub enum EntryBytes {
998    /// The stored entry **exactly as received**, addressed in the archive's
999    /// coordinate space. Header and all: `offset` is the entry's type/size
1000    /// varint and `len` runs to where the next entry starts.
1001    ///
1002    /// This is what a full clone is made of, end to end. Nothing copies it until
1003    /// [`emit_pack`] writes it to the wire.
1004    Extent { offset: u64, len: u64 },
1005    /// **The exception: bytes that are on no disk.**
1006    ///
1007    /// Three shapes reach here, and every one of them genuinely computed bytes
1008    /// that no extent addresses:
1009    ///
1010    ///  * a delta whose base the request does not carry, rebuilt whole or
1011    ///    re-deltified against a base it does carry ([`Self::recompressed`] —
1012    ///    `crate::delta`);
1013    ///  * an `OFS_DELTA` re-headed as a `REF_DELTA` for a client with no
1014    ///    `ofs-delta` capability, or for a thin fetch — the *payload* is a copy
1015    ///    but the header in front of it is new, so the concatenation is new;
1016    ///  * a fixture built by hand in a test.
1017    ///
1018    /// It is **zero for a whole-repository clone**, which is the measurement the
1019    /// extent form exists to make true rather than a promise this makes.
1020    Owned(Vec<u8>),
1021}
1022
1023impl EntryBytes {
1024    /// How many bytes this entry contributes, without resolving it.
1025    ///
1026    /// Free for an [`Extent`](Self::Extent) — the length is the address — which
1027    /// is what lets a caller size or account for a pack it has not read.
1028    pub fn len(&self) -> u64 {
1029        match self {
1030            EntryBytes::Extent { len, .. } => *len,
1031            EntryBytes::Owned(v) => v.len() as u64,
1032        }
1033    }
1034
1035    /// Whether this entry contributes nothing. Only a hand-built fixture can.
1036    pub fn is_empty(&self) -> bool {
1037        self.len() == 0
1038    }
1039
1040    /// The bytes, when this entry carries them itself. `None` for an extent,
1041    /// which has to be resolved against the archive.
1042    pub fn owned(&self) -> Option<&[u8]> {
1043        match self {
1044            EntryBytes::Owned(v) => Some(v),
1045            EntryBytes::Extent { .. } => None,
1046        }
1047    }
1048}
1049
1050/// One entry's bytes, resolved against a buffer that spans the archive's
1051/// coordinate space — **the in-memory resolver**, and the one
1052/// [`emit_pack`]'s callers use when the archive is a `&[u8]` rather than a
1053/// mapping.
1054///
1055/// The mapped path is [`crate::git_ops::GitStore::resolve_emit_payloads`]; this
1056/// is the same two-arm decision over a slice, written once so that a test
1057/// fixture and a hand-built pack cannot drift from what a served clone does
1058/// (LAW 5). An extent that runs off the buffer is an error and never a short
1059/// slice, for the reason [`crate::archive_map::Mapped::get`] gives: a truncated
1060/// entry spliced into a pack is data the client will only reject much later.
1061pub fn resolve_against<'b>(e: &'b EmitEntry, archive: &'b [u8]) -> Result<&'b [u8]> {
1062    match &e.stored {
1063        EntryBytes::Owned(v) => Ok(v),
1064        EntryBytes::Extent { offset, len } => {
1065            let (a, b) = (*offset as usize, (*offset + *len) as usize);
1066            archive.get(a..b).ok_or_else(|| {
1067                anyhow!(
1068                    "the entry at archive offset {offset} spans ({offset}, {len}), which runs off \
1069                     the end of a {}-byte buffer",
1070                    archive.len()
1071                )
1072            })
1073        }
1074    }
1075}
1076
1077/// One object to emit: where its stored entry is, and what it deltas against.
1078#[derive(Debug, Clone)]
1079pub struct EmitEntry {
1080    /// The object id, for `REF_DELTA` rewriting and for reporting.
1081    pub oid: Vec<u8>,
1082    /// The bytes this entry contributes — header and all.
1083    ///
1084    /// Normally an [`EntryBytes::Extent`]: the stored entry **exactly as
1085    /// received**, addressed and not copied. For the entry shapes that cannot be
1086    /// shipped verbatim — chiefly a delta whose base the caller is not sending —
1087    /// [`crate::git_ops::GitStore::emit_set`] rebuilds the bytes from the
1088    /// resolved object, hands them over as [`EntryBytes::Owned`], and says so in
1089    /// [`Self::recompressed`].
1090    pub stored: EntryBytes,
1091    /// The entry type **of `stored`**, so a copied delta is
1092    /// `OfsDelta`/`RefDelta` and a rebuilt one is the resolved type.
1093    pub obj_type: ObjType,
1094    /// Post-resolution size, which is what the entry header carries.
1095    pub uncompressed_size: u64,
1096    /// The base's archive offset, `0` for none.
1097    pub delta_base: u64,
1098    /// This entry's own archive offset — the key other entries name it by.
1099    pub offset: u64,
1100    /// **`stored` was rebuilt rather than copied**, so its payload was inflated
1101    /// and re-deflated.
1102    ///
1103    /// Carried on the entry rather than inferred at emission because nothing
1104    /// downstream can tell one zlib stream from another: a re-deflate and a copy
1105    /// produce packs that both pass `git index-pack --strict` and differ only in
1106    /// the CPU they cost. This flag is what makes [`EmitReport::recompressed`] a
1107    /// count of what happened instead of a hopeful zero.
1108    pub recompressed: bool,
1109    /// **`stored` is a delta this server COMPUTED**, against a base the request
1110    /// does carry — rather than the whole object.
1111    ///
1112    /// A strict refinement of [`Self::recompressed`], never set without it: such
1113    /// an entry really was inflated and re-deflated, so it is not `copied` and
1114    /// must not be counted as one. What it adds is *which* rebuild happened,
1115    /// because the two differ by ~4× on the wire and the receipt is the only
1116    /// place that difference is visible — a whole rebuild and a computed delta
1117    /// both pass `index-pack --strict` and `fsck`.
1118    ///
1119    /// Zero for a full clone, for the same reason `recompressed` is: a
1120    /// whole-repository request contains every base, so nothing is rebuilt at
1121    /// all. See [`crate::delta`].
1122    pub deltified: bool,
1123}
1124
1125/// Order `entries` so that every delta follows the base it names.
1126///
1127/// # Why a topological order and not the input order
1128///
1129/// `OFS_DELTA` names its base by **backwards** distance. A base that has not
1130/// been written yet has no distance to name, so an order that puts a delta first
1131/// is not merely inefficient — it cannot be encoded at all.
1132///
1133/// The edges are already data: `delta_base` is an absolute archive offset, and
1134/// `offset` is what an entry is named by. So this is a sort over facts the index
1135/// holds, not a graph anyone has to build.
1136///
1137/// # An entry whose base is not in the set
1138///
1139/// Left where it is, and reported. The caller decides: add the base (right for a
1140/// clone, where the base is reachable anyway) or rewrite the entry as a
1141/// `REF_DELTA` naming the base by oid (right for a thin fetch, where the client
1142/// consented and already holds it). Deciding here would make one of those two
1143/// impossible.
1144///
1145/// Returns the ordered entries and the offsets that were named but absent.
1146pub fn topological_order(entries: Vec<EmitEntry>) -> (Vec<EmitEntry>, Vec<u64>) {
1147    use std::collections::{HashMap, HashSet};
1148
1149    let present: HashMap<u64, usize> = entries
1150        .iter()
1151        .enumerate()
1152        .map(|(i, e)| (e.offset, i))
1153        .collect();
1154
1155    let mut missing = Vec::new();
1156    let mut done: HashSet<usize> = HashSet::new();
1157    // The ORDER, not the entries: the traversal used to push
1158    // `entries[i].clone()`, and an `EmitEntry` owns its stored bytes — so
1159    // ordering a pack cloned every payload in it, a full second copy of the
1160    // emission. Profiled on oden 2026-08-12 as part of the ~44 % of serve CPU
1161    // spent copying/zeroing at 32 concurrent clones. The entries are moved out
1162    // by slot once the order is known; not one payload byte is copied here.
1163    let mut order: Vec<usize> = Vec::with_capacity(entries.len());
1164
1165    // Iterative rather than recursive: a delta chain is allowed to be 50 deep by
1166    // default and nothing forbids a pathological one, so the depth belongs on
1167    // the heap where it cannot take the thread's stack with it.
1168    for start in 0..entries.len() {
1169        if done.contains(&start) {
1170            continue;
1171        }
1172        let mut stack = vec![start];
1173        let mut on_path: HashSet<usize> = HashSet::new();
1174        while let Some(&i) = stack.last() {
1175            if done.contains(&i) {
1176                stack.pop();
1177                continue;
1178            }
1179            let base = entries[i].delta_base;
1180            let pending = if base == 0 {
1181                None
1182            } else {
1183                match present.get(&base) {
1184                    Some(&b) if !done.contains(&b) => {
1185                        // A cycle is impossible in a well-formed pack — a base
1186                        // always precedes its delta, so the offsets strictly
1187                        // decrease — but a corrupt one could claim otherwise,
1188                        // and looping for ever is a worse answer than emitting
1189                        // in an order the encoder will then refuse.
1190                        if on_path.contains(&b) {
1191                            None
1192                        } else {
1193                            Some(b)
1194                        }
1195                    }
1196                    Some(_) => None,
1197                    None => {
1198                        missing.push(base);
1199                        None
1200                    }
1201                }
1202            };
1203            match pending {
1204                Some(b) => {
1205                    on_path.insert(i);
1206                    stack.push(b);
1207                }
1208                None => {
1209                    stack.pop();
1210                    on_path.remove(&i);
1211                    done.insert(i);
1212                    order.push(i);
1213                }
1214            }
1215        }
1216    }
1217
1218    missing.sort_unstable();
1219    missing.dedup();
1220    // Materialise by MOVING each entry into its ordered place. Every index is
1221    // in `order` exactly once (`done` gates the push), so every slot is taken
1222    // exactly once.
1223    let mut slots: Vec<Option<EmitEntry>> = entries.into_iter().map(Some).collect();
1224    let out: Vec<EmitEntry> = order
1225        .into_iter()
1226        .map(|i| {
1227            slots[i]
1228                .take()
1229                .expect("topological_order emitted an index twice")
1230        })
1231        .collect();
1232    (out, missing)
1233}
1234
1235#[cfg(test)]
1236mod order_tests {
1237    use super::*;
1238
1239    fn e(offset: u64, delta_base: u64) -> EmitEntry {
1240        EmitEntry {
1241            oid: vec![offset as u8],
1242            stored: EntryBytes::Owned(Vec::new()),
1243            obj_type: if delta_base == 0 {
1244                ObjType::Blob
1245            } else {
1246                ObjType::OfsDelta
1247            },
1248            uncompressed_size: 0,
1249            delta_base,
1250            offset,
1251            recompressed: false,
1252            deltified: false,
1253        }
1254    }
1255
1256    /// **Every base precedes every delta that names it**, asserted as a
1257    /// position comparison rather than by eyeballing the sequence.
1258    ///
1259    /// The input is deliberately worst-case: a chain handed in exactly
1260    /// backwards, so an implementation that returned its input — or that sorted
1261    /// by offset, which looks right and is not — fails. Sorting by offset
1262    /// happens to work here only because the chain is linear, so a fork is in
1263    /// the fixture too.
1264    #[test]
1265    fn a_base_always_precedes_the_delta_that_names_it() {
1266        // 100 <- 200 <- 300, plus 400 forking off 200, handed in reverse.
1267        let entries = vec![e(400, 200), e(300, 200), e(200, 100), e(100, 0)];
1268        let (ordered, missing) = topological_order(entries);
1269
1270        assert!(missing.is_empty(), "nothing was absent: {missing:?}");
1271        assert_eq!(ordered.len(), 4, "every entry must be emitted exactly once");
1272
1273        let at = |off: u64| ordered.iter().position(|x| x.offset == off).unwrap();
1274        for (delta, base) in [(200u64, 100u64), (300, 200), (400, 200)] {
1275            assert!(
1276                at(base) < at(delta),
1277                "base {base} at {} must precede delta {delta} at {}",
1278                at(base),
1279                at(delta)
1280            );
1281        }
1282    }
1283
1284    /// A base outside the set is **reported, not invented**.
1285    ///
1286    /// The caller has two correct answers — add the base, or rewrite as
1287    /// `REF_DELTA` — and both need to know which offset was missing. Silently
1288    /// dropping the entry would produce a pack whose closure does not hold, and
1289    /// `index-pack --strict` would be the first thing to say so, a long way from
1290    /// here.
1291    #[test]
1292    fn a_base_outside_the_set_is_reported_rather_than_dropped() {
1293        let (ordered, missing) = topological_order(vec![e(300, 999), e(100, 0)]);
1294        assert_eq!(missing, vec![999], "the absent base must be named");
1295        assert_eq!(ordered.len(), 2, "the entry stays; the caller decides");
1296    }
1297
1298    /// A cycle cannot happen in a well-formed pack — offsets strictly decrease
1299    /// along a chain — but a corrupt one may claim it, and looping for ever is
1300    /// the worst possible answer.
1301    #[test]
1302    fn a_cycle_terminates_instead_of_hanging() {
1303        let (ordered, _) = topological_order(vec![e(100, 200), e(200, 100)]);
1304        assert_eq!(ordered.len(), 2, "both entries must still be emitted");
1305    }
1306}
1307
1308/// What emitting produced, in facts a caller can assert on.
1309#[derive(Debug, Clone, PartialEq, Eq, Default)]
1310pub struct EmitReport {
1311    /// Entries written.
1312    pub written: u32,
1313    /// Entries whose stored payload was copied unchanged. It is counted rather
1314    /// than assumed because "correct but slow" is the failure `index-pack
1315    /// --strict` and `fsck` both pass.
1316    pub copied: u32,
1317    /// Entries whose payload was **inflated and re-deflated** because the caller
1318    /// could not ship the stored bytes — a delta whose base is outside the
1319    /// request. `copied + recompressed == written`, always.
1320    ///
1321    /// Zero for a whole-repository clone, which is why the copy claim survives:
1322    /// a full clone's selection contains every base, so no entry ever needs
1323    /// rebuilding. It is non-zero exactly at the boundary a narrowed request
1324    /// cuts through, and that is the number worth watching.
1325    pub recompressed: u32,
1326    /// Of the [`Self::recompressed`], how many went out as a **computed delta**
1327    /// against a base the pack carries rather than as a whole object.
1328    /// `deltified <= recompressed`, always.
1329    ///
1330    /// The two rebuilds cost the same receipt and very different bytes, so
1331    /// without this column a boundary-heavy request looks identical whether the
1332    /// re-delta ran or not. Measured on `h2h-linear-sha1-2048c-1024f-16k`'s
1333    /// narrowed clone: **961 recompressed, of which 859 deltified**, and the
1334    /// pack fell from 11 613 666 bytes to 6 424 726 for the identical 31 805
1335    /// objects.
1336    pub deltified: u32,
1337    /// Entries whose `OFS_DELTA` distance had to be re-encoded because the gap
1338    /// to their base changed. Zero only if the subset happened to preserve every
1339    /// gap, which for anything but a whole pack it will not.
1340    pub rebased: u32,
1341    /// Bytes on the wire, trailer included.
1342    pub bytes: u64,
1343}
1344
1345/// Assemble ordered entries into a packfile.
1346///
1347/// # What this does and does not do
1348///
1349/// It copies. Every entry's compressed payload goes out **byte for byte** as
1350/// `payload_of` hands it over; nothing here inflates, deflates or computes a
1351/// delta. The only thing rewritten is the entry header, and only because
1352/// `OFS_DELTA` carries a distance that is relative to a position in the *input*
1353/// pack. An entry whose payload the *caller* rebuilt says so in
1354/// [`EmitEntry::recompressed`] and is counted apart, so the receipt names which
1355/// of the two happened rather than assuming.
1356///
1357/// `entries` must already be in [`topological_order`] and must be closed —
1358/// every `OFS_DELTA` base present. A base that is absent is an error here
1359/// rather than a silently dropped back-reference, because the alternative is a
1360/// pack whose closure does not hold and a client that discovers it.
1361///
1362/// The caller supplies `stored_of`, which **borrows** entry `i`'s whole stored
1363/// bytes — header included. Splitting it that way keeps this function free of
1364/// any opinion about where bytes live: they may be a slice of a mapping of the
1365/// archive, a slice of an owned buffer the caller rebuilt, or anything else that
1366/// outlives the call.
1367///
1368/// # Why the WHOLE entry and not the payload
1369///
1370/// It used to be `payload_of`, handing over `stored[header_len..]`, and the
1371/// header the caller had just stripped was then re-parsed **here** anyway — for
1372/// the stated size, and for a `REF_DELTA`'s base oid. Three readings of one
1373/// varint across two files, and the caller's `header_len` had to agree with this
1374/// function's `type_and_size` or a ref-delta went out with its base named twice
1375/// (which it once did: `git index-pack --strict` answered `inflate returned 1`).
1376/// One reading, in one place, is LAW 5's fix by construction.
1377///
1378/// # It streams, and it borrows — `P-018` and `P-025`
1379///
1380/// Two properties, both deliberate, both previously absent:
1381///
1382/// * **`stored_of` returns `&[u8]`, not `Vec<u8>`.** It used to return an owned
1383///   buffer, which is `P-025` in its exact form: `data.to_owned()`, **one heap
1384///   allocation per object served**, on a path whose entire claim is that it
1385///   copies stored bytes without touching them. It is indexed by entry rather
1386///   than handed an `&EmitEntry` so that the bytes may live *outside* the entry —
1387///   which since 2026-08-14 they do: an [`EntryBytes::Extent`] is an address, and
1388///   the bytes it names are a slice of the mapped archive.
1389/// * **The pack goes to `out` as it is built, and the trailer is hashed
1390///   incrementally.** It used to accumulate the whole pack in a `Vec<u8>` and
1391///   hash it at the end, so serving a 2 GiB clone meant holding 2 GiB. The one
1392///   fact the old shape got for free — the output offset an `OFS_DELTA` distance
1393///   is measured against — is counted here instead, which is cheaper than the
1394///   buffer that was carrying it.
1395///
1396/// What is emphatically **not** here is gix's pack pipeline: no counting pass
1397/// with a serial reduce, no `sort_by` over counts, no `BTreeMap` reorder, no
1398/// hashing of every byte on a consuming thread. Those are `P-018`'s serial tail,
1399/// and this function is the reason none of it is needed — the bytes are already
1400/// deflated and already delta-encoded, so emitting is a copy and an addition.
1401pub fn emit_pack<'p>(
1402    entries: &'p [EmitEntry],
1403    hash: GitHashKind,
1404    out: &mut dyn std::io::Write,
1405    stored_of: &dyn Fn(usize) -> Result<&'p [u8]>,
1406) -> Result<EmitReport> {
1407    use std::collections::HashMap;
1408
1409    let count = u32::try_from(entries.len()).map_err(|_| {
1410        anyhow!(
1411            "a pack holds at most u32::MAX entries, was given {}",
1412            entries.len()
1413        )
1414    })?;
1415
1416    let mut out = Trailing::new(out, hash);
1417    out.put(b"PACK")?;
1418    out.put(&2u32.to_be_bytes())?;
1419    out.put(&count.to_be_bytes())?;
1420    // One scratch buffer for entry headers, reused for every entry. A header is
1421    // a handful of bytes and there is one per object, so allocating it per entry
1422    // would be the same defect `payload_of` above just stopped committing.
1423    let mut hdr: Vec<u8> = Vec::with_capacity(32);
1424
1425    // Where each input offset landed in the output. Built as we go, which is
1426    // exactly why the order has to be topological: a delta's base must already
1427    // be in here when the delta is written.
1428    let mut placed: HashMap<u64, u64> = HashMap::with_capacity(entries.len());
1429    let mut report = EmitReport::default();
1430
1431    for (i, e) in entries.iter().enumerate() {
1432        let here = out.written();
1433
1434        // The entry's whole stored bytes — a slice of the mapped archive for
1435        // everything a clone sends, a slice of a rebuilt buffer for the
1436        // exceptions. Resolved once and read three times below; asking for it
1437        // per use would be three bounds checks or three preads.
1438        let stored = stored_of(i)?;
1439
1440        // **The size in the header is the one the ENTRY states, not the one the
1441        // index holds.** For a delta they are different numbers: the header
1442        // carries the length of the delta stream, and `uncompressed_size` is the
1443        // size after the chain is applied. Re-encoding a delta header with the
1444        // resolved size produces a stream `git index-pack --strict` calls
1445        // `inflate returned 1`, which is what it did. Re-reading the grammar
1446        // rather than trusting a neighbouring column is the same discipline
1447        // `header_len` follows, and for the same reason.
1448        let (_, stated_size, varint_len) = type_and_size(stored)?;
1449
1450        match e.obj_type {
1451            ObjType::OfsDelta => {
1452                let base_at = *placed.get(&e.delta_base).ok_or_else(|| {
1453                    anyhow!(
1454                        "entry at {} deltas against archive offset {}, which is not in this pack \
1455                         — the set is not closed and was not ordered by `topological_order`",
1456                        e.offset,
1457                        e.delta_base
1458                    )
1459                })?;
1460                // Backwards distance in OUTPUT coordinates. `here` is always
1461                // greater, because the base was written first.
1462                let distance = here - base_at;
1463                hdr.clear();
1464                encode_type_and_size(&mut hdr, ObjType::OfsDelta, stated_size);
1465                encode_ofs_distance(&mut hdr, distance);
1466                out.put(&hdr)?;
1467                report.rebased += 1;
1468            }
1469            ObjType::RefDelta => {
1470                hdr.clear();
1471                encode_type_and_size(&mut hdr, ObjType::RefDelta, stated_size);
1472                // The base oid follows the header verbatim; it names an object
1473                // rather than a position, so a subset never invalidates it.
1474                let oid_len = hash.oid_len();
1475                // The oid sits directly after the type/size varint — NOT after
1476                // `header_len`, which now includes the oid itself.
1477                let base = stored.get(varint_len..).and_then(|r| r.get(..oid_len));
1478                let base = base.ok_or_else(|| {
1479                    anyhow!(
1480                        "a ref-delta entry at {} has no base oid after its header",
1481                        e.offset
1482                    )
1483                })?;
1484                hdr.extend_from_slice(base);
1485                out.put(&hdr)?;
1486            }
1487            t => {
1488                hdr.clear();
1489                encode_type_and_size(&mut hdr, t, stated_size);
1490                out.put(&hdr)?;
1491            }
1492        }
1493
1494        // The payload is what follows the header the loop above just re-encoded,
1495        // and `header_len` is the one reading of that grammar. **This is the only
1496        // place a stored byte is touched** — a page fault on the mapping, a hash
1497        // update and a write, with no buffer in between.
1498        out.put(&stored[header_len(stored, hash)?..])?;
1499        if e.recompressed {
1500            report.recompressed += 1;
1501            if e.deltified {
1502                report.deltified += 1;
1503            }
1504        } else {
1505            report.copied += 1;
1506            debug_assert!(
1507                !e.deltified,
1508                "a computed delta was inflated and re-deflated to produce it; counting it as a \
1509                 copy would make the receipt a claim rather than a measurement"
1510            );
1511        }
1512        report.written += 1;
1513        placed.insert(e.offset, here);
1514    }
1515
1516    // The trailer is over everything written, which is what a reader checks.
1517    report.bytes = out.finish()?;
1518    Ok(report)
1519}
1520
1521/// `out`, plus the running pack checksum and the byte count `OFS_DELTA`
1522/// distances are measured against.
1523///
1524/// The two facts the old `Vec<u8>` accumulator was really being kept for. Held
1525/// as three fields instead, so a clone streams and nothing sizes an allocation
1526/// by the repository.
1527struct Trailing<'a> {
1528    inner: &'a mut dyn std::io::Write,
1529    digest: Digest,
1530    written: u64,
1531}
1532
1533/// The pack trailer's hash, fed as the pack is written.
1534///
1535/// An enum and not a `Box<dyn Digest>`: there are exactly two, both are
1536/// already direct dependencies of this crate, and a vtable per `update` on the
1537/// one path that touches every emitted byte is not a cost this takes for two
1538/// variants.
1539enum Digest {
1540    Sha1(sha1::Sha1),
1541    Sha256(sha2::Sha256),
1542}
1543
1544impl<'a> Trailing<'a> {
1545    fn new(inner: &'a mut dyn std::io::Write, hash: GitHashKind) -> Self {
1546        use sha1::Digest as _;
1547        Trailing {
1548            inner,
1549            digest: match hash {
1550                GitHashKind::Sha1 => Digest::Sha1(sha1::Sha1::new()),
1551                GitHashKind::Sha256 => Digest::Sha256(sha2::Sha256::new()),
1552            },
1553            written: 0,
1554        }
1555    }
1556
1557    /// Bytes written so far — the output coordinate an `OFS_DELTA` distance is
1558    /// relative to.
1559    fn written(&self) -> u64 {
1560        self.written
1561    }
1562
1563    fn put(&mut self, bytes: &[u8]) -> Result<()> {
1564        use sha1::Digest as _;
1565        match &mut self.digest {
1566            Digest::Sha1(d) => d.update(bytes),
1567            Digest::Sha256(d) => d.update(bytes),
1568        }
1569        self.inner
1570            .write_all(bytes)
1571            .context("writing an emitted pack")?;
1572        self.written += bytes.len() as u64;
1573        Ok(())
1574    }
1575
1576    /// Append the trailer and report the total, trailer included.
1577    fn finish(self) -> Result<u64> {
1578        use sha1::Digest as _;
1579        let digest: Vec<u8> = match self.digest {
1580            Digest::Sha1(d) => d.finalize().to_vec(),
1581            Digest::Sha256(d) => d.finalize().to_vec(),
1582        };
1583        self.inner
1584            .write_all(&digest)
1585            .context("writing the pack trailer")?;
1586        self.inner.flush().context("flushing an emitted pack")?;
1587        Ok(self.written + digest.len() as u64)
1588    }
1589}
1590
1591/// How many bytes of a stored entry are header — everything before the
1592/// compressed payload.
1593///
1594/// For a delta that includes the back-reference: the `OFS_DELTA` distance or the
1595/// `REF_DELTA` oid. This is what a caller strips to get the payload, and it is
1596/// derived by re-reading the grammar rather than remembered, so it cannot drift
1597/// from what the walk parses.
1598pub fn header_len(stored: &[u8], hash: GitHashKind) -> Result<usize> {
1599    let (t, _, n) = type_and_size(stored)?;
1600    Ok(match t {
1601        ObjType::OfsDelta => {
1602            let (_, d) = ofs_distance(stored.get(n..).unwrap_or(&[]))?;
1603            n + d
1604        }
1605        // **The base oid is part of the header, not of the payload.** It was
1606        // excluded here once, on the reasoning that its width is the caller's to
1607        // know. That made `stored[header_len..]` still contain the oid, and
1608        // `emit_pack` writes the oid itself — so every ref-delta went out with
1609        // its base named twice and `git index-pack --strict` answered
1610        // `inflate returned 1`. The hash kind is a parameter now, and the
1611        // ambiguity is gone rather than documented.
1612        ObjType::RefDelta => n + hash.oid_len(),
1613        _ => n,
1614    })
1615}
1616
1617#[cfg(test)]
1618mod emit_tests {
1619    use super::*;
1620
1621    /// **A pack this emits is one `git index-pack --strict` accepts.**
1622    ///
1623    /// The arbiter is stock git and nothing else. Our own parser reading back
1624    /// what our own writer produced proves the two agree with each other, which
1625    /// is exactly the circularity that lets a wrong varint through — both halves
1626    /// would be wrong the same way. `index-pack --strict` shares no code with
1627    /// this file.
1628    ///
1629    /// The corpus is a real pack off this machine, re-emitted whole. Whole
1630    /// rather than a subset for the first assertion, because a whole pack is the
1631    /// case where every delta base is present by construction, so a failure here
1632    /// is the encoder and cannot be the closure.
1633    #[test]
1634    fn stock_git_accepts_a_pack_we_emitted() {
1635        if std::process::Command::new("git")
1636            .arg("--version")
1637            .output()
1638            .is_err()
1639        {
1640            eprintln!("skipping: no git on PATH");
1641            return;
1642        }
1643        let (pack, _) = crate::store::tests::corpus_pack();
1644        let walk = walk(&pack, GitHashKind::Sha1.oid_len()).expect("the corpus pack walks");
1645
1646        let entries: Vec<EmitEntry> = walk
1647            .entries
1648            .iter()
1649            .map(|e| EmitEntry {
1650                oid: Vec::new(),
1651                // The corpus pack is in memory here, so the extent IS the pack's
1652                // own offset and the resolver below slices it. That is exactly
1653                // the shape a store uses, with the mapping in place of `pack`.
1654                stored: EntryBytes::Extent {
1655                    offset: e.offset,
1656                    len: e.len,
1657                },
1658                obj_type: e.obj_type,
1659                uncompressed_size: e.uncompressed_size,
1660                delta_base: e.delta_base.as_offset(),
1661                offset: e.offset,
1662                recompressed: false,
1663                deltified: false,
1664            })
1665            .collect();
1666        let n = entries.len();
1667        assert!(
1668            n > 0,
1669            "the corpus pack has no entries; nothing is being tested"
1670        );
1671
1672        let (ordered, missing) = topological_order(entries);
1673        assert!(
1674            missing.is_empty(),
1675            "a whole pack must be closed: {missing:?}"
1676        );
1677
1678        let mut bytes = Vec::new();
1679        let report = emit_pack(&ordered, GitHashKind::Sha1, &mut bytes, &|i| {
1680            resolve_against(&ordered[i], &pack)
1681        })
1682        .expect("emitting");
1683        assert_eq!(
1684            report.bytes as usize,
1685            bytes.len(),
1686            "the report's byte count must be what was actually written"
1687        );
1688
1689        assert_eq!(report.written as usize, n, "every entry must be written");
1690        assert_eq!(
1691            report.copied, report.written,
1692            "every payload must be COPIED"
1693        );
1694
1695        // 🔴 Through [`crate::git_oracle`], and that is the 2026-08-11 fix here:
1696        // this ran `index-pack --strict` in a plain `tempfile::tempdir()`, and
1697        // outside a repository that command **segfaults** on any pack it would
1698        // have rejected — silently, with no output. The oracle now runs inside a
1699        // fresh bare repo and refuses to read a signal death as a verdict.
1700        let dir = tempfile::tempdir().expect("tempdir");
1701        crate::git_oracle::assert_git_accepts(
1702            dir.path(),
1703            "emitted.git",
1704            &bytes,
1705            crate::git_oracle::Strictness::Connected,
1706        );
1707    }
1708}