# gitbaby A Rust library that implements git server backend operations on top of [`gix`](https://github.com/GitoxideLabs/gitoxide) (`gitoxide`). It provides an async, streaming-first API for reading and writing repository objects, refs, trees, blobs, LFS pointers, archives, bundles and more — designed to be embedded in a git hosting product (servers, admin tools, mirror agents). ## Status - **Library only.** No binary, no workspace. Single crate, edition `2024` (requires rustc ≥ 1.85). - The streaming-first HTTP/git-protocol server surface (`src/server/`) exists as a contract but is **not implemented yet** — see [Server (planned)](#server-planned). ## Features | Module | Capabilities | |---|---| | `commit` | `get_commit`, `rev_parse`, `is_ancestor`, `list_commits`, `commit_stats`, `get_tree_entries`, `last_commit_for_path` | | `branch` | `list_branches`, `find_branch`, `branch_exists`, default-branch get/set, `create_branch`(`_force`), `delete_branch`, `rename_branch`, `update_head` | | `tag` | `list_tags`, `find_tag`, `tag_exists`, lightweight / annotated / signed tag creation, `verify_tag_signature`, `delete_tag` | | `refs` | `list_refs`, `ref_exists`, `resolve_revision`, `update_ref`, `delete_ref`, `find_refs_by_oid`, atomic `update_refs_atomic` / `delete_refs_atomic` | | `tree` | `tree_latest_commit`, `commit_tree` (fast tree writes), `walk_tree`, `tree_entries_with_latest`, `diff_trees` | | `blob` | streaming `get_blob` / `stream_blob`, `get_blob_size`, `blob_exists`, `write_blob`, `list_blobs` / `list_all_blobs`, LFS pointer listing (`list_lfs_pointers`, `list_all_lfs_pointers`, `get_lfs_pointers`) | | `diff` | `ChangedPath`-based diff requests (`DiffRequest`), `NumStat`, `ShortStat`, `RangeDiffSpec`, whitespace modes | | `merge` + `conflict` | `merge_base`, `merge_tree`, conflict listing (`list_conflict_files`), `resolve_conflicts` | | `compare` | `DivergeObject` — divergence analysis between refs | | `remote` | `add_remote` / `remove_remote`, `get_remote_address`, `fetch_remote_commit`, `find_remote_repository` (URL cap check), `find_remote_root_ref`, `update_remote_mirror` | | `archive` | `create_archive`, streaming `get_archive`, `create_bundle` | | `setup` | `init_repository`, `is_repository_exists`, `fast_import` (git-fast-import based bulk loading) | | `cleanup` | `prune`, `apply_bfg_object_map` (BFG-style object history rewriting) | | `config` | `config_get`/`set`/`add`/`unset_all`/`get_all`/`get_regexp` (repo + global scope), managed-config variants | | `blame` | `BlameHunk`-based async blame with `Range` limits | | `submodule` | `list_submodules`, `get_submodule` | | `command` | Low-level child git process execution: `Cmd` with order-preserving `Env`, streaming pipes, output cap, timeouts | | `cache` | foyer-backed LRU+Tiered cache for commits, tree entries, blob metadata, rev→oid mappings; LFS cache; per-config on-disk store | | `server` | **Planned** streaming git/http protocol surface (see below) | ## Architecture ``` GitBaby (src/lib.rs) ← single public entrypoint ├─ facade: Arc ← consumer-provided repo access └─ feature modules (src//) mod.rs → re-exports public types types.rs → request/response structs (serde, additive-only fields) helper.rs → validation + git command construction / error classification usecase.rs→ `impl GitBaby { pub async fn ... }` ← the public API ``` - Every feature is a set of **methods implemented directly on `GitBaby`** via `impl GitBaby` blocks in each `usecase.rs`. The struct itself is tiny: just an `Arc`. - **`RepositoryFacade`** (`src/repo.rs`) is consumer-implemented (`async_trait`): `git_repo_dir()`, `git_alternate_object_directories()`, `gix_repo()`. There is no default implementation. - Most read/write operations shell out to the system `git` binary through `Cmd` (`src/command/cmd.rs`) — ordered env, stdin/stdout/stderr handling, and a 64 MiB output cap (`DEFAULT_OUTPUT_CAP_BYTES`, enforced as `CmdError::PayloadTooLarge`). Metadata reads (commits, tree entries) go through the `gix` object store, which is also the read path that feeds the cache. - Errors are hand-written: `BabyError` (`src/error.rs`, ~50 variants) and `CmdError` (`src/command/error.rs`) implement `Display` + `std::error::Error` manually. (`thiserror` is a declared dependency but deliberately unused.) - `Env` (`src/command/env.rs`) is an **order-preserving** `Vec<(String, String)>`; `with` appends, `set` replaces in place. Env ordering matters for spawned processes. ## Quick start ```toml [dependencies] gitbaby = "0.1" gix = "0.86" async-trait = "0.1" tokio = { version = "1", features = ["full"] } ``` ```rust use std::path::{Path, PathBuf}; use std::sync::Arc; use async_trait::async_trait; use gitbaby::repo::RepositoryFacade; use gitbaby::error::BabyError; use gitbaby::GitBaby; struct MyRepo(PathBuf); #[async_trait] impl RepositoryFacade for MyRepo { async fn git_repo_dir(&self) -> Result { Ok(self.0.clone()) } async fn git_alternate_object_directories(&self) -> Result, BabyError> { Ok(Vec::new()) } async fn gix_repo(&self) -> Result { gix::open(&self.0).map_err(|e| { BabyError::Custom(format!("open {}: {e}", self.0.display())) }) } } #[tokio::main] async fn main() -> Result<(), BabyError> { let git = GitBaby::new(Arc::new(MyRepo("/path/to/repo".into()))); let head = git.get_commit("HEAD").await?; println!("{head:?}"); let tree = git.get_tree_entries("HEAD", Path::new("")).await?; println!("{} entries", tree.len()); let oid = git.rev_parse("HEAD").await?; println!("HEAD = {oid}"); let branches = git.list_branches(gitbaby::branch::ListBranchesOptions::default()).await?; println!("{} branches", branches.len()); Ok(()) } ``` All operations are async and need a Tokio runtime (`#[tokio::test]` / `#[tokio::main]`). ## Server (planned) `src/server/` defines the streaming-first server contract but every `ServerBackend` method still returns `ServerError::Unimplemented(...)`. The contract is intentional: - Methods: `authorize`, `list_refs_stream`, `produce_pack_stream`, `ingest_pack_stream`, `lfs_get_stream`, `lfs_put_stream` (async-trait). - All bodies cross the API as `Box` (`BoxedAsyncRead`) — **no `Vec` bodies**; ingest methods return `IngestReport`. - `HeaderMap = HashMap` (no `http` crate). - Output cap defaults to `DEFAULT_OUTPUT_CAP_BYTES` (64 MiB); LFS uploads cap at `DEFAULT_LFS_OBJECT_CAP_BYTES` (5 GiB) — `server::DEFAULT_LFS_OBJECT_CAP_BYTES`. - `RefUpdateBatch` is server-local (oid strings, no `gix` types); `ServerRequest` is not `Clone`; `StreamGuard` is `Clone + Send + Sync` with `cancel()` / `handle()`. Do not implement this surface until a concrete git/lfs protocol task exists. ## Cache - `src/cache/` is a **single-process** foyer-backed store: fixed on-disk directory per config, in-process invalidation hooks (`hook_ref_updated`, `hook_blob_written`). Do not share across processes on the same repo — metadata (rev→oid, blob-exists negatives) can go stale. - Namespaces: commit info (`NS_COMMIT`), tree entries (`NS_TREE_ENTRIES`), rev→oid (`NS_REV`), blob metadata/exists (`NS_BLOB`, `NS_BLOB_EXISTS`, `NS_BLOB_SIZE`), LFS (`NS_LFS`). - Lifecycle: `init_global_cache` / `init_global_cache_async` / `shutdown_global_cache` / `try_global_cache`; `CachePools::open(cfg)` / `close()`. ## Compatibility conventions Serde-serialized cache values (`CommitInfo`, `Vec`, …) are **additive-only**: never rename or remove a field, and every new field must be `#[serde(default)]` so stale cached payloads still deserialize. ## Testing ```sh cargo test # unit + #[tokio::test] integration tests in tests/ cargo test --test cargo clippy --all-targets -- -D warnings cargo fmt --check ``` ## License TBD.