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

nordisk/edda

crates/nornir-git/tests/checkout_parity.rs

//! **Every transport must leave the same bytes on disk.**
//!
//! WHY THIS FILE EXISTS — measured on oden 2026-09-12. This crate had **two**
//! worktree writers and they disagreed about content filters:
//!
//! | writer | reached by | smudge |
//! |---|---|---|
//! | gix `PrepareCheckout::main_worktree` | `clone_or_fetch` over a local / `file://` / `https` remote | **applied** |
//! | `materialize_worktree` (a hand-rolled `std::fs::write` of each index blob) | `ssh_sync`, `set_head_and_checkout`, `detach_head_and_checkout` | **skipped** |
//!
//! So the same commit checked out through one transport and through the other
//! produced *different files*, and which one you got depended on the URL scheme
//! in a descriptor. For an LFS-tracked path that is the difference between the
//! asset and its 131-byte pointer, which is why skade, holger and Skidbladnir
//! failed checkout: `gunnar-client::unfiltered_paths` REFUSES a pointer when
//! `filter.lfs.required=true`, which is git's own contract.
//!
//! # The fixture is a filter, not git-lfs
//!
//! The subject is "does this writer run the smudge driver the repository
//! declares", not "is git-lfs installed". So the fixture declares its own driver,
//! whose effect is unmistakable and whose input is committed verbatim:
//!
//! * the blob in the odb is the literal line `POINTER`;
//! * `.gitattributes` routes `*.bin` through `filter=probe`;
//! * `filter.probe.smudge` rewrites that line to `SMUDGED`, and `clean` rewrites
//!   it back, so the pair round-trips exactly and a checkout can also be asked
//!   to read CLEAN afterwards.
//!
//! A writer that runs the smudge produces `SMUDGED`; one that does not produces
//! `POINTER`. There is no third answer, and unlike a git-lfs fixture this one is
//! present on every box, so the file can never decay into a skip that proves
//! nothing.
//!
//! # Why one of the two halves is a source scan
//!
//! [`a_materialised_checkout_runs_the_configured_smudge_driver`] is the
//! behaviour. [`there_is_exactly_one_worktree_writer`] is the *reason* the two
//! transports now agree: there is one writer, so a future transport cannot pick
//! the wrong one because there is no wrong one to pick. That property is "no
//! other code path exists", and no execution of any one path can witness it — a
//! runtime assertion here would be the hollow guard, not the scan.

use std::path::{Path, PathBuf};

use nornir_git::gitio;

/// The committed bytes of the filtered path — what an unfiltered writer emits.
const POINTER: &[u8] = b"POINTER\n";
/// What the smudge driver turns [`POINTER`] into.
const SMUDGED: &[u8] = b"SMUDGED\n";

/// The `[filter "probe"]` block, as a repository would carry it.
///
/// Two `sed` substitutions and no shell quoting: git-config strips quotes and
/// gix then hands the value to `sh -c`, so a driver that quotes its own argument
/// arrives truncated ("Unterminated quoted string") and the test would be
/// measuring the fixture instead of the writer.
fn probe_driver() -> String {
    "[filter \"probe\"]\n\
     \tsmudge = sed s/POINTER/SMUDGED/\n\
     \tclean = sed s/SMUDGED/POINTER/\n\
     \trequired = true\n"
        .to_string()
}

fn append_driver(repo_root: &Path) {
    let cfg = repo_root.join(".git").join("config");
    let mut text = std::fs::read_to_string(&cfg).unwrap_or_default();
    text.push_str(&probe_driver());
    std::fs::write(&cfg, text).expect("write .git/config");
}

/// A repo with one filtered path whose committed blob is `POINTER`.
///
/// `gitio::commit_all` writes blobs verbatim (it runs no `clean`), which is what
/// makes the odb content predictable: the blob IS `POINTER`.
fn origin_with_a_filtered_path() -> (tempfile::TempDir, PathBuf, String) {
    let td = tempfile::tempdir().expect("tempdir");
    let root = td.path().join("origin");
    gitio::init(&root).expect("git init");

    std::fs::write(root.join(".gitattributes"), b"*.bin filter=probe -text\n").expect("attrs");
    std::fs::write(root.join("asset.bin"), POINTER).expect("asset");
    std::fs::write(root.join("README.md"), b"hello\n").expect("readme");
    let sha = gitio::commit_all(&root, "initial").expect("commit");

    (td, root, sha)
}

fn asset_of(dir: &Path) -> Vec<u8> {
    std::fs::read(dir.join("asset.bin")).expect("read asset.bin")
}

/// THE RED PROOF. A checkout written by this crate must run the smudge driver
/// the repository declares.
///
/// The clone comes first and carries no driver, so the file lands as the raw
/// blob; the driver is then declared exactly as `/etc/gitconfig` declares
/// `filter.lfs.*` on a real box, and the SAME commit is checked out again.
/// Before the fix the second checkout still wrote `POINTER` — `materialize_worktree`
/// hand-wrote `blob.data` and never asked the filter pipeline anything.
///
/// The assertion is on **applied output** — the bytes on disk — and not on a
/// config round-trip, because "the driver is configured" is exactly what was
/// true all along while the file was still wrong.
#[test]
fn a_materialised_checkout_runs_the_configured_smudge_driver() {
    let (_td, origin, sha) = origin_with_a_filtered_path();

    let dst_td = tempfile::tempdir().expect("tempdir");
    let dst = dst_td.path().join("clone");
    gitio::clone_or_fetch(origin.to_str().expect("utf-8"), &dst, None).expect("clone");

    // Non-vacuity, in the negative direction: with NO driver declared, the same
    // writer must leave the raw blob. If this already read `SMUDGED` the test
    // below would pass against a writer that fabricates content rather than one
    // that filters it.
    assert_eq!(
        asset_of(&dst),
        POINTER,
        "a clone of a repo with no `filter.probe` declared must leave the blob verbatim"
    );

    append_driver(&dst);

    // The branch is read off the clone rather than assumed — `gix::init`'s
    // default name is not this test's business, and guessing it would make a
    // rename look like a filter bug.
    let branch = gitio::head_branch(&dst).expect("head branch");
    gitio::set_head_and_checkout(&dst, &branch, &sha).expect("re-materialise");

    let after = asset_of(&dst);
    assert_eq!(
        after,
        SMUDGED,
        "the worktree writer skipped the configured smudge driver: `asset.bin` reads {:?} \
         where the driver produces {:?}. An LFS-tracked path checked out this way is the \
         pointer, which `gunnar-client::unfiltered_paths` refuses under \
         `filter.lfs.required=true`.",
        String::from_utf8_lossy(&after),
        String::from_utf8_lossy(SMUDGED)
    );
}

/// A smudged checkout must not read back as dirty.
///
/// This is the other half of the contract and the one a blunter fix breaks: a
/// writer that expands the content but leaves the index describing the raw blob
/// makes every filtered path modified forever, which is worse than the pointer
/// it cured — `worktree_freshness` feeds the release freshness gate.
#[test]
fn a_smudged_checkout_reads_clean() {
    let (_td, origin, sha) = origin_with_a_filtered_path();

    let dst_td = tempfile::tempdir().expect("tempdir");
    let dst = dst_td.path().join("clone");
    gitio::clone_or_fetch(origin.to_str().expect("utf-8"), &dst, None).expect("clone");
    append_driver(&dst);
    let branch = gitio::head_branch(&dst).expect("head branch");
    gitio::set_head_and_checkout(&dst, &branch, &sha).expect("re-materialise");

    assert_eq!(asset_of(&dst), SMUDGED, "precondition: the smudge ran");
    let fresh = gitio::worktree_freshness(&dst).expect("status walk");
    assert!(
        !fresh.dirty,
        "a checkout whose smudged content cleans back to the committed blob must read clean; \
         reporting dirty here would make every filtered path look modified forever"
    );
}

/// NON-VACUITY PROBE for the fixture: the blob in the object database is the
/// SHORT form. If this ever reads 4096 the fixture has started committing the
/// smudged bytes and the tests above became tautologies.
#[test]
fn the_committed_blob_is_the_unsmudged_form() {
    let (_td, origin, _sha) = origin_with_a_filtered_path();
    let repo = gix::open(&origin).expect("open");
    let tree = repo.head_commit().expect("head").tree().expect("tree");
    let entry = tree
        .lookup_entry_by_path("asset.bin")
        .expect("lookup")
        .expect("asset.bin is in the tree");
    let blob = entry.object().expect("blob");
    assert_eq!(
        blob.data, POINTER,
        "the fixture must commit the UNSMUDGED bytes, or the parity tests compare nothing"
    );
}

/// THE CONSTRUCTION GUARD — one writer, so a future transport cannot skip the
/// smudge by reaching for the other one.
///
/// The bug this file exists for was not a wrong line; it was a *choice* offered
/// at every call site. `gix_worktree_state::checkout` is now called from exactly
/// one place in this crate, and gix's `main_worktree` — the second writer, whose
/// mere existence was the defect — from none.
#[test]
fn there_is_exactly_one_worktree_writer() {
    let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
    let mut checkout_calls: Vec<String> = Vec::new();
    let mut main_worktree_calls: Vec<String> = Vec::new();

    let mut files: Vec<PathBuf> = std::fs::read_dir(&src)
        .expect("read src/")
        .filter_map(|e| e.ok().map(|e| e.path()))
        .filter(|p| p.extension().is_some_and(|e| e == "rs"))
        .collect();
    files.sort();
    assert!(files.len() > 3, "the scan found {} source files — that is a broken scan", files.len());

    for path in &files {
        let text = std::fs::read_to_string(path).expect("read source");
        for (i, line) in text.lines().enumerate() {
            // Comments talk ABOUT both writers on purpose; only code counts.
            let code = line.split("//").next().unwrap_or("");
            let at = format!("{}:{}", path.file_name().unwrap().to_string_lossy(), i + 1);
            if code.contains("state::checkout(") || code.contains("gix_worktree_state::checkout(") {
                checkout_calls.push(at.clone());
            }
            if code.contains(".main_worktree(") {
                main_worktree_calls.push(at);
            }
        }
    }

    assert!(
        main_worktree_calls.is_empty(),
        "gix's `main_worktree` is a SECOND worktree writer and it is back at \
         {main_worktree_calls:?} — every checkout in this crate goes through \
         `materialize_worktree` so that the smudge decision has exactly one author"
    );
    assert_eq!(
        checkout_calls.len(),
        1,
        "expected exactly ONE `gix::worktree::state::checkout` call site (inside \
         `materialize_worktree`), found {checkout_calls:?}"
    );
}