nornir-git 0.1.3

Pure-Rust git helpers over gix — read-side inspection (gitio), history deep-clean / path-removal (gitclean), and an SSH transport bridge (russh → gix blocking I/O). No `git` binary, no libgit2/C; the airgap-clean git leaf lifted out of nornir.
Documentation
//! # Functional-status emit bridge — `nornir-git` → test matrix
//!
//! The single internal call site this crate uses to push a *functional* row
//! into the [`nornir_testmatrix`] matrix, so `nornir test --features testmatrix`
//! records whether `nornir-git`'s real git ops (init / commit / head-sha /
//! head-branch / worktree-freshness / url classification) ACTUALLY work.
//!
//! ## Release strips everything
//! The `testmatrix` cargo feature gates the whole bridge: with it OFF (the
//! release default) [`emit`] is an `#[inline]` no-op and `nornir-testmatrix` is
//! not even linked. Lit ONLY under `cargo … --features testmatrix`.

/// The aspect tag every self-emitted row carries (matches `functional_row`).
pub const ASPECT_FUNCTIONAL: &str = "functional";

/// Push ONE functional-status row for a single real check (feature ON).
#[cfg(feature = "testmatrix")]
#[inline]
pub fn emit(component: &str, check: &str, ok: bool, detail: &str) {
    nornir_testmatrix::functional_status(component, check, ok, detail);
}

/// No-op stub when the `testmatrix` feature is OFF (release builds).
#[cfg(not(feature = "testmatrix"))]
#[inline]
pub fn emit(_component: &str, _check: &str, _ok: bool, _detail: &str) {}

/// `assert!`-with-emit: assert on the real return value AND record the verdict
/// as a matrix row.
#[macro_export]
macro_rules! assert_emit {
    ($component:expr, $check:expr, $ok:expr, $($detail:tt)+) => {{
        let __ok: bool = $ok;
        let __detail = ::std::format!($($detail)+);
        $crate::selftest::emit($component, $check, __ok, &__detail);
        ::std::assert!(__ok, "{}::{} — {}", $component, $check, __detail);
    }};
}

// ─── inject-and-assert wiring test (feature ON only) ─────────────────────────
#[cfg(all(test, feature = "testmatrix"))]
mod wiring {
    use crate::gitio;
    use std::io::Write;

    /// Drive `nornir-git`'s real git ops on a synthetic repo, emit an honest row
    /// per surface, then drain the process-global functional buffer and assert
    /// the rows landed — proving the emit bridge is wired end to end.
    #[test]
    fn emits_honest_rows_for_git_ops() {
        let dir = tempfile::tempdir().unwrap();
        let root = dir.path();

        // Surface: init a fresh repo.
        let inited = gitio::init(root).is_ok();
        crate::selftest::emit("nornir-git", "init_creates_repo", inited, &format!("root={}", root.display()));

        // Seed a file + make the first commit.
        {
            let mut f = std::fs::File::create(root.join("README.md")).unwrap();
            writeln!(f, "hello").unwrap();
        }
        let sha = gitio::commit_all(root, "initial");
        let committed = sha.as_ref().map(|s| s.len() >= 40).unwrap_or(false);
        crate::selftest::emit(
            "nornir-git",
            "commit_all_returns_sha",
            committed,
            &format!("sha_len={:?}", sha.as_ref().map(|s| s.len())),
        );

        // Surface: read HEAD sha back — must equal what commit_all reported.
        let head = gitio::head_sha(root);
        let head_ok = matches!((&head, &sha), (Ok(h), Ok(s)) if h == s);
        assert_emit!(
            "nornir-git",
            "head_sha_matches_commit",
            head_ok,
            "head={:?} commit={:?}",
            head.as_deref().ok(),
            sha.as_deref().ok()
        );

        // Surface: read the HEAD branch name.
        let branch = gitio::head_branch(root);
        crate::selftest::emit(
            "nornir-git",
            "head_branch_resolves",
            branch.as_ref().map(|b| !b.is_empty()).unwrap_or(false),
            &format!("branch={:?}", branch.as_deref().ok()),
        );

        // Surface: worktree freshness reads without error on a clean repo.
        let fresh = gitio::worktree_freshness(root);
        crate::selftest::emit(
            "nornir-git",
            "worktree_freshness_reads",
            fresh.is_ok(),
            &format!("ok={}", fresh.is_ok()),
        );

        // Pure surfaces: url classification (no I/O).
        let ssh_ok = gitio::is_ssh_url("git@codeberg.org:nordisk/nornir.git")
            && !gitio::is_ssh_url("https://codeberg.org/nordisk/nornir.git");
        assert_emit!("nornir-git", "is_ssh_url_classifies", ssh_ok, "ssh+https classified");
        let rewrite_ok = gitio::https_to_ssh("https://codeberg.org/nordisk/nornir.git")
            .as_deref()
            .map(|s| gitio::is_ssh_url(s))
            .unwrap_or(false);
        assert_emit!("nornir-git", "https_to_ssh_roundtrips", rewrite_ok, "https→ssh yields ssh url");

        let rows = nornir_testmatrix::drain_functional_rows();
        for want in [
            "init_creates_repo",
            "commit_all_returns_sha",
            "head_sha_matches_commit",
            "head_branch_resolves",
            "worktree_freshness_reads",
            "is_ssh_url_classifies",
            "https_to_ssh_roundtrips",
        ] {
            assert!(
                rows.iter().any(|r| r.suite == "nornir-git" && r.test_name == want && r.status == "pass"),
                "missing green row {want}: {rows:?}"
            );
        }
        assert!(rows.iter().all(|r| r.aspect == "functional"), "rows={rows:?}");
    }
}