crates/storage-git-gix/parked-gix-2/gunnar-wire-gix/tests/second_sweep.rs
//! **What the first sweep's lenses could not see.** Every case here is a diff
//! against stock `git` 2.53.0 rather than against gunnar's own understanding,
//! and every one of them runs the request through the *real* client.
//!
//! Two families, and both are the same defect class the `caps` module was
//! written to prevent — a promise and an implementation that disagree — caught
//! on the two surfaces that module does not cover:
//!
//! * **protocol v0's capability line is hand-written**, not derived from a
//! table, and it under-promises: gunnar serves `want`s for unadvertised but
//! reachable objects and never says so, which a stock client refuses on its
//! own side before a byte reaches the server.
//! * **`deepen-not` takes a ref name**, and gunnar required a fully-qualified
//! one where git expands a short one, discarding anything it could not parse
//! without telling the client.
//!
//! Each family has a *stock-git control arm* asserting what git's own server
//! does with the identical request, so a case cannot pass by both halves being
//! wrong the same way.
#[path = "git_harness/mod.rs"]
mod git_harness;
use git_harness::*;
use gix_hash::Kind;
use gunnar_wire::ProtocolVersion;
fn main() {
serve_if_asked();
let cases: &[(&str, fn())] = &[
(
"CONTROL_stock_git_v0_serves_a_reachable_non_tip_want",
control_stock_git_v0_unadvertised_want,
),
("v0_fetch_of_a_reachable_non_tip_oid", v0_fetch_non_tip_oid),
("v2_fetch_of_a_reachable_non_tip_oid", v2_fetch_non_tip_oid),
(
"v0_partial_clone_blob_none_checks_out",
v0_partial_clone_checks_out,
),
(
"CONTROL_stock_git_expands_a_short_shallow_exclude",
control_stock_git_short_shallow_exclude,
),
(
"deepen_not_short_name_matches_stock_git",
deepen_not_short_name,
),
(
"deepen_not_unknown_ref_is_refused_by_name",
deepen_not_unknown_ref,
),
];
run_cases("second_sweep", cases, 7);
}
// ── family 1: the v0 want policy is served but never advertised ─────────────
/// The oid of a commit that is **not** any reference's tip and not a peeled
/// tag, so no advertisement carries it — but which is reachable from `main`,
/// so gunnar's default [`WantPolicy::ReachableFromRefs`] serves it.
fn non_tip_oid(scene: &Scene) -> String {
let id = scene
.origin_git()
.args(["rev-parse", "refs/heads/main~2"])
.ok();
let advertised = scene.origin_git().args(["show-ref"]).ok();
assert!(
!advertised.contains(&id),
"main~2 is in the advertisement, so this case would prove nothing:\n{advertised}"
);
id
}
/// A fresh empty repository to fetch into.
fn empty_repo(scene: &Scene, name: &str) -> std::path::PathBuf {
let path = scene.root.join(name);
Git::new()
.cwd(&scene.root)
.args(["init", "-q", "-b", "main"])
.arg(path.to_str().unwrap())
.ok();
path
}
/// **The contract, and it is a v0-only contract.**
///
/// `protocol.version=0` is pinned on both arms, and that pin is the whole
/// point rather than tidiness: `upload_pack_v2` sets `ALLOW_ANY_SHA1`
/// unconditionally, so over v2 stock git serves this request whatever
/// `uploadpack.allow*SHA1InWant` says and the arm below could not go red. This
/// case was written without the pin first and passed its "allowed" half and
/// failed its "denied" half for exactly that reason (oden 2026-08-05) — which
/// is the same shape as a control that never fails.
///
/// With v0 pinned, the two arms bracket the capability: off, git's **client**
/// refuses before sending, naming the reason; on, the request is served.
///
/// The setting goes into the **origin's own config**, not onto the client's
/// `-c` line, and that is not a style choice either: `GIT_CONFIG_PARAMETERS`
/// does not reach the `upload-pack` a `file://` fetch spawns, so a `-c
/// uploadpack.allowReachableSHA1InWant=true` client is refused exactly as an
/// unset one is (MEASURED oden 2026-08-05). A control written that way is red
/// for the wrong reason, which is only one step better than green for the
/// wrong reason.
fn control_stock_git_v0_unadvertised_want() {
let scene = Scene::new(Kind::Sha1);
let id = non_tip_oid(&scene);
// The default. git's own client refuses, and says why.
let denied = empty_repo(&scene, "control-denied");
let (ok, _, stderr) = git_in(&denied)
.args(["-c", "protocol.version=0", "fetch", "-q"])
.arg(scene.local_url())
.arg(&id)
.run();
assert!(
!ok,
"stock git accepted a want it had advertised no capability for"
);
assert!(
stderr.contains("unadvertised object"),
"the refusal did not name the reason; git said:\n{stderr}"
);
// The same request, once the server says the capability out loud. Nothing
// about the request changed; only the advertisement did.
scene
.origin_git()
.args(["config", "uploadpack.allowReachableSHA1InWant", "true"])
.ok();
let allowed = empty_repo(&scene, "control-allowed");
let (ok, _, stderr) = git_in(&allowed)
.args(["-c", "protocol.version=0", "fetch", "-q"])
.arg(scene.local_url())
.arg(&id)
.run();
assert!(ok, "stock git refused a reachable non-tip want:\n{stderr}");
// And the capability gunnar now emits is the one git emits, spelled the
// same. A capability nobody matches is the same as no capability.
let trace = git_in(&scene.root)
.args(["-c", "protocol.version=0", "ls-remote"])
.arg(scene.local_url())
.env("GIT_TRACE_PACKET", "1")
.run()
.2;
assert!(
trace.contains("allow-reachable-sha1-in-want"),
"stock git's v0 advertisement does not carry the string gunnar copies:\n{trace}"
);
}
/// gunnar serves reachable non-tip `want`s by default. Over v0 a stock client
/// will not even ask unless the capability is advertised, so this is a request
/// the server would have answered and never sees.
fn v0_fetch_non_tip_oid() {
let scene = Scene::new(Kind::Sha1);
let id = non_tip_oid(&scene);
let into = empty_repo(&scene, "v0-fetch");
let (ok, _, stderr) = scene
.client(ProtocolVersion::V0)
.cwd(&into)
.args(["fetch", "-q"])
.arg(scene.url())
.arg(&id)
.run();
assert!(
ok,
"gunnar serves this want over v2 and refuses to be asked for it over \
v0, because the v0 advertisement does not carry the capability the \
client gates on. git said:\n{stderr}"
);
assert_eq!(
git_in(&into).args(["rev-parse", "FETCH_HEAD"]).ok(),
id,
"the fetch reported success without landing the object"
);
}
/// The same request over v2, where git has no such capability to gate on. This
/// is the arm that was green while the one above was red, and it is here so
/// the asymmetry is visible in one run rather than inferred.
fn v2_fetch_non_tip_oid() {
let scene = Scene::new(Kind::Sha1);
let id = non_tip_oid(&scene);
let into = empty_repo(&scene, "v2-fetch");
let (ok, _, stderr) = scene
.client(ProtocolVersion::V2)
.cwd(&into)
.args(["fetch", "-q"])
.arg(scene.url())
.arg(&id)
.run();
assert!(
ok,
"gunnar refused a reachable non-tip want over v2:\n{stderr}"
);
assert_eq!(
git_in(&into).args(["rev-parse", "FETCH_HEAD"]).ok(),
id,
"the fetch reported success without landing the object"
);
}
/// The consequence that is not an edge case. A `blob:none` clone has to fetch
/// its blobs back by bare oid, and those oids are in no advertisement — so the
/// missing capability turns every checkout in a partial clone into a failure,
/// on the protocol version git falls back to whenever `GIT_PROTOCOL` does not
/// reach the server.
fn v0_partial_clone_checks_out() {
let scene = Scene::new(Kind::Sha1);
let target = scene.root.join("v0-partial");
let (ok, _, stderr) = scene
.client(ProtocolVersion::V0)
.args(["clone", "-q", "--filter=blob:none"])
.arg(scene.url())
.arg(target.to_str().unwrap())
.run();
assert!(
ok,
"a blob:none clone over v0 could not fetch its blobs back:\n{stderr}"
);
// The clone succeeding is not the claim; the lazy fetch working is. Read a
// blob that the filter deliberately left out of the transfer.
let (ok, content, stderr) = git_in(&target).args(["cat-file", "-p", "HEAD:d.txt"]).run();
assert!(ok, "the lazy fetch of a filtered blob failed:\n{stderr}");
assert_eq!(content, "four", "the lazy fetch returned the wrong content");
}
// ── family 2: deepen-not takes a ref name, and git expands short ones ───────
/// Which commits arrived, and where the client's `.git/shallow` says the
/// history is cut. The boundary is the discriminating fact — see
/// `Scene::dated`.
fn deepening(dir: &std::path::Path) -> (usize, Vec<String>) {
let commits: usize = git_in(dir)
.args(["rev-list", "--count", "HEAD"])
.ok()
.parse()
.unwrap();
let mut shallow: Vec<String> = std::fs::read_to_string(dir.join(".git").join("shallow"))
.unwrap_or_default()
.lines()
.map(str::to_owned)
.collect();
shallow.sort();
(commits, shallow)
}
/// **The contract.** `--shallow-exclude=side` — the short name, which is what
/// `Scene::dated`'s own table is written in and what a person types — is
/// expanded by `git-upload-pack` to `refs/heads/side` and cuts at `B`.
fn control_stock_git_short_shallow_exclude() {
let scene = Scene::dated(Kind::Sha1);
let short = scene.root.join("control-short");
let long = scene.root.join("control-long");
Git::new()
.cwd(&scene.root)
.args(["clone", "-q", "--shallow-exclude=side"])
.arg(scene.local_url())
.arg(short.to_str().unwrap())
.ok();
Git::new()
.cwd(&scene.root)
.args(["clone", "-q", "--shallow-exclude=refs/heads/side"])
.arg(scene.local_url())
.arg(long.to_str().unwrap())
.ok();
assert_eq!(
deepening(&short),
deepening(&long),
"stock git does not treat the short and long spellings alike, so this \
family is diffed against the wrong contract"
);
let (commits, shallow) = deepening(&short);
assert_eq!(commits, 2, "--shallow-exclude=side lands D and M");
assert_eq!(shallow.len(), 2, "it cuts at B and M");
}
/// gunnar took `deepen-not` as a fully-qualified name only, and silently
/// discarded anything else — so the short spelling asked for an exclusion and
/// got the whole history, with no error on either end.
fn deepen_not_short_name() {
let scene = Scene::dated(Kind::Sha1);
let mine = scene.root.join("gunnar-short");
let stock = scene.root.join("stock-short");
scene
.client(ProtocolVersion::V2)
.args(["clone", "-q", "--shallow-exclude=side"])
.arg(scene.url())
.arg(mine.to_str().unwrap())
.ok();
Git::new()
.cwd(&scene.root)
.args(["clone", "-q", "--shallow-exclude=side"])
.arg(scene.local_url())
.arg(stock.to_str().unwrap())
.ok();
assert_eq!(
deepening(&mine),
deepening(&stock),
"gunnar and stock git disagree about `--shallow-exclude=side`"
);
}
/// A `deepen-not` naming nothing is a request the server cannot honour, and
/// git says so by name (`ambiguous deepen-not`). Discarding it produces a
/// clone the client believes is excluded and is not — the silent half of the
/// same bug.
fn deepen_not_unknown_ref() {
let scene = Scene::dated(Kind::Sha1);
let (stock_ok, _, stock_err) = Git::new()
.cwd(&scene.root)
.args(["clone", "-q", "--shallow-exclude=no-such-ref"])
.arg(scene.local_url())
.arg(scene.root.join("stock-unknown").to_str().unwrap())
.run();
assert!(
!stock_ok,
"stock git accepted a deepen-not naming nothing, so there is no \
contract here to hold gunnar to"
);
assert!(
stock_err.contains("deepen-not"),
"stock git's refusal did not name deepen-not:\n{stock_err}"
);
let (ok, _, stderr) = scene
.client(ProtocolVersion::V2)
.args(["clone", "-q", "--shallow-exclude=no-such-ref"])
.arg(scene.url())
.arg(scene.root.join("gunnar-unknown").to_str().unwrap())
.run();
assert!(
!ok,
"gunnar accepted a `deepen-not` it could not resolve and served the \
history the client asked to exclude"
);
assert!(
stderr.contains("deepen-not") || stderr.contains("no-such-ref"),
"the refusal reached the client without naming its subject:\n{stderr}"
);
}