From 41154f520b62f52f3ea36b836e56236e811e613e Mon Sep 17 00:00:00 2001 From: zhenyi <434836402@qq.com> Date: Fri, 14 Aug 2026 19:44:24 +0800 Subject: [PATCH] docs: add README describing status, modules, and conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 / 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. --- README.md | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a7043a --- /dev/null +++ b/README.md @@ -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 ← 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. \ No newline at end of file