>_ENGINEERING REPORT
zvcs is a git-shadowing superset VCS in Rust: a single binary named git shadows stock git on PATH, serves git-compat porcelain via vendored gitoxide, and adds a superset coordination layer for meta-repos under many concurrent agents. This report describes the architecture, the superset design, the concurrency model, the current state of the tree, and the dependency posture. The statements below are facts about the design and the source, not aspirational metrics.
Summary
gitoxide already ports git to Rust. zvcs does the thing git structurally cannot: it wraps that library in a single binary named git that shadows stock git on PATH, and layers on coordination verbs designed for the exact failure modes of driving a large meta-repo of submodules under many concurrent automated agents. There is no fork/exec of stock git — every subcommand is served in-process against the vendored gix crates, so tools already on PATH (RustRover, gh, cargo) read the same on-disk .git and see identical behavior.
The world's-first leg is the superset, not the port. Its founding three verbs close three reproducible stock-git failure modes: zdaemon replaces the index.lock flock with a fair FIFO backed by a machine-wide daemon; zsync reconciles submodules to their tracked mainline and keeps HEAD attached; zbump makes gitlink bumps forward-only and commits them. The superset has since grown to 116 verbs — coordination, an async job queue, a machine-wide repo index and status cache, cross-repo timeline and undo, tree-wide snapshots, per-agent worktrees, policy, and live monitoring.
The current tree dispatches 181 git-compat verbs and 119 superset verbs natively — no fork/exec of stock git at any point — across ~381k lines in src/extensions, on top of 67 vendored gix crates, with 2,557 integration tests across 404 files. Coverage is not parity: a subcommand that dispatches is not thereby byte-faithful, and depth varies per subcommand. See the performance section for how the read path compares to stock git, measured rather than asserted.
Architecture
One dispatch table routes two namespaces; the execution path never leaves the process:
git <subcommand> → dispatch::run ┬─ superset verbs (z*, 119) → daemon · queue · ledger · fleet
└─ git-compat porcelain (181) → gitoxide (gix) library
No fork/exec
Every subcommand runs in-process against the vendored gix crates. Unported subcommands error terse (zsh-style zvcs: <cmd>: <reason> on stderr) rather than falling through to stock git — this is a from-scratch engine, not a shim (src/dispatch.rs).
Vendored, owned gitoxide
src/ported holds the gix + gix-* crates in-tree as a self-contained workspace, excluded from the root and consumed as a path dependency. The gix/ein CLIs and gitoxide-core backend are removed; git is the only binary.
Same on-disk .git
The binary discovers and reads the same .git directory stock git does, so RustRover, gh, and cargo observe identical repository state and a consistently staged index.
Pure-Rust TLS fetch
gix is built with blocking-http-transport-reqwest-rust-tls, so zsync's reconcile fetch runs over HTTPS on a pure-Rust TLS stack with no curl/openssl C toolchain.
The concurrency model — zdaemon
Stock git serializes index writes with an index.lock file created via O_EXCL: a contended writer does not wait, it fails (fatal: Unable to create '.git/index.lock': File exists). Under many concurrent agents that turns a queue into a thundering herd of retries with no fairness guarantee. zdaemon replaces the flock with a FIFO userspace barrier: a single worker thread owns the abstract critical section and drains an mpsc channel of requests in arrival order. That arrival order is the fairness guarantee — first-come-first-served, no starvation, no lost wakeups.
Clients reach the daemon through RepoLock::acquire (src/lock.rs), an RAII guard that blocks in the FIFO and returns only when the caller holds the lock. Release is automatic: dropping the guard sends RELEASE and closes the socket, and the daemon also auto-releases on socket EOF, so a crashed holder can never wedge the repo. If no daemon is reachable the lock degrades to a no-op guard — the operation still runs (stock-git behavior minus the fair queue); ensuring a daemon is up is the autonomous layer's job, not the writer's.
| Line | Direction | Meaning |
|---|---|---|
| ACQUIRE <id> | client → daemon | Enqueue a lock request; answered GRANTED on the same stream at the FIFO head. The client keeps the connection open while it holds the lock. |
| RELEASE <id> | client → daemon | Current holder releases; the daemon grants the next queued waiter. |
| STATUS | client → daemon | Reply one line holder=<id|none> queue=<depth>, then close. |
| STOP | client → daemon | Reply STOPPING, remove the socket at <git-dir>/zvcs.sock, exit. |
| GRANTED | daemon → client | The lock is now yours (in response to ACQUIRE). |
| ERR <reason> | daemon → client | Malformed request. |
The submodule model — zsync & zbump
zsync reconciles each submodule (or a named subset) to its tracked mainline. Mainline detection prefers refs/remotes/origin/main and falls back to origin/master; a repo with neither is skipped, not errored. The operation fetches origin, then fast-forwards only: a dirty worktree is skipped untouched, and an unpushed local commit is never regressed or clobbered. On a genuine fast-forward the local mainline branch is advanced, HEAD is re-attached to it, and the clean worktree plus index are moved to the new tree by writing only the files that actually changed (reconcile_repo in src/superset/zsync.rs).
zbump advances the parent's recorded gitlink to each submodule worktree's current HEAD, but only when that HEAD is a descendant of the pointer already recorded — a fast-forward. It never regresses or diverges a pointer. The parent index is opened once, mutated for every qualifying submodule, and staged once at the end, so tools on PATH observe a single consistent index write (src/superset/zbump.rs).
Always attached
zsync leaves HEAD pointing at the mainline branch, never detached — the orphaning failure mode of git submodule update cannot occur.
Never destructive
Fast-forward only, dirty worktrees skipped. An unpushed local commit is preserved, not clobbered; there is no reset-to-remote.
Forward-only pointers
zbump's descendant check makes a backwards gitlink move structurally impossible, closing the stale-worktree regression hole of a blanket git add.
Minimal writes
Reconcile moves only the files that differ between the old and new tree, keeping worktree churn and index writes proportional to the actual change.
Component status
| Component | State | Notes |
|---|---|---|
| git shadow binary + dispatch | Implemented | Single binary named git; two-namespace dispatch, terse zsh-style errors (src/main.rs, src/dispatch.rs). |
vendored gitoxide (src/ported) | Implemented | In-tree gix + gix-* crates, own excluded workspace; gix/ein/gitoxide-core removed. |
| zdaemon — fair FIFO index-lock coordinator | Implemented | start/stop/status; mpsc arrival-order barrier, socket-EOF auto-release (src/superset/zdaemon.rs). |
| RepoLock daemon client | Implemented | RAII acquire/release, no-op-guard fallback when no daemon is reachable (src/lock.rs). |
| zsync — reconcile-to-mainline attacher | Implemented | Fast-forward only; skips dirty; re-attaches HEAD; writes only changed files (src/superset/zsync.rs). |
| zbump — forward-only gitlink bumps | Implemented | Descendant check before staging; parent index staged once (src/superset/zbump.rs). |
| rev-parse | Partial | Resolves HEAD and --abbrev-ref HEAD only (src/porcelain.rs). |
| git-compat porcelain surface | Planned | status, add, commit, log, and the rest ported incrementally on gitoxide. |
gitconfig coverage
zvcs honors 152 of the 1003 configuration keys git 2.55 documents via git help --config — about 15%. This is a measured figure, not an estimate: git's key list intersected with the keys read in src/extensions/src and the keys the vendored gix engine declares in config/tree. A setting is only counted where the behavior that consumes it exists; the loader itself (gix) parses every key regardless.
| Source | Keys | Notes |
|---|---|---|
| git 2.55 documented total | 1003 | Everything git help --config lists. |
| Honored by zvcs porcelain | 92 | Read and applied directly in the ported subcommands (e.g. core.pager, alias.*, help.autocorrect, merge.ff, tag.sort, format.*). |
| Honored by the gix engine | 75 | Applied when gix performs the operation — core.autocrlf on checkout, diff.renames on diff, and the rest of its typed config tree. |
| Distinct union | 152 (15%) | Union of the two, de-duplicated against git's key list (15 keys overlap). |
The remaining ~851 keys are dominated by configuration for commands that are not yet ported or are server-side, where reading the key would be meaningless without the command behind it: receive.* (94), fsck.* (77), sendemail.* (42), and the gui / gitweb / imap / gitcvs / uploadpack / instaweb / trace2 families. Coverage of the config surface reachable from ported commands is far higher than the headline 15%.
Dependency posture
Dependencies are kept foundational and durable. The src/extensions crate has a deliberately small direct-dependency surface; everything git-related comes from the vendored, owned gitoxide tree rather than external crates.
| Dependency | Role |
|---|---|
gix (path: src/ported/gix) | The vendored gitoxide library — repository discovery, refs, index, submodules, transport. Built with blocking-http-transport-reqwest-rust-tls for pure-Rust HTTPS fetch. |
| anyhow | Error propagation for the dispatch layer and terse stderr reporting. |
| std::os::unix::net | The zdaemon unix-socket coordinator and RepoLock client (standard library, no dependency). |
Compatibility & longevity
Drop-in on PATH
The binary is named git and reads the same on-disk .git, so it slots under existing tooling without config changes for ported subcommands.
Cross-architecture
macOS aarch64 and Linux x86_64 / aarch64 — the same targets the meta-repo agents run on. No C toolchain required for the TLS fetch path.
Crash-safe locking
Socket-EOF auto-release means a killed lock holder cannot wedge the repo — a strict improvement over a stale index.lock that must be removed by hand.
Owned vendor tree
gitoxide is vendored in-tree, not pulled from a registry, so the build is reproducible and the port surface is under direct control.
Links
- Docs — index.html
- Performance architecture — index.html#performance — diagrams of how the read path beats stock git
- Source — github.com/MenkeTechnologies/zvcs
- Issues — github.com/MenkeTechnologies/zvcs/issues
- gitoxide — github.com/GitoxideLabs/gitoxide (the ported git library)
- License — MIT (LICENSE)