Two-part change:
1) Submodule split of src/cache/ (was a single ~150-line mod.rs)
- src/cache/config.rs: CacheConfig, defaults, default_cache_dir(),
DEFAULT_NAME / DEFAULT_MEMORY_CAPACITY_BYTES /
DEFAULT_DISK_CAPACITY_BYTES constants
- src/cache/key.rs: CacheKey / CacheValue type aliases and key
derivation helpers (rev -> oid, blob-exists negatives)
- src/cache/store.rs: CacheStore — the foyer HybridCache wrapper
(open / close / get / insert / remove / contains), all errors
mapped to BabyError::Cache with op-tagged source
- src/cache/pools.rs: CachePools — multi-store aggregator used by
the global singleton; replaces the old monolithic CacheStore
- src/cache/stats.rs: CacheStats + counter accessors
- src/cache/invalidation.rs: hook_ref_updated / hook_blob_written
(single-process invalidation hooks, fired from usecase impls)
- src/cache/mod.rs: now just submodule re-exports of
(config, global, invalidation, key, pools, stats, store)
- src/cache/global.rs: GITBABY_CACHE singleton + init / try /
shutdown now operate on Arc<CachePools> instead of Arc<CacheStore>
2) Cache integration in usecase modules
- src/blob/usecase.rs: read paths go through cache.get / insert;
invalidation hooks fire on write
- src/commit/usecase.rs: commit-list results cached, invalidated on
commit write
- src/branch/usecase.rs: branch-list results cached, invalidated on
ref update
- src/refs/usecase.rs: lookup-cache + ref-update hook
- src/tags/usecase.rs: tag-info cache + write hook
3) AGENTS.md
- Document that cached serde payloads (CommitInfo, Vec<TreeEntry>,
etc.) follow additive-only compatibility: never rename/remove a
field, every new field gets #[serde(default)] so stale payloads
still deserialize.
- Note that the foyer cache is single-process: fixed on-disk dir
per config + in-process invalidation hooks. Sharing across
processes on the same repo can leave stale metadata; do not do
that.
CI: cargo build + cargo test (61 passed) green.
BREAKING: the public cache surface moved. Anyone reaching into
cache::CacheStore directly should switch to cache::CachePools (or
cache::global::try_global_cache). The high-level helpers in
cache::global keep the same name; only the inner type changed.
52 lines
5.6 KiB
Markdown
52 lines
5.6 KiB
Markdown
# AGENTS.md — gitbaby
|
|
|
|
## What this is
|
|
- Single-crate Rust **library** (no binary, no workspace). Edition `2024`.
|
|
- Public entrypoint: `GitBaby::new(facade: Arc<dyn RepositoryFacade>, pipe: Pipe)` in `src/lib.rs`.
|
|
- Consumers **must implement** `RepositoryFacade` (`src/repo.rs`) — three async fns returning a path/paths and a `gix::Repository`. There is no default impl.
|
|
|
|
## Build / test / verify
|
|
- Standard cargo only. No CI, no pre-commit, no scripts, no rust-toolchain pin.
|
|
- Edition 2024 requires a recent stable toolchain (rustc ≥ 1.85). Local env verified at `rustc 1.97.1`.
|
|
- Useful commands (no order dependency beyond what's standard):
|
|
- `cargo build` / `cargo check` / `cargo build --release`
|
|
- `cargo test` (sync + `#[tokio::test]` integration tests in `tests/`)
|
|
- `cargo test --test <file>` to run a single integration test file
|
|
- `cargo test <name>` to filter by test name substring
|
|
- `cargo clippy --all-targets -- -D warnings` if strict
|
|
- `cargo fmt --check` / `cargo fmt` (default rustfmt; no `rustfmt.toml`)
|
|
|
|
## Architecture notes
|
|
- **Error pattern is hand-written.** `BabyError` (`src/error.rs`, ~50 variants) and `CmdError` (`src/command/error.rs`) implement `Display` + `std::error::Error` by hand. `thiserror` is in `Cargo.toml` **but not used via `#[derive(thiserror::Error)]`**. Do not "modernize" by adding the derive — keep the hand-written impls consistent.
|
|
- `Env` in `src/command/env.rs` is **order-preserving** (`Vec<(String, String)>`), not a `HashMap`. `with` appends, `set` replaces in place. This matters for env-var ordering in spawned processes.
|
|
- `Cmd::Display` = `prog` + `config_args` + `args` (config_args are prepended to args at runtime).
|
|
- `Pipe` is `Clone`; `cancel_pipe()` sets `cancelled=true` and sends `start_kill()` to every running child but does **not** remove them from `cmds`.
|
|
|
|
## Module layout convention
|
|
- Most feature modules follow `src/<feature>/{mod.rs, types.rs, helper.rs, usecase.rs}`; `mod.rs` re-exports the public types from `types.rs`.
|
|
- **`src/share/` is the outlier**: it uses `{cmd.rs, env.rs, error.rs, path.rs}` instead. Don't "normalize" it.
|
|
- `src/command/` uses `{cmd.rs, context.rs, env.rs, error.rs, pipe.rs}` and has its own `error.rs` separate from `src/error.rs`.
|
|
|
|
## Test quirks (`tests/`)
|
|
- `tests/cmd_run.rs` defines a local `unwrap_or_recover_or_panic!`-style macro for asserting command output — reuse it, don't reinvent.
|
|
- `tests/env.rs` mutates process env. It guards tests with `static ENV_LOCK: Mutex<()>` and uses keys prefixed `GITBABY_TEST_ENV_<suffix>` for isolation. `std::env::set_var` / `remove_var` are wrapped in local `unsafe fn`s; follow that pattern when adding env-mutating tests.
|
|
- `tests/pipe.rs` async cancel tests call `tokio::time::sleep(Duration::from_millis(200))` after `cancel_pipe()` to let `Child::try_wait` observe exit — keep that delay when adding new cancel tests.
|
|
|
|
## Gotchas
|
|
- No README. No docs beyond the code. Treat `Cargo.toml` + source as the only source of truth.
|
|
- `gix = "0.86"`, `tokio = 1.53` (full features), `time = 0.3` — all recent; do not bump them speculatively.
|
|
- Cache values that are serde-serialized (e.g. `CommitInfo`, `Vec<TreeEntry>`) follow a compatibility convention: fields are additive only — never rename/remove a field, and every new field must carry `#[serde(default)]` so stale cached payloads still deserialize.
|
|
- The foyer-backed cache (`src/cache/`) is single-process: it uses a fixed on-disk directory per config and relies on in-process invalidation hooks (`hook_ref_updated`, `hook_blob_written`). It must not be shared across processes on the same repo, or metadata (rev→oid, blob-exists negatives) can go stale.
|
|
- Public API surfaces async (`async_trait`); sync callers must use a runtime (tests use `#[tokio::test]`).
|
|
|
|
## Planned but unimplemented (streaming-first server)
|
|
- `src/server/` exists (mod.rs, backend.rs, endpoint.rs, error.rs, limits.rs, request.rs, stream.rs) but every `ServerBackend` method currently returns `ServerError::Unimplemented("…")` placeholders. Do not "fill them in" with arbitrary bodies — wait for a concrete git/lfs protocol task before adding logic.
|
|
- Streaming-only contract is intentional:
|
|
- All request/response bodies cross the API as `Box<dyn AsyncRead + Unpin + Send + Sync>` (alias `BoxedAsyncRead`). Never introduce a `Vec<u8>` body, `ServerResponse::body`, or `read_to_end` convenience into the public surface.
|
|
- `HeaderMap = HashMap<String, String>` (no `http` crate).
|
|
- `output_cap` defaults to `crate::command::cmd::DEFAULT_OUTPUT_CAP_BYTES` (64 MiB). The server re-exports it as `server::DEFAULT_OUTPUT_CAP_BYTES`.
|
|
- `LFS` upload cap is `DEFAULT_LFS_OBJECT_CAP_BYTES` (5 GiB) — a separate constant, not a magic number.
|
|
- `RefUpdateBatch` is a server-local struct (oid strings, not `gix::ObjectId`) so the API does not depend on `gix` types. Do **not** alias `refs::types::RefUpdate` here.
|
|
- `ServerBackend` async-trait method set: `authorize`, `list_refs_stream`, `produce_pack_stream`, `ingest_pack_stream`, `lfs_get_stream`, `lfs_put_stream`. Methods returning a body must return `BoxedAsyncRead`; ingest methods return `IngestReport`. The legacy `head_object` / `write_ref` / `get_object` shapes are intentionally removed.
|
|
- `ServerRequest` is **not** `Clone` (body is a boxed reader); it carries a manual `Debug` impl. `StreamGuard` is `Clone + Send + Sync` and exposes `cancel()` / `handle()` so spawned workers can race the request.
|
|
- Tests: `tests/server.rs` reuses the project-wide `static ENV_LOCK: Mutex<()>` pattern from `tests/env.rs` and the `tokio::time::sleep(Duration::from_millis(200))` cancel convention from `tests/pipe.rs`. Add new server tests there. |