gunnar-sendpack 1.1.0

git's receive-pack wire format, both ends: the send-pack conversation gitoxide does not have, plus the server-side encoders for the same grammar. Plumbing only, no gunnar types.
Documentation
//! This crate must stay liftable out of gunnar's tree with `cp -r`.
//!
//! That is decision D21: anything that plainly belongs in gitoxide is built as a
//! standalone crate from the first line, because extraction after the fact never
//! happens; by then the code has grown the host project's types through it. The
//! manifest already says so in a comment. A comment is not a check, and the
//! coupling this forbids arrives one convenient `version.workspace = true` at a
//! time, in a commit whose diff looks like tidying.
//!
//! # Every check here is proved red before it is trusted
//!
//! The checks are pure functions over text, and each one is run against a
//! deliberately-bad input in this same file. A guard that has only ever seen the
//! passing case is a guard nobody has tested, and the failure mode is silence,
//! forever. So `a_gunnar_dependency_is_detected`, `workspace_inheritance_is_detected`
//! and `a_rayon_dependency_is_detected` exist to watch the watchers.

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

fn crate_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

fn manifest() -> String {
    let p = crate_root().join("Cargo.toml");
    std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {p:?}: {e}"))
}

/// Dependency table entries: `(section, crate name, the whole line)`.
///
/// Deliberately literal: it walks section headers and takes the identifier
/// before the first `=`. A TOML parser is a dependency this crate does not have
/// and does not want for one test.
fn dependency_lines(manifest: &str) -> Vec<(String, String, String)> {
    let mut section = String::new();
    let mut out = Vec::new();
    for raw in manifest.lines() {
        let line = raw.trim();
        if let Some(name) = line.strip_prefix('[').and_then(|l| l.strip_suffix(']')) {
            section = name.to_owned();
            continue;
        }
        if line.is_empty() || line.starts_with('#') || !section.contains("dependencies") {
            continue;
        }
        let Some((lhs, _)) = line.split_once('=') else {
            continue;
        };
        out.push((section.clone(), lhs.trim().to_owned(), line.to_owned()));
    }
    out
}

/// Names of dependencies that would tie this crate to its host project.
fn host_dependencies(manifest: &str) -> Vec<String> {
    dependency_lines(manifest)
        .into_iter()
        .filter(|(_, name, _)| name.starts_with("gunnar"))
        .map(|(section, name, _)| format!("[{section}] {name}"))
        .collect()
}

/// Dependency lines that inherit from a workspace, which is what makes a
/// manifest meaningless outside the tree it was written in.
fn workspace_inherited(manifest: &str) -> Vec<String> {
    dependency_lines(manifest)
        .into_iter()
        .filter(|(_, _, line)| line.contains("workspace") && line.contains("true"))
        .map(|(section, name, _)| format!("[{section}] {name}"))
        .collect()
}

/// LAW 3, restated where it can be enforced: concurrency here is
/// `gix_features::parallel`, never rayon.
fn rayon_dependencies(manifest: &str) -> Vec<String> {
    dependency_lines(manifest)
        .into_iter()
        .filter(|(_, name, _)| name.contains("rayon"))
        .map(|(section, name, _)| format!("[{section}] {name}"))
        .collect()
}

fn rust_sources(dir: &Path) -> Vec<PathBuf> {
    let mut out = Vec::new();
    let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read {dir:?}: {e}"));
    for entry in entries {
        let path = entry.expect("dir entry").path();
        if path.is_dir() {
            out.extend(rust_sources(&path));
        } else if path.extension().is_some_and(|x| x == "rs") {
            out.push(path);
        }
    }
    out.sort();
    out
}

#[test]
fn the_manifest_names_no_dependency_from_the_host_project() {
    let found = host_dependencies(&manifest());
    assert!(
        found.is_empty(),
        "gunnar-sendpack depends on {found:?}. D21: this crate carries no gunnar types and \
         no gunnar dependency, ever. If it needs one, the design is wrong"
    );
}

#[test]
fn no_dependency_inherits_from_the_workspace() {
    let found = workspace_inherited(&manifest());
    assert!(
        found.is_empty(),
        "{found:?} inherit from the workspace, which makes this manifest meaningless \
         outside gunnar's tree. That is the exact coupling D21 exists to prevent"
    );
}

#[test]
fn concurrency_is_not_rayon() {
    let found = rayon_dependencies(&manifest());
    assert!(
        found.is_empty(),
        "rayon appears in the manifest as {found:?}"
    );

    for path in rust_sources(&crate_root().join("src")) {
        let text = std::fs::read_to_string(&path).expect("read source");
        for (n, line) in text.lines().enumerate() {
            let code = line.split("//").next().unwrap_or("");
            assert!(
                !code.contains("rayon"),
                "{}:{} reaches for rayon: {line}",
                path.display(),
                n + 1
            );
        }
    }
}

#[test]
fn no_source_file_imports_the_host_project() {
    let mut checked = 0usize;
    for path in rust_sources(&crate_root().join("src")) {
        let text = std::fs::read_to_string(&path).expect("read source");
        for (n, line) in text.lines().enumerate() {
            let code = line.split("//").next().unwrap_or("");
            assert!(
                !code.contains("gunnar_") && !code.contains("gunnar::"),
                "{}:{} reaches into the host project: {line}",
                path.display(),
                n + 1
            );
        }
        checked += 1;
    }
    assert!(checked >= 5, "only {checked} source files were scanned");
}

#[test]
fn the_licence_matches_gitoxides() {
    let m = manifest();
    assert!(
        m.contains(r#"license = "MIT OR Apache-2.0""#),
        "the licence must match gitoxide's or the code cannot be offered to them"
    );
}

// ── the negative controls: every check above, proved red ─────────────────────

#[test]
fn a_gunnar_dependency_is_detected() {
    let bad = "[dependencies]\ngix-hash = \"0.26\"\ngunnar-core = { path = \"../gunnar-core\" }\n";
    assert_eq!(host_dependencies(bad), vec!["[dependencies] gunnar-core"]);
    assert!(host_dependencies("[dependencies]\ngix-hash = \"0.26\"\n").is_empty());
}

#[test]
fn workspace_inheritance_is_detected() {
    let bad = "[package]\nversion.workspace = true\n[dependencies]\nthiserror.workspace = true\n";
    // Only dependency sections are policed; `[package]` inheritance is a
    // different (and lesser) sin, so the check must not fire on it.
    assert_eq!(
        workspace_inherited(bad),
        vec!["[dependencies] thiserror.workspace"]
    );
    assert!(workspace_inherited("[dependencies]\nthiserror = \"2\"\n").is_empty());
}

#[test]
fn a_rayon_dependency_is_detected() {
    let bad = "[dev-dependencies]\nrayon = \"1\"\n";
    assert_eq!(rayon_dependencies(bad), vec!["[dev-dependencies] rayon"]);
    assert!(rayon_dependencies("[dependencies]\ngix-features = \"0.49\"\n").is_empty());
}

#[test]
fn a_commented_out_dependency_is_not_mistaken_for_a_real_one() {
    // The manifest carries a long comment block about D21 that names gunnar
    // repeatedly. If the scanner counted comments it would fire on the very
    // paragraph explaining why it exists, and someone would delete it.
    let m = "[dependencies]\n# gunnar-core = { path = \"..\" }\ngix-hash = \"0.26\"\n";
    assert!(host_dependencies(m).is_empty());
}

// ── the second promise this crate makes, and it is a manifest property ───────
//
// The grammar half must cost a server NOTHING beyond `gix-hash`, `bstr` and
// `thiserror`. That is not a style preference: gunnar's appliance guard counts
// crates in the resolved serve-path graph, and `gunnar-wire` consumes this
// crate to stop writing the command list and the status report twice. If
// `gix-packetline` ever stopped being optional, the appliance would grow a
// pkt-line framer it already has one of, and nothing would say so — the build
// would simply get bigger.

/// Dependencies that are not gated behind a cargo feature.
fn unconditional_dependencies(manifest: &str) -> Vec<String> {
    dependency_lines(manifest)
        .into_iter()
        .filter(|(section, _, line)| section == "dependencies" && !line.contains("optional = true"))
        .map(|(_, name, _)| name)
        .collect()
}

#[test]
fn the_grammar_half_depends_on_three_crates_and_no_framer() {
    let found = unconditional_dependencies(&manifest());
    assert_eq!(
        found,
        vec!["gix-hash", "bstr", "thiserror"],
        "the always-on dependency set changed. Every name here is paid for by a \
         server that wants only the four wire formats, and `gix-packetline` in \
         particular must stay behind `blocking-io` — see the manifest"
    );
}

#[test]
fn a_dependency_escaping_its_feature_gate_is_detected() {
    // The red half. Without this the check above is a list nobody has watched
    // change, and the failure mode is a silently larger appliance.
    let bad = "[dependencies]\ngix-hash = \"0.26\"\ngix-packetline = \"0.22\"\n";
    assert_eq!(
        unconditional_dependencies(bad),
        vec!["gix-hash", "gix-packetline"]
    );
    let good = "[dependencies]\ngix-hash = \"0.26\"\n\
                gix-packetline = { version = \"0.22\", optional = true }\n";
    assert_eq!(unconditional_dependencies(good), vec!["gix-hash"]);
}

#[test]
fn the_io_feature_is_declared_and_gates_the_framer() {
    let m = manifest();
    assert!(
        m.contains(r#"blocking-io = ["dep:gix-packetline"]"#),
        "`blocking-io` must be the only thing that turns the framer on"
    );
    assert!(
        m.contains(r#"default = ["blocking-io"]"#),
        "a client is the majority consumer, so the I/O half is on by default"
    );
}