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.
This commit is contained in:
zhenyi 2026-08-14 19:44:24 +08:00
parent 240b5214dd
commit 41154f520b

175
README.md Normal file
View File

@ -0,0 +1,175 @@
# 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<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
```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<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
```sh
cargo test # unit + #[tokio::test] integration tests in tests/
cargo test --test <file>
cargo clippy --all-targets -- -D warnings
cargo fmt --check
```
## License
TBD.