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.
|
||
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
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
GitBabyviaimpl GitBabyblocks in eachusecase.rs. The struct itself is tiny: just anArc<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
gitbinary throughCmd(src/command/cmd.rs) — ordered env, stdin/stdout/stderr handling, and a 64 MiB output cap (DEFAULT_OUTPUT_CAP_BYTES, enforced asCmdError::PayloadTooLarge). Metadata reads (commits, tree entries) go through thegixobject store, which is also the read path that feeds the cache. - Errors are hand-written:
BabyError(src/error.rs, ~50 variants) andCmdError(src/command/error.rs) implementDisplay+std::error::Errormanually. (thiserroris a declared dependency but deliberately unused.) Env(src/command/env.rs) is an order-preservingVec<(String, String)>;withappends,setreplaces 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) — noVec<u8>bodies; ingest methods returnIngestReport. HeaderMap = HashMap<String, String>(nohttpcrate).- Output cap defaults to
DEFAULT_OUTPUT_CAP_BYTES(64 MiB); LFS uploads cap atDEFAULT_LFS_OBJECT_CAP_BYTES(5 GiB) —server::DEFAULT_LFS_OBJECT_CAP_BYTES. RefUpdateBatchis server-local (oid strings, nogixtypes);ServerRequestis notClone;StreamGuardisClone + Send + Syncwithcancel()/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.