Skip to main content

znippy_plugin_git/
serve.rs

1//! **The reading contract, answered out of Arrow IPC.** No gix, anywhere.
2//!
3//! ZNIPPY-GIT APACHE ARROW IPC IS LAW. [`crate::store`] holds the eleven — what
4//! a caller *stores*; this file holds [`GitServe`] — what a caller *reads*, and
5//! what a clone is served from. The split is the contract's, not this crate's:
6//! `GitOps::get` hands back the pack entry **byte for byte as the client sent
7//! it**, which for a delta is a delta, and nothing on the eleven inflates one.
8//!
9//! ```text
10//!   read / header / sizes        →  the objects table, then §14's exploded table
11//!   head / set_head              →  the ref log; HEAD is NOT a row in refs()
12//!   select                       →  roaring bitmaps over the store's ordinals
13//!   emit_pack                    →  emit_set → topological_order → byte-range copy
14//! ```
15//!
16//! # 🚨 The trap this file exists to avoid, stated once
17//!
18//! The working reference for every method below is `gunnar-coldtier`'s
19//! `git_store.rs`, and it carries **87 `gix_` references**. It reads objects
20//! through `gix_pack::Find` / `decode_entry` / `decode_header`, and it emits
21//! packs through `iter_from_counts` / `InOrderIter` / `FromEntriesIter`.
22//! **Its intent is ported here. Not one line of its code is.**
23//!
24//! `znippy-plugin-git` links **zero gix in `[dependencies]`** — the four gix
25//! crates it names are `[dev-dependencies]`, the differential oracle in
26//! [`crate::pack_walk`] that proves this crate's parser agrees with gitoxide
27//! *without shipping it*. That separation is the entire point of two
28//! implementations behind one contract, and no method here may create an edge
29//! into it. Not for a parser, not for `gix-hash`, not temporarily.
30//!
31//! Nor would copying it be desirable. That pack pipeline **is** `P-018`'s serial
32//! tail — counting's serial reduce, a `sort_by` over counts, `InOrderIter`'s
33//! `BTreeMap` reorder, `FromEntriesIter` hashing every byte on the consuming
34//! thread — plus `P-025`'s residue, `data.to_owned()`, one heap allocation per
35//! object served. The Arrow arm's answer to all of it is a **byte-range copy out
36//! of the archive**, and [`crate::pack_walk::emit_pack`] is where it lives.
37//!
38//! # The serving tier is serial — `P-004`, DECIDED, with one bounded exception
39//!
40//! `sizes` does not fan out. `select` does not fan out. The two-tier split is
41//! settled: **request concurrency, no intra-request fan-out**, one core's worth
42//! per transfer behind an admission gate. At saturation, parallelising *one*
43//! clone across N cores does not raise clones per second — it is the same work
44//! rearranged — and the constellation's fan-out primitive has no reentrancy
45//! detection, so a fan-out here inside a fan-out there silently spawns W².
46//! `gatling` belongs in [`crate::indexer`]'s background tier, which is where
47//! nearly all of it is.
48//!
49//! The exception, added 2026-08-14, is **phase 1 of `emit_oids`** —
50//! [`GitStore::resolve_emit_payloads`](crate::git_ops::GitStore::resolve_emit_payloads),
51//! which turns each entry's archive address into a slice of the mapped archive.
52//! It is allowed for the reason the decision above is stated in terms of and not
53//! against it:
54//!
55//!  * it is **bounded at 4 workers**, not at ncores, so 32 admitted transfers
56//!    are 128 threads at worst rather than 32 × ncores — the admission gate
57//!    stays in charge of the machine;
58//!  * it **does not run at all** below 4096 entries, so every request small
59//!    enough for thread-spawn to dominate is byte for byte the old serial path;
60//!  * it takes **no lock**, because the archive is append-only and immutable
61//!    behind a snapshot, so it is not the "same work rearranged" — it is work
62//!    that has no serial dependency to rearrange around.
63//!
64//! Phase 2 — output offsets, `OFS_DELTA` distances, the writes and the running
65//! hash — stays strictly serial, and that is not a bound anyone chose: an
66//! `OFS_DELTA` names its base by distance back in the *output* pack, so entry
67//! *n* cannot be encoded until every earlier entry's length is known.
68//!
69//! Every method **blocks**. Never call one from an async task without
70//! `tokio::task::spawn_blocking`.
71
72use anyhow::{anyhow, bail, Context as _, Result};
73
74pub use git_storage_trait::{Caps, GitServe, PackStats, ReachSet};
75
76use crate::git_ops::{GitOps, GitStore, RefRow, TxId};
77use crate::index_layout::{ObjType, ObjectIndex};
78use crate::object::GitObjectKind;
79use crate::refs::RefUpdate;
80use crate::store::Oid;
81
82/// `HEAD`, the one pseudo-ref this contract names.
83///
84/// It is **not** a row in [`GitOps::refs`] — see [`GitStore::head`] for the
85/// argument, which is a type constraint rather than a preference.
86pub const HEAD: &str = "HEAD";
87
88/// The resolved kind, in the contract's vocabulary.
89///
90/// A total function, and that is the guarantee: [`GitObjectKind`] has exactly
91/// the four real types and no delta variants, so a value that came out of a
92/// resolver **cannot** be labelled `OfsDelta` or `RefDelta` by accident. The
93/// contract's `ObjType` has six codes and this is the one place the narrowing is
94/// written down — [`crate::git_ops::GitStore::emit_set`] rebuilds whole entries
95/// and needs the same narrowing, and calls this rather than repeating it.
96pub(crate) fn resolved_type(kind: GitObjectKind) -> ObjType {
97    match kind {
98        GitObjectKind::Commit => ObjType::Commit,
99        GitObjectKind::Tree => ObjType::Tree,
100        GitObjectKind::Blob => ObjType::Blob,
101        GitObjectKind::Tag => ObjType::Tag,
102    }
103}
104
105/// How many bytes of an entry are read to find its type and its base.
106///
107/// A type/size varint is at most 10 bytes and an `OFS_DELTA` distance at most 9,
108/// so 64 covers every header plus a 32-byte `REF_DELTA` base oid with room over.
109/// It is a **ceiling on a `pread`**, not a promise about the entry: a shorter
110/// entry reads short and the grammar still terminates.
111const HEADER_PROBE: u64 = 64;
112
113impl<S: ObjectIndex + 'static> GitStore<S> {
114    /// The chain walk behind [`GitServe::header`]: the **resolved** type of the
115    /// entry at `offset`, reading entry headers only.
116    ///
117    /// # Why this is not "just read the column"
118    ///
119    /// `objects.obj_type` is the entry's type **in the pack**, so a delta's is
120    /// `OfsDelta` or `RefDelta` and the resolved kind is one or more entries
121    /// away. `objects.uncompressed_size` has no such problem — it is written
122    /// after delta application — which is why [`GitServe::sizes`] is a pure
123    /// index pass and this is not.
124    ///
125    /// # What it costs, exactly
126    ///
127    /// One 64-byte `pread` per link of the chain, and **no inflate, no delta
128    /// application, no allocation sized by the object**. gix answers the same
129    /// question with `decode_header`, which walks the same links; the difference
130    /// is that this one reads the offsets straight out of §13's `delta_base`
131    /// column for the first hop rather than reconstructing them, and does not
132    /// need a pack handle, a zlib scratch or a delta cache to do it.
133    ///
134    /// Chains are **not** bounded by the pushing client's `pack.depth`: §14
135    /// stores a thin pack verbatim, so a `REF_DELTA` goes on naming a base in
136    /// an earlier pack and a repository pushed one commit at a time grows one
137    /// link per push. The walk therefore carries a termination *proof*
138    /// ([`crate::resolve::CycleGuard`], the same one
139    /// [`crate::resolve::materialize_at`] uses — LAW 5) rather than a link
140    /// count: a repeated offset is a cycle and is refused by name, a deep chain
141    /// is not. The hand-written `MAX_LINKS = 100` that stood here refused the
142    /// 101st link of an ordinary push history; see that constant's 🔴.
143    fn resolved_type_at(&self, offset: u64, obj_type: ObjType, delta_base: u64) -> Result<ObjType> {
144        // One mapping for the whole chain walk. A probe used to be one `pread`
145        // per link — an allocation and a kernel→user copy for 64 bytes that are
146        // already in the page cache; through the mapping it is a bounds check
147        // and a pointer, and the `Cow` is `Borrowed` on every link the snapshot
148        // covers.
149        let snap = self.archive_snapshot()?;
150        let mut at = offset;
151        let mut t = obj_type;
152        let mut base = delta_base;
153        let mut guard = crate::resolve::CycleGuard::new();
154        loop {
155            guard.step(at)?;
156            match t {
157                ObjType::Commit | ObjType::Tree | ObjType::Blob | ObjType::Tag => return Ok(t),
158                ObjType::OfsDelta => {
159                    // §13's column already holds the base's **absolute archive
160                    // offset**, resolved at absorb time. No distance arithmetic
161                    // and no second parse of this entry.
162                    if base == 0 {
163                        bail!(
164                            "the entry at archive offset {at} is an ofs-delta whose base column is \
165                             0, which is the no-base sentinel — the index disagrees with itself"
166                        );
167                    }
168                    at = base;
169                }
170                ObjType::RefDelta => {
171                    // A ref-delta's `delta_base` column is 0 by construction —
172                    // its base is named by oid, inside the entry, directly after
173                    // the type/size varint. The same few bytes
174                    // `GitStore::emit_set` reads for the same reason.
175                    let head = self.extent(&snap, at, HEADER_PROBE)?;
176                    let (_, _, n) = crate::pack_walk::type_and_size_of(&head)?;
177                    let oid_len = self.hash_kind().oid_len();
178                    let base_oid = head.get(n..n + oid_len).ok_or_else(|| {
179                        anyhow!(
180                            "the ref-delta entry at archive offset {at} has no base oid after its \
181                             header"
182                        )
183                    })?;
184                    let Some(row) = self.index().lookup(base_oid) else {
185                        bail!(
186                            "the ref-delta entry at archive offset {at} names a base this \
187                             repository does not have — its type cannot be resolved"
188                        );
189                    };
190                    at = row.offset;
191                    t = row.obj_type;
192                    base = row.delta_base;
193                    continue;
194                }
195            }
196            // One header read at the new position. The type column would be a
197            // second lookup keyed the wrong way (by offset, and the index is
198            // keyed by oid), so the entry's own grammar answers it.
199            let head = self.extent(&snap, at, HEADER_PROBE)?;
200            let (next_t, _, n) = crate::pack_walk::type_and_size_of(&head)?;
201            t = next_t;
202            base = match next_t {
203                ObjType::OfsDelta => {
204                    let (distance, _) = crate::pack_walk::ofs_distance_of(&head[n..])?;
205                    at.checked_sub(distance).ok_or_else(|| {
206                        anyhow!(
207                            "the ofs-delta at archive offset {at} names a base {distance} bytes \
208                             back, which is before the start of the archive"
209                        )
210                    })?
211                }
212                _ => 0,
213            };
214        }
215    }
216
217    /// **Emit a packfile for an explicit object set**, closed over its delta
218    /// bases — and over **nothing else**.
219    ///
220    /// This is the whole of [`GitServe::emit_pack`] as of 2026-08-10: that
221    /// method is this call and a discarded `have`. It was written as the
222    /// *inherent* escape route for a trait method that closed over its input,
223    /// so that a caller told *"walk it yourself"* by [`GitServe::select`]'s
224    /// `Ok(None)` had somewhere to put the set it had walked. The trait method
225    /// stopped closing over its input, so the escape route and the front door
226    /// are now the same door. It stays inherent because the gix arm needs no
227    /// equivalent and the contract does not grow a method for it.
228    ///
229    /// # 🔴 No closure at all any more, and that is the 2026-08-11 fix
230    ///
231    /// [`GitStore::emit_set`] used to add a delta's base when the base fell
232    /// outside the request, on the reasoning that an `OFS_DELTA` names its base
233    /// by position and so cannot be encoded without it. That reasoning is
234    /// correct about the *pack format* and wrong about the *clone*: a base
235    /// pulled in is an object the client did not ask for, and if it is a tree it
236    /// arrives owing children the pack does not contain. `git index-pack
237    /// --check-self-contained-and-connected` — what a clone runs — then dies
238    /// with `did not receive expected object`, while this server logs
239    /// `git.upload_pack.served`. See `emit_set` for the measured numbers.
240    ///
241    /// So the set is now **exactly** the caller's, and the base decides how each
242    /// entry is *encoded* rather than what the pack contains: base inside the
243    /// request, copy the stored bytes; base outside it, rebuild the object
244    /// whole. That is stock `pack-objects`' rule, and `recompressed` counts the
245    /// second case honestly instead of being a literal zero.
246    ///
247    /// # Three steps, all of them znippy's own
248    ///
249    /// 1. [`GitStore::emit_set`] reads each requested entry and decides copy or
250    ///    rebuild, never adding an object;
251    /// 2. [`crate::pack_walk::topological_order`] puts every base before the
252    ///    delta naming it, which backwards distances require;
253    /// 3. [`crate::pack_walk::emit_pack`] re-encodes each entry header — the one
254    ///    thing that must change, because a distance is relative to a position
255    ///    in the *input* pack — and streams every payload **byte for byte**.
256    ///
257    /// `copied + recompressed == objects` always, and for a whole-repository
258    /// clone `recompressed` is 0 because such a request contains every base.
259    /// Both are counted off what happened rather than asserted: a byte count and
260    /// a wall clock cannot tell a pack-copy from a re-deflate, and both pass
261    /// `index-pack --strict`.
262    pub fn emit_oids(
263        &self,
264        oids: &[Oid<'_>],
265        have: &[Oid<'_>],
266        caps: &Caps,
267        out: &mut dyn std::io::Write,
268    ) -> Result<PackStats> {
269        // **Both halves, or nothing.** `caps.thin` is the client's *consent* to
270        // receive a delta whose base it must supply itself; `have` is the
271        // negotiation's answer to *which* bases those may be. Consent with no
272        // negotiated tips is a clone — the client holds nothing, so there is
273        // nothing a base could safely point at — and tips without consent is a
274        // client that never agreed to run `index-pack --fix-thin`. Either alone
275        // is a failed fetch rather than a smaller one, so either alone is `None`
276        // and the boundary entries go out whole exactly as before.
277        let thin_haves = (caps.thin && !have.is_empty()).then_some(have);
278
279        let entries = self
280            .emit_set(oids, caps.ofs_delta, thin_haves)
281            .context("building the emit set")?;
282        let (ordered, missing) = crate::pack_walk::topological_order(entries);
283        if !missing.is_empty() {
284            bail!(
285                "the emit set is not closed: {} delta base(s) absent, first at archive offset {} \
286                 — refusing to emit a pack whose closure does not hold",
287                missing.len(),
288                missing[0]
289            );
290        }
291
292        // **The one capability that changes which bytes come out**, and it is
293        // acted on in `emit_set` rather than here. A client that did not
294        // advertise `ofs-delta` used to be REFUSED by name at this point,
295        // because this engine copies stored entries and had no way to re-name a
296        // base. It has one now: an `OFS_DELTA` whose base is in the request is
297        // re-headed as a `REF_DELTA` carrying the same compressed delta stream,
298        // so that client is served a pack it can parse without a single byte
299        // being re-deflated. What remains here is the assertion that it worked —
300        // emitting an `OFS_DELTA` to such a client is the failure that looks
301        // like a working server.
302        if !caps.ofs_delta {
303            if let Some(e) = ordered.iter().find(|e| e.obj_type == ObjType::OfsDelta) {
304                bail!(
305                    "this client did not advertise ofs-delta and the entry at archive offset {} \
306                     is still an ofs-delta after the emit set was built; refusing rather than \
307                     answering with a pack the client cannot parse",
308                    e.offset
309                );
310            }
311        }
312        // `caps.thin` IS acted on now, in `emit_set`, and the pass it needed
313        // turned out to belong exactly where the note that used to sit here said
314        // it would: beside the negotiation that produced `have`. What it does is
315        // narrow, and deliberately so — it changes nothing about *which* objects
316        // are sent, only whether a boundary entry may name a base the pack does
317        // not carry instead of being rebuilt whole.
318
319        // **Borrowed, not owned.** Every entry a clone sends is an
320        // `EntryBytes::Extent` — an address, 16 bytes — which `emit_ordered`
321        // resolves to a slice of one mapping of the archive (phase 1, parallel)
322        // before streaming it (phase 2, serial). The `to_vec()` that used to sit
323        // on this path is `P-025` exactly: one heap allocation per object
324        // served, on the path whose whole claim is that it copies stored bytes
325        // without touching them.
326        let report = self
327            .emit_ordered(&ordered, out)
328            .context("emitting the pack")?;
329
330        Ok(PackStats {
331            bytes: report.bytes,
332            objects: u64::from(report.written),
333            copied: u64::from(report.copied),
334            // **Counted, not asserted.** This was the literal `0` for as long as
335            // nothing on the path could re-deflate; `emit_set` can now, for
336            // exactly the entries whose delta base the request does not contain,
337            // and the receipt says how many rather than continuing to claim
338            // none. A whole-repository clone still reports 0 — and that is a
339            // measurement of a full selection, not a promise.
340            recompressed: u64::from(report.recompressed),
341        })
342    }
343}
344
345impl<S: ObjectIndex + 'static> GitServe for GitStore<S> {
346    /// **The object, inflated and delta-resolved** — §14's exploded table, one
347    /// point lookup.
348    ///
349    /// This is the question [`GitOps::get`] deliberately does not answer, and
350    /// [`GitStore::content`] has answered it since §14 landed; the trait method
351    /// is that call plus the kind narrowing. Two paths under it, returning
352    /// identical bytes and distinguishable only by
353    /// [`crate::exploded::ExplodedStats`]: the table, or a re-derivation from
354    /// the verbatim truth when the table has been dropped or has not caught up.
355    ///
356    /// The returned [`ObjType`] can never be a delta — see [`resolved_type`].
357    fn read(&self, oid: Oid<'_>) -> Result<Option<(ObjType, Vec<u8>)>> {
358        Ok(self
359            .content(oid)?
360            .map(|(kind, bytes)| (resolved_type(kind), bytes)))
361    }
362
363    /// **Kind and post-resolution size, without the payload.**
364    ///
365    /// The size is a column, and the *right* column: `uncompressed_size` is
366    /// written after delta application, so it is the fact a `.idx` and a `.rev`
367    /// together cannot answer, and it is read rather than measured.
368    ///
369    /// The kind is not a column — `obj_type` is the entry's type in the pack —
370    /// so for the common case (a non-delta entry) this touches nothing else, and
371    /// for a delta it walks the chain's **headers** through
372    /// [`GitStore::resolved_type_at`]. Still no inflate and still no payload.
373    fn header(&self, oid: Oid<'_>) -> Result<Option<(ObjType, u64)>> {
374        // Same preamble as `has`/`extents`, and for the same reason: a store
375        // holding a pack whose bytes are durable and whose objects are not
376        // indexed yet cannot answer "absent" without lying.
377        let Some(row) = self.lookup_one(oid)? else {
378            return Ok(None);
379        };
380        let kind = self
381            .resolved_type_at(row.offset, row.obj_type, row.delta_base)
382            .with_context(|| format!("resolving the type of {}", hex::encode(oid)))?;
383        Ok(Some((kind, row.uncompressed_size)))
384    }
385
386    /// **Post-resolution sizes in bulk: one index pass, no chain touched.**
387    ///
388    /// The half of [`header`](GitServe::header) that is already data. On the
389    /// `h2h` fixture one clone paid **34 124** per-object header walks, every
390    /// one of which this collapses into a single `lookup_batch`, to establish
391    /// that a repository whose largest blob is 16 KiB holds nothing over the
392    /// 1 GiB default ceiling.
393    ///
394    /// # The preamble is not optional
395    ///
396    /// This reaches [`GitStore::index`] directly, so it absorbs pending index
397    /// work itself. Without it a pack that landed since the last drain reads as
398    /// **absent**, and an absent size that a caller treated as a pass would
399    /// silently skip the ceiling for exactly the objects a push had just
400    /// introduced. `None` at a position is *"unknown, go ask `header`"* and
401    /// never *"fine"*.
402    fn sizes(&self, oids: &[Oid<'_>]) -> Result<Vec<Option<u64>>> {
403        Ok(self
404            .lookup_batch_never_wait(oids)?
405            .into_iter()
406            .map(|row| row.map(|r| r.uncompressed_size))
407            .collect())
408    }
409
410    /// **Finish the deferred index** — one absorb per pending pack, on this
411    /// thread. See [`crate::git_ops::Absorber::index_pending_now`]: the
412    /// connectivity check is the one caller that may wait here, and it must,
413    /// because the alternative it took before was resolving the same pack a
414    /// second time on the connection thread and never finishing.
415    fn index_now(&self) -> Result<usize> {
416        GitStore::index_now(self)
417    }
418
419    /// **`HEAD`, which is not a row in [`GitOps::refs`].**
420    ///
421    /// The gix arm's `iter()` *"walks `refs/` (loose and packed) and deliberately
422    /// excludes the pseudo-refs such as `HEAD`, which is exactly the contract
423    /// every other backend honours"* — and as of this change so does this arm
424    /// (see [`GitOps::refs`]). The reason is a type constraint rather than
425    /// taste: a name type that admits `HEAD` also admits `MERGE_HEAD` and
426    /// `FETCH_HEAD`, so putting pseudo-refs in the row stream means either
427    /// widening the name type or filtering at every consumer.
428    ///
429    /// So it is read here, off the same fold [`GitOps::refs`] reads, rather than
430    /// from a second place that could disagree with it. `None` for a repository
431    /// that has never pointed one — an empty repository has no `HEAD`.
432    fn head(&self) -> Result<Option<RefRow>> {
433        // One keyed read, not a clone of the whole namespace: `HEAD` is the only
434        // row this needs, and a client asks it on every connection.
435        let Some(s) = self.ref_get(HEAD)? else {
436            return Ok(None);
437        };
438        let decode = |h: &Option<String>| -> Result<Option<Vec<u8>>> {
439            match h {
440                Some(h) => Ok(Some(hex::decode(h).map_err(|e| {
441                    anyhow!("HEAD holds `{h}`, which is not a hex oid: {e}")
442                })?)),
443                None => Ok(None),
444            }
445        };
446        Ok(Some(RefRow {
447            name: HEAD.to_string(),
448            oid: decode(&s.target)?,
449            peeled: decode(&s.peeled)?,
450            symref_target: s.symref_target.clone(),
451        }))
452    }
453
454    /// Point `HEAD`, through the same log a push writes to.
455    ///
456    /// # The two shapes of a target, and how they are told apart
457    ///
458    /// `HEAD` is symbolic in every repository anyone serves — `ref:
459    /// refs/heads/main` — and detached in the one case git also supports. The
460    /// contract's parameter is one `&str` for both, so:
461    ///
462    /// * a target starting with `refs/` is a **symbolic** ref, written as one;
463    /// * anything else must be a hex oid of exactly this store's width, written
464    ///   as a direct target.
465    ///
466    /// The two cannot be confused: a ref name and a 40- or 64-character hex
467    /// string are disjoint. Anything that is neither is **refused** rather than
468    /// guessed at — a `HEAD` pointing at a name nothing resolves is an
469    /// unclonable repository, and it is cheaper to say so here.
470    ///
471    /// A symbolic target is deliberately **not** checked for existence.
472    /// `git init` points `HEAD` at `refs/heads/main` before that branch exists,
473    /// and refusing it would make an empty repository unrepresentable. A direct
474    /// target *is* checked, by [`GitOps::put_refs`], because a detached `HEAD`
475    /// naming an absent object is dangling in exactly the sense that refuses.
476    fn set_head(&self, target: &str) -> Result<TxId> {
477        let update = if target.starts_with("refs/") {
478            RefUpdate::symbolic(HEAD, target)
479        } else {
480            let hex_len = self.hash_kind().hex_len();
481            if target.len() != hex_len || hex::decode(target).is_err() {
482                bail!(
483                    "HEAD can be pointed at a ref name under `refs/` or at a {hex_len}-character \
484                     hex oid; `{target}` is neither, and guessing which was meant is how a \
485                     repository ends up unclonable"
486                );
487            }
488            RefUpdate::set(HEAD, target.to_ascii_lowercase())
489        };
490        self.put_refs(&[update])
491    }
492
493    /// `HEAD` from the catalog, written **without a barrier of its own**.
494    ///
495    /// The contract's promise is above; what this arm does with it is skip the
496    /// `fsync`, because on a repository create that barrier was 6.0 ms of a
497    /// 68.5 ms push-create (lane V's per-step medians on the 2 vCPU rig) spent
498    /// making durable the one piece of a store that is re-derived on every open
499    /// anyway. It is still a whole frame in the same log a push writes to —
500    /// [`crate::pushlog`] gives whole-or-absent from the framing, not from the
501    /// barrier — and the ref frame of the push that follows a create fsyncs the
502    /// same file milliseconds later and carries it along.
503    ///
504    /// A **detached** `HEAD` is not projected from anything, so it falls
505    /// straight through to [`set_head`](GitServe::set_head) and keeps its
506    /// barrier. Only the symbolic form, which is the form a default branch
507    /// takes, is treated as rebuildable.
508    fn set_head_projected(&self, target: &str) -> Result<TxId> {
509        if !target.starts_with("refs/") {
510            return self.set_head(target);
511        }
512        let push_seq = self
513            .ref_log()
514            .push_rebuildable(&[RefUpdate::symbolic(HEAD, target)])?;
515        Ok(TxId { pack_id: None, extent: None, push_seq: Some(push_seq) })
516    }
517
518    /// **Emit a packfile containing exactly `objects`.**
519    ///
520    /// One line of delegation to [`GitStore::emit_oids`], and the absence of a
521    /// second line is the contract.
522    ///
523    /// # 🔴 This must NOT compute a closure. Fixed 2026-08-10.
524    ///
525    /// Until that date this method opened with `self.select(objects, have)?` and
526    /// emitted *that* — it **closed over its own input**. The caller's set came
527    /// back larger than it went in, and the caller is upload-pack holding
528    /// `selection.objects`: already post-filter, post-shallow, post-`include-tag`
529    /// and *deliberately not closed*. So a `--filter=blob:none`,
530    /// `--filter=tree:0` or `--depth=N` fetch was served **exactly the objects it
531    /// had asked to be left out**.
532    ///
533    /// The reason it needed a rename and a guard rather than a shrug is that
534    /// **nothing we own could see it**: the over-sent pack passes
535    /// `git index-pack --strict` *and* `git fsck`, the clone succeeds, the exit
536    /// code is zero, and the client just silently receives more than it asked
537    /// for. `P-027`'s shape — a change no test can see. Pinned now by
538    /// `emit_pack_emits_exactly_the_set_it_is_given_and_never_its_closure`,
539    /// which asserts a **count** (seen RED at 4 against an expected 1).
540    ///
541    /// It had a second, louder failure mode too, and it is the one that proves
542    /// the two questions were never the same question: [`select`](GitServe::select)'s tip check
543    /// declines a `want` that is not a commit in the graph, so a selection
544    /// containing a tree — which every real one does — could not be emitted at
545    /// all.
546    ///
547    /// # Where the closure went
548    ///
549    /// Nowhere. [`select`](GitServe::select) still owns the close-over-tips half
550    /// and still answers `Ok(None)` when its projection cannot cover a request;
551    /// that hatch is untouched and still load-bearing. The two halves are now
552    /// cleanly separated: *what to send* is asked of `select`, *send exactly
553    /// this* is asked of here. A caller that `select` told to walk the graph
554    /// itself hands the walked set straight back to this method.
555    ///
556    /// There is no closure left on this path at all: since 2026-08-11 the base
557    /// decides how an entry is *encoded*, never what the pack contains.
558    ///
559    /// # `have` is READ, and only for thin-pack base selection
560    ///
561    /// The contract defines `have` as the negotiated common **tips**, *"used for
562    /// thin-pack base selection, never to derive membership"*, and that is
563    /// exactly and only what this arm does with it. Paired with `caps.thin`, it
564    /// lets a boundary entry go out as a `REF_DELTA` naming a base the receiver
565    /// already holds — carrying the stored delta stream unchanged — instead of
566    /// being rebuilt whole. See [`GitStore::client_bases`] for the closure and
567    /// for why the caller has to be able to stand behind the voucher.
568    ///
569    /// It is deliberately **not** repurposed into an exclusion set. Deriving
570    /// membership from `have` is precisely the closure this method stopped
571    /// doing, in the opposite direction: it would *remove* objects the caller's
572    /// finished selection had already decided to send, which is an under-send,
573    /// which is the failure that exits zero. The narrowing below changes the
574    /// *encoding* of an entry and never whether it is emitted.
575    fn emit_pack(
576        &self,
577        objects: &[Oid<'_>],
578        have: &[Oid<'_>],
579        caps: &Caps,
580        out: &mut dyn std::io::Write,
581    ) -> Result<PackStats> {
582        self.emit_oids(objects, have, caps, out)
583    }
584
585    /// **`want` minus `have`, out of §13's bitmaps, with no object opened** —
586    /// plus the two facts the caller cannot recompute cheaply.
587    ///
588    /// # The refusal is the part that matters
589    ///
590    /// [`GitOps::reachable`] treats a `want` it has no bitmap for as contributing
591    /// **itself and nothing else**. That is the safe direction for its own
592    /// caller — `live_set` over-keeps, and over-keeping is harmless — and a
593    /// silent **under-send** for this one. A clone served that way exits zero
594    /// with one object per branch.
595    ///
596    /// So every tip is checked against the store's commit graph first, and a tip
597    /// that is not in it declines the whole request.
598    ///
599    /// # 🔴 *"In the graph"* and *"has a bitmap"* are NO LONGER the same fact
600    ///
601    /// They were, and this paragraph used to say so, because the live table was
602    /// built with no commit cap precisely so that they would be. That cost 530
603    /// 218 allocations a fetch (see [`crate::git_ops`]'s `LIVE_REACH_COMMITS`),
604    /// and since 2026-08-14 the table is **sampled** at 512 like the sealed
605    /// archive's.
606    ///
607    /// The check below is unchanged and is still exact, but it is now exact for
608    /// a different reason. It is *"in the graph"* that matters, not *"has a
609    /// bitmap"*: [`crate::reach::accumulate`] walks a bitmapless commit down to
610    /// the first bitmapped one behind it, so every graph commit is answerable,
611    /// with or without a bitmap of its own. A tip that is not a graph row is
612    /// still refused — there is nothing to walk — and that is the case this
613    /// check exists for.
614    ///
615    /// `Ok(None)` is therefore reachable in three ways, and all three are the
616    /// same statement — *the projection does not cover this request*:
617    /// an empty graph (which a clean restart produces), a tip that is not a
618    /// commit in it, and a graph row whose oid does not parse.
619    ///
620    /// # The selection is NOT re-ordered, and that is measured
621    ///
622    /// `reachable` answers in ordinal order, which is oid-lexicographic and
623    /// therefore unrelated to where the bytes are. Sorting by archive offset so
624    /// a delta's base precedes it was written and could not be made to fail:
625    /// **measured 2026-08-08 on a 154-object fixture — with the sort, without
626    /// it, and with the whole selection deliberately reversed, all three give
627    /// `copied=154 recompressed=0` and the identical object graph**, because
628    /// [`crate::pack_walk::topological_order`] orders the emission itself. The
629    /// sort was a batch index lookup per request buying a property the emitter
630    /// already owns (LAW 5), and its absence is something no guard can see
631    /// (LAW 2). It is gone.
632    fn select(&self, want: &[Oid<'_>], have: &[Oid<'_>]) -> Result<Option<ReachSet>> {
633        // The cheapest form of the same refusal, and the one a clean restart
634        // takes: with no commit graph there is no bitmap for any want, so
635        // `reachable` would contribute the wants alone.
636        if self.commit_count() == 0 {
637            return Ok(None);
638        }
639
640        // The graph as raw oids. Needed twice — to refuse a tip it does not
641        // cover, and to name the commits in the answer.
642        //
643        // **Folded, not rebuilt per request.** This used to be
644        // `graph_snapshot()` — a deep clone of every `CommitNode`, oid `String`,
645        // parent list and all — followed by a `hex::decode` into a fresh
646        // `Vec<u8>` per commit, on every single `select`. The set cannot change
647        // between folds, so [`crate::git_ops::Derived::commit_raw`] holds it and
648        // this borrows. `None` is the identical refusal the decode arm used to
649        // produce: a graph row whose oid does not parse is a corrupt derivation,
650        // and declining is safe because the caller's own walk still serves the
651        // request.
652        let Some(commits) = self.commit_oids_raw()? else {
653            return Ok(None);
654        };
655        if want.iter().any(|tip| !commits.contains(*tip)) {
656            return Ok(None);
657        }
658
659        let objects = self
660            .reachable(want, have)
661            .context("selecting want minus have")?;
662
663        // What the client held BEFORE this transfer: the closure of `have`, and
664        // only it. Empty for a clone, which is the request with no excludes at
665        // all and therefore the one that pays nothing for this.
666        //
667        // **Raw bytes, in one buffer.** This is the repository-sized half of the
668        // answer — the closure of what the client already holds, 12 112 oids to
669        // serve a 69-object fetch on the measured corpus — and it used to be
670        // built as oid hex and `hex::decode`d back into a `Vec<u8>` per object
671        // on its way into a `Vec<Vec<u8>>` the caller only ever counts and
672        // scans. `git_storage_trait::OidList` carries it in one allocation; see
673        // `Derived::oids_raw`.
674        let client_has = if have.is_empty() {
675            git_storage_trait::OidList::new()
676        } else {
677            self.reachable_raw(have, &[])
678                .context("closing over what the client already holds")?
679        };
680
681        let selected_commits = objects
682            .iter()
683            .filter(|oid| commits.contains(*oid))
684            .cloned()
685            .collect();
686
687        Ok(Some(ReachSet {
688            objects,
689            commits: selected_commits,
690            client_has,
691        }))
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use crate::object::{canonical, GitHashKind};
699    use crate::pack_fixture::{build_pack, whole_object_pack, Item};
700    use crate::store::tests::{corpus_pack, one_blob_pack, tmpdir};
701    use git_storage_trait::{Observed, RefCas, RefRejection};
702
703    /// **`header` names the RESOLVED kind for every entry in a real pack —
704    /// deltas included — and it does it with no gix and no inflate.**
705    ///
706    /// The oracle is [`crate::resolve::Resolved`], which carries both
707    /// `stored_type` (what the entry is in the pack) and `kind` (what the chain
708    /// resolves to). Asserting against `kind` is the whole point: a `header`
709    /// that read the type column would agree on every non-delta and be wrong on
710    /// exactly the deltas, which is the failure a spot-check misses.
711    ///
712    /// The test refuses to pass on a corpus that would not have caught that: it
713    /// asserts the pack actually contained deltas first.
714    #[test]
715    fn header_resolves_every_delta_to_its_real_kind() {
716        let dir = tmpdir("serve-header");
717        let store = GitStore::open(&dir, "rickard").unwrap();
718        let (pack, rows) = corpus_pack();
719        store.put(&pack, &[]).unwrap();
720        store.wait_indexed();
721        store.absorb_pending().unwrap();
722
723        let deltas = rows
724            .iter()
725            .filter(|r| matches!(r.stored_type, ObjType::OfsDelta | ObjType::RefDelta))
726            .count();
727        assert!(
728            deltas > 0,
729            "this corpus pack has no delta entries, so it cannot tell a chain walk from a column \
730             read and the test proves nothing"
731        );
732
733        for r in &rows {
734            let (kind, size) = GitServe::header(&store, &r.oid)
735                .unwrap()
736                .unwrap_or_else(|| panic!("{} is in the pack but header said absent", hex::encode(&r.oid)));
737            assert_eq!(
738                kind,
739                resolved_type(r.kind),
740                "{} is stored as {:?} and resolves to {:?}; header said {:?}",
741                hex::encode(&r.oid),
742                r.stored_type,
743                r.kind,
744                kind
745            );
746            assert_eq!(
747                size,
748                r.uncompressed_size,
749                "{} post-resolution size",
750                hex::encode(&r.oid)
751            );
752        }
753    }
754
755    /// `sizes` answers the same numbers as `header`, in one pass, positionally.
756    #[test]
757    fn sizes_is_header_in_bulk_and_stays_positional() {
758        let dir = tmpdir("serve-sizes");
759        let store = GitStore::open(&dir, "rickard").unwrap();
760        let (pack, rows) = corpus_pack();
761        store.put(&pack, &[]).unwrap();
762        store.wait_indexed();
763
764        // An oid nothing has, deliberately in the MIDDLE: a `sizes` that
765        // filtered misses rather than answering `None` in place would shift
766        // every later answer by one and still return the right count.
767        let absent = vec![0xABu8; 20];
768        let mut oids: Vec<&[u8]> = rows.iter().map(|r| r.oid.as_slice()).collect();
769        let middle = oids.len() / 2;
770        oids.insert(middle, &absent);
771
772        let got = GitServe::sizes(&store, &oids).unwrap();
773        assert_eq!(got.len(), oids.len(), "sizes must be positional");
774        assert_eq!(got[middle], None, "an absent oid answers None IN PLACE");
775        for (i, r) in rows.iter().enumerate() {
776            let at = if i < middle { i } else { i + 1 };
777            assert_eq!(got[at], Some(r.uncompressed_size), "row {i}");
778        }
779    }
780
781    /// **`HEAD` is not a row in `refs()`, and it has its own accessor** — §3f
782    /// Q1, and the conformance check the suite could not previously make.
783    ///
784    /// Seen RED by deleting the filter in [`GitOps::refs`]: `HEAD` appears in
785    /// the ref stream and the first assertion fails. The two halves are both
786    /// needed — a `refs()` that dropped `HEAD` and a `head()` that could not
787    /// find it would pass the first assertion and leave the repository with no
788    /// default branch to advertise.
789    #[test]
790    fn head_is_not_a_ref_row_but_is_reachable_through_its_own_accessor() {
791        let dir = tmpdir("serve-head");
792        let store = GitStore::open(&dir, "rickard").unwrap();
793        let (pack, oid) = one_blob_pack(b"a blob to point at");
794        store.put(&pack, &[]).unwrap();
795        store.wait_indexed();
796        store
797            .put_refs(&[RefUpdate::set("refs/heads/main", hex::encode(&oid))])
798            .unwrap();
799
800        GitServe::set_head(&store, "refs/heads/main").unwrap();
801
802        let rows = store.refs().unwrap();
803        assert!(
804            rows.iter().all(|r| r.name != HEAD),
805            "HEAD is a pseudo-ref and must not appear in the ref stream: {:?}",
806            rows.iter().map(|r| &r.name).collect::<Vec<_>>()
807        );
808        assert!(
809            rows.iter().any(|r| r.name == "refs/heads/main"),
810            "filtering HEAD must not have filtered anything else"
811        );
812
813        let head = GitServe::head(&store).unwrap().expect("HEAD was just set");
814        assert_eq!(head.name, HEAD);
815        assert_eq!(head.symref_target.as_deref(), Some("refs/heads/main"));
816        assert_eq!(head.oid, None, "a symbolic HEAD has no direct target");
817
818        // A store that has never been pointed has no HEAD, and that is `None`
819        // rather than an error or an empty row.
820        let dir2 = tmpdir("serve-head-empty");
821        let fresh = GitStore::open(&dir2, "rickard").unwrap();
822        assert!(GitServe::head(&fresh).unwrap().is_none());
823    }
824
825    /// **A projected `HEAD` is written, not merely returned `Ok`.**
826    ///
827    /// `set_head_projected` skips the `fsync`, and the failure mode of a change
828    /// like that is not a lost fsync — it is a lost *write* that nothing
829    /// notices, because the writer's own handle answers from memory and the call
830    /// returns `Ok` either way. So this asserts through a **second store opened
831    /// over the same directory**, which folds the log off the disk and has no
832    /// memory of the write at all.
833    ///
834    /// Seen RED by making the method return `Ok(TxId::default())` without
835    /// appending: the writer's own `head()` still answered, and the cold store
836    /// answered `None`.
837    #[test]
838    fn a_projected_head_is_on_disk_for_a_store_that_did_not_write_it() {
839        let dir = tmpdir("serve-head-projected");
840        let store = GitStore::open(&dir, "rickard").unwrap();
841        GitServe::set_head_projected(&store, "refs/heads/main").unwrap();
842        assert_eq!(
843            GitServe::head(&store).unwrap().and_then(|r| r.symref_target).as_deref(),
844            Some("refs/heads/main")
845        );
846
847        // A detached target is derivable from nothing, so it keeps `set_head`'s
848        // barrier — and its refusal of a target that is neither.
849        let err = GitServe::set_head_projected(&store, "main").unwrap_err();
850        assert!(
851            format!("{err:#}").contains("is neither"),
852            "the projected door must not become a way past set_head's check: {err:#}"
853        );
854
855        // One process at a time appends to a store's blob file, so the writer
856        // has to let go before the reader can look.
857        drop(store);
858        let cold = GitStore::open(&dir, "rickard").unwrap();
859        assert_eq!(
860            GitServe::head(&cold).unwrap().and_then(|r| r.symref_target).as_deref(),
861            Some("refs/heads/main"),
862            "a store that did not do the write cannot see it, so the frame never reached the file"
863        );
864        assert!(
865            cold.refs().unwrap().iter().all(|r| r.name != HEAD),
866            "a projected HEAD is still not a row"
867        );
868    }
869
870    /// `set_head` refuses a target that is neither a ref name nor an oid rather
871    /// than guessing, and takes a detached oid when given one.
872    #[test]
873    fn set_head_takes_a_ref_or_an_oid_and_refuses_anything_else() {
874        let dir = tmpdir("serve-sethead");
875        let store = GitStore::open(&dir, "rickard").unwrap();
876        let (pack, oid) = one_blob_pack(b"detached");
877        store.put(&pack, &[]).unwrap();
878        store.wait_indexed();
879
880        let err = GitServe::set_head(&store, "main").unwrap_err();
881        assert!(
882            format!("{err:#}").contains("is neither"),
883            "a bare branch name must be refused, not guessed at: {err:#}"
884        );
885
886        GitServe::set_head(&store, &hex::encode(&oid)).unwrap();
887        let head = GitServe::head(&store).unwrap().unwrap();
888        assert_eq!(head.oid.as_deref(), Some(oid.as_slice()));
889        assert_eq!(head.symref_target, None);
890    }
891
892    /// **`select` declines rather than under-sending when it has no graph — and
893    /// `emit_pack` is not affected by that at all.**
894    ///
895    /// The load-bearing `Ok(None)`. A store holding one blob and no commit has
896    /// no reachability projection, and the wrong answer here is not an error —
897    /// it is a `Some` containing the want alone, which serves a clone that exits
898    /// zero and is short by everything.
899    ///
900    /// # What the second half of this test used to assert, and why it changed
901    ///
902    /// Until 2026-08-10 it asserted that `emit_pack` **refused** on the back of
903    /// this same decline, because `emit_pack` began by calling `select`. It no
904    /// longer does — that call was the over-send bug, see
905    /// [`GitServe::emit_pack`] — so the assertion would now be asserting the
906    /// defect. It is **replaced rather than deleted**, by the stronger statement
907    /// the separation makes true: a store that cannot select *anything* still
908    /// emits exactly the set it is handed. The refusal is not lost, it has an
909    /// owner: `select` says `None`, and the caller decides.
910    #[test]
911    fn select_declines_when_the_projection_cannot_cover_the_request() {
912        let dir = tmpdir("serve-select-none");
913        let store = GitStore::open(&dir, "rickard").unwrap();
914        let (pack, oid) = one_blob_pack(b"no commits here");
915        store.put(&pack, &[]).unwrap();
916        store.wait_indexed();
917        store.absorb_pending().unwrap();
918
919        assert_eq!(store.commit_count(), 0, "the fixture must have no commits");
920        assert!(
921            GitServe::select(&store, &[&oid], &[]).unwrap().is_none(),
922            "a store with no commit graph must decline, not answer with the tip alone"
923        );
924
925        // And `emit_pack` is INDEPENDENT of that decline: it was handed one
926        // object and it emits one object, out of the very store whose
927        // projection cannot answer a thing. A single blob is a legitimate set
928        // to emit — it is what a `--filter` fetch of one path looks like — and
929        // an engine that consulted `select` here would refuse it.
930        let mut sink = Vec::new();
931        let stats = GitServe::emit_pack(&store, &[&oid], &[], &Caps::modern(), &mut sink).unwrap();
932        assert_eq!(
933            stats.objects, 1,
934            "emit_pack emits what it is given; it does not ask select whether it may"
935        );
936        assert_eq!(
937            u32::from_be_bytes(sink[8..12].try_into().unwrap()),
938            1,
939            "the emitted pack header must count what the receipt counts"
940        );
941    }
942
943    // The pack assembler these fixtures are built with lives in
944    // [`crate::pack_fixture`], beside the generated corpus that uses the same
945    // primitives. It was a private copy here until 2026-08-25; two assemblers
946    // that must agree on git's grammar is one more than LAW 5 allows.
947
948    /// One commit, its tree, and two blobs under that tree — the smallest graph
949    /// in which the closure of a subset is strictly larger than the subset.
950    ///
951    /// Returns `(pack, commit_oid, tree_oid, blob_oids)`, all raw (not hex).
952    fn commit_tree_two_blobs() -> (Vec<u8>, Vec<u8>, Vec<u8>, Vec<Vec<u8>>) {
953        let hash = GitHashKind::Sha1;
954        let bodies: [&[u8]; 2] = [b"first blob", b"second blob"];
955        let mut blobs = Vec::new();
956        let mut objects = Vec::new();
957        let mut tree_payload = Vec::new();
958        for (name, body) in ["a.txt", "b.txt"].iter().zip(bodies) {
959            let canon = canonical(GitObjectKind::Blob, body);
960            let oid = hash.oid_of(&canon);
961            tree_payload.extend_from_slice(format!("100644 {name}\0").as_bytes());
962            tree_payload.extend_from_slice(&oid);
963            objects.push((GitObjectKind::Blob, body.to_vec()));
964            blobs.push(oid);
965        }
966        let tree_canon = canonical(GitObjectKind::Tree, &tree_payload);
967        let tree_oid = hash.oid_of(&tree_canon);
968
969        let body = format!(
970            "tree {}\nauthor A <a@x> 1700000000 +0000\ncommitter A <a@x> 1700000000 +0000\n\n\
971             the closure fixture\n",
972            hex::encode(&tree_oid)
973        );
974        let commit_canon = canonical(GitObjectKind::Commit, body.as_bytes());
975        let commit_oid = hash.oid_of(&commit_canon);
976
977        objects.push((GitObjectKind::Tree, tree_payload));
978        objects.push((GitObjectKind::Commit, body.into_bytes()));
979        (whole_object_pack(&objects), commit_oid, tree_oid, blobs)
980    }
981
982    /// 🔴 **`emit_pack` emits the set it is HANDED, and never that set's
983    /// reachability closure.**
984    ///
985    /// This is the guard for the defect the parameter rename names. Until
986    /// 2026-08-10 this method read
987    ///
988    /// ```text
989    ///     let Some(set) = self.select(objects, have)? else { bail!(..) };
990    ///     self.emit_oids(&set.objects, caps, out)
991    /// ```
992    ///
993    /// — it **closed over its own input**. Upload-pack hands it
994    /// `selection.objects`, which is already post-filter, post-shallow and
995    /// post-`include-tag` and is *deliberately not closed*, so the closure added
996    /// back exactly what `--filter=blob:none`, `--filter=tree:0` or `--depth=N`
997    /// had excluded.
998    ///
999    /// **Nothing else in the tree can see that.** The over-sent pack passes
1000    /// `git index-pack --strict` and `git fsck`, the clone succeeds, the exit
1001    /// code is zero, and the client silently receives objects it asked not to
1002    /// have — `P-027`'s shape, a change no test can see. So the assertion is on
1003    /// a **count**: never on bytes, never on a clock.
1004    ///
1005    /// Two shapes, because the closure broke the method in two different
1006    /// directions and both had to stop:
1007    ///
1008    /// 1. `[commit]` alone — a real `--filter=tree:0` fetch. **RED before the
1009    ///    fix: `objects` was 4, not 1** (the tree and both blobs came back).
1010    /// 2. `[commit, tree]` — a set whose members are not all commits. **RED
1011    ///    before the fix with a refusal, not a count**: the old body routed
1012    ///    through `select`, whose tip check rejects a non-commit tip, so a
1013    ///    perfectly ordinary partial-clone selection could not be emitted at
1014    ///    all.
1015    ///
1016    /// The fixture asserts its own premise first — that the closure really is
1017    /// strictly larger than either input — or it would prove nothing on a
1018    /// repository where the two happened to coincide.
1019    #[test]
1020    fn emit_pack_emits_exactly_the_set_it_is_given_and_never_its_closure() {
1021        let dir = tmpdir("serve-emit-exact");
1022        let store = GitStore::open(&dir, "rickard").unwrap();
1023        let (pack, commit, tree, blobs) = commit_tree_two_blobs();
1024        store.put(&pack, &[]).unwrap();
1025        store.wait_indexed();
1026        store.absorb_pending().unwrap();
1027
1028        // The premise. Without it the two assertions below are satisfied by a
1029        // store that happens to hold nothing beneath the commit.
1030        assert_eq!(
1031            store.commit_count(),
1032            1,
1033            "the fixture must have a commit graph"
1034        );
1035        let closure = GitServe::select(&store, &[&commit], &[])
1036            .unwrap()
1037            .expect("the projection covers a commit it folded");
1038        assert_eq!(
1039            closure.objects.len(),
1040            4,
1041            "the fixture's closure must be strictly larger than the subsets emitted below, or \
1042             this test cannot tell an exact emission from a closed one: {:?}",
1043            closure.objects.iter().map(hex::encode).collect::<Vec<_>>()
1044        );
1045        for b in &blobs {
1046            assert!(
1047                closure.objects.contains(b),
1048                "the closure must reach the blobs — that is what must NOT be emitted"
1049            );
1050        }
1051        // The mechanism behind shape 2 below, pinned as a live fact rather than
1052        // left as history: `select` declines a tip that is not a commit in its
1053        // graph, which is correct for `select` and is exactly why routing
1054        // `emit_pack` through it made an ordinary selection unservable.
1055        assert!(
1056            GitServe::select(&store, &[&commit, &tree], &[])
1057                .unwrap()
1058                .is_none(),
1059            "select is a close-over-TIPS question and a tree is not a tip it can answer for"
1060        );
1061
1062        // 1. `--filter=tree:0`: the commit and nothing else.
1063        let mut out = Vec::new();
1064        let stats =
1065            GitServe::emit_pack(&store, &[&commit], &[], &Caps::modern(), &mut out).unwrap();
1066        assert_eq!(
1067            stats.objects, 1,
1068            "emit_pack was handed ONE object and must emit ONE; a closure here re-adds exactly \
1069             what the filter excluded, and index-pack --strict and fsck both accept the result"
1070        );
1071        // The receipt and the bytes must agree: the pack's own object-count
1072        // field is bytes 8..12, and reading it back is the check that the
1073        // counter is not simply reporting what it was asked for.
1074        assert_eq!(
1075            u32::from_be_bytes(out[8..12].try_into().unwrap()),
1076            1,
1077            "the emitted pack header must count what the receipt counts"
1078        );
1079
1080        // 2. A post-filter selection whose members are not all commits.
1081        let mut out = Vec::new();
1082        let stats =
1083            GitServe::emit_pack(&store, &[&commit, &tree], &[], &Caps::modern(), &mut out).unwrap();
1084        assert_eq!(
1085            stats.objects, 2,
1086            "emit_pack was handed TWO objects and must emit TWO"
1087        );
1088        assert_eq!(
1089            u32::from_be_bytes(out[8..12].try_into().unwrap()),
1090            2,
1091            "the emitted pack header must count what the receipt counts"
1092        );
1093        assert_eq!(stats.copied, stats.objects, "still a byte-range copy");
1094        assert_eq!(stats.recompressed, 0, "still nothing re-deflated");
1095    }
1096
1097    /// Two branches whose trees are near-identical, with the **second branch's
1098    /// tree stored as a delta against the first branch's tree**.
1099    ///
1100    /// That one fact is the whole fixture. Narrow a request to `base` and it
1101    /// contains a delta whose base is not in it — and the base is a *tree*, so
1102    /// pulling it in would drag an object owing children the request does not
1103    /// contain. It is the `h2h-linear-sha1-2048c-1024f-16k` failure in six
1104    /// objects.
1105    ///
1106    /// `shared_entries` is how many tree entries the two trees hold in **common**
1107    /// before the one they differ in. It is `0` for the connectivity test, which
1108    /// wants the smallest fixture that has the defect in it, and large for the
1109    /// thin-pack test, which has to be able to see the delta *win* in bytes: at
1110    /// 0 the whole tree is 33 bytes and a `REF_DELTA`'s 20-byte base oid costs
1111    /// more than the delta saves, so a byte assertion there would measure the
1112    /// fixture rather than the mechanism. The shared entries all name `b_main`,
1113    /// so nothing new becomes reachable and no tree owes a child that is not in
1114    /// one of the two closures.
1115    ///
1116    /// Returns the pack and, in order, `(c_main, t_main, b_main, c_base,
1117    /// t_base, b_base)`, all raw.
1118    #[allow(clippy::type_complexity)]
1119    fn two_branches_with_a_cross_branch_delta(shared_entries: usize) -> (Vec<u8>, [Vec<u8>; 6]) {
1120        let hash = GitHashKind::Sha1;
1121        let oid_of = |kind, body: &[u8]| hash.oid_of(&canonical(kind, body));
1122
1123        let b_main = b"the payload that only main can reach\n".to_vec();
1124        let b_main_oid = oid_of(GitObjectKind::Blob, &b_main);
1125        let b_base = b"the payload that only base can reach\n".to_vec();
1126        let b_base_oid = oid_of(GitObjectKind::Blob, &b_base);
1127
1128        let tree_for = |blob: &[u8]| {
1129            let mut t = Vec::new();
1130            // Sorted, and `zz.txt` sorts after every `fNN.txt`: an unsorted tree
1131            // is what `fsck --strict` rejects, and the oracles below run it.
1132            for i in 0..shared_entries {
1133                t.extend_from_slice(format!("100644 f{i:03}.txt\0").as_bytes());
1134                t.extend_from_slice(&b_main_oid);
1135            }
1136            t.extend_from_slice(b"100644 zz.txt\0");
1137            t.extend_from_slice(blob);
1138            t
1139        };
1140        let t_main = tree_for(&b_main_oid);
1141        let t_main_oid = oid_of(GitObjectKind::Tree, &t_main);
1142        let t_base = tree_for(&b_base_oid);
1143        let t_base_oid = oid_of(GitObjectKind::Tree, &t_base);
1144
1145        let commit_for = |tree: &[u8], msg: &str| {
1146            format!(
1147                "tree {}\nauthor A <a@x> 1700000000 +0000\ncommitter A <a@x> 1700000000 +0000\n\n\
1148                 {msg}\n",
1149                hex::encode(tree)
1150            )
1151            .into_bytes()
1152        };
1153        let c_main = commit_for(&t_main_oid, "main");
1154        let c_main_oid = oid_of(GitObjectKind::Commit, &c_main);
1155        let c_base = commit_for(&t_base_oid, "base");
1156        let c_base_oid = oid_of(GitObjectKind::Commit, &c_base);
1157
1158        // `t_base` is entry 4 and deltas against entry 1, `t_main`. Both trees
1159        // are 33 bytes and differ in 20 of them, which is what makes this the
1160        // delta a real packer would also choose.
1161        let pack = build_pack(&[
1162            Item::Whole(GitObjectKind::Blob, b_main),
1163            Item::Whole(GitObjectKind::Tree, t_main),
1164            Item::Whole(GitObjectKind::Commit, c_main),
1165            Item::Whole(GitObjectKind::Blob, b_base),
1166            Item::OfsDelta {
1167                base: 1,
1168                body: t_base,
1169            },
1170            Item::Whole(GitObjectKind::Commit, c_base),
1171        ]);
1172        (
1173            pack,
1174            [
1175                c_main_oid, t_main_oid, b_main_oid, c_base_oid, t_base_oid, b_base_oid,
1176            ],
1177        )
1178    }
1179
1180    /// 🔴 **A NARROWED clone gets a pack that is self-contained *and connected*
1181    /// — the delta base is not smuggled in with it.**
1182    ///
1183    /// The defect, seen live on 2026-08-11 against
1184    /// `h2h-linear-sha1-2048c-1024f-16k`:
1185    ///
1186    /// ```text
1187    /// git clone --bare <url>                          → 36 172 objects, OK
1188    /// git clone --bare --single-branch --branch base  → FAILS
1189    ///     fatal: did not receive expected object 8601ec33920b7d701c9887fa04916501136d6e90
1190    ///     fatal: fetch-pack: invalid index-pack output
1191    /// event="git.upload_pack.served" objects=33773    ← the server logged SUCCESS
1192    /// ```
1193    ///
1194    /// `base` reaches 31 805 objects; `emit_set`'s delta-base closure added
1195    /// 1 968 more, giving exactly the 33 773 the server logged. **507 of the
1196    /// additions were trees**, and between them they named 204 objects the pack
1197    /// did not contain. `git index-pack --check-self-contained-and-connected` —
1198    /// what a clone runs, and which turns on `strict` — walks every received
1199    /// object's links and demands each one exist, so it died on the first, and
1200    /// nothing on the server ever knew.
1201    ///
1202    /// # Why the assertions are shaped the way they are
1203    ///
1204    /// The premise is asserted first and it is the load-bearing one: the request
1205    /// must actually contain a delta whose base is outside it. On a corpus where
1206    /// that happens not to hold — which is every full clone, by construction —
1207    /// this test cannot fail no matter what the code does.
1208    ///
1209    /// Then the **count**, not an exit code: the emitted pack must hold exactly
1210    /// the three objects `base` reaches. **RED before the fix at 4**, the fourth
1211    /// being `t_main`.
1212    ///
1213    /// Then stock git, in a **repository**: `index-pack --strict` outside one
1214    /// segfaults instead of judging (exit 139, no output), which is a green that
1215    /// was never looked at. Inside a fresh bare repo it is the exact arbiter that
1216    /// failed in production, and before the fix it prints `did not receive
1217    /// expected object <b_main>`.
1218    #[test]
1219    fn a_narrowed_clone_is_served_a_connected_pack_not_its_delta_bases() {
1220        let dir = tmpdir("serve-narrowed");
1221        let store = GitStore::open(&dir, "rickard").unwrap();
1222        let (pack, [c_main, t_main, b_main, c_base, t_base, b_base]) =
1223            two_branches_with_a_cross_branch_delta(0);
1224        store.put(&pack, &[]).unwrap();
1225        store.wait_indexed();
1226        store.absorb_pending().unwrap();
1227        assert_eq!(store.commit_count(), 2, "both branches must be in the graph");
1228
1229        // ── the narrowed request ──────────────────────────────────────────────
1230        let set = GitServe::select(&store, &[&c_base], &[])
1231            .unwrap()
1232            .expect("the projection covers a commit it folded");
1233        let mut want = set.objects.clone();
1234        want.sort();
1235        let mut expected = vec![c_base.clone(), t_base.clone(), b_base.clone()];
1236        expected.sort();
1237        assert_eq!(
1238            want,
1239            expected,
1240            "`base` reaches its own commit, tree and blob and nothing of main's"
1241        );
1242
1243        // ── THE PREMISE, or this test proves nothing ──────────────────────────
1244        //
1245        // `t_base` must really be stored as a delta against `t_main`, and
1246        // `t_main` must really be outside the request. Without both, an
1247        // implementation that pulls bases in is indistinguishable from one that
1248        // does not.
1249        let row = store
1250            .index()
1251            .lookup(&t_base)
1252            .expect("the tree base reaches is indexed");
1253        assert_eq!(
1254            row.obj_type,
1255            ObjType::OfsDelta,
1256            "the fixture must store base's tree as a delta or there is no base to leave out"
1257        );
1258        let base_row = store
1259            .index()
1260            .lookup(&t_main)
1261            .expect("the tree main reaches is indexed");
1262        assert_eq!(
1263            row.delta_base, base_row.offset,
1264            "the fixture's delta must name main's tree as its base"
1265        );
1266        assert!(
1267            !set.objects.contains(&t_main),
1268            "the base must be OUTSIDE the request, or nothing is being closed over"
1269        );
1270
1271        // ── the count ─────────────────────────────────────────────────────────
1272        let oids: Vec<Oid<'_>> = set.objects.iter().map(Vec::as_slice).collect();
1273        let mut out = Vec::new();
1274        let stats = store.emit_oids(&oids, &[], &Caps::modern(), &mut out).unwrap();
1275        assert_eq!(
1276            stats.objects, 3,
1277            "a narrowed clone must be served exactly the objects it reaches; adding the delta \
1278             base makes it 4 and the fourth owes children the pack does not carry"
1279        );
1280        assert_eq!(
1281            u32::from_be_bytes(out[8..12].try_into().unwrap()),
1282            3,
1283            "the emitted pack header must count what the receipt counts"
1284        );
1285        // Applied output for the mechanism itself: two entries copied byte for
1286        // byte, and exactly the one whose base was left out rebuilt.
1287        assert_eq!(stats.copied, 2, "everything but the boundary entry is copied");
1288        assert_eq!(
1289            stats.recompressed, 1,
1290            "the entry whose base is outside the request is the one that must be rebuilt whole"
1291        );
1292        assert_eq!(stats.copied + stats.recompressed, stats.objects);
1293
1294        // ── and the object main reaches must not be in the bytes ──────────────
1295        let walked = crate::pack_walk::walk(&out, 20).expect("our own walk reads what we emitted");
1296        assert_eq!(walked.entries.len(), 3);
1297        assert!(
1298            walked.entries.iter().all(|e| e.obj_type != ObjType::OfsDelta),
1299            "the boundary entry must be whole, not a delta naming a base that is not here"
1300        );
1301
1302        // ── stock git, inside a repository, as the arbiter ────────────────────
1303        let scratch = tmpdir("serve-narrowed-idx");
1304        crate::git_oracle::assert_git_accepts(
1305            &scratch,
1306            "dst.git",
1307            &out,
1308            crate::git_oracle::Strictness::Connected,
1309        );
1310
1311        // The full clone is unaffected, and that is asserted rather than
1312        // assumed: every base is inside a whole-repository request, so nothing
1313        // is rebuilt and the copy claim survives intact.
1314        let all = vec![
1315            c_main.as_slice(),
1316            t_main.as_slice(),
1317            b_main.as_slice(),
1318            c_base.as_slice(),
1319            t_base.as_slice(),
1320            b_base.as_slice(),
1321        ];
1322        let mut full = Vec::new();
1323        let stats = store.emit_oids(&all, &[], &Caps::modern(), &mut full).unwrap();
1324        assert_eq!(stats.objects, 6);
1325        assert_eq!(
1326            stats.recompressed, 0,
1327            "a full clone contains every base, so it must still be a pure byte-range copy"
1328        );
1329        assert_eq!(stats.copied, 6);
1330    }
1331
1332    /// **Three revisions of one 16 KiB blob, the middle one off-branch**, which
1333    /// is the `h2h-linear-sha1-2048c-1024f-16k` boundary entry in nine objects.
1334    ///
1335    /// The chain is `v1 <- v2 <- v3`, stored in that order, and only `v2` is
1336    /// off-branch. So a request for `base` holds `v1` and `v3` and not `v2`:
1337    /// `v3`'s stored base is outside it, and `v3`'s **chain ancestor `v1` is
1338    /// inside it**. That second fact is the whole fixture — without it the
1339    /// re-delta has nothing to aim at, and the measured production shape is
1340    /// exactly this one (859 of 961 boundary entries have such an ancestor).
1341    ///
1342    /// The blob bodies are incompressible noise with a small splice between
1343    /// revisions, deliberately: on compressible filler a whole rebuild is nearly
1344    /// free and a byte assertion would be measuring zlib rather than the delta.
1345    /// At 16 KiB of noise, whole costs ~16.5 KB on the wire and a delta costs a
1346    /// few hundred bytes, so the two cannot be confused.
1347    ///
1348    /// Returns the pack and `(c_base_tip, t3, v3, c1, t1, v1, c_main, t2, v2)`.
1349    #[allow(clippy::type_complexity)]
1350    fn three_revisions_with_the_middle_one_off_branch() -> (Vec<u8>, [Vec<u8>; 9]) {
1351        let hash = GitHashKind::Sha1;
1352        let oid_of = |kind, body: &[u8]| hash.oid_of(&canonical(kind, body));
1353
1354        // xorshift, so the bytes are reproducible and do not deflate.
1355        let noise = |n: usize, seed: u64| -> Vec<u8> {
1356            let mut s = seed | 1;
1357            (0..n)
1358                .map(|_| {
1359                    s ^= s << 13;
1360                    s ^= s >> 7;
1361                    s ^= s << 17;
1362                    (s >> 33) as u8
1363                })
1364                .collect::<Vec<u8>>()
1365        };
1366        let v1 = noise(16 * 1024, 0xC0FFEE);
1367        let mut v2 = v1.clone();
1368        v2[2048..2112].copy_from_slice(&noise(64, 2));
1369        let mut v3 = v2.clone();
1370        v3[8192..8256].copy_from_slice(&noise(64, 3));
1371
1372        let v1_oid = oid_of(GitObjectKind::Blob, &v1);
1373        let v2_oid = oid_of(GitObjectKind::Blob, &v2);
1374        let v3_oid = oid_of(GitObjectKind::Blob, &v3);
1375
1376        let tree_for = |blob: &[u8]| {
1377            let mut t = b"100644 f.bin\0".to_vec();
1378            t.extend_from_slice(blob);
1379            t
1380        };
1381        let (t1, t2, t3) = (tree_for(&v1_oid), tree_for(&v2_oid), tree_for(&v3_oid));
1382        let (t1_oid, t2_oid, t3_oid) = (
1383            oid_of(GitObjectKind::Tree, &t1),
1384            oid_of(GitObjectKind::Tree, &t2),
1385            oid_of(GitObjectKind::Tree, &t3),
1386        );
1387
1388        let commit_for = |tree: &[u8], parent: Option<&[u8]>, msg: &str| {
1389            let mut c = format!("tree {}\n", hex::encode(tree));
1390            if let Some(p) = parent {
1391                c.push_str(&format!("parent {}\n", hex::encode(p)));
1392            }
1393            c.push_str(
1394                "author A <a@x> 1700000000 +0000\ncommitter A <a@x> 1700000000 +0000\n\n",
1395            );
1396            c.push_str(msg);
1397            c.push('\n');
1398            c.into_bytes()
1399        };
1400        let c1 = commit_for(&t1_oid, None, "r1");
1401        let c1_oid = oid_of(GitObjectKind::Commit, &c1);
1402        // `main`'s commit is parentless, so nothing of `base` becomes reachable
1403        // through it and the two closures stay the disjoint-plus-v1 shape the
1404        // assertions below rely on.
1405        let c_main = commit_for(&t2_oid, None, "r2 on main only");
1406        let c_main_oid = oid_of(GitObjectKind::Commit, &c_main);
1407        let c3 = commit_for(&t3_oid, Some(&c1_oid), "r3");
1408        let c3_oid = oid_of(GitObjectKind::Commit, &c3);
1409
1410        // Entry 0 is `v1` whole; entry 1 is `v2` as an ofs-delta on it; entry 2
1411        // is `v3` as an ofs-delta on `v2`. Offsets increase along the chain,
1412        // which is the invariant the ancestor walk's acyclicity rests on.
1413        let pack = build_pack(&[
1414            Item::Whole(GitObjectKind::Blob, v1),
1415            Item::OfsDelta { base: 0, body: v2 },
1416            Item::OfsDelta { base: 1, body: v3 },
1417            Item::Whole(GitObjectKind::Tree, t1),
1418            Item::Whole(GitObjectKind::Tree, t2),
1419            Item::Whole(GitObjectKind::Tree, t3),
1420            Item::Whole(GitObjectKind::Commit, c1),
1421            Item::Whole(GitObjectKind::Commit, c_main),
1422            Item::Whole(GitObjectKind::Commit, c3),
1423        ]);
1424        (
1425            pack,
1426            [
1427                c3_oid, t3_oid, v3_oid, c1_oid, t1_oid, v1_oid, c_main_oid, t2_oid, v2_oid,
1428            ],
1429        )
1430    }
1431
1432    /// 🔴 **A narrowed clone re-deltas its boundary entry against a base the
1433    /// pack DOES carry, instead of shipping it whole — and the pack still holds
1434    /// exactly the same objects, byte for byte.**
1435    ///
1436    /// # The gap this closes, measured before it existed
1437    ///
1438    /// `h2h-linear-sha1-2048c-1024f-16k`, one server process, oden 2026-08-11:
1439    /// a narrowed clone of `base` was **11 613 666** bytes for 31 805 objects
1440    /// where stock git sends 5 820 000 for the identical set — 2.0×. The cause
1441    /// is 961 entries (3.0 %) whose stored delta base falls outside the request:
1442    /// they are 16 KiB blobs stored as ~284-byte deltas, and shipping them whole
1443    /// costs ~5.8 KB each. Thin-pack narrowing (`d2ebb2c`) fixed the *fetch*
1444    /// half and provably cannot touch this one — a clone's receiver holds
1445    /// nothing, so there is no external base to name.
1446    ///
1447    /// # What is asserted, and why each part is needed
1448    ///
1449    /// * **The premise first.** `v3` must really be stored as a delta on `v2`,
1450    ///   `v2` must really be outside the request, and `v1` must really be inside
1451    ///   it and be `v2`'s base. On a corpus where any of those fails, every
1452    ///   implementation produces the same pack and this test is decoration.
1453    /// * **The count, held equal.** A smaller pack that dropped an object would
1454    ///   satisfy a byte assertion perfectly. The count is checked in the
1455    ///   receipt, in the pack's own header field, and in what git reads back.
1456    /// * **The bytes.** The load-bearing number. Seen red at **33 970** bytes
1457    ///   with `ZNIPPY_GIT_BOUNDARY_DELTA=0` — which is the pre-change behaviour
1458    ///   in this same binary, against the same store — and green at **16 839**.
1459    ///   The 2.02× between them is the same ratio the production fixture shows.
1460    /// * **The object graph, out of stock git.** A wrong copy offset produces a
1461    ///   pack `index-pack` accepts and whose objects are *different* — it files
1462    ///   each entry under the oid of whatever it decoded. Only reading the bytes
1463    ///   back and comparing them against the store catches it, so
1464    ///   [`crate::git_oracle::git_reads_back`] does exactly that for all six.
1465    /// * **The receipt.** `copied + recompressed == objects` still holds, and
1466    ///   the rebuilt entry is `deltified` rather than whole. Both are counted
1467    ///   off what happened; a byte count cannot tell the two rebuilds apart.
1468    #[test]
1469    fn a_narrowed_clone_re_deltas_its_boundary_entry_against_a_base_the_pack_carries() {
1470        let dir = tmpdir("serve-reboundary");
1471        let store = GitStore::open(&dir, "rickard").unwrap();
1472        let (pack, [c3, t3, v3, c1, t1, v1, c_main, _t2, v2]) =
1473            three_revisions_with_the_middle_one_off_branch();
1474        store.put(&pack, &[]).unwrap();
1475        store.wait_indexed();
1476        store.absorb_pending().unwrap();
1477        assert_eq!(store.commit_count(), 3, "all three commits must be folded");
1478
1479        let set = GitServe::select(&store, &[&c3], &[])
1480            .unwrap()
1481            .expect("the projection covers a commit it folded");
1482        let mut want = set.objects.clone();
1483        want.sort();
1484        let mut expected = vec![
1485            c3.clone(),
1486            t3.clone(),
1487            v3.clone(),
1488            c1.clone(),
1489            t1.clone(),
1490            v1.clone(),
1491        ];
1492        expected.sort();
1493        assert_eq!(want, expected, "`base` reaches r1 and r3 and nothing of main");
1494
1495        // ── THE PREMISE ───────────────────────────────────────────────────────
1496        let row_v3 = store.index().lookup(&v3).expect("v3 is indexed");
1497        let row_v2 = store.index().lookup(&v2).expect("v2 is indexed");
1498        let row_v1 = store.index().lookup(&v1).expect("v1 is indexed");
1499        assert_eq!(
1500            row_v3.obj_type,
1501            ObjType::OfsDelta,
1502            "v3 must be stored as a delta or there is no boundary entry"
1503        );
1504        assert_eq!(
1505            row_v3.delta_base, row_v2.offset,
1506            "v3's stored base must be v2 — the off-branch revision"
1507        );
1508        assert!(
1509            !set.objects.contains(&v2),
1510            "v2 must be OUTSIDE the request, or nothing is re-deltified"
1511        );
1512        assert_eq!(
1513            row_v2.delta_base, row_v1.offset,
1514            "v2's own base must be v1, or the chain ancestor this aims at does not exist"
1515        );
1516        assert!(
1517            set.objects.contains(&v1),
1518            "v1 must be INSIDE the request — it is the base the re-delta names"
1519        );
1520        assert!(
1521            row_v1.offset < row_v3.offset,
1522            "the ancestor must sit earlier in the archive; that ordering is what makes the new \
1523             delta edge acyclic by construction"
1524        );
1525        assert!(
1526            !c_main.is_empty(),
1527            "main's commit exists and is what keeps v2 in the repository but out of the request"
1528        );
1529
1530        // ── the emission ──────────────────────────────────────────────────────
1531        let oids: Vec<Oid<'_>> = set.objects.iter().map(Vec::as_slice).collect();
1532        let mut out = Vec::new();
1533        let stats = store.emit_oids(&oids, &[], &Caps::modern(), &mut out).unwrap();
1534
1535        assert_eq!(stats.objects, 6, "the count is held equal");
1536        assert_eq!(
1537            u32::from_be_bytes(out[8..12].try_into().unwrap()),
1538            6,
1539            "the emitted pack header must count what the receipt counts"
1540        );
1541
1542        // ── THE BYTES ─────────────────────────────────────────────────────────
1543        //
1544        // 🔴 **33 970 with `ZNIPPY_GIT_BOUNDARY_DELTA=0`, 16 839 with it on** —
1545        // measured, same binary, same store. The floor is `v1` itself: it is
1546        // 16 KiB of noise and it is *copied* in both arms, so the pack can never
1547        // be small. What halves is the second copy of the same 16 KiB that `v3`
1548        // used to be, and the threshold sits between the two behaviours rather
1549        // than on the green figure, so a zlib version bump moves the number
1550        // without moving the verdict.
1551        assert!(
1552            stats.bytes < 20_000,
1553            "a 16 KiB boundary blob with an in-request chain ancestor must go out as a delta, not \
1554             whole: the pack is {} bytes, and shipping it whole measures 33 970",
1555            stats.bytes
1556        );
1557        assert_eq!(
1558            stats.bytes as usize,
1559            out.len(),
1560            "the receipt's byte count must be what was actually written"
1561        );
1562
1563        // ── THE RECEIPT ───────────────────────────────────────────────────────
1564        assert_eq!(
1565            stats.copied + stats.recompressed,
1566            stats.objects,
1567            "every entry is one or the other, always"
1568        );
1569        assert_eq!(
1570            stats.recompressed, 1,
1571            "exactly the boundary entry is rebuilt; a re-delta is still a rebuild and must not be \
1572             counted as a copy"
1573        );
1574        let entries = store.emit_set(&oids, true, None).unwrap();
1575        let deltified: Vec<&crate::pack_walk::EmitEntry> =
1576            entries.iter().filter(|e| e.deltified).collect();
1577        assert_eq!(deltified.len(), 1, "one entry, and it is the boundary one");
1578        assert_eq!(deltified[0].oid, v3, "and it is v3");
1579        assert_eq!(
1580            deltified[0].delta_base, row_v1.offset,
1581            "the computed delta must name the in-request chain ancestor as its base"
1582        );
1583        // The mechanism, isolated from `v1`'s unavoidable 16 KiB: the entry
1584        // itself. Whole it is ~16.5 KB deflated; against `v1` it is a few
1585        // hundred bytes, and the gap between those two is the entire result.
1586        assert!(
1587            deltified[0].stored.len() < 1_000,
1588            "the re-deltified entry is {} bytes; whole it is ~16 500, and anything in between \
1589             means the delta was computed against the wrong base",
1590            deltified[0].stored.len()
1591        );
1592        assert!(
1593            entries.iter().all(|e| !e.deltified || e.recompressed),
1594            "`deltified` is a strict refinement of `recompressed`"
1595        );
1596
1597        // ── THE OBJECT GRAPH, out of stock git ────────────────────────────────
1598        let scratch = tmpdir("serve-reboundary-idx");
1599        crate::git_oracle::assert_git_accepts(
1600            &scratch,
1601            "dst.git",
1602            &out,
1603            crate::git_oracle::Strictness::Connected,
1604        );
1605        let read_back = crate::git_oracle::git_reads_back(&scratch, "readback.git", &out)
1606            .expect("stock git reads back the pack it just accepted");
1607        assert_eq!(read_back.len(), 6, "git must hold exactly the six objects");
1608        for (oid_hex, body) in &read_back {
1609            let raw = hex::decode(oid_hex).expect("git prints hex oids");
1610            let (_, ours) = store
1611                .content(&raw)
1612                .unwrap()
1613                .unwrap_or_else(|| panic!("git read back {oid_hex}, which this store does not hold"));
1614            assert_eq!(
1615                &ours, body,
1616                "{oid_hex} came back from git with different bytes than the store holds — a \
1617                 computed delta with a wrong copy offset produces exactly this and passes \
1618                 index-pack"
1619            );
1620        }
1621
1622        // ── and a full clone is untouched ─────────────────────────────────────
1623        let all: Vec<Oid<'_>> = [&c3, &t3, &v3, &c1, &t1, &v1, &c_main, &_t2, &v2]
1624            .iter()
1625            .map(|o| o.as_slice())
1626            .collect();
1627        let mut full = Vec::new();
1628        let stats = store.emit_oids(&all, &[], &Caps::modern(), &mut full).unwrap();
1629        assert_eq!(stats.objects, 9);
1630        assert_eq!(
1631            stats.recompressed, 0,
1632            "a whole-repository request contains every base, so nothing is rebuilt and nothing is \
1633             deltified — the copy claim is unchanged"
1634        );
1635        assert_eq!(stats.copied, 9);
1636    }
1637
1638    /// A fresh bare repository, and the objects `pack` carries unpacked into it.
1639    ///
1640    /// `git unpack-objects` rather than `index-pack`, because the point is a
1641    /// receiver that **holds** these objects, not one that has a pack file
1642    /// sitting next to its repository.
1643    fn bare_repo_holding(scratch: &std::path::Path, name: &str, pack: &[u8]) -> std::path::PathBuf {
1644        use std::io::Write as _;
1645        let repo = scratch.join(name);
1646        let init = std::process::Command::new("git")
1647            .args(["init", "-q", "--bare"])
1648            .arg(&repo)
1649            .output()
1650            .expect("running git init");
1651        assert!(init.status.success(), "git init failed");
1652        if !pack.is_empty() {
1653            let mut child = std::process::Command::new("git")
1654                .args(["unpack-objects", "-q"])
1655                .current_dir(&repo)
1656                .stdin(std::process::Stdio::piped())
1657                .stdout(std::process::Stdio::piped())
1658                .stderr(std::process::Stdio::piped())
1659                .spawn()
1660                .expect("running git unpack-objects");
1661            child.stdin.take().unwrap().write_all(pack).unwrap();
1662            let done = child.wait_with_output().unwrap();
1663            assert!(
1664                done.status.success(),
1665                "seeding the receiver failed:\n{}",
1666                String::from_utf8_lossy(&done.stderr)
1667            );
1668        }
1669        repo
1670    }
1671
1672    /// `git index-pack --stdin --fix-thin --strict` inside `repo`. The exact
1673    /// command a fetching client runs when it advertised `thin-pack`, and the
1674    /// only one that can judge a pack whose bases are somewhere else.
1675    fn fix_thin_into(repo: &std::path::Path, pack: &[u8]) -> std::process::Output {
1676        use std::io::Write as _;
1677        let mut child = std::process::Command::new("git")
1678            .args(["index-pack", "--stdin", "--fix-thin", "--strict"])
1679            .current_dir(repo)
1680            .stdin(std::process::Stdio::piped())
1681            .stdout(std::process::Stdio::piped())
1682            .stderr(std::process::Stdio::piped())
1683            .spawn()
1684            .expect("running git index-pack --fix-thin");
1685        child.stdin.take().unwrap().write_all(pack).unwrap();
1686        child.wait_with_output().unwrap()
1687    }
1688
1689    /// 🔴 **A thin FETCH names a base the receiver already holds instead of
1690    /// rebuilding the boundary entry whole — and stock git resolves it.**
1691    ///
1692    /// This is the cost `8c2679d` left behind, for the half of it that can be
1693    /// paid cheaply. A narrowed request cuts delta chains, and until now every
1694    /// entry on the cut went out **whole**: correct, connected, and measured at
1695    /// 11.6 MB against git's 5.82 MB on `h2h-linear-sha1-2048c-1024f-16k`. When
1696    /// the receiver already holds the base, none of that is necessary — a
1697    /// `REF_DELTA` naming an external base is exactly what `thin-pack` means,
1698    /// and the stored delta stream goes out unchanged.
1699    ///
1700    /// # Four assertions, and each one can fail on its own
1701    ///
1702    /// 1. **The premise.** `t_base` is stored as an `OFS_DELTA` on `t_main`,
1703    ///    `t_main` is outside the request, and the client holds it. Without all
1704    ///    three there is no thin entry to make and the rest proves nothing.
1705    /// 2. **The receipt.** `recompressed` must be **0** — RED before this
1706    ///    change at **1**, because the boundary entry was rebuilt.
1707    /// 3. **Applied output, on the bytes.** The emitted pack must contain a
1708    ///    `REF_DELTA` whose base oid is `t_main`, and `t_main` must **not** be
1709    ///    an entry in that pack. A receipt cannot tell a thin delta from a
1710    ///    copied one; the wire can.
1711    /// 4. **Stock git, both directions.** `index-pack --stdin --fix-thin
1712    ///    --strict` accepts it in a repository that holds `main`, and **fails**
1713    ///    in an empty one. The second half is what proves the pack is genuinely
1714    ///    thin rather than accidentally self-contained, and it is what makes the
1715    ///    first half worth anything.
1716    ///
1717    /// And the two arms that must NOT change: the same request without
1718    /// `caps.thin`, and the same request with no `have`, both still rebuild the
1719    /// boundary entry whole. A clone is the second of those.
1720    #[test]
1721    fn a_thin_fetch_names_a_base_the_client_holds_instead_of_rebuilding_it() {
1722        let dir = tmpdir("serve-thin");
1723        let store = GitStore::open(&dir, "rickard").unwrap();
1724        let (pack, [c_main, t_main, b_main, c_base, t_base, b_base]) =
1725            two_branches_with_a_cross_branch_delta(256);
1726        store.put(&pack, &[]).unwrap();
1727        store.wait_indexed();
1728        store.absorb_pending().unwrap();
1729
1730        // The fetch: the client is on `main` and wants `base`.
1731        let set = GitServe::select(&store, &[&c_base], &[&c_main])
1732            .unwrap()
1733            .expect("the projection covers both commits it folded");
1734        let oids: Vec<Oid<'_>> = set.objects.iter().map(Vec::as_slice).collect();
1735
1736        // ── THE PREMISE ───────────────────────────────────────────────────────
1737        assert!(
1738            !set.objects.contains(&t_main),
1739            "main's tree must be OUTSIDE the request or there is no external base"
1740        );
1741        assert!(
1742            set.objects.contains(&t_base),
1743            "base's tree must be IN the request — it is the boundary entry"
1744        );
1745        let row = store.index().lookup(&t_base).expect("indexed");
1746        assert_eq!(
1747            row.obj_type,
1748            ObjType::OfsDelta,
1749            "the fixture must store base's tree as a delta or there is no base to name"
1750        );
1751        assert_eq!(
1752            row.delta_base,
1753            store.index().lookup(&t_main).expect("indexed").offset,
1754            "the fixture's delta must name main's tree as its base"
1755        );
1756        assert!(
1757            set.client_has.contains(&t_main),
1758            "the negotiation must vouch that the client holds main's tree"
1759        );
1760
1761        let thin_caps = Caps {
1762            thin: true,
1763            ofs_delta: true,
1764        };
1765        let have: Vec<Oid<'_>> = vec![&c_main];
1766
1767        // ── the receipt ───────────────────────────────────────────────────────
1768        let mut thin = Vec::new();
1769        let stats = store
1770            .emit_oids(&oids, &have, &thin_caps, &mut thin)
1771            .unwrap();
1772        assert_eq!(stats.objects, 3, "the set is still exactly what was selected");
1773        assert_eq!(
1774            stats.recompressed, 0,
1775            "the boundary entry's base is one the client holds, so nothing may be rebuilt"
1776        );
1777        assert_eq!(stats.copied, 3, "every entry is a byte-for-byte copy");
1778
1779        // ── applied output, on the emitted bytes ──────────────────────────────
1780        let walked = crate::pack_walk::walk(&thin, store.hash_kind().oid_len())
1781            .expect("our own walk reads what we emitted");
1782        assert_eq!(walked.entries.len(), 3);
1783        assert_eq!(
1784            walked.closure().external_refs,
1785            vec![t_main.clone()],
1786            "exactly one entry may name a base by oid, and it must be main's tree — which the \
1787             pack does not carry, because the pack carries only the three objects selected"
1788        );
1789
1790        // ── stock git, both directions ────────────────────────────────────────
1791        let scratch = tmpdir("serve-thin-git");
1792        // What the client already has: main's three objects.
1793        let mut mains = Vec::new();
1794        store
1795            .emit_oids(
1796                &[&c_main, &t_main, &b_main],
1797                &[],
1798                &Caps::modern(),
1799                &mut mains,
1800            )
1801            .unwrap();
1802        let holder = bare_repo_holding(&scratch, "holder.git", &mains);
1803        let got = fix_thin_into(&holder, &thin);
1804        assert!(
1805            got.status.success(),
1806            "git refused a thin pack whose base it holds — status {:?}\n{}",
1807            got.status.code(),
1808            String::from_utf8_lossy(&got.stderr)
1809        );
1810
1811        // …and it must FAIL where the base is absent — for the RIGHT reason.
1812        //
1813        // The receiver here holds `b_main` and nothing else, which is chosen
1814        // rather than convenient: an *empty* repository refuses this pack either
1815        // way, because `t_base` names `b_main` as a child and a strict index-pack
1816        // demands children exist, so a refusal there would prove connectivity and
1817        // say nothing about thinness. Holding exactly `b_main` makes the pack
1818        // connected and leaves only the delta base missing, so the only thing
1819        // git can complain about is the external base — and with the thin arm
1820        // switched off this same call **succeeds**, which is what makes it a
1821        // guard rather than a decoration.
1822        let blob_only = bare_repo_holding(&scratch, "blob-only.git", &{
1823            let mut p = Vec::new();
1824            store
1825                .emit_oids(&[&b_main], &[], &Caps::modern(), &mut p)
1826                .unwrap();
1827            p
1828        });
1829        let refused = fix_thin_into(&blob_only, &thin);
1830        assert!(
1831            !refused.status.success(),
1832            "a receiver without main's TREE accepted the pack, so nothing was actually \
1833             offered as external and this whole test is hollow"
1834        );
1835        let why = String::from_utf8_lossy(&refused.stderr).to_lowercase();
1836        assert!(
1837            why.contains("delta"),
1838            "the refusal must be about the delta base this pack does not carry, and it said: \
1839             {why}"
1840        );
1841
1842        // ── and the two arms that must not have moved ─────────────────────────
1843        let mut not_thin = Vec::new();
1844        let stats = store
1845            .emit_oids(&oids, &have, &Caps::modern(), &mut not_thin)
1846            .unwrap();
1847        assert_eq!(
1848            stats.recompressed, 1,
1849            "a client that did not advertise thin-pack must still get the entry whole"
1850        );
1851        let mut no_have = Vec::new();
1852        let stats = store
1853            .emit_oids(&oids, &[], &thin_caps, &mut no_have)
1854            .unwrap();
1855        assert_eq!(
1856            stats.recompressed, 1,
1857            "consent with nothing negotiated is a clone, and a clone must be unchanged"
1858        );
1859
1860        // The whole point, in bytes: the thin pack is smaller than the one that
1861        // rebuilt the entry.
1862        assert!(
1863            thin.len() < not_thin.len(),
1864            "a thin pack that is not smaller has bought nothing: {} vs {}",
1865            thin.len(),
1866            not_thin.len()
1867        );
1868
1869        // And a FULL request is byte-identical whether or not thin is offered —
1870        // it has no base outside itself for any of this to apply to.
1871        let all: Vec<Oid<'_>> = vec![&c_main, &t_main, &b_main, &c_base, &t_base, &b_base];
1872        let mut full_thin = Vec::new();
1873        store
1874            .emit_oids(&all, &have, &thin_caps, &mut full_thin)
1875            .unwrap();
1876        let mut full_plain = Vec::new();
1877        store
1878            .emit_oids(&all, &[], &Caps::modern(), &mut full_plain)
1879            .unwrap();
1880        assert_eq!(
1881            full_thin, full_plain,
1882            "a full clone must be byte-identical with and without the thin allowance"
1883        );
1884    }
1885
1886    /// **The emitted pack is a byte-range copy, and stock git accepts it.**
1887    ///
1888    /// Proven against `git index-pack --strict` rather than against this
1889    /// crate's own reader: our parser reading our writer would agree with itself
1890    /// even if both were wrong the same way.
1891    ///
1892    /// The receipt is asserted too, and that is not decoration: `copied ==
1893    /// objects` and `recompressed == 0` are the `P-001` applied-output
1894    /// assertion. A pipeline that inflated every object in order to deflate it
1895    /// again would pass `index-pack --strict` **and** `fsck` while sending a
1896    /// measured 18.4x the wire bytes, and only these two counters can see it.
1897    #[test]
1898    fn an_emitted_pack_is_copied_not_recompressed_and_stock_git_accepts_it() {
1899        let dir = tmpdir("serve-emit");
1900        let store = GitStore::open(&dir, "rickard").unwrap();
1901        let (pack, rows) = corpus_pack();
1902        store.put(&pack, &[]).unwrap();
1903        store.wait_indexed();
1904        store.absorb_pending().unwrap();
1905
1906        let oids: Vec<Oid<'_>> = rows.iter().map(|r| r.oid.as_slice()).collect();
1907        let mut out = Vec::new();
1908        let stats = store.emit_oids(&oids, &[], &Caps::modern(), &mut out).unwrap();
1909
1910        assert_eq!(
1911            stats.objects,
1912            rows.len() as u64,
1913            "every requested object must be emitted"
1914        );
1915        assert_eq!(
1916            stats.copied, stats.objects,
1917            "every payload must be COPIED, not re-deflated"
1918        );
1919        assert_eq!(stats.recompressed, 0, "nothing on this path re-deflates");
1920        assert_eq!(
1921            stats.bytes,
1922            out.len() as u64,
1923            "the receipt's byte count is what was written, not what was intended"
1924        );
1925
1926        // 🔴 Through [`crate::git_oracle`], and that is the 2026-08-11 fix here:
1927        // this ran `index-pack --strict` in `scratch`, which is a plain
1928        // directory and not a repository, and outside a repository that command
1929        // **segfaults** on any pack it would have rejected — exit 139, no
1930        // output. The whole corpus is emitted, so the set is closed and
1931        // `Connected` is the honest question.
1932        let scratch = tmpdir("serve-emit-idx");
1933        crate::git_oracle::assert_git_accepts(
1934            &scratch,
1935            "emitted.git",
1936            &out,
1937            crate::git_oracle::Strictness::Connected,
1938        );
1939    }
1940
1941    /// **A client without `ofs-delta` is SERVED — a ref-delta carrying the same
1942    /// bytes — and never handed an entry it cannot parse.**
1943    ///
1944    /// Until 2026-08-11 this asserted a refusal, because the engine copies
1945    /// stored entries and had no way to re-name a base; that is the
1946    /// `gunnar.clone_no_ofs_delta` arm, red with *"the selection contains at
1947    /// least one stored ofs-delta entry"*. It does have a way now, and it costs
1948    /// nothing: the two delta forms differ only in **how the base is named**, so
1949    /// re-heading an `OFS_DELTA` as a `REF_DELTA` is a header swap over an
1950    /// unchanged compressed payload.
1951    ///
1952    /// Both halves are asserted. Serving is not enough on its own — the
1953    /// silent-corruption direction is to emit the `OFS_DELTA` anyway and exit
1954    /// zero — so the emitted bytes are walked and every entry checked, and the
1955    /// receipt must still say `recompressed = 0` or the swap has quietly become
1956    /// a re-deflate.
1957    #[test]
1958    fn a_client_that_cannot_read_ofs_delta_is_served_ref_deltas_instead() {
1959        let dir = tmpdir("serve-caps");
1960        let store = GitStore::open(&dir, "rickard").unwrap();
1961        let (pack, rows) = corpus_pack();
1962        store.put(&pack, &[]).unwrap();
1963        store.wait_indexed();
1964        store.absorb_pending().unwrap();
1965
1966        let stored_ofs = rows
1967            .iter()
1968            .filter(|r| r.stored_type == ObjType::OfsDelta)
1969            .count();
1970        assert!(
1971            stored_ofs > 0,
1972            "the corpus must contain an ofs-delta or this asserts nothing"
1973        );
1974
1975        let oids: Vec<Oid<'_>> = rows.iter().map(|r| r.oid.as_slice()).collect();
1976        let caps = Caps {
1977            thin: false,
1978            ofs_delta: false,
1979        };
1980        let mut out = Vec::new();
1981        let stats = store.emit_oids(&oids, &[], &caps, &mut out).unwrap();
1982        assert_eq!(stats.objects, rows.len() as u64);
1983        assert_eq!(
1984            stats.recompressed, 0,
1985            "re-naming a base must not re-deflate a payload"
1986        );
1987
1988        let walked = crate::pack_walk::walk(&out, store.hash_kind().oid_len())
1989            .expect("our own walk reads what we emitted");
1990        assert!(
1991            walked.entries.iter().all(|e| e.obj_type != ObjType::OfsDelta),
1992            "not one ofs-delta may reach a client that cannot parse one"
1993        );
1994        assert_eq!(
1995            walked
1996                .entries
1997                .iter()
1998                .filter(|e| e.obj_type == ObjType::RefDelta)
1999                .count(),
2000            stored_ofs
2001                + rows
2002                    .iter()
2003                    .filter(|r| r.stored_type == ObjType::RefDelta)
2004                    .count(),
2005            "every stored delta must still be a delta — re-headed, not flattened"
2006        );
2007
2008        // And stock git reads it. The whole corpus is emitted, so the set is
2009        // closed and `Connected` is the question a clone would ask.
2010        let scratch = tmpdir("serve-caps-idx");
2011        crate::git_oracle::assert_git_accepts(
2012            &scratch,
2013            "dst.git",
2014            &out,
2015            crate::git_oracle::Strictness::Connected,
2016        );
2017    }
2018
2019    /// **`put_refs_cas` applies every edit or none, and says which one lost.**
2020    ///
2021    /// Two properties in one test because they are one property: the batch that
2022    /// fails must leave the namespace exactly as it was, and the caller must be
2023    /// able to learn *which* ref and *what was there* **without reading the
2024    /// message**.
2025    #[test]
2026    fn an_atomic_batch_applies_every_edit_or_none_and_names_the_loser() {
2027        let dir = tmpdir("serve-cas");
2028        let store = GitStore::open(&dir, "rickard").unwrap();
2029        let (pack, oid) = one_blob_pack(b"cas fixture");
2030        store.put(&pack, &[]).unwrap();
2031        store.wait_indexed();
2032        store
2033            .put_refs(&[RefUpdate::set("refs/heads/taken", hex::encode(&oid))])
2034            .unwrap();
2035
2036        // Edit 1 would succeed on its own; edit 2 must not. Neither may land.
2037        let edits = [
2038            RefCas {
2039                name: "refs/heads/fresh".into(),
2040                old: None,
2041                new: Some(&oid),
2042                peeled: None,
2043            },
2044            RefCas {
2045                name: "refs/heads/taken".into(),
2046                old: None, // "must not exist" — and it does
2047                new: Some(&oid),
2048                peeled: None,
2049            },
2050        ];
2051        let err = store.put_refs_cas(&edits).unwrap_err();
2052
2053        let rejection = RefRejection::of(&err).expect(
2054            "a lost compare-and-swap must be TYPED — receive-pack cannot tell a lost race from a \
2055             broken disk by reading a message",
2056        );
2057        assert!(rejection.is_cas_failure());
2058        assert!(!rejection.is_lock_contention());
2059        assert_eq!(rejection.name(), "refs/heads/taken");
2060        match rejection {
2061            RefRejection::Cas {
2062                expected, actual, ..
2063            } => {
2064                assert_eq!(*expected, Observed::Nothing, "the caller claimed absent");
2065                assert_eq!(
2066                    *actual,
2067                    Observed::oid(&oid),
2068                    "the rejection must carry what was ACTUALLY there"
2069                );
2070            }
2071            other => panic!("wrong variant: {other:?}"),
2072        }
2073
2074        let names: Vec<String> = store.refs().unwrap().into_iter().map(|r| r.name).collect();
2075        assert!(
2076            !names.iter().any(|n| n == "refs/heads/fresh"),
2077            "the first edit of a failed atomic batch must NOT have landed: {names:?}"
2078        );
2079
2080        // And the same batch, with the expectation corrected, applies whole.
2081        let edits = [
2082            RefCas {
2083                name: "refs/heads/fresh".into(),
2084                old: None,
2085                new: Some(&oid),
2086                peeled: None,
2087            },
2088            RefCas {
2089                name: "refs/heads/taken".into(),
2090                old: Some(&oid),
2091                new: None, // delete
2092                peeled: None,
2093            },
2094        ];
2095        store.put_refs_cas(&edits).unwrap();
2096        let names: Vec<String> = store.refs().unwrap().into_iter().map(|r| r.name).collect();
2097        assert!(names.iter().any(|n| n == "refs/heads/fresh"));
2098        assert!(!names.iter().any(|n| n == "refs/heads/taken"));
2099    }
2100
2101    /// **`S-023`: a create that must fail still fails when the new value equals
2102    /// the current one.**
2103    ///
2104    /// The defect this guards is a backend short-circuiting an edit whose new
2105    /// value already matches, never evaluating the caller's expectation and
2106    /// turning a `MustNotExist` into a **silent success**. It is not
2107    /// hypothetical — it was found in gix's file ref store, on the only backend
2108    /// that survives a restart — and it breaks *"exactly one creator wins"*,
2109    /// which is how receive-pack arbitrates two racing pushes.
2110    ///
2111    /// Seen RED by moving the expectation check after the write.
2112    #[test]
2113    fn s023_a_must_not_exist_create_fails_even_when_the_value_is_unchanged() {
2114        let dir = tmpdir("serve-s023");
2115        let store = GitStore::open(&dir, "rickard").unwrap();
2116        let (pack, oid) = one_blob_pack(b"s023");
2117        store.put(&pack, &[]).unwrap();
2118        store.wait_indexed();
2119        store
2120            .put_refs(&[RefUpdate::set("refs/heads/racy", hex::encode(&oid))])
2121            .unwrap();
2122
2123        // Same value the ref already holds, with `old: None` — the exact shape a
2124        // short-circuiting backend turns into a no-op success.
2125        let edits = [RefCas {
2126            name: "refs/heads/racy".into(),
2127            old: None,
2128            new: Some(&oid),
2129            peeled: None,
2130        }];
2131        let err = store.put_refs_cas(&edits).unwrap_err();
2132        let rejection = RefRejection::of(&err).expect("must be a typed CAS rejection");
2133        assert!(
2134            rejection.is_cas_failure(),
2135            "a create-that-must-not-exist against an existing ref is a lost race, not a fault"
2136        );
2137    }
2138
2139    /// An empty atomic batch is a no-op that succeeded.
2140    #[test]
2141    fn an_empty_atomic_batch_is_a_no_op_rather_than_an_error() {
2142        let dir = tmpdir("serve-cas-empty");
2143        let store = GitStore::open(&dir, "rickard").unwrap();
2144        assert_eq!(store.put_refs_cas(&[]).unwrap(), TxId::default());
2145    }
2146
2147    /// The hash width the emitter trailers with is the store's, not a constant.
2148    #[test]
2149    fn the_emitted_trailer_is_the_stores_hash_width() {
2150        for hash in [GitHashKind::Sha1, GitHashKind::Sha256] {
2151            let dir = tmpdir(&format!("serve-trailer-{}", hash.oid_len()));
2152            let store = GitStore::open_with(&dir, "rickard", hash).unwrap();
2153            // No objects: the pack is header + trailer and nothing else, which
2154            // is exactly the shape that makes the widths comparable.
2155            let mut out = Vec::new();
2156            let stats = store.emit_oids(&[], &[], &Caps::modern(), &mut out).unwrap();
2157            assert_eq!(stats.objects, 0);
2158            assert_eq!(out.len(), 12 + hash.oid_len());
2159            assert_eq!(stats.bytes, out.len() as u64);
2160        }
2161    }
2162}