Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

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}"
    );
}