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

nordisk/gunnar

crates/gunnar-wire/tests/advertise_peel_cost.rs

//! **The ref advertisement peels a tag zero times once its peel is stored.**
//!
//! The advertisement (v2 `ls-refs`, and the opening round of every clone, fetch
//! and replication) has to report each annotated tag's peeled target on a
//! `peeled:` suffix. It can get that target two ways, and the difference is the
//! whole of the finding:
//!
//! * **stored** — the ref row carries a peeled column, filled once at ingest
//!   (`receive_pack::edit_for` → `RefCas::peeled` → the store), the way git's
//!   `packed-refs` keeps a `^peeled` line. `advertise_from` reads it straight
//!   out of the row: **zero object reads**.
//! * **re-derived** — the row has no peel (an older push, a backend without the
//!   column), so `advertise_from` falls back to `graph.peel`, which reads the
//!   tag object to learn its target: **one object read per tag**, the floor
//!   measured after the read-halving fix.
//!
//! Both must advertise the **byte-identical** `peeled:` oid — the stored one
//! is exactly what the fallback would derive. So the guard is an op-count, not
//! a wall clock (measure, never assert): a tag written the new way costs 0
//! reads, one written the old way costs 1, and the advertised bytes are the
//! same either way. A test that only checked the bytes would pass the unstored
//! code, which is why the read count is the assertion.

mod common;

use std::io::Cursor;

use gix_hash::Kind;
use gunnar_store::git_storage_trait::{GitOps, RefUpdate};
use gunnar_wire::receive_pack::AllowAll;
use gunnar_wire::{ReceivePack, RefStatus, Session, UploadPack};

use common::{empty_pack, push_bytes, validating_ingest, write_object, Fixture};

/// A v2 `command=ls-refs` asking for `peel` over `refs/` — the exact shape
/// `git ls-remote` and every clone's opening round send.
fn ls_refs_bytes(kind: Kind) -> Vec<u8> {
    let fmt = match kind {
        Kind::Sha1 => "sha1",
        _ => "sha256",
    };
    let pkt = |payload: &str| {
        let mut out = format!("{:04x}", payload.len() + 4).into_bytes();
        out.extend_from_slice(payload.as_bytes());
        out
    };
    let mut req = Vec::new();
    req.extend(pkt("command=ls-refs\n"));
    req.extend(pkt(&format!("object-format={fmt}\n")));
    req.extend_from_slice(b"0001");
    req.extend(pkt("peel\n"));
    req.extend(pkt("ref-prefix refs/\n"));
    req.extend_from_slice(b"0000");
    req
}

/// Serve one `ls-refs` against `fixture`, returning the object reads it charged
/// the store and the advertisement text. The store is opened once (untimed and
/// uncounted) by `Fixture::build`; only the advertisement is measured, so the
/// number is the re-derivation and nothing else.
fn advertise(fixture: &Fixture) -> (usize, String) {
    let upload = UploadPack::new(&fixture.git, fixture.kind);
    let req = ls_refs_bytes(fixture.kind);
    fixture.git.reset();
    let mut input = Cursor::new(req);
    let mut out = Vec::new();
    upload
        .serve_v2_session(Session::StatelessRpc, &mut input, &mut out)
        .expect("the ls-refs advertisement completes");
    (
        fixture.git.read_count(),
        String::from_utf8(out).expect("the advertisement is text"),
    )
}

/// The peeled oid the advertisement reported for `name`, from its `peeled:`
/// suffix — `None` if the ref was advertised without one.
fn advertised_peel(advertisement: &str, name: &str) -> Option<String> {
    advertisement
        .lines()
        .find(|line| line.contains(&format!(" {name}")) || line.contains(&format!("{name} ")))
        .and_then(|line| {
            line.split_whitespace()
                .find_map(|word| word.strip_prefix("peeled:"))
                .map(str::to_owned)
        })
}

/// **The read-side red→green, in one test: old-way tag = 1 read, new-way tag =
/// 0 reads, identical `peeled:` bytes.**
///
/// `Fixture::build` installs `refs/tags/v1` as an annotated tag over `merge`
/// with **no** stored peel — the old way, and the backward-compatible one: a
/// ref row that predates this fix. Advertising it must fall back to `graph.peel`
/// and cost exactly one object read (the tag body). Then the same ref is
/// re-installed **with** its peel stored, and advertising it must cost zero —
/// while reporting the same commit on the wire, to the byte.
#[test]
fn a_stored_peel_advertises_a_tag_with_zero_object_reads() {
    for kind in [Kind::Sha1, Kind::Sha256] {
        let fixture = Fixture::build(kind);

        // ── OLD WAY: the row carries no peel (backward compat). One read. ──
        let (old_reads, old_out) = advertise(&fixture);
        assert_eq!(
            old_reads, 1,
            "{kind:?}: an unstored annotated tag must re-peel with exactly one object read \
             (the floor); got {old_reads}"
        );
        let old_peel = advertised_peel(&old_out, "refs/tags/v1")
            .expect("the tag was advertised without a peeled: line");
        assert_eq!(
            old_peel,
            fixture.merge.to_string(),
            "{kind:?}: the fallback peeled to the wrong commit"
        );

        // ── NEW WAY: store the peel in the row, exactly as ingest now does. ──
        fixture
            .git
            .put_refs(&[RefUpdate::set("refs/tags/v1", fixture.tag.to_string())
                .with_peeled(fixture.merge.to_string())])
            .expect("re-install the tag with its peel stored");

        let (new_reads, new_out) = advertise(&fixture);
        assert_eq!(
            new_reads, 0,
            "{kind:?}: a tag whose peel is stored must advertise with zero object reads; \
             got {new_reads}"
        );
        let new_peel = advertised_peel(&new_out, "refs/tags/v1")
            .expect("the tag was advertised without a peeled: line after storing its peel");

        // The whole point: 0 reads and 1 read produce the SAME wire answer.
        assert_eq!(
            new_peel, old_peel,
            "{kind:?}: storing the peel changed the advertised peeled oid — the wire moved, \
             which it must not"
        );
    }
}

/// A tag object body naming `target` as a commit.
fn tag_body(target: &gix_hash::oid, name: &str) -> Vec<u8> {
    format!(
        "object {}\ntype commit\ntag {name}\n\
         tagger Gunnar Test <gunnar@example.invalid> 1700000006 +0000\n\nrelease\n",
        target.to_hex()
    )
    .into_bytes()
}

/// Drive one non-atomic push and hand back the statuses. Mirrors
/// `force_update.rs::push` — the ordinary `git push` path, no `--atomic`.
fn push(fixture: &Fixture, commands: &[(&str, &str, &str)], pack: &[u8]) -> Vec<RefStatus> {
    let receive = ReceivePack::new(&fixture.git, fixture.kind);
    let input = push_bytes(commands, "report-status object-format=sha1", pack);
    let mut input = Cursor::new(input);
    let mut out = Vec::new();
    receive
        .serve(
            &mut input,
            &mut out,
            &validating_ingest(Kind::Sha1),
            &AllowAll,
        )
        .expect("the session completes")
        .expect("a push outcome")
        .statuses
}

/// **End-to-end: an annotated tag pushed the ordinary way stores its peel, so
/// the next advertisement re-derives nothing.**
///
/// This is the write half filed as the cross-repo follow-up, proven
/// through the real receive path rather than by hand-writing the ref row. A new
/// annotated tag `refs/tags/v2` over `four` is pushed (non-atomic — the default
/// `git push` shape); afterwards the row must carry `four` as its peel and the
/// advertisement must cost **zero** reads for it.
///
/// Seen RED before `edit_for` peeled: the push succeeded, the row's peel was
/// `None`, and the advertisement charged one read to re-derive it.
#[test]
fn a_pushed_annotated_tag_stores_its_peel_and_advertises_free() {
    let fixture = Fixture::build(Kind::Sha1);
    // A tag object over `four`, already in the store so the push only validates.
    let tag = write_object(
        &fixture.git,
        Kind::Sha1,
        gix_object::Kind::Tag,
        &tag_body(&fixture.four, "v2"),
    );

    let zero = "0".repeat(40);
    let statuses = push(
        &fixture,
        &[(&zero, &tag.to_string(), "refs/tags/v2")],
        &empty_pack(Kind::Sha1),
    );
    assert_eq!(statuses[0], RefStatus::Ok, "the tag push was refused");

    // The row now carries the peel, filled at ingest.
    let row_peel = fixture
        .git
        .refs()
        .expect("list refs")
        .into_iter()
        .find(|r| r.name == "refs/tags/v2")
        .and_then(|r| r.peeled)
        .map(|raw| gix_hash::ObjectId::try_from(raw.as_slice()).expect("a well-sized oid"));
    assert_eq!(
        row_peel,
        Some(fixture.four),
        "the push did not store the tag's peeled target"
    );

    // And so the advertisement re-derives nothing for it. `refs/tags/v1` (the
    // fixture's other, unstored tag) still costs its one fallback read, so the
    // whole advertisement costs exactly that one — never two.
    let (reads, out) = advertise(&fixture);
    assert_eq!(
        advertised_peel(&out, "refs/tags/v2").as_deref(),
        Some(fixture.four.to_string().as_str()),
        "the pushed tag advertised the wrong peel"
    );
    assert_eq!(
        reads, 1,
        "with v2's peel stored, only v1 (unstored) should re-peel; the whole advertisement \
         charged {reads} reads, so a stored peel was still being re-derived"
    );
}

/// **A lightweight tag stores no peel, and that is correct.**
///
/// A ref that points straight at a commit has nothing to peel — git writes no
/// `^peeled` for it — so `edit_for` must leave the row's peel `None`, and the
/// advertisement must report no `peeled:` for it while charging no read.
#[test]
fn a_lightweight_tag_stores_no_peel() {
    let fixture = Fixture::build(Kind::Sha1);
    let zero = "0".repeat(40);

    let statuses = push(
        &fixture,
        // Straight at `four`, a commit: no tag object, nothing to peel.
        &[(&zero, &fixture.four.to_string(), "refs/tags/lw")],
        &empty_pack(Kind::Sha1),
    );
    assert_eq!(
        statuses[0],
        RefStatus::Ok,
        "the lightweight tag was refused"
    );

    let row = fixture
        .git
        .refs()
        .expect("list refs")
        .into_iter()
        .find(|r| r.name == "refs/tags/lw")
        .expect("the lightweight tag row");
    assert_eq!(
        row.peeled, None,
        "a lightweight tag invented a peeled target it has no business having"
    );

    let (_reads, out) = advertise(&fixture);
    assert_eq!(
        advertised_peel(&out, "refs/tags/lw"),
        None,
        "a lightweight tag was advertised with a peeled: line"
    );
}