gunnar-server 3.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! **A repository's first commit, written by the server** (F3, 2026-10-08) — proven against
//! `gix-object` as an outside oracle, here and not in the serving tier (xtask `engine_seam`).

use gunnar_server::first_commit::first_commit_pack;
use gunnar_store::git_storage_trait::{GitOps, GitServe, RefUpdate};
use gunnar_store::mem::MemGitServe;

/// ★ **The first commit is git's own, in either hash** (F3, 2026-10-08): the pack is
/// absorbed by the store a push lands in, the commit, its tree and the README read back,
/// every id is the one `gix-object` computes, and the branch points at the commit.
#[test]
fn the_first_commit_is_gits_own_in_either_hash() {
    for kind in [gix_hash::Kind::Sha1, gix_hash::Kind::Sha256] {
        let text = b"# Welcome\n\nThank you for buying your private Gunnar.\n";
        let (pack, commit) =
            first_commit_pack(kind, "README.md", text, "Welcome", 1_791_500_000).unwrap();
        let store = MemGitServe::new(kind);
        store
            .put(&pack, &[RefUpdate::set("refs/heads/main", commit.clone())])
            .unwrap();
        let commit_id = gix_hash::ObjectId::from_hex(commit.as_bytes()).unwrap();
        let main = store
            .refs()
            .unwrap()
            .into_iter()
            .find(|r| r.name == "refs/heads/main")
            .expect("the branch");
        assert_eq!(
            main.oid.as_deref(),
            Some(commit_id.as_bytes()),
            "{kind:?}: the branch points at the commit"
        );
        let blob = gix_object::compute_hash(kind, gix_object::Kind::Blob, text).unwrap();
        let (_, readme) = store
            .read(blob.as_bytes())
            .unwrap()
            .expect("the README is in the store");
        assert_eq!(&readme[..], &text[..], "{kind:?}");
        let (_, c) = store
            .read(commit_id.as_bytes())
            .unwrap()
            .expect("the commit");
        assert_eq!(
            gix_object::compute_hash(kind, gix_object::Kind::Commit, &c).unwrap(),
            commit_id,
            "{kind:?}: the id is the commit's own"
        );
        let parsed = gix_object::CommitRef::from_bytes(&c, kind).unwrap();
        assert!(
            parsed.parents.is_empty() && parsed.message.starts_with(b"Welcome"),
            "{kind:?}"
        );
        let (_, tree) = store
            .read(parsed.tree().as_bytes())
            .unwrap()
            .expect("the tree");
        let t = gix_object::TreeRef::from_bytes(&tree, kind).unwrap();
        assert_eq!(t.entries.len(), 1);
        assert_eq!(
            (
                t.entries[0].filename.to_string(),
                t.entries[0].oid.to_owned()
            ),
            ("README.md".to_string(), blob)
        );
    }
    assert!(
        first_commit_pack(gix_hash::Kind::Sha1, "a/b", b"x", "m", 0).is_err(),
        "only a root file"
    );
}