GitBaby/README.md
zhenyi 41154f520b docs: add README describing status, modules, and conventions
Adds the project's first README. The repo previously had no README
(per AGENTS.md: "No README. No docs beyond the code."); this doc
collects the existing notes from AGENTS.md into a public-facing
overview so external consumers don't have to read source first.

Sections:
- Status: library only, single crate, edition 2024 (rustc ≥ 1.85),
  streaming-first server surface exists as a contract but is not
  implemented.
- Features: a per-module capability table for every public feature
  module (commit, branch, tag, refs, tree, blob, diff, merge,
  conflict, compare, remote, archive, setup, cleanup, config, blame,
  submodule, command, cache, server).
- Architecture: ASCII diagram of GitBaby -> facade + feature
  modules, plus bullets explaining:
    * `impl GitBaby { ... }` blocks are where the public surface
      lives,
    * `RepositoryFacade` is consumer-implemented (no default),
    * read/write ops mostly shell out to git via `Cmd` (order-
      preserving `Env`, 64 MiB output cap, timeouts); metadata reads
      go through `gix`,
    * errors are hand-written (thiserror is declared but unused),
    * `Env` order matters.
- Quick start: Cargo.toml snippet and a runnable `RepositoryFacade`
  implementation that exercises `get_commit`, `get_tree_entries`,
  `rev_parse`, and `list_branches`.
- Server (planned): lists the streaming contract methods, the
  BoxedAsyncRead alias, the 64 MiB / 5 GiB caps, and the "do not
  implement without a concrete protocol task" caveat.
- Cache: single-process foyer-backed store, namespace list, and
  lifecycle helpers.
- Compatibility conventions: serde-serialized cache payloads are
  additive-only with #[serde(default)] on every new field.
- Testing: cargo test / cargo test --test <file> / clippy / fmt
  commands.
- License: TBD (placeholder).

CI: docs only, no compile impact. README is not picked up by cargo
build or any of the existing tests.

Notes:
- Cargo metadata (description, license, keywords, repository) is
  intentionally NOT touched here — those belong in Cargo.toml and
  can be added in a follow-up.
- The README duplicates a few bullets from AGENTS.md. AGENTS.md
  stays as the project-internal contributor note; this README is
  the consumer-facing entry point. A future cleanup could
  deduplicate, but for now both stay since they serve different
  audiences.
2026-08-14 19:44:24 +08:00

7.9 KiB

gitbaby

A Rust library that implements git server backend operations on top of gix (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).

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<dyn RepositoryFacade> ← consumer-provided repo access
 └─ feature modules (src/<feature>/)
      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<dyn RepositoryFacade>.
  • 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

[dependencies]
gitbaby = "0.1"
gix = "0.86"
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }
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<PathBuf, BabyError> {
        Ok(self.0.clone())
    }
    async fn git_alternate_object_directories(&self) -> Result<Vec<PathBuf>, BabyError> {
        Ok(Vec::new())
    }
    async fn gix_repo(&self) -> Result<gix::Repository, BabyError> {
        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<dyn AsyncRead + Unpin + Send + Sync> (BoxedAsyncRead) — no Vec<u8> bodies; ingest methods return IngestReport.
  • HeaderMap = HashMap<String, String> (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<TreeEntry>, …) are additive-only: never rename or remove a field, and every new field must be #[serde(default)] so stale cached payloads still deserialize.

Testing

cargo test          # unit + #[tokio::test] integration tests in tests/
cargo test --test <file>
cargo clippy --all-targets -- -D warnings
cargo fmt --check

License

TBD.