Expand description
Per-mem version control via gix. Each mem owns a gix repository
whose gitdir and worktree are resolved from the mem’s config at
Engine::init time — isolated from any outer project repo and from
the developer’s ~/.gitconfig.
Gitdir defaults to <mem>/.git/ and worktree to the mem root; the
optional vcs block in .memstead/config.json overrides either (notably
the shared-gitdir idiom { "../.git", ".." }). On first init the
repo is bootstrapped and its per-repo config is patched with
core.logallrefupdates = true + commit.gpgsign = false;
core.worktree is written only when the declared worktree disagrees
with the gitdir’s natural parent (shared idiom).
On every init we re-apply commit.gpgsign = false so a developer
with global signing enabled does not hang every mutation waiting for
a passphrase.
The committer (engine <noreply@memstead.io>) is set explicitly per
commit via commit_as and is therefore independent of any
user.name/user.email config — global or per-repo. The author is
derived per commit from a CommitContext so provenance (agent / cli /
external drift) is visible in git log without storing PII.
Trailer contract: callers pass prose only. The engine appends trailers
(Tool:, Actor:, Client:) after a single \n\n separator. Callers
MUST NOT write those keys themselves — duplicates would confuse
git interpret-trailers consumers.
§In-process serialization: per-branch mutex
A mem’s commits race on a single git ref (today: HEAD’s symref target on the disk adapter, an explicit branch name on the git-tree adapter). Without serialization two concurrent commits against the same ref would either produce an orphan parent chain or fail gix’s reference-transaction check. The mutex’s job is to keep the tree-build + commit + ref-advance window atomic for one ref.
A process-wide registry (see [acquire_branch_mutex]) maps
canonical ref-name strings (e.g. refs/heads/main) to
Arc<Mutex<()>>. Both adapters acquire the mutex for the ref they
are about to advance: the disk adapter resolves HEAD’s symref to a
concrete refs/heads/<name> first so a future git-tree writer
committing onto the same branch shares the same key. Different ref
names hold different mutexes and proceed in parallel.
Cross-process contention is out of scope for this layer. A
second process committing against the same ref hits gix’s own
lockfile discipline (<gitdir>/index.lock, <gitdir>/HEAD.lock,
…), which this module surfaces as VcsError::Git →
VCS_ERROR-coded envelopes. The human-readable message includes
retry guidance; the industry norm (libgit2, GitHub Desktop) is to
propagate lockfile errors back to the caller rather than introduce
a custom flock layer.
Lock-order rule. When a code path holds more than one per-branch
mutex simultaneously (e.g. a cross-mem move that touches two
branches under one shared multi-root gitdir), the mutexes MUST be
acquired in lexicographic ref-name order to prevent deadlock.
[acquire_branch_mutexes_in_order] enforces this by sorting before
acquisition; ad-hoc multi-mutex code in debug builds is caught by
the assertion inside [acquire_branch_mutex].
Structs§
- Client
Id - Identity of the process speaking to the engine. For MCP, this is the
clientInfofrom the initialize handshake (e.g.ClientId { name: "claude-code", version: "2.1.0" }). For CLI-direct mutations, the crate populates it with its own name and version. - Commit
Context - Provenance bundle for a single commit. Produced at the caller boundary
(
memstead-mcptool handler,memstead-clisubcommand, engine-internal drift flush) and threaded through to the VCS commit path. - NoopVcs
- Test-only VCS that records nothing and never errors. Used by engine tests
that exercise mutation paths without touching a real repo. The returned
SHAs are deterministic monotonic sentinels (
noop-0,noop-1, …) distinguishable from real SHAs by prefix —changes_sincerelies on that distinction to produce empty deltas for noop mems.
Enums§
- Actor
- Caller categories for the
Actor:trailer and for picking an author signature.Agent,Cli, andAppget their author from the pairedClientIdwhen one is present;Externalalways uses the syntheticexternal <external@memstead.io>identity (no client is known);Unknownfalls back to the committer identity. - VcsError
Traits§
- Vcs
- VCS operations trait — minimal surface. Only
commitis needed by the engine today.changes_sincegoes straight togix::diff::treewithout expanding this trait.
Functions§
- author_
identity - Build the per-commit author
(name, email)pair from the context.Nonemeans “fall back to the committer identity” — adapters then reuse the committer signature for the author slot. - create_
vcs - Open or initialize the per-mem gix repository.
- format_
commit_ message - Append the trailer block to the caller’s prose, separated by exactly
one blank line. Normalises trailing newlines so
"subject"and"subject\n"both produce"subject\n\nActor: …\n…". - sanitise_
client_ name - Sanitise a raw client name to a git-safe local-part matching
[a-z0-9._-]+. Empty/whitespace-only input falls back to"unknown".