gunnar-server 2.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");
}