crates/storage-git-gix/tests/serving_the_wire.rs
//! `GitServe` on the gix arm, judged by **stock git** and by counts.
//!
//! Two rules the whole file is built on, both from `locked_on_target.md`:
//!
//! * **`S-026`** — *"the real-git interop suites self-skip and report green"*.
//! Nothing here skips. A missing `git` is a failure.
//! * **§6** — *prefer counts over clocks*. Every assertion below is a count, a
//! byte comparison or a verdict from `git index-pack --strict`. There is no
//! wall clock in this file and there is nothing for load to move.
//!
//! # The two defects this file exists to catch, neither of which has a symptom
//!
//! 1. **An over-send.** `emit_pack`'s first parameter is an exact set. An engine
//! that closes over it serves a `--filter=blob:none` fetch precisely the
//! objects it asked to be left out — and the resulting pack passes
//! `index-pack --strict` *and* `fsck`, the clone succeeds, the exit code is
//! zero. Only a **count** can see it.
//! 2. **A re-deflate.** An emitter that inflates every object in order to
//! deflate it again produces a pack that is equally valid and a measured
//! **18.4×** the wire bytes. Only the `copied`/`recompressed` receipt can see
//! it.
use std::io::Write as _;
use std::path::Path;
use std::process::Command;
use git_storage_trait::{Caps, GitOps, GitServe, ObjType, Oid, RefCas, RefRejection, RefUpdate};
use storage_git_gix::GixStore;
// ── fixtures ─────────────────────────────────────────────────────────────────
fn git(dir: &Path, args: &[&str]) -> String {
let out = Command::new("git")
.current_dir(dir)
.args(args)
.output()
.unwrap_or_else(|e| panic!("`git {}` could not be run: {e}", args.join(" ")));
assert!(
out.status.success(),
"`git {}` failed: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn hex_to_bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
fn to_hex(raw: &[u8]) -> String {
raw.iter().map(|b| format!("{b:02x}")).collect()
}
/// A history whose blobs are similar enough that `git pack-objects` **must**
/// deltify them.
///
/// The similarity is the fixture's whole point: a corpus with no deltas would
/// let a broken `OFS_DELTA` rewrite pass every test in this file. The tests that
/// depend on a delta assert one exists before they assert anything else.
///
/// `--delta-base-offset` for the reason `round_trip.rs` gives at length: gitoxide
/// cannot index a pack whose `REF_DELTA` bases are inside that same pack
/// (`S-003`, filed as GitoxideLabs/gitoxide#2882), and every modern client sends
/// `OFS_DELTA` anyway.
fn fixture(root: &Path) -> (Vec<u8>, String) {
let src = root.join("src");
std::fs::create_dir_all(&src).unwrap();
git(&src, &["init", "-q", "--initial-branch=main"]);
git(&src, &["config", "user.email", "t@example.invalid"]);
git(&src, &["config", "user.name", "T"]);
let mut body = String::new();
for i in 0..6 {
body.push_str(&format!("line {i} — {}\n", "y".repeat(600)));
std::fs::write(src.join("big.txt"), &body).unwrap();
std::fs::create_dir_all(src.join("sub")).unwrap();
std::fs::write(src.join("sub").join("s.txt"), format!("sub {i}\n")).unwrap();
git(&src, &["add", "-A"]);
git(&src, &["commit", "-q", "-m", &format!("commit {i}")]);
}
let head = git(&src, &["rev-parse", "HEAD"]);
let out = Command::new("git")
.current_dir(&src)
.args(["pack-objects", "--revs", "--delta-base-offset", "--stdout"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.and_then(|mut c| {
c.stdin.take().unwrap().write_all(b"HEAD\n")?;
c.wait_with_output()
})
.expect("git pack-objects");
assert!(out.status.success(), "pack-objects failed");
(out.stdout, head)
}
/// A store holding that history, plus `refs/heads/main` and `HEAD`.
fn store_with_history(root: &Path) -> (GixStore, String) {
let (pack, head) = fixture(root);
let bare = root.join("bare.git");
let store = GixStore::open_or_init(&bare, gix_hash::Kind::Sha1).unwrap();
store
.put(&pack, &[RefUpdate::set("refs/heads/main", &head)])
.unwrap();
(store, head)
}
/// Hand `bytes` to stock `git index-pack` and return the object count it found.
///
/// **This is the assertion the emitter exists to pass**, and it is deliberately
/// not "our reader reads it back": a pack only its author can parse has proved
/// nothing.
///
/// # Why `strict` is a parameter and not always on
///
/// `--strict` adds a **connectivity** check — every object a tree or commit
/// names must be present — on top of the format checks. That is exactly right
/// for a whole clone and wrong for a deliberately partial emission, which is
/// what a `--filter` fetch and the exact-set tests below both are: the pack is
/// *supposed* to be short of its referents. So the complete emissions run
/// `--strict` and the partial ones run the format check alone, rather than the
/// partial ones quietly asserting less than they look like they do.
///
/// (On git 2.53.0 in this environment `--strict` over such a pack does not
/// report an error — it **segfaults**, `SIGSEGV`, reproduced standalone with
/// `git pack-objects` output and no gunnar code involved. That is not the reason
/// for the parameter, but it does mean "just turn it on everywhere" is not
/// available even if it were correct.)
fn index_pack(root: &Path, bytes: &[u8], strict: bool) -> usize {
let dir = root.join(format!("verify-{}-{strict}", bytes.len()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("in.pack");
std::fs::write(&path, bytes).unwrap();
let mut args = vec!["index-pack"];
if strict {
args.push("--strict");
}
args.extend(["-v", "in.pack"]);
let out = Command::new("git")
.current_dir(&dir)
.args(&args)
.output()
.expect("git index-pack");
assert!(
out.status.success(),
"stock git refused the emitted pack: {}",
String::from_utf8_lossy(&out.stderr)
);
let idx = path.with_extension("idx");
let listed = Command::new("git")
.current_dir(&dir)
.args(["verify-pack", "-v", idx.to_str().unwrap()])
.output()
.expect("git verify-pack");
assert!(
listed.status.success(),
"git verify-pack refused it: {}",
String::from_utf8_lossy(&listed.stderr)
);
String::from_utf8_lossy(&listed.stdout)
.lines()
.filter(|l| {
let mut f = l.split_whitespace();
let oid = f.next().unwrap_or("");
oid.len() == 40
&& oid.bytes().all(|b| b.is_ascii_hexdigit())
&& matches!(f.next(), Some("commit" | "tree" | "blob" | "tag"))
})
.count()
}
/// One object of the given **stored** shape, or `None`.
fn find_stored<'a>(store: &GixStore, all: &'a [Vec<u8>], delta: bool) -> Option<&'a Vec<u8>> {
all.iter().find(|raw| {
store
.get(raw)
.unwrap()
.is_some_and(|s| matches!(s.obj_type, ObjType::OfsDelta | ObjType::RefDelta) == delta)
})
}
// ── emit_pack: the exact set ─────────────────────────────────────────────────
/// **The `P-027`-shaped defect: an emitter that closes over its own input.**
///
/// Agent K found this live on the Arrow arm and it read `left: 4 right: 1`. The
/// property is stated on the contract in as many words — *"no object is added
/// because the engine walked the graph"* — and it cannot be seen any other way:
/// an over-sent pack passes `index-pack --strict` and `fsck`, and the clone
/// exits zero.
///
/// The subject is a **non-delta** object with a non-trivial reachability
/// closure, so the two numbers are guaranteed to differ if the engine walks: a
/// tree reaches its blobs, and emitting it must still be one entry because the
/// format forces no base. The premise is asserted first, or the test proves
/// nothing.
#[test]
fn emit_pack_emits_exactly_the_set_it_is_given_and_never_its_closure() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
// A stored-whole object whose closure is bigger than itself.
let subject = all
.iter()
.find(|raw| {
let whole = store
.get(raw)
.unwrap()
.is_some_and(|s| !matches!(s.obj_type, ObjType::OfsDelta | ObjType::RefDelta));
whole && store.reachable(&[raw.as_slice()], &[]).unwrap().len() > 1
})
.expect("the fixture must contain a whole object that reaches others")
.clone();
let closure = store.reachable(&[&subject], &[]).unwrap().len();
assert!(
closure > 1,
"premise: the subject must reach more than itself, or this test cannot fail"
);
let mut out = Vec::new();
let stats = store
.emit_pack(&[&subject], &[], &Caps::modern(), &mut out)
.unwrap();
assert_eq!(
stats.objects, 1,
"emit_pack was handed ONE object with a {closure}-object closure and must emit exactly \
one; more means the engine walked the graph, which is the over-send that serves a \
`--filter=blob:none` fetch the objects it asked to be left out"
);
assert_eq!(
stats.copied, stats.objects,
"a byte-range copy, not a rebuild"
);
assert_eq!(stats.recompressed, 0, "nothing on this path re-deflates");
assert_eq!(
index_pack(tmp.path(), &out, false),
1,
"and stock git must count the same one object"
);
}
/// **Not even a delta base may grow the set** — a stored delta whose base is
/// outside the request is REBUILT, not accompanied.
///
/// This test asserted the opposite until 2026-08-14. It required
/// `stats.objects > 1` for a lone delta and then required the emitted count to
/// equal the length of its stored base chain, on the reasoning that an
/// `OFS_DELTA` names its base by position and cannot be encoded without it.
/// That is true of the pack *format* and false of the *clone*: a base pulled in
/// is an object the client never asked for, and an out-of-set base that is a
/// **tree** arrives owing children the pack does not contain, so the clone's own
/// receiver refuses the whole transfer with `did not receive expected object`
/// while the server logs a success. `a_clone_carries_the_closure_it_advertised`
/// is the end-to-end guard on that; this is the unit-level statement of the same
/// rule, which is stock `pack-objects`': **reuse a stored delta only when its
/// base is also being packed.**
///
/// The premise — that the subject really is a stored delta with a real base
/// chain, so the two answers ("1" and "the chain") differ — is measured first,
/// or this proves nothing.
#[test]
fn a_delta_whose_base_is_outside_the_request_is_rebuilt_rather_than_accompanied() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
let delta = find_stored(&store, &all, true)
.expect("premise: the fixture must contain a stored delta, or this test proves nothing")
.clone();
// How long is the chain, according to the stored entries themselves? That
// is the number this used to emit, and it is the number that must NOT come
// out now.
let chain = {
let mut n = 1usize;
let mut here = delta.clone();
// Bounded: a corrupt back-reference must not spin this test for ever.
for _ in 0..64 {
let stored = store.get(&here).unwrap().expect("an entry for every hop");
if !matches!(stored.obj_type, ObjType::OfsDelta | ObjType::RefDelta) {
break;
}
// `read` is the resolved object; walking the chain by hand needs the
// store's own answer for where the base is, and `verify-pack` is the
// independent check on it.
n += 1;
here = base_of(&store, &here);
}
n
};
assert!(
chain > 1,
"premise: the subject must have a stored base outside the request, or the old answer \
and the new one are the same number and this test cannot fail"
);
let mut out = Vec::new();
let stats = store
.emit_pack(&[&delta], &[], &Caps::modern(), &mut out)
.unwrap();
assert_eq!(
stats.objects, 1,
"emit_pack was handed ONE object whose stored base chain is {chain} long and must emit \
exactly one; the base is not in the request, so it decides how the entry is ENCODED \
and never what the pack contains"
);
assert_eq!(
stats.recompressed, 1,
"the entry had to be rebuilt, and the receipt has to say so rather than claim a copy"
);
assert_eq!(stats.copied, 0);
// Stock git must find the one object — and it must be the RIGHT object,
// which a rebuilt entry is the only way to get wrong. `index_pack` counts;
// `cat-file` reads the content back out of the pack git itself indexed.
assert_eq!(index_pack(tmp.path(), &out, true), 1);
let scratch = tmp.path().join("rebuilt.git");
git(
tmp.path(),
&["init", "-q", "--bare", scratch.to_str().unwrap()],
);
let mut child = Command::new("git")
.current_dir(&scratch)
.args(["index-pack", "--stdin", "--fix-thin"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("git index-pack --stdin");
child.stdin.take().unwrap().write_all(&out).unwrap();
let done = child.wait_with_output().unwrap();
assert!(
done.status.success(),
"git refused the rebuilt entry: {}",
String::from_utf8_lossy(&done.stderr)
);
let hex = to_hex(&delta);
let content = Command::new("git")
.current_dir(&scratch)
.args(["cat-file", "-p", &hex])
.output()
.expect("git cat-file");
assert!(content.status.success(), "the rebuilt object is unreadable");
let expected = store.read(&delta).unwrap().expect("the store has it").1;
assert_eq!(
content.stdout, expected,
"the rebuilt entry decodes to different bytes than the store holds — a re-deflate that \
lost the object is exactly the failure a byte count cannot see"
);
}
/// Pipe `bytes` to `git <args>` on stdin; `Ok` iff git exits zero, else its
/// stderr as the error. Shared by the thin-pack guard below (LAW 5) because it
/// feeds git a pack three times and reads the exit each time.
fn feed(dir: &Path, args: &[&str], bytes: &[u8]) -> std::io::Result<()> {
let mut child = Command::new("git")
.current_dir(dir)
.args(args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()?;
child.stdin.take().unwrap().write_all(bytes)?;
let done = child.wait_with_output()?;
if done.status.success() {
Ok(())
} else {
Err(std::io::Error::other(
String::from_utf8_lossy(&done.stderr).into_owned(),
))
}
}
/// **The fetch-path twin of the rebuild above**, and the unit-level statement of
/// the fix for `gunnar.vs_git_fetch_divergent`'s non-zero recompress count.
///
/// The same stored delta whose base is outside the request, but this time the
/// client VOUCHES for that base by advertising `head` as a `have`: a divergent
/// fetch offers the common history it shares with the server, and a boundary
/// delta's base sits inside that shared closure. So the entry goes out as a
/// **thin copy** — its stored delta stream verbatim under a `REF_DELTA` naming
/// the base by id, `recompressed` staying zero — not rebuilt whole. Before the
/// fix the gix arm discarded `have` and rebuilt every boundary delta, which was
/// the entire recompress count of the divergent-fetch arm.
///
/// The premise that makes this differ from the rebuild test is asserted first:
/// the base is genuinely outside a one-object request AND genuinely inside
/// head's closure, so a rebuild and a thin copy are two different answers.
///
/// Byte-correctness is stock git's, twice: with the base ABSENT the pack is
/// unresolvable (so this really is thin, not a smuggled whole object), and with
/// the base PRESENT `index-pack --fix-thin` resolves it to the exact bytes the
/// store holds.
#[test]
fn a_delta_whose_base_the_client_holds_is_copied_thin_not_rebuilt() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
let delta = find_stored(&store, &all, true)
.expect("premise: the fixture must contain a stored delta, or this proves nothing")
.clone();
let base = base_of(&store, &delta);
// PREMISE: the base is outside a one-object request, and it is exactly what
// a client offering `head` already holds — `reachable(head, [])` is the same
// closure `have_closure(head)` computes, so this is the set the External
// arm's `held.contains(base)` tests against.
assert_ne!(base, delta, "a delta is not its own base");
assert!(
all.iter().any(|o| o == &base),
"premise: the base must sit in head's closure, or the client does not hold it and this \
would be a genuine rebuild, not a thin copy"
);
let thin_caps = Caps {
thin: true,
ofs_delta: true,
};
// The receipt: RED before the fix was `recompressed: 1, copied: 0`.
let mut out = Vec::new();
let stats = store
.emit_pack(&[&delta], &[&head_raw], &thin_caps, &mut out)
.unwrap();
assert_eq!(stats.objects, 1, "exactly the one requested object is emitted");
assert_eq!(
stats.recompressed, 0,
"the base is on the client's side of the cut and it vouched for it, so the stored delta \
stream is copied verbatim — nothing here inflates in order to deflate again"
);
assert_eq!(stats.copied, 1, "the one entry is the thin copy");
// BYTE-CORRECTNESS 1 — it really is THIN. A repository that does not hold
// the base cannot resolve this pack; if git could, the "thin copy" would be
// a self-contained object and the receipt a fiction.
let no_base = tmp.path().join("no-base.git");
git(tmp.path(), &["init", "-q", "--bare", no_base.to_str().unwrap()]);
assert!(
feed(&no_base, &["index-pack", "--stdin", "--fix-thin"], &out).is_err(),
"a thin pack resolved against a repository that does NOT hold its base — then it was not \
thin, and copied==1 measured the wrong thing"
);
// BYTE-CORRECTNESS 2 — with the base present, git resolves the thin delta
// to the EXACT bytes the store holds. The base is delivered first as its own
// self-contained pack, which is the starting point a real fetching client is
// already in.
let with_base = tmp.path().join("with-base.git");
git(tmp.path(), &["init", "-q", "--bare", with_base.to_str().unwrap()]);
let mut base_pack = Vec::new();
store
.emit_pack(&[&base], &[], &Caps::modern(), &mut base_pack)
.unwrap();
feed(&with_base, &["index-pack", "--stdin"], &base_pack)
.expect("the base pack indexes into the scratch repo");
feed(&with_base, &["index-pack", "--stdin", "--fix-thin"], &out)
.expect("git resolves the thin delta against the base it now holds");
let hex = to_hex(&delta);
let content = Command::new("git")
.current_dir(&with_base)
.args(["cat-file", "-p", &hex])
.output()
.expect("git cat-file");
assert!(content.status.success(), "the thin-resolved object is unreadable");
let expected = store.read(&delta).unwrap().expect("the store has it").1;
assert_eq!(
content.stdout, expected,
"the thin delta resolved to different bytes than the store holds — a copy that lost the \
object is exactly the failure a byte count cannot see"
);
}
/// The base of one stored delta, read out of the pack the same way the emitter
/// reads it.
///
/// Written here rather than exported from the crate on purpose: a test that
/// asks the implementation *"what did you decide?"* proves nothing. This asks
/// `git verify-pack` instead, which is an independent reader.
fn base_of(store: &GixStore, oid: &[u8]) -> Vec<u8> {
let pack_dir = store.git_dir().join("objects").join("pack");
let idx = std::fs::read_dir(&pack_dir)
.unwrap()
.filter_map(|e| e.ok().map(|e| e.path()))
.find(|p| p.extension().is_some_and(|e| e == "idx"))
.expect("one pack index");
let listed = Command::new("git")
.args(["verify-pack", "-v", idx.to_str().unwrap()])
.output()
.expect("git verify-pack");
assert!(listed.status.success());
let hex = to_hex(oid);
let line = String::from_utf8_lossy(&listed.stdout)
.lines()
.find(|l| l.starts_with(&hex))
.expect("the object must be listed")
.to_string();
// `<oid> <type> <size> <in-pack> <offset> <depth> <base-oid>`
let base = line
.split_whitespace()
.last()
.expect("a delta line ends with its base oid");
assert_eq!(base.len(), 40, "expected a base oid, got {line:?}");
hex_to_bytes(base)
}
/// **`copied == objects`, `recompressed == 0`, and stock git accepts the whole
/// clone.** The `P-001` applied-output assertion.
///
/// A byte count and a wall clock cannot tell a pack-copy from a re-deflate:
/// both produce a pack that passes `index-pack --strict` and `fsck`, and the
/// slow one is only visible as a number nobody was recording. This records it.
#[test]
fn a_whole_clone_is_copied_not_recompressed_and_stock_git_accepts_it() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let set = store
.select(&[&head_raw], &[])
.unwrap()
.expect("this arm walks the graph, so it can always answer for a tip it holds");
let refs: Vec<Oid<'_>> = set.objects.iter().map(|o| o.as_slice()).collect();
let mut out = Vec::new();
let stats = store
.emit_pack(&refs, &[], &Caps::modern(), &mut out)
.unwrap();
assert_eq!(
stats.objects as usize,
set.objects.len(),
"a whole clone is already closed over its bases, so nothing should be added"
);
assert_eq!(
stats.copied, stats.objects,
"every entry was a byte-range copy"
);
assert_eq!(
stats.recompressed, 0,
"nothing here inflates in order to deflate again — that is the 18.4x fallback"
);
assert_eq!(
stats.bytes as usize,
out.len(),
"the receipt's byte count must be the bytes actually written, trailer included"
);
assert_eq!(
index_pack(tmp.path(), &out, true),
set.objects.len(),
"stock git must find exactly the objects we selected"
);
// And the emitted pack is smaller than the objects it carries inflated,
// which is only true because the stored deflate streams were copied.
let inflated: u64 = set
.objects
.iter()
.map(|o| store.size(o).unwrap().unwrap_or(0))
.sum();
// The receipt, as a bench row. Counts and byte totals only — nothing here
// moves with load, which is §6's rule. Visible with `--nocapture`.
eprintln!(
"RECEIPT emit_pack whole-clone: objects={} copied={} recompressed={} pack_bytes={} \
inflated_bytes={inflated}",
stats.objects, stats.copied, stats.recompressed, stats.bytes
);
assert!(
stats.bytes < inflated,
"a copied pack must be smaller than the sum of its inflated objects ({} vs {inflated})",
stats.bytes
);
}
/// The trailer is the **store's** hash width, not a hard-coded 20.
///
/// Checked by recomputing it rather than by counting bytes: a 20-byte tail is
/// also what a truncated 32-byte one looks like.
#[test]
fn the_emitted_trailer_is_the_stores_own_hash_over_the_body() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let mut out = Vec::new();
store
.emit_pack(&[&head_raw], &[], &Caps::modern(), &mut out)
.unwrap();
let width = store.hash_kind().len_in_bytes();
assert!(out.len() > 12 + width);
let (body, trailer) = out.split_at(out.len() - width);
let mut h = gix_hash::hasher(store.hash_kind());
h.update(body);
assert_eq!(
h.try_finalize().unwrap().as_slice(),
trailer,
"the trailer must be this store's hash over everything before it"
);
assert_eq!(&out[..4], b"PACK");
assert_eq!(&out[4..8], &2u32.to_be_bytes(), "pack version 2");
}
/// The delta spelling of every delta entry in `bytes`, in pack order.
///
/// **Not** `git verify-pack -v`, which was tried first and is the wrong
/// instrument: it reports each entry's *resolved* type (`blob`, `tree`, …) plus
/// a chain depth, and never the two words this is about — so the filter matched
/// nothing and the assertion passed a `[]` around as if it were evidence.
///
/// `gix_pack`'s streaming entry iterator reads the headers themselves. It is
/// gitoxide's parser rather than this crate's, so it is still not the author
/// grading its own homework, and unlike stock git it is willing to say which of
/// the two forms it found.
fn delta_kinds(bytes: &[u8], hash: gix_hash::Kind) -> Vec<&'static str> {
gix_pack::data::input::BytesToEntriesIter::new_from_header(
std::io::BufReader::new(bytes),
gix_pack::data::input::Mode::AsIs,
gix_pack::data::input::EntryDataMode::Ignore,
hash,
)
.expect("the emitted bytes are a pack with a v2 header")
.filter_map(|e| match e.expect("every entry parses").header {
gix_pack::data::entry::Header::OfsDelta { .. } => Some("ofs-delta"),
gix_pack::data::entry::Header::RefDelta { .. } => Some("ref-delta"),
_ => None,
})
.collect()
}
/// **A client that cannot parse `OFS_DELTA` is SERVED — in the other spelling.**
///
/// This test asserted the opposite until 2026-08-14: that the emission was
/// *refused*, because this backend copied stored entries verbatim and had no way
/// to re-point a delta's base. That refusal was honest about the gap and it was
/// still a server saying no to a client git says yes to, and `gunnar`'s own
/// `clone_no_ofs_delta` arm was red on it.
///
/// A delta entry is `[header][deflated delta stream]` and the two spellings
/// differ **only in the header** — a backwards distance against the base's raw
/// object id — so the answer is a header rewrite over an untouched payload. What
/// this test holds to is therefore three things at once, none of which is a
/// state round-trip:
///
/// * **stock git accepts both packs and finds the same objects in them**;
/// * **git reports no `ofs-delta` at all** in the pack served to the client that
/// cannot read one, and at least one in the pack served to the client that
/// can — the same selection, the same bytes of payload, two spellings;
/// * **nothing was recompressed** on either path, which is what makes this a
/// re-heading rather than a fallback to a whole-object writer.
///
/// The premise — that the selection really does contain a stored `OFS_DELTA` —
/// is asserted first, or none of it proves anything.
///
/// The subject is the **whole clone** rather than the lone delta it used to be.
/// Since 2026-08-14 an entry whose base is outside the request is rebuilt rather
/// than accompanied, so a request of one delta contains no delta at all and
/// there would be no spelling left to assert. A whole clone contains every base
/// by construction, which is the case this capability is actually about.
#[test]
fn a_client_that_cannot_read_ofs_delta_is_served_ref_deltas_instead() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
assert!(
all.iter().any(|raw| {
store
.get(raw)
.unwrap()
.is_some_and(|s| s.obj_type == ObjType::OfsDelta)
}),
"premise: the fixture must contain a stored OFS_DELTA"
);
let set: Vec<&[u8]> = all.iter().map(|v| v.as_slice()).collect();
let mut plain = Vec::new();
let plain_stats = store
.emit_pack(
&set,
&[],
&Caps {
thin: false,
ofs_delta: false,
},
&mut plain,
)
.expect("a client without ofs-delta is served, in ref-delta form");
let mut modern = Vec::new();
let modern_stats = store
.emit_pack(&set, &[], &Caps::modern(), &mut modern)
.expect("a modern client gets the pack");
// The same clone either way — the capability changes the encoding, never the
// contents.
assert_eq!(
plain_stats.objects, modern_stats.objects,
"the two capabilities produced different object counts, so they are not the same clone"
);
assert_eq!(
index_pack(tmp.path(), &plain, false),
index_pack(tmp.path(), &modern, false),
"stock git found different objects in the two packs"
);
// Neither path inflates and re-deflates: this is a header rewrite.
assert_eq!(plain_stats.recompressed, 0, "{plain_stats:?}");
assert_eq!(modern_stats.recompressed, 0, "{modern_stats:?}");
// And the spellings themselves, as stock git reports them.
let plain_kinds = delta_kinds(&plain, store.hash_kind());
let modern_kinds = delta_kinds(&modern, store.hash_kind());
assert!(
!plain_kinds.contains(&"ofs-delta"),
"a client that did not advertise ofs-delta was sent one anyway: {plain_kinds:?}"
);
assert!(
plain_kinds.contains(&"ref-delta"),
"the delta was not served as a ref-delta either, so the payload was not re-headed at \
all: {plain_kinds:?}"
);
assert!(
modern_kinds.contains(&"ofs-delta"),
"a modern client must still get the compact spelling: {modern_kinds:?}"
);
// Naming the base by id costs one hash width per delta entry against a 1-3
// byte varint, so the older spelling is strictly the larger one.
assert!(
plain.len() > modern.len(),
"the ref-delta pack ({}) is not larger than the ofs-delta one ({}), which means the \
base is not being named by id",
plain.len(),
modern.len()
);
}
// ── read / header / sizes ────────────────────────────────────────────────────
/// `read` is inflated and delta-resolved where `get` deliberately is not.
///
/// The subject is a stored **delta**, which is the only case where the two
/// answers can differ — an implementation that forwarded `get` would agree on
/// everything else and be wrong on exactly these.
#[test]
fn read_resolves_a_delta_that_get_hands_back_as_one() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
let delta = find_stored(&store, &all, true)
.expect("premise: the fixture must contain a stored delta")
.clone();
let stored = store.get(&delta).unwrap().unwrap();
assert!(
matches!(stored.obj_type, ObjType::OfsDelta | ObjType::RefDelta),
"premise: `get` must hand this back as a delta"
);
let (kind, bytes) = store.read(&delta).unwrap().expect("read must find it");
assert!(
!matches!(kind, ObjType::OfsDelta | ObjType::RefDelta),
"`read` returns the RESOLVED kind and can never be a delta, got {kind:?}"
);
assert_eq!(
bytes.len() as u64,
stored.uncompressed_size,
"the inflated length must be the post-resolution size"
);
// And header agrees with read, without paying for the payload.
let (hkind, hsize) = store.header(&delta).unwrap().unwrap();
assert_eq!(hkind, kind);
assert_eq!(hsize, bytes.len() as u64);
// Stock git is the oracle for the bytes themselves.
let by_git = Command::new("git")
.current_dir(store.git_dir())
.args(["cat-file", kind.as_str(), &to_hex(&delta)])
.output()
.expect("git cat-file");
assert!(by_git.status.success());
assert_eq!(
bytes, by_git.stdout,
"the inflated bytes must be what stock git says they are"
);
// A miss is a clean None on all three, not an error.
let absent = vec![0x5au8; 20];
assert!(store.read(&absent).unwrap().is_none());
assert!(store.header(&absent).unwrap().is_none());
assert_eq!(store.sizes(&[&absent]).unwrap(), vec![None]);
}
/// `sizes` is `header` in bulk and it stays **positional**.
///
/// The absent oid is deliberately in the middle: an implementation that filtered
/// misses out would shift every later answer by one and still return a plausible
/// count.
#[test]
fn sizes_is_header_in_bulk_and_stays_positional() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
assert!(all.len() >= 3);
let absent = vec![0x77u8; 20];
let probe: Vec<Oid<'_>> = vec![&all[0], &absent, &all[1], &all[2]];
let got = store.sizes(&probe).unwrap();
assert_eq!(got.len(), probe.len(), "one answer per question, in order");
assert_eq!(got[1], None, "the miss keeps its slot");
for i in [0usize, 2, 3] {
let expected = store.header(probe[i]).unwrap().map(|(_, s)| s);
assert_eq!(got[i], expected, "sizes[{i}] must equal header()'s size");
assert!(got[i].is_some());
}
assert_eq!(store.sizes(&[]).unwrap(), Vec::<Option<u64>>::new());
}
// ── HEAD ─────────────────────────────────────────────────────────────────────
/// **`HEAD` is not a row in `refs()`, and it is reachable through its own
/// accessor.** §3f Q1, decided.
///
/// This is exactly the two-arm disagreement a conformance harness exists to
/// catch: the suite never exercised `HEAD`, so nothing could see the arms
/// diverge. Both halves are asserted here — the absence *and* the accessor —
/// because either one alone passes for the wrong reason.
#[test]
fn head_is_not_a_ref_row_but_is_reachable_through_its_own_accessor() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let rows = store.refs().unwrap();
assert!(
rows.iter().all(|r| r.name != "HEAD"),
"`refs()` walks `refs/` and excludes pseudo-refs; HEAD must not be a row: {rows:?}"
);
assert_eq!(rows.len(), 1, "just the branch we pushed");
let h = store.head().unwrap().expect("a bare repo has a HEAD");
assert_eq!(h.name, "HEAD");
assert_eq!(
h.symref_target.as_deref(),
Some("refs/heads/main"),
"reported as STORED, the same shape `refs()` reports every other row in"
);
assert!(
h.oid.is_none(),
"a symbolic ref carries no oid of its own — resolving it here would make HEAD the one \
row shaped differently from its neighbours"
);
// Repointing it is visible through the accessor and still not a row.
store
.update_ref("refs/heads/other", None, Some(&hex_to_bytes(&head)))
.unwrap();
store.set_head("refs/heads/other").unwrap();
assert_eq!(
store.head().unwrap().unwrap().symref_target.as_deref(),
Some("refs/heads/other")
);
assert!(store.refs().unwrap().iter().all(|r| r.name != "HEAD"));
// And stock git agrees about where HEAD points.
assert_eq!(
git(store.git_dir(), &["symbolic-ref", "HEAD"]),
"refs/heads/other"
);
}
/// `set_head` takes a ref name or an oid, and **refuses anything else**.
///
/// A `HEAD` pointing at a name nothing resolves is an unclonable repository, so
/// guessing which of the two was meant is the expensive mistake. The accepted
/// grammar is matched to the Arrow arm deliberately.
#[test]
fn set_head_takes_a_ref_or_an_oid_and_refuses_anything_else() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
// A symbolic target is NOT checked for existence: `git init` points HEAD at
// a branch before it exists, and refusing that makes an empty repository
// unrepresentable.
store.set_head("refs/heads/not-yet-created").unwrap();
assert_eq!(
store.head().unwrap().unwrap().symref_target.as_deref(),
Some("refs/heads/not-yet-created")
);
// A detached HEAD, by oid.
store.set_head(&head).unwrap();
let h = store.head().unwrap().unwrap();
assert_eq!(h.oid.as_deref(), Some(hex_to_bytes(&head).as_slice()));
assert!(h.symref_target.is_none());
assert_eq!(git(store.git_dir(), &["rev-parse", "HEAD"]), head);
for bad in ["main", "", "refs", &head[..39], &format!("{head}00"), "zz"] {
assert!(
store.set_head(bad).is_err(),
"{bad:?} is neither a `refs/` name nor a full hex oid and must be refused"
);
}
}
// ── select ───────────────────────────────────────────────────────────────────
/// `select` answers `want` minus `have`, with the two derived facts, and
/// **declines** rather than under-sending.
///
/// The `Ok(None)` hatch is the load-bearing part: `reachable` treats a `want` it
/// cannot find as contributing itself and nothing else, which is safe for a `gc`
/// and a silent under-send here — a clone that exits zero with one object per
/// branch.
#[test]
fn select_declines_a_want_it_does_not_hold_rather_than_under_sending() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let full = store.select(&[&head_raw], &[]).unwrap().unwrap();
assert_eq!(
full.objects,
store.reachable(&[&head_raw], &[]).unwrap(),
"`select`'s object set is `reachable`'s, and they must not drift"
);
assert!(!full.commits.is_empty(), "a history has commits");
assert!(
full.commits.iter().all(|c| full.objects.contains(c)),
"every commit reported must be one of the objects being sent"
);
assert_eq!(
full.commits.len(),
git(store.git_dir(), &["rev-list", "--count", &head])
.parse::<usize>()
.unwrap(),
"the commit count must be `git rev-list --count`"
);
assert!(
full.client_has.is_empty(),
"a clone: the client held nothing"
);
// A fetch the client is already up to date for.
let none = store.select(&[&head_raw], &[&head_raw]).unwrap().unwrap();
assert!(none.objects.is_empty(), "want minus itself is empty");
assert_eq!(
none.client_has.iter().map(<[u8]>::to_vec).collect::<Vec<_>>(),
full.objects,
"`client_has` is the closure of `have` — what the receiver held BEFORE this transfer"
);
assert!(
!none
.client_has
.iter()
.any(|h| none.objects.iter().any(|o| o == h)),
"`client_has` must never name an object this transfer carries"
);
// The hatch.
let absent = vec![0x99u8; 20];
assert!(
store.select(&[&absent], &[]).unwrap().is_none(),
"a want this store does not hold declines the whole request; answering it would send \
one object and exit zero"
);
// …but an unknown `have` is the ordinary case and must NOT decline: it is
// the client describing its own history.
assert!(
store.select(&[&head_raw], &[&absent]).unwrap().is_some(),
"a `have` this store does not hold is a negotiation, not a refusal"
);
}
/// The two halves are separate: what `select` walks, `emit_pack` sends verbatim.
#[test]
fn a_set_select_walked_is_emitted_unchanged_by_emit_pack() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let set = store.select(&[&head_raw], &[]).unwrap().unwrap();
let refs: Vec<Oid<'_>> = set.objects.iter().map(|o| o.as_slice()).collect();
let mut out = Vec::new();
let stats = store
.emit_pack(&refs, &[], &Caps::modern(), &mut out)
.unwrap();
assert_eq!(stats.objects as usize, set.objects.len());
// Reversed, because `ReachSet` says order is not part of the contract and
// the emitter orders its own output.
let mut reversed = refs.clone();
reversed.reverse();
let mut out2 = Vec::new();
let stats2 = store
.emit_pack(&reversed, &[], &Caps::modern(), &mut out2)
.unwrap();
assert_eq!(
(stats2.objects, stats2.copied, stats2.recompressed),
(stats.objects, stats.copied, stats.recompressed),
"the receipt must not depend on the order the caller listed the set in"
);
assert_eq!(
index_pack(tmp.path(), &out2, true),
set.objects.len(),
"and the reversed emission is just as valid"
);
}
// ── put_refs_cas ─────────────────────────────────────────────────────────────
/// **Every edit or none, and the loser is named — typed, never by message.**
#[test]
fn an_atomic_batch_applies_every_edit_or_none_and_names_the_loser() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
// Two creates that must both land.
store
.put_refs_cas(&[
RefCas {
name: "refs/heads/a".into(),
old: None,
new: Some(&head_raw),
peeled: None,
},
RefCas {
name: "refs/heads/b".into(),
old: None,
new: Some(&head_raw),
peeled: None,
},
])
.unwrap();
let names: Vec<String> = store.refs().unwrap().into_iter().map(|r| r.name).collect();
assert!(names.contains(&"refs/heads/a".to_string()));
assert!(names.contains(&"refs/heads/b".to_string()));
// A batch whose SECOND edit loses must apply NOTHING — including its first,
// which would otherwise have succeeded.
let wrong = vec![0x22u8; 20];
let err = store
.put_refs_cas(&[
RefCas {
name: "refs/heads/c".into(),
old: None,
new: Some(&head_raw),
peeled: None,
},
RefCas {
name: "refs/heads/a".into(),
old: Some(&wrong),
new: Some(&head_raw),
peeled: None,
},
])
.expect_err("the second edit's expectation does not hold");
assert!(
store
.refs()
.unwrap()
.iter()
.all(|r| r.name != "refs/heads/c"),
"an atomic batch that failed must not have applied its first edit"
);
let rejection = RefRejection::of(&err)
.expect("the rejection must be TYPED — receive-pack must never sniff a message");
assert!(rejection.is_cas_failure(), "a lost race, not a fault");
assert!(!rejection.is_lock_contention());
assert_eq!(rejection.name(), "refs/heads/a", "it must name the loser");
match rejection {
RefRejection::Cas {
expected, actual, ..
} => {
assert_eq!(
expected.to_string(),
to_hex(&wrong),
"the caller's claim, echoed back"
);
assert_eq!(
actual.to_string(),
head,
"what was ACTUALLY there — the thing a client cannot otherwise learn"
);
}
other => panic!("expected a Cas rejection, got {other:?}"),
}
}
/// **`S-023` in its exact shape**, through the batch primitive.
///
/// gix short-circuits an edit whose new value equals the value the reference
/// already holds: it rewrites `expected` to `MustExistAndMatch(current)` and
/// never evaluates the one the caller wrote. For a must-not-exist create that
/// turns a failure into a **silent success**, so "exactly one creator wins" —
/// the property receive-pack arbitrates two racing pushes with — was not true.
///
/// Seen RED by deleting the `precheck_cas` call from `put_refs_cas`.
#[test]
fn s023_a_must_not_exist_create_fails_even_when_the_value_is_unchanged() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let create = |name: &str| {
store.put_refs_cas(&[RefCas {
name: name.into(),
old: None,
new: Some(&head_raw),
peeled: None,
}])
};
create("refs/heads/once").unwrap();
let err = create("refs/heads/once")
.expect_err("a create over an existing ref must fail even when the value is IDENTICAL");
let rejection = RefRejection::of(&err).expect("and it must be typed");
assert!(rejection.is_cas_failure());
assert_eq!(rejection.name(), "refs/heads/once");
match rejection {
RefRejection::Cas {
expected, actual, ..
} => {
assert_eq!(
expected.to_string(),
"nothing",
"`old: None` is *must not exist*, and that is what is echoed back"
);
assert_eq!(actual.to_string(), head);
}
other => panic!("expected a Cas rejection, got {other:?}"),
}
}
/// An empty batch is a no-op that succeeds.
///
/// A deletions-free push with nothing to apply calls this, and refusing it would
/// make every call site special-case the empty case.
#[test]
fn an_empty_atomic_batch_is_a_no_op_rather_than_an_error() {
let tmp = tempfile::tempdir().unwrap();
let bare = tmp.path().join("bare.git");
let store = GixStore::open_or_init(&bare, gix_hash::Kind::Sha1).unwrap();
store.put_refs_cas(&[]).expect("an empty batch succeeds");
assert!(store.refs().unwrap().is_empty());
}
/// A delete is `new: None`, and it is a compare-and-swap like any other.
#[test]
fn an_atomic_delete_checks_its_expectation_too() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let wrong = vec![0x33u8; 20];
let err = store
.put_refs_cas(&[RefCas {
name: "refs/heads/main".into(),
old: Some(&wrong),
new: None,
peeled: None,
}])
.expect_err("deleting against the wrong old value must fail");
assert!(RefRejection::of(&err).unwrap().is_cas_failure());
assert_eq!(store.refs().unwrap().len(), 1, "and it must still be there");
store
.put_refs_cas(&[RefCas {
name: "refs/heads/main".into(),
old: Some(&head_raw),
new: None,
peeled: None,
}])
.expect("the right old value deletes");
assert!(store.refs().unwrap().is_empty());
}
/// **The gatling rebuild fan-out emits the byte-identical pack the serial pass
/// does** — and the same guard, run against a fan that dropped one unit, fails.
///
/// `GixStore::rebuild_bodies` divides the one thing on the serving path that
/// costs CPU: an entry whose stored delta names a base the request does not
/// carry is inflated, resolved through its chain and deflated whole. PROFILED on
/// oden 2026-08-14 over a 12 216-object clone, that inflate + deflate was
/// **36 % of the request's CPU** and it ran one object at a time inside
/// `plan_emission`'s loop.
///
/// # Why the subject is every delta and not one
///
/// The set is **every stored delta in the fixture and none of their bases**, so
/// every entry in it is a rebuild. One rebuild would let a fan-out that
/// serialised itself, or that raced on a shared buffer, pass — there would be
/// nothing to interleave.
///
/// # Why it goes through `emit_oids_with` and not the front door
///
/// The shipped threshold is 64 rebuilds (`FAN_OUT_AT`) and this fixture has a
/// handful, so a guard calling `emit_pack` would take the serial arm on every
/// run and the gatling arm on none — green for ever, and blind. Naming the
/// worker count is the whole point: `Some(1)` is the calling thread, `Some(4)`
/// is the fan, and the two must produce the same bytes.
///
/// # Seen RED first (LAW 2), 2026-08-14
///
/// With `out.pop()` inserted at the end of `rebuild_bodies_with` — a fan that
/// dropped its last unit — this test fails on the `PendingRebuild` refusal
/// rather than on a mismatch, which is stronger: the emitter names the entry
/// instead of shipping an empty object under a real oid.
#[test]
fn the_fan_out_rebuilds_the_identical_pack_the_serial_pass_does() {
let tmp = tempfile::tempdir().unwrap();
let (store, head) = store_with_history(tmp.path());
let head_raw = hex_to_bytes(&head);
let all = store.reachable(&[&head_raw], &[]).unwrap();
// Every stored delta, and deliberately none of their bases: each one is
// therefore a rebuild rather than a copy.
let deltas: Vec<Vec<u8>> = all
.iter()
.filter(|raw| {
store
.get(raw)
.unwrap()
.is_some_and(|s| matches!(s.obj_type, ObjType::OfsDelta | ObjType::RefDelta))
})
.cloned()
.collect();
assert!(
deltas.len() >= 3,
"premise: the fixture must hold at least three stored deltas, or one worker and four \
have nothing to disagree about and this test cannot fail — found {}",
deltas.len()
);
let refs: Vec<&[u8]> = deltas.iter().map(Vec::as_slice).collect();
let mut serial = Vec::new();
let s_stats = store
.emit_oids_with(&refs, &Caps::modern(), &mut serial, Some(1))
.expect("the serial arm emits");
let mut fanned = Vec::new();
let f_stats = store
.emit_oids_with(&refs, &Caps::modern(), &mut fanned, Some(4))
.expect("the fanned arm emits");
// The premise again, this time measured off what happened: if nothing was
// rebuilt then the fan-out had no units and both arms are the copy path.
assert_eq!(
s_stats.recompressed,
deltas.len() as u64,
"premise: every entry in this set must have been REBUILT, or the fan-out under test \
never ran — receipt says {s_stats:?}"
);
assert_eq!(
s_stats.recompressed, f_stats.recompressed,
"one worker and four disagree about how many entries were rebuilt"
);
assert_eq!(
s_stats.copied, f_stats.copied,
"one worker and four disagree about how many entries were copied"
);
assert_eq!(
serial.len(),
fanned.len(),
"the fanned pack is a different LENGTH from the serial one ({} vs {}) — a dropped or \
duplicated rebuild",
serial.len(),
fanned.len()
);
assert_eq!(
serial, fanned,
"the fanned pack differs from the serial one byte for byte; the worker count decides \
who does a deflate and must decide nothing else"
);
// And the bytes are a pack stock git accepts, not merely two copies of the
// same wrong answer. Format-only: the set is deliberately partial.
assert_eq!(index_pack(tmp.path(), &fanned, false), deltas.len());
}