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.
5.6 KiB
5.6 KiB
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)insrc/lib.rs. - Consumers must implement
RepositoryFacade(src/repo.rs) — three async fns returning a path/paths and agix::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 --releasecargo test(sync +#[tokio::test]integration tests intests/)cargo test --test <file>to run a single integration test filecargo test <name>to filter by test name substringcargo clippy --all-targets -- -D warningsif strictcargo fmt --check/cargo fmt(default rustfmt; norustfmt.toml)
Architecture notes
- Error pattern is hand-written.
BabyError(src/error.rs, ~50 variants) andCmdError(src/command/error.rs) implementDisplay+std::error::Errorby hand.thiserroris inCargo.tomlbut not used via#[derive(thiserror::Error)]. Do not "modernize" by adding the derive — keep the hand-written impls consistent. Envinsrc/command/env.rsis order-preserving (Vec<(String, String)>), not aHashMap.withappends,setreplaces 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).PipeisClone;cancel_pipe()setscancelled=trueand sendsstart_kill()to every running child but does not remove them fromcmds.
Module layout convention
- Most feature modules follow
src/<feature>/{mod.rs, types.rs, helper.rs, usecase.rs};mod.rsre-exports the public types fromtypes.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 ownerror.rsseparate fromsrc/error.rs.
Test quirks (tests/)
tests/cmd_run.rsdefines a localunwrap_or_recover_or_panic!-style macro for asserting command output — reuse it, don't reinvent.tests/env.rsmutates process env. It guards tests withstatic ENV_LOCK: Mutex<()>and uses keys prefixedGITBABY_TEST_ENV_<suffix>for isolation.std::env::set_var/remove_varare wrapped in localunsafe fns; follow that pattern when adding env-mutating tests.tests/pipe.rsasync cancel tests calltokio::time::sleep(Duration::from_millis(200))aftercancel_pipe()to letChild::try_waitobserve 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 everyServerBackendmethod currently returnsServerError::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>(aliasBoxedAsyncRead). Never introduce aVec<u8>body,ServerResponse::body, orread_to_endconvenience into the public surface. HeaderMap = HashMap<String, String>(nohttpcrate).output_capdefaults tocrate::command::cmd::DEFAULT_OUTPUT_CAP_BYTES(64 MiB). The server re-exports it asserver::DEFAULT_OUTPUT_CAP_BYTES.LFSupload cap isDEFAULT_LFS_OBJECT_CAP_BYTES(5 GiB) — a separate constant, not a magic number.RefUpdateBatchis a server-local struct (oid strings, notgix::ObjectId) so the API does not depend ongixtypes. Do not aliasrefs::types::RefUpdatehere.
- All request/response bodies cross the API as
ServerBackendasync-trait method set:authorize,list_refs_stream,produce_pack_stream,ingest_pack_stream,lfs_get_stream,lfs_put_stream. Methods returning a body must returnBoxedAsyncRead; ingest methods returnIngestReport. The legacyhead_object/write_ref/get_objectshapes are intentionally removed.ServerRequestis notClone(body is a boxed reader); it carries a manualDebugimpl.StreamGuardisClone + Send + Syncand exposescancel()/handle()so spawned workers can race the request.- Tests:
tests/server.rsreuses the project-widestatic ENV_LOCK: Mutex<()>pattern fromtests/env.rsand thetokio::time::sleep(Duration::from_millis(200))cancel convention fromtests/pipe.rs. Add new server tests there.