the binary is named git and shadows stock git

August 30, 2026 · View on GitHub

███████╗██╗   ██╗ ██████╗███████╗
╚══███╔╝██║   ██║██╔════╝██╔════╝
  ███╔╝ ██║   ██║██║     ███████╗
 ███╔╝  ╚██╗ ██╔╝██║     ╚════██║
███████╗ ╚████╔╝ ╚██████╗███████║
╚══════╝  ╚═══╝   ╚═════╝╚══════╝

Rust Docs Built on status license

[GIT, SHADOWED AND SUPERSET — ONE RUST BINARY NAMED git]

"gitoxide already ported git to Rust. zvcs does the thing git structurally cannot: fair mutual exclusion, self-attaching submodules, forward-only pointer bumps — a VCS built for a meta-repo under many concurrent agents."

zvcs is a git-shadowing superset VCS. It ships a single Rust binary named git that shadows stock git on PATH and serves subcommands natively via vendored gitoxide crates — there is no fork/exec of stock git. On top of git compatibility it adds the zvcs superset: coordination verbs stock git cannot have, aimed at the exact failure modes of driving a large meta-repo of submodules under many concurrent automated agents.

The world's-first leg is not "git in Rust" — gitoxide is already that. It is the superset coordination layer: a fair FIFO index-lock daemon, a reconcile-to-mainline attacher, and forward-only gitlink bumps, served from the same binary that answers rev-parse.

Read the Docs · Engineering Report · Parity Report · gitoxide


Table of Contents


[0x00] OVERVIEW

zvcs is a from-source VCS, not a wrapper. The git binary discovers and reads the same on-disk .git directory stock git does, so tools already on PATH (RustRover, gh, cargo) see identical behavior. Git-compat porcelain is ported incrementally on top of the vendored gitoxide (gix) library; when a subcommand is not yet ported the binary errors terse rather than falling through to stock git — this is a from-scratch engine, not a shim.

The reason zvcs exists is the superset. The meta-repo it targets is a shell of git submodules driven by up to 16 concurrent automated agents. Stock git handles that topology poorly in three specific, reproducible ways, and each superset verb closes one of them.

[0x01] THE PROBLEM IT SOLVES

Stock-git failure modezvcs answer
index.lock contention. Git guards index writes with an O_EXCL lockfile; a contended writer does not wait, it fails (Unable to create '.git/index.lock'). Under N agents that is a thundering herd of retries with no fairness.zdaemon — one machine-wide daemon with a per-repo FIFO lane replaces the flock. A contended writer blocks in arrival order and is answered GRANTED only at the head of its repo's lane; first-come-first-served, no starvation, unrelated repos fully parallel. A foreign holder of git's own lockfile (stock git, an IDE, a hook) is invisible to that lane, so it is waited out (bounded) and then the command is queued as a job — never surfaced as a lock error.
Detached HEAD by default. git submodule update leaves every submodule on a detached HEAD, so committed work is orphaned unless the caller re-attaches by hand.zsync + the daemon's attach-scan reconcile each submodule to its tracked mainline (origin/main, else origin/master) and keep HEAD attached — even a dirty detached HEAD is rescued in place (no-clobber ref op). Fast-forward only.
Constant modified: <sub> (new commits) markers + stale pointers. Every submodule commit dirties the parent's gitlink; a blanket git add can also move it backwards.zbump + autobump — forward-only gitlink bumps, committed (clears the marker), coalesced, done by the daemon on a file-watch so agents never touch the root. Never regresses a pointer.
Agents colliding on one shared tree. N agents editing one meta tree collide on files, index, and HEAD.zworktree — one command gives each agent a private, object-sharing worktree of the whole submodule tree; complete isolation, no re-clone.

[0x02] BUILD

Homebrew installs the binary as zvcs, so it never clobbers the git formula — shadowing is opt-in, and zvcs zshadow is the whole opt-in:

brew install menketechnologies/menketech/zvcs
zvcs zshadow                  # install ~/.zvcs/{bin,man,completions}, print the shell lines
eval "$(zvcs zshadow)"        # …apply them here, or paste them into ~/.zshrc
git zdoctor                   # in a new shell, `git` is zvcs

Re-run zvcs zshadow after a brew upgrade so the ~/.zvcs/bin/git symlink follows the new build. From source:

git clone https://github.com/MenkeTechnologies/zvcs
cd zvcs
cargo build
# the binary is named `git` and shadows stock git — put it first on PATH
export PATH="$PWD/target/debug:$PATH"
git rev-parse HEAD

git zshadow then installs the permanent shadow — a git symlink and the git-<verb> dashed links in ~/.zvcs/bin, every man page in ~/.zvcs/man, the HTML documentation set in ~/.zvcs/share/doc/git-doc, and the forked zsh _git in ~/.zvcs/completions — and prints the three shell lines that activate them (stdout is shell code only; the summary goes to stderr):

git zshadow                 # install, then print the lines
eval "$(git zshadow)"       # …and apply them to this shell
git zshadow --print --all   # print without installing (to paste into ~/.zshrc)

The workspace has two members-by-convention: src/ported (the vendored gitoxide crates, a self-contained workspace excluded from the root) is consumed by src/extensions (the zvcs crate) as a path dependency. gix is built with the blocking-http-transport-reqwest-rust-tls feature so zsync's reconcile fetch runs over HTTPS on a pure-Rust TLS stack — no curl/openssl C toolchain.

[0x03] SUBCOMMANDS

Two namespaces share one dispatch table (src/extensions/src/dispatch.rs):

  • superset verbs (z*) — the novel coordination layer.
  • git-compat porcelain — stock git subcommands served via gitoxide, ported incrementally.
Verb groupVerbsWhat it does
Coordinationzdaemon zsync zbump zupsingleton daemon; reconcile submodules; forward-only bumps; zup brings the whole tree (parent + nested submodules) to latest origin/main
Stashzstash zunstash zstashespark/restore uncommitted work across the whole submodule tree as one unit
Repo indexzrepos zreindexmachine-wide index of every git repo (retires a shell repo-list)
Async queuezcommit zpush zsubmit zjobs zjobfire-and-forget commit/push/arbitrary-command jobs on the daemon's worker pool + ledger (zjob stop/restart); zsubmit [--] <cmd> ships any command and returns a job id
Multi-agentzclaim zunclaim zwhoadvisory per-repo leases so agents don't collide
Observabilityzstatus [--all] zlog zundoinstant machine-wide status; cross-repo timeline; one-step rewind
Snapshotszsnapshot zrestore zsnapshotstree-wide restore points across all submodules
Worktreeszworktree add/list/removeper-agent isolated, object-sharing worktree of the whole tree
Fan-outzforeach [selectors] -- <cmd>run a command across all/subset of indexed repos, in parallel (selectors: --repo/--dirty/--ahead/--behind/--claimed/--session)
Selectorszselectorsthe shared [selectors] grammar the fleet verbs accept — a bare path pattern, --dirty/--ahead/--behind, --claimed/--session, ANDed (git help zselectors)
Parallel queryzheads zdirty zbranches ztags zremotes zsize zage zcommits zpristinenative, fork-free reads fanned across every indexed repo — HEAD/branch, dirty set, branches, tag counts, remotes, .git sizes, HEAD age, commit depth, the clean-and-in-sync set; all honor the [selectors] grammar
Parallel pullzpull [selectors]fetch + fast-forward every indexed repo in parallel (ff-only, same native reconcile as zsync; dirty/diverged skipped)
Reviewzreview [selectors]aggregate the pending uncommitted change across the fleet — every dirty repo's status --short + diffstat, grouped on one screen (the read-side companion to zcommitall)
Search & analyticszgrep [-i] <pattern> zahead zbehind zunpushed zunpulled zauthors zhot [<days>] zconflictscross-repo, fanned in parallel — regex content search; upstream ahead/behind counts and the detailed per-repo unpushed/unpulled commit lists; commit counts by author; repos by recent activity; repos mid-merge/rebase/conflicted
Securityzscan [selectors]parallel secret scan of tracked content across the fleet — AWS/GitHub/Slack/Google/JWT/PEM keys + high-entropy key = "…" assignments, path:line:pattern per hit; exits non-zero when any found, so it drops straight into a pre-push / CI gate
Signatureszsigs [selectors] [-n <count>]fleet commit-signature check — flags unsigned/bad/unverifiable HEAD commits (git's %G? codes) across the tree; signature + payload reconstructed natively, verified via gpg (the tool git itself uses), exits non-zero if any found. Also adds the %G? / %GK pretty-format placeholders to git log
Parallel mutationszfetch zgc zfsck zprune zreset zabort zcheckout <branch> ztagall <tag> zcommitall -m <msg> zpushall zclean -frun a git operation across every indexed repo in parallel (via this binary's own porcelain + fair lane); zreset parallel-resets, zabort aborts an in-progress merge/rebase/cherry-pick/revert in mid-op repos; ops that don't apply are skipped, not forced
Re-attachzattach [selectors]put every detached-HEAD indexed repo back on its mainline branch — the state git submodule update leaves a tree in, and the state in which a commit is written unreachable. The same local, no-clobber attach zsync applies to submodules (ensure_attached): never contacts a remote, never moves the checked-out commit, never touches the worktree or index, so it is safe on a dirty repo. A repo whose local mainline is ahead of or diverged from HEAD is refused, one with no main/master is skipped, and the tally reports each class
Remoteszremote set <old> <new> [selectors] [-n]fleet-wide remote-URL rewrite — replace substring <old> with <new> in every matching remote across the tree (org move / host change / ssh↔https); -n / --dry-run previews without applying
Rollbackzrollback [selectors] [--steps <n>] [--apply] [--force]fleet-wide undo of the last mutating op — resolve HEAD@{n} from the reflog and reset --hard to it (the multi-repo zundo); dry-run unless --apply, and skips dirty / mid-op / would-diverge-from-remote repos unless --force
Live monitorztop [selectors]full-screen htop-style fleet monitor — every indexed repo, sorted by churn, read from the daemon status cache each frame (scales to thousands). 31 htoprs colorschemes with a live picker (c) + palette editor (~), F1 help, F6 sort-by-column, / search, p full-path toggle
Command feedzcommandslive feed of every git command run across the fleet — time, pid←ppid (which agent), cwd, and argv, appended to its own $ZVCS_HOME/commands.log; a single stat gates the hot path when off
Auditzaudit [--agent <ppid>] [--repo <s>] [--cmd <s>] [--mutating] [--summary]queryable audit trail over that same command log — filter by agent (which ppid ran it), repo, or command; --mutating keeps only state-changing commands; --summary tallies per-agent and per-command; --json for tooling
Event feedzevents ztailone live semantic feed of commits, reconciles, and status changes across the whole tree (from the append-only events table)
AOPzintercept before|after|around <pattern> -- <cmd>aspect-oriented hooks on git commands (ported from zshrs) — run advice before/after/around any matching command, with INTERCEPT_NAME/ARGS/CMD (and STATUS/MS/US for after) in the environment; an around advice runs eval "$INTERCEPT_CMD" to proceed
Pluginsznative add|load|remove|list|info|update|gc <SOURCE|NAME>the plugin package manager (ported from zshrs) — install third-party subcommands from one content-addressed store under $ZVCS_HOME/pkg, in two kinds: a native plugin is a Rust cdylib compiled against the stable znative C ABI and dlopened, a script plugin is a repo of git-<verb> executables. Sources are owner/repo, git+URL or path:DIR, @ref-pinnable and SHA-256 integrity-pinned; a plugin verb resolves after built-ins and before the git-<verb> PATH lookup, and a native plugin may override an existing verb and delegate back to the original
Policyzguard/zpolicy deny|warn <pattern> [--when <pred>]declarative fleet-wide command policy — refuse or warn on a matching git command before it runs (the veto evolution of zintercept). Glob on the command line (deny 'push*--force*', warn 'rm*-rf*') plus repo-state predicates (--when detached|dirty|protected|unsigned, e.g. deny 'commit*' --when unsigned requires signed commits). list/rm/clear/test; a single stat on the hot path when no rule is set
Autonomy configzconfig [<name> on|off|<n>]toggle the daemon's feature switches from the CLI (autoreconcile, autobump, autostatus, …, statusinterval, watchmru); all on|off flips them together, reloading a running daemon
Automationzpin zunpin zbroadcast zhandoff zon zsince zcontend zwaitfor zgraph zrewindfreeze repos from autonomy; inter-agent messaging and claim hand-off; run a command on a semantic feed event; a time-window feed; live agent-vs-agent contention; block until a tree-wide state holds; fleet topology (dup groups)
Schedulerzsched add <duration> -- <cmd> zsched list/rm/clear/rundaemon-hosted cron for the tree — fire any command on an interval (add 5m -- git zpull --dirty); the CLI owns the schedule file, the daemon reads it each tick and fires due jobs (add/rm take effect within a tick, no reload)
Coordinationzwait [<path>] zqueue zbarrierthe join side of the async queue — wait for one repo's jobs to drain, list what's queued/running, or block until the whole queue is idle
Profilingzstale [<days>] zlast zbig [<n>] zfiles zdivergent zorphansnative, fanned in parallel — abandoned repos, most-recently-committed, largest tracked files, file counts, repos diverged from upstream, repos with no remote
Multi-agent viewzsessions zidle zdashboard zppid zprocssessions ranked by repos held; repos free to pick up; an instant one-screen health summary aggregated from the status cache + ledger (dirty/ahead/behind/diverged/detached/no-upstream + claims/sessions/queue/procs) — no live walk, so it scales to thousands of repos like zstatus --all; a per-process commit tally (zppid) — the durable process responsible for each commit (found by walking up past the throwaway per-command shells to the real agent/program/login-shell), with its pid, command, cwd, live/dead state, and commits landed; and a per-process command breakdown (zprocs) — how many of each mutating verb (commit/push/add/merge/rebase/…) every process has run
Precomputezprecache [-n <commits>] [-q]fill the log caches — abbreviations and the per-file line tallies --stat/--numstat/--shortstat/--name-status need — for the newest commits, so those formats read a memory-mapped image instead of the object store. All of it is a pure function of immutable objects, so an entry is correct forever. The daemon does this on its own whenever a watched repo's refs move (zvcs.precache, on by default); this verb is the same pass on demand, e.g. after a clone or a fetch that landed while the daemon was down. git cannot do this at all — no part of git runs between two commands, so its first log --stat after a fetch always pays in full
Hookszhook set/unset/show/list/testmanage & test the current repo's ref-change hook (zvcs.hook); zvcs.autohook fires each repo's own local hook
Triggersztrigger DIR <cmd> [--throttle <dur>] ztrigger list/rm/test/tail/topwatch any directory (git repo or not) and run a command on any file change under it — command runs with the dir as cwd and $ZVCS_DIR set; a leading-edge throttle (default 500ms) collapses the event burst of one file action into a single fire; tail streams fires live, top is an in-place fire-rate HUD
Watchzwatch DIR zwatch list/rmwatch any directory and log each change to the daemon log (a trigger with a built-in logging command)
Consolezrepl zbanner [--color|--no-color]interactive line console over every command — each line runs as git <line>, so the z* verbs and all git porcelain work alike (startup stats banner + Tab completion of every verb); zbanner reprints that banner on demand, with every stat re-read at call time
Shellzcd zpwd zls zenv zunset zecho zmkdir ztouch zrm zcp zmv zcat zlnshell builtins so zrepl drives like a shell — zcd/zenv/zunset mutate the console's cwd/environment and persist across lines; zls is a git-aware listing (per-file status like eza --git); zmkdir/ztouch/zrm/zcp/zmv/zcat/zln are native filesystem commands (zrm/zmv are on-disk, distinct from git rm/git mv)
Discoveryzverbslist every extension verb and its one-line usage (sourced from each verb's own -h)
Setupzshadow [<dir>] [-n|--print] [--all]install the whole ~/.zvcs shadow — git shim + git-<verb> dashed links in ~/.zvcs/bin, man pages in ~/.zvcs/man, the HTML doc set in ~/.zvcs/share/doc/git-doc, the forked zsh _git in ~/.zvcs/completions — then print the PATH, MANPATH, and fpath lines on stdout (shell code only, so eval "$(git zshadow)" works; a line the environment already satisfies is printed commented out)
Healthzdoctorenvironment health check — git shadow on PATH, daemon, ledger, man pages, MANPATH, dashed forms, installed completion (OK/WARN/FAIL, exits non-zero on FAIL)
git-compatevery stock subcommanddispatched natively; depth varies — see the parity report

Scripting output. Every read verb — the query, analytics, discovery, and coordination views (zheads, zdirty, zrepos, ztags, zstatus --all, zjobs, zwho, zcontend, zgraph, zsince, zdashboard, …) — takes --json for NDJSON: one JSON object per line, so git zdirty --json | jq -r .repo streams and jq -s slurps to an array. The live feeds (zevents/ztail, zcommands) stream --json too. An integration test parses every verb's --json output so it can't drift.

Every subcommand stock git ships has a dispatch arm, so nothing reaches the not yet ported path; there is no fallthrough to stock git. Dispatching is not the same as agreeing with git, and the two are measured separately — an unimplemented flag errors terse rather than guessing, and the parity harness scores that as a failure.

External and dashed forms (full shadow). An unknown verb follows git's exact precedence — builtin → git-<verb> on PATH (git.c's execv_dashed_external) → help_unknown_cmd — so third-party subcommands (git fuzzy, git lfs, git flow, …) work under the shim; without this, git-fuzzy breaks (it recurses through git fuzzy helper on every keystroke). The binary also honors dashed invocation: run as git-<verb> it strips the prefix from argv[0] and dispatches <verb>. git zdashed [<dir>] installs a git-<verb> symlink for every verb into <dir> (default ~/.zvcs/bin), so the dashed forms exist once stock git is removed (git zshadow does the same as one step of the full install). Verbs come from the dispatch tables, so the set never drifts.

Run the harness to see current depth per subcommand:

cargo run -p zvcs-parity                 # curated corpus
cargo run -p zvcs-parity -- --fuzz 12    # plus generated flag combinations and workflows
cargo run -p zvcs-parity -- --fuzz 12 --fuzz-sequences 0   # flag combinations only
cargo run -p zvcs-parity -- --fuzz 12 --list-cases         # print what that run would execute
cargo run -p zvcs-parity -- --alt-git /usr/bin/git         # name the second oracle
cargo run -p zvcs-parity -- --alt-git-every-case           # ask it about passing cases too
cargo run -p zvcs-parity -- --concurrency                  # concurrent writers and held locks

It builds fixture repositories with stock git, runs each invocation against both binaries, and compares stdout, exit code, and the resulting repository state.

A fourth comparison covers what the first three structurally cannot. Every state probe asks stock git what a repository means and recomputes the answer from scratch, so it is blind by construction to everything a repository holds beyond its logical content — the index cache-tree, the untracked cache, the split index, pack indexes, bitmaps, the multi-pack-index — because all of those are accelerators the logical view can rebuild without. That blindness shipped a defect: zvcs add destroyed the index cache-tree, so every stock write-tree, commit or status afterwards had to rebuild it, and the case scored a match on stdout, exit code, refs, objects, index entries and config alike. So for every case that writes under the git directory, both finished repositories are handed back to stock git — fsck --strict for validity, write-tree for whether stock can use the index as written or has to repair it first — and the same write-tree question is put to the binary under test, since a port that cannot read what git writes is the same class of bug as one that writes what git cannot read. A disagreement there is reported as its own verdict and its own column, because "the port wrote a structure git would not have" is a different finding from "the port printed the wrong thing". The probe never mutates what it measures: GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES redirect every write it would make into a scratch directory, and each run prints how many of its cases the gate opened for.

Cases come in two shapes. Most are a single invocation against a pristine copy of a fixture. The rest are sequences — an ordered list of invocations against one repository, compared after every step — because git's stateful operations are multi-step by construction and the state that makes --continue, --skip and --abort work (.git/sequencer/todo, .git/rebase-merge/, MERGE_HEAD, BISECT_*, ORIG_HEAD, the reflog trail) is written by one invocation and read by the next. A sequence stops at the first divergence and reports it against that step's own argv, so a conflicted cherry-pick whose --continue writes the wrong reflog message is named as step 6 of 7 rather than as a difference that appeared somewhere in a workflow. Every step after the first therefore runs on a premise already proven identical on both sides.

Both shapes are also generated. --fuzz <n> draws n invocations per subcommand from grammars extracted from git's own documentation; the same flag draws n workflows per entry point, and --fuzz-sequences <n> sets that count on its own because a sequence costs several invocations where a case costs one. A generated workflow is not a random chain of commands — that would be two independent cases sharing a directory. Each one encodes a dependency: park the repository in an interrupted operation and walk the resumption verbs including the illegal transitions (rebase --continue with a cherry-pick in progress); run a mutating invocation and then the readers whose answer it should have changed; run an operation and then its inverse, where the end state must equal the start. Every draw is a pure function of the seed, so a reported step replays exactly.

Configuration is sampled as two dimensions, not one. Git's behaviour is a function of its configuration at least as much as of its argv, and it reads that configuration from a sequence of sources — /etc-style system, global, .git/config, .git/config.worktree, .gitmodules, GIT_CONFIG_KEY_<n>, and -c — each a different parser with a defined precedence between them. A draw picks a set of keys that interact (core.autocrlf with core.eol and core.safecrlf; diff.noprefix with diff.srcPrefix and diff.mnemonicPrefix; feature.manyFiles with index.skipHash; push.default with branch.<name>.merge) and then a scope for each one, so one key set twice in one file is the last-value-wins rule and one key set in two scopes is precedence — neither of which a -c-only harness can express. The file scopes also carry malformed content, which is where git's line-numbered bad config line 12 in file .git/config lives; -c has no line to number. Each case id names the scope every setting came from, so a failure is reconstructed by hand from the id alone.

Every dimension above runs one writer against a repository nobody else is touching, which is the one condition under which lock handling cannot be wrong — and not the condition this port ships into, where sixteen agents and stock git share a worktree. --concurrency adds the two dimensions that ask about the rest, and neither can be a byte comparison. Stock git guards the index with an O_EXCL lock and does not wait, so its losers die; zvcs routes contended writers through a daemon FIFO so they queue and land. Under six-way contention stock stages one file and zvcs stages six, and diffing that reports the fair queue as a six-way regression.

So each asserts an invariant instead. Concurrent writers — N processes released against one repository at the same instant — assert that a writer that exits 0 has done its work, which stock satisfies by failing its losers honestly and which the port may satisfy by queueing or serializing. Every case also runs against stock, and an invariant stock breaks too is not scored, so the bar stays git's. Deferral is not loss: a writer that announced a queued job is given a settle window before its effect is required. Foreign locks plant a lock a killed process or a concurrent pack-refs would have left, and assert only the indefensible direction — whatever stock completes with the lock held, the port must complete too. Doing more than git under contention is the feature; doing less is an availability failure. When both refuse, their exit codes must match, which is the one axis the queue cannot excuse. A lock that is released part way through the wait is its own case: it is the only shape that measures the queue succeeding rather than merely expiring, and it is where the port's differentiator is either real or it is not.

Writers within one case are heterogeneous where the interesting races are: a case names one or more roles, writer i takes role i mod roles, and each role carries its own effect, because an add and a commit have finished different work. A case may also pin the port's daemon-less fallback by pointing ZVCS_SOCK at a path that cannot exist, so the run measures that path deliberately rather than whichever one the machine happened to offer. Every writer is bounded: its side has a deadline, its output goes to a file rather than a pipe nobody drains, and expiry kills the whole process group, so the git a writer forked dies with it. A killed writer is scored as a failure, not skipped — which is how a commit that never returns under a held branch lock became a finding instead of a wedged run.

Both are opt-in, and the only dimensions that are: a case costs seconds rather than milliseconds, and its result is a distribution rather than a fact, since a race reproduces on some runs and not others. A defect either finds still fails the run — reporting success for work that never happened is not a softer failure than a stdout diff — but a clean run is evidence, not proof. A case where no writer succeeded scores Vacuous rather than passing, because an invariant that held for want of any writer that exited 0 has measured nothing.

A case that fails is then re-run on both sides, because a byte comparison is only meaningful between values each binary can produce twice. A case stock git cannot reproduce itself (a temp name, a wall-clock stamp) is excluded from the parity denominator; a case zvcs cannot reproduce itself is reported in its own zvcs-flaky bucket and counted as a failure, never as a match. Both are named individually under --verbose, so an exclusion can never read as coverage.

The stock git it measures against is resolved explicitly, never through PATHPATH finds this binary on any machine where the shadow is installed, and comparing zvcs with zvcs measures nothing. Each of /usr/bin/git, /opt/homebrew/bin/git and /usr/local/bin/git is probed, any that answers the superset verb zverbs is rejected as this binary wearing git's name, and the newest of the rest wins. One older than the version this port targets is refused outright rather than measured against: the two disagree about real behaviour, so its numbers would read like the others while describing a git nobody runs. Name a specific binary with ZVCS_STOCK_GIT to override the search.

Measuring against one git leaves a question it cannot answer. When zvcs differs from the newest installed git, either the port is wrong or git changed between releases and the port reproduces the older behaviour — and both read as the same stdout-diff with the same diff attached, so one of them costs an afternoon spent matching a behaviour upstream moved on purpose. So when a second real git is installed, a failing case is run against it too and the three answers are classified together: the two gits agreeing is a port defect corroborated by two independent releases; the two gits disagreeing while zvcs reproduces the second one is a version difference, reported as version-skew in its own column; the two gits disagreeing while zvcs matches neither keeps the verdict it earned, because no choice of target version makes that case right. A second oracle killed by the case timeout concludes nothing in either direction. Every case where the two gits answered differently is listed by name, which is a useful artifact on its own: it is the set of behaviours where "parity" has no single answer, and therefore the set of curated expectations that may be pinned to the wrong side of a git release.

A version difference is counted as a failure and stays inside the parity denominator, and the report prints the forgiving number on a line of its own rather than folding it into the headline. Excluding those cases would be an exclusion the binary under test can trigger — the condition is "the gits disagree and zvcs reproduces the older one", and the second half is zvcs's own output, so a port that reproduced the older git on its hardest cases would shrink its own denominator. The parity percentage is therefore bit-identical with and without a second oracle: the dimension can move a case from one failure bucket to another and can never move one into or out of the numerator. Cost is one extra invocation on a case that already failed and nothing at all on one that matched, and each run prints how many cases it paid for. The second git is discovered automatically (the newest installed git of a different version than the primary oracle); --alt-git <path> or ZVCS_STOCK_GIT_ALT names one, --no-alt-git or ZVCS_STOCK_GIT_ALT=none switches it off, and a machine with one git behaves exactly as it did before the dimension existed — no third invocation, no new report line, no new column.

[0x04] THE SUPERSET VERBS

Over a hundred superset (z*) verbs share the one binary, grouped into a few families below. git zverbs prints the complete, authoritative list with each verb's one-line usage (each verb also answers -h), and git help <zverb> opens its man page — so the live set is always a command away and never drifts from the prose here.

Coordination. git zsync [<path>...] reconciles submodules to their tracked mainline (origin/main, else origin/master), fast-forward only, leaving HEAD attached; a dirty worktree is skipped. git zbump [<path>...] advances the parent's gitlink to each submodule's HEAD only on a fast-forward, then commits the coalesced bumps (clearing the (new commits) marker). git zdaemon <start|stop|status> controls the singleton coordinator (below). git zdaemon <start|stop|restart|reload|status|info|ping|log> is the full daemon control surface — restart/reload respawn it (re-reading config), ping is a scriptable liveness check, info prints pid/socket/paths/config, and log [-n N] [-f] shows/tails ~/.zvcs/zvcs.log — every line there opens with a local RFC-3339 stamp (2026-08-20T14:03:11-07:00) ahead of its [zvcs ...] tag, so a day's activity is a plain grep and a stall is visible in the tail. git zup brings the whole tree — the top-level repo and every nested submodule — to latest origin/main (fetch + fast-forward, attached; dirty/diverged skipped).

Stash. git zstash [<name>] parks uncommitted work across every dirty repo in the tree as one named unit, git zunstash [<name>] restores it (LIFO), and git zstashes lists them. Restore applies onto the same commits it was stashed on (3-way apply onto a moved HEAD is the not-yet-ported porcelain territory; a repo whose HEAD moved is reported and its stash kept intact).

Repo index. git zreindex [--sync|--async] [<path>...] crawls for git repositories and records them in the ledger, pruning ones deleted from disk; git zrepos lists them (pipe-clean, one path per line) — a drop-in for a shell git-repo index. The walk is parallel and skips the mounts that would hang or loop a whole-device scan (zreindex /): kernel pseudo-filesystems, automounted/network volumes, and the macOS data-volume firmlink reflection. At a terminal it runs async by default — the crawl detaches to the background (results → zvcs.log, follow with git zdaemon log -f) and the prompt returns immediately; piped or scripted it runs inline so indexed N, pruned M stays on stdout. --sync and --async override the default. Pruning is not something you have to remember to run: every crawl ends with it, and the daemon sweeps the whole index at startup and hourly, so repos deleted (or thrown away with a temp directory) leave the index on their own rather than inflating the count zrepos and zdashboard report.

Async queue. git zcommit <paths> -m <msg> [--push] and git zpush submit fire-and-forget jobs to the daemon (with a network-free / live ls-refs push pre-flight that refuses a non-fast-forward before enqueue) and return a job number; git zjobs / git zjob <id> show the ledger, and git zjob stop|restart <id> control a running/queued job. Falls back to synchronous execution when no daemon is running.

Multi-agent. git zclaim [<path>] takes an advisory per-repo lease for the session (ZVCS_SESSION), refusing if another agent holds it; git zunclaim releases and git zwho lists who holds what.

Observability. git zstatus reports the current repo's status live; git zstatus --all reads every indexed repo's status from the daemon-maintained cache (zero-walk). git zlog merges every repo's reflog into one machine-wide timeline; git zundo [<path>] rewinds a repo one step (reset --hard to the previous HEAD, refuses on dirty).

Live monitoring. git ztop is a full-screen, htop-style monitor of the whole indexed tree: every repo with a churn bar, HEAD, state, and last-change age, sorted so the repos changing right now rise to the top. It reads the daemon-maintained status cache each frame — no live walk — so it stays responsive across thousands of repos. It ships the 31 htoprs colorschemes with a live picker (c) and a palette editor (~), an F1 help overlay, sort-by-column (F6, </>, I), incremental / search, and a p full-path toggle; the theme choice persists. git zcommands is a live feed of every git command run across the fleet — because the one binary is the sole dispatcher, each invocation logs its time, pid←ppid (so you can tell which agent ran it), cwd, and argv to its own $ZVCS_HOME/commands.log; a single stat gates the hot path when logging is off. git zevents (alias ztail) is one live semantic feed of commits, reconciles, and status changes across the tree.

Interception (AOP). git zintercept before|after|around <pattern> -- <cmd> registers aspect-oriented advice on git commands, ported from zshrs. A before hook runs a shell command before every matching git command, an after hook runs after it (with the command's exit status and timing in INTERCEPT_STATUS/_MS), and an around hook replaces it — running eval "$INTERCEPT_CMD" to proceed with the original. The advice sees the intercepted command through INTERCEPT_NAME / INTERCEPT_ARGS / INTERCEPT_CMD; the pattern matches the git subcommand (commit, commit *, */all). Registrations persist to $ZVCS_HOME/intercepts.tsv and load at dispatch, gated by a single stat when none are set. list / remove <id> / clear manage them.

Plugins. git znative add <source> installs a plugin that adds subcommands to this binary, from one content-addressed store under $ZVCS_HOME/pkg — ported from the zshrs package manager of the same name. Two kinds share that store: a native plugin is a Rust cdylib compiled against the stable znative C ABI and loaded with dlopen, and a script plugin is a repository of git-<verb> executables. A source is owner/repo, github:owner/repo, git+URL, or a local path:DIR, optionally @ref-pinned so update re-fetches that exact tag or commit; each install is SHA-256 pinned. Installing needs no second VCS on the machine — the clone runs through this binary's own native clone.

A plugin verb resolves after built-in verbs and aliases and before the git-<verb> PATH lookup, so it wins over a same-named script on PATH and can never shadow a git command by accident. A native plugin may instead override an existing verb, running in place of the built-in implementation and calling the host back to run the original when it wants it — so it can wrap git blame without reimplementing it. git znative itself can never be overridden.

Nothing is loaded until a verb proves to belong to a plugin: the verbs a native plugin registers are discovered by loading it once at install time and recorded in the index's two derived tables, which are deleted rather than written empty when there is nothing in them. A machine with no plugins installed therefore pays two failed stats per command. See ZNATIVE.md for the ABI, and three runnable plugins: plugin-hello (the minimum, plus a verb override), plugin-wip (git wip composing add/commit through the host, no fork), and plugin-todo (the script kind — a git-todo shell script run from the store).

Autonomy config. git zconfig toggles the daemon's feature switches from the CLI instead of editing ~/.gitconfig: with no argument it lists every setting and whether it is on; git zconfig <name> on|off (or a count) sets one, git zconfig all on|off flips them together, and a running daemon is reloaded so the change takes effect at once.

Snapshots. git zsnapshot <name> records the HEAD of the repo + every nested submodule as one restore point; git zrestore <name> resets the whole tree back to it; git zsnapshots lists them. A restore discards tracked changes and keeps untracked files, and because a snapshot holds commit ids rather than branch names it moves whichever branch is checked out when it runs.

Worktrees. git zworktree add <name> provisions a complete, object-sharing, isolated worktree of the repo + all nested submodules (each on a zwt/<name> branch) at ~/.zvcs/worktrees/<name>/, so each agent gets a private tree that can't collide with any other — no re-clone. list / remove <name> manage them.

Console. git zrepl opens an interactive line console. Each line is run exactly as git <line> would be, so it drives every dispatchable command — the z* superset verbs and every git-compat porcelain command alike (the latter operating on the current repo) — doubling as a live daemon/ledger console. On a tty it opens with a stats banner and edits with Tab-completion of every verb plus persistent history; piped stdin falls back to a raw reader so scripts stay usable. git zbanner [--color|--no-color] prints that banner again on demand — the logo plus os/arch/pid, cores, indexed repos, and the superset/git-compat/total verb counts, all read at call time, so a long-lived console can refresh the numbers instead of showing what was true when it opened.

Shell builtins. Because the console is one long-lived process, a handful of shell verbs make it navigable like a shell: git zcd [<dir>|-] changes the working directory (persisting across lines, ~/- supported), git zpwd prints it, and git zls [-alrt] [<path>] is a git-aware listing — each entry carries a two-column git status field (staged, then unstaged) like eza --git, a directory folding the status of the paths under it, colored from the same palette eza reads (LS_COLORS for file kinds/extensions, EXA_COLORS/EZA_COLORS for permissions, size, date, and git columns). git zenv [<NAME=VALUE>...] prints, sets, or queries environment variables — anything set persists so every later git line sees it — git zunset <NAME>... clears them, and git zecho [-n] <arg>... prints its arguments. The mutating verbs (zcd/zenv/zunset) only affect this process, so they are aimed at the console. Rounding out the set are native filesystem commands — git zmkdir [-p], git ztouch, git zrm [-r] [-f], git zcp [-r], git zmv, git zcat, and git zln [-s] — so files can be created, copied, moved, and removed without leaving the console. These act on disk (no fork); zrm/zmv are distinct from git rm/git mv, which stage changes in the index.

Discovery & help. git zverbs lists every extension verb with its one-line usage (each verb also answers -h with the same line). git help <zverb> opens a full man page — the pages are generated from a table in src/extensions/src/superset/manpage.rs (one source of truth, covering every verb in SUPERSET_VERBS), written on demand under ~/.zvcs/man and opened with man -M, so it works with no setup. git zshadow (or git zdashed) writes them all up front, so man git-<verb> resolves once ~/.zvcs/man is on MANPATH — the line zshadow prints:

export MANPATH="$HOME/.zvcs/man:$MANPATH"
man git-zsync

git help -w <cmd> opens the HTML manual instead, and resolves it the way the man-page path does: git's own installed documentation first. git help status hands the page name to the viewer chain — every man.viewer in configuration order, then $GIT_MAN_VIEWER, then plain man, with man.<tool>.path naming the program for the three viewers git drives itself (man, woman, konqueror) and man.<tool>.cmd supplying the whole command line for any other. git help -w status opens the git-status.html that same installation laid down — so the two viewers show one manual, and git's asciidoc prose is never re-written here. The installed directory is found from the man page itself (make install puts share/man/man<n> and share/doc/git-doc under one prefix), so no path is compiled in and a host with no git man pages is exactly a host with no git HTML pages.

For everything no git installation holds — the superset verbs, and every page at all on a host without git's documentation — zvcs ships its own set under ~/.zvcs/share/doc/git-doc. It is generated from the same two tables the rest of git help reads — the command-list blocks git help -a/-g print, and the superset manual in manpage.rs — so a verb cannot exist without a page. A z* verb's page is its complete manual; a stock command's page carries git's own one-line description, the category it is filed under, whether this build dispatches it, and the cross-reference to the man page that holds the prose. Pages are written on demand by git help -w, and all at once by git zshadow / git zdashed; nothing is generated at startup.

git --html-path reports whichever of the two the viewer resolves stock pages against — the installed directory when there is one, the generated set otherwise (--man-path and --info-path report ~/.zvcs, which is where this build's man pages and info tree live).

git --html-path                       # e.g. /usr/share/doc/git-doc, else ~/.zvcs/share/doc/git-doc
git help -w status                    # open git's own git-status.html
git help -w zsync                     # write git-zsync.html into the generated set, open it
git -c help.htmlpath=/elsewhere help -w status   # or point it at your own tree

[0x05] THE zdaemon COORDINATOR

zdaemon is one machine-wide daemon (state under ~/.zvcs/, socket ~/.zvcs/zvcs.sock) — the fair replacement for index.lock plus the host for autonomy, the SQLite ledger, and the async job queue. It is reactive: no timers, no polling; a git pull/commit updates local refs, a notify file-watch fires, and the daemon reacts. It never contacts a remote itself.

The lock is per-repo: unrelated repos run fully in parallel; only writers to the same repo serialize, first-come-first-served. Clients reach it through RepoLock::acquire (src/extensions/src/lock.rs), an RAII guard; release is automatic on drop and on socket EOF, so a crashed holder can't wedge a repo. Index writes also go through index.lock via gix-lock for interop with stock git.

With no daemon the lock falls back to a lane file<git_dir>/zvcs-lane.lock, held with flock(LOCK_EX) for the whole command. It cannot be a no-op: an index write is a read-modify-write, and the port's only index lock is the one the writer takes at write time, so two unserialized writers each read the same base index and write back their own copy — the loser's change disappears and both exit 0. (Git avoids this by holding index.lock from before it reads: builtin/add.c calls repo_hold_locked_index(..., LOCK_DIE_ON_ERROR) ahead of repo_read_index_preload().) The lane file is deliberately not index.lock, which gix-lock acquires with Fail::Immediately and which zvcs holding would make zvcs's own writer fail. Exclusion is the kernel's flock, not the file's existence, so a killed holder releases instantly and wedges nothing; a lane a live peer will not release within the wait budget makes the command exit non-zero with a message rather than run unserialized.

A foreign holder of that lockfile — stock git, an IDE, a hook shelling out — is invisible to the lane, and the index writer takes the file with one attempt and no wait. So an index-writing command first waits out a foreign index.lock (2 s, ZVCS_INDEX_LOCK_WAIT_MS overrides, 0 disables); if it is still held, the command is submitted to the queue as a job rather than failing, and runs on the repo's fair lane once the lock clears.

The second contention shape takes no lockfile at all: a ref race, where two writers each committed cleanly and the loser's compare-and-swap on refs/heads/<branch> is rejected because the winner moved it first. That is routed to the queue too (ref moved under another writer — queueing), since a re-run once the winner has landed is exactly what resolves it. A job's own re-run never re-queues itself, so a conflict that keeps losing reports the failure instead of spawning jobs forever.

Wire protocol — line-based over the unix socket:

LineDirectionMeaning
ACQUIRE <id> <git-dir>client → daemonEnqueue on that repo's lane; answered GRANTED at its head.
RELEASE <id>client → daemonCurrent holder releases; next waiter granted.
SUBMIT <json>client → daemonQueue an async job; answered JOB <id>.
JOBSTOP <id> / JOBRESTART <id>client → daemonCancel / re-enqueue a job.
STATUS / STOPclient → daemonSnapshot / shut down.

Autonomous mode + configuration

All autonomy is gated by [zvcs] gitconfig and defaults off, so it runs in the dev environment and nowhere else. Enable it in ~/.gitconfig or a repo's .git/config; stock git ignores the keys:

[zvcs]
    autoreconcile = true            ; reconcile clean submodules to origin/main (reactive)
    autobump      = true            ; forward-only local pointer bumps + commit (kills the marker)
    interval      = 2               ; debounce window (seconds) for coalescing bursts
    autocrawl     = true            ; background repo-index crawl on daemon start
    crawlroots    = /abs/src /abs/wk ; crawler roots (absolute; default $HOME)
    autostatus    = true            ; reactively update a repo's status on its ref-change
    statusinterval = 10             ; continuous status maintainer: any non-zero enables the always-on worker pool; 0 disables
    watchmru       = 512            ; file-watch the N most-recently-used repos so their status updates instantly on change; 0 disables
    hook          = /abs/on-change  ; run on ref-change in any indexed repo (typed event env)
    autohook      = true            ; fire each repo's own local zvcs.hook (no global hook needed)
    worktreebase  = /abs/worktrees  ; base for zworktree (default ~/.zvcs/worktrees)
    precache      = false           ; stop precomputing log caches on ref-change (default on)
    replvimode    = true            ; vi keybindings in the `git zrepl` console (default emacs)

git ztop writes its own topscheme / toppalette keys when you pick a colour scheme in its UI; nothing else reads them.

When anything is enabled, a git invocation auto-spawns the daemon (detached, output to ~/.zvcs/zvcs.log); it watches indexed repos and reacts by attaching detached HEADs, fetch-free reconciling, forward-only autobumping, maintaining status, and firing hooks. A dirty worktree or a diverged/ahead branch is always skipped — autonomy never regresses or clobbers in-flight work. Headless failures are recorded in the ledger and surfaced on your next git command (stderr).

Hooks get a typed environment: ZVCS_EVENT (commit/checkout/merge/pull/rebase/ reset), ZVCS_REPO, ZVCS_GIT_DIR, ZVCS_OLD_SHA, ZVCS_NEW_SHA, ZVCS_REF — enough for "on commit in X, do Y in repo Z" cross-repo rules.

ztrigger watches any directory — a git repo or not — and runs a command on any file change under it. Triggers live in the triggers index (keyed by path), so no git config is involved:

$ git ztrigger ~/Desktop  'say 45'           # any dir works — not just repos
$ git ztrigger ~/src/api  'make test'        # a repo works too (watches worktree + .git)
$ git ztrigger ~/logs 'reload' --throttle 2s # coalesce bursts to one fire per 2s
$ git ztrigger list                          # path <tab> command <tab> throttle
$ git ztrigger test ~/Desktop                # run its command once now
$ git ztrigger rm   ~/Desktop                # remove it

$ git ztrigger tail                          # live stream of fires as they happen
$ git ztrigger top                           # in-place HUD: fires, events, /sec, last
$ git zwatch ~/Downloads                     # watch a dir and log each change

The command runs via sh -c with the watched directory as cwd and $ZVCS_DIR set. One file action emits several filesystem events, so each trigger has a leading-edge throttle (default 500ms, --throttle <dur>, 0 disables): the first event fires immediately, the rest of the burst is coalesced into that one fire — so a save fires once, not five times. The daemon records every fire to ~/.zvcs/fires.log; ztrigger tail streams them and ztrigger top shows a live per-trigger rate HUD (spot a runaway trigger at a glance). It watches only the directories you triggered, so startup stays instant no matter how many repos are indexed. Caveats: it fires on every change under the dir — including a repo's .git churn — and a command that writes back into the watched dir re-fires on its own writes. For a repo's git hook (ref-change semantics in .git/config), use git zhook instead.

[0x06] LAYOUT

PathContents
src/portedVendored gitoxide crates (gix + the gix-* library crates), in-tree. A self-contained workspace, excluded from the root and consumed as a path dependency. The gix/ein CLI binaries and their gitoxide-core backend are removed; git is the only binary.
src/extensionsThe zvcs crate (library + the git binary): main.rs/lib.rs (entry, session_key, notify-on-next-command), dispatch.rs (routing), porcelain/ (git-compat), lock.rs (daemon client), config.rs ([zvcs] settings plus the shared stock-git config primitives, the ordered config walk, and the config-file refusals — bad config line <n> in file <path> and the repository-format check, neither of which -c can reach), repo_settings.rs/default_config.rs/diff_config.rs/status_config.rs/log_config.rs/cmd_config.rs (git's config callbacks, which refuse an unreadable value with git's own diagnostic before the command runs), autostart.rs (daemon auto-spawn), db.rs (SQLite ledger/index), rcache.rs (zero-copy rkyv caches for tree diffs/blames/abbreviations), crawler.rs (repo crawl), jobpool.rs/jobrun.rs/index_commit.rs (async jobs), worktree.rs (checkout helper), and superset/ (zdaemon, zsync, zbump, reconcile, attach, watch, hooks, trigger, ledger, status, oplog, snapshot, claim, queue, repl, zworktree).
src/pluginThe znative plugin SDK — the stable, versioned C ABI (#[repr(C)] structs + extern "C" function pointers) that the plugin host and every native plugin compile against, so the two agree on the exact layout. Deliberately dependency-free.
examplesStandalone plugin crates, built on install by git znative add path:examples/… rather than by this workspace.

[0x07] STATUS & ROADMAP

Early and in active development.

The coordination and superset layers are implemented and tested: the singleton daemon with per-repo FIFO lanes; reactive file-watcher autonomy (attach, autobump-with-commit, fetch-free reconcile) toggled from the CLI (zconfig); the SQLite ledger + repo index with a daemon-maintained status cache; async zcommit/zpush/zsubmit with job control; multi-agent claims and messaging; the parallel fleet layer over the indexed set (fork-free queries, analytics, mutations, all sharing one [selectors] grammar and a bounded worker pool); live monitoring (ztop htop-style, zcommands command feed, zevents semantic feed); AOP command interception (zintercept); machine-wide zstatus; the cross-repo op ledger (zlog/zundo); tree-wide snapshots; typed cross-repo hooks; and per-agent isolated worktrees (zworktree). Each is covered by an integration test, and zvcs↔stock-git interoperability (round-trip read, git fsck, submodule pointer bumps, worktrees) is verified by a regression suite. See DESIGN.md for the architecture and the honest list of partials.

Git compatibility is tracked as two independent numbers, because a subcommand that dispatches is not thereby correct:

  • Coverage — every subcommand stock git ships is dispatched natively.
  • Parity — the share of harness cases whose stdout, exit code, and resulting repository state match stock git exactly, and — for the cases that write to the repository — where stock git still reads both finished repositories the same way.

Parity is the number that matters and it is the work that remains. Depth varies widely per subcommand; some are byte-faithful across their documented flag set, others implement the common flags and bail terse on the rest. A few subcommands are honest skeletons that name the missing substrate instead of pretending: the foreign-SCM bridges (p4, cvsimport, cvsserver, cvsexportcommit, archimport) have no gitoxide backing to port onto. Their argument surface is still git's — the option parsers, usage blocks and exit codes are ported from the stock Perl and Python, so an invocation that never reaches a Perforce or CVS server answers exactly as stock does; only the parts needing a foreign server refuse, by name.

Compressed output is byte-identical to stock. gix-zlib no longer compresses through zlib-rs, whose zlib-ng-lineage match finders produce a valid but different deflate stream at levels 0 through 8. gix_zlib::deflate is a transcription of zlib's own deflate.c and trees.c, so a packfile, a bundle, a loose object and a diff --binary payload are the same bytes git would have written — which also means two clones of one history agree on disk, not merely on object ids. Decompression stays on zlib-rs, where the output is fixed by the format and it is the faster decoder. The cost is confined to compression and is paid where zlib is slower than zlib-ng: pack-objects over this repository (15098 objects, level 6) goes from 4.04s to 4.93s, while loose-object writes at git's default level 1 are unchanged at 0.44s and get 8% smaller, because zlib-ng's level-1 coder trades ratio for speed.

Two limits are structural rather than unfinished work.

A handful of commands print a path into their own installationgit p4's usage embeds sys.argv[0], and git help --all --no-verbose heads its listing with available git commands in '<exec-path>' — so no independent implementation can reproduce those bytes. Matching them would mean reporting some other installation's libexec/git-core as this binary's own exec-path, which would be false. Everything below that heading does match: the command names stock lists are exactly the ones this binary dispatches, laid out through the same column engine at the same width, followed by the same trailer and the same fall-through to the common help. Stock's libexec/git-core shell libraries (git-sh-setup, git-sh-i18n, git-mergetool--lib) are not in that listing on either side — they are not executable, so list_commands_in_dir() skips them.

git version --build-options describes the build that prints it. Stock reports the C toolchain it was compiled and linked against; this binary reports what is true of a Rust binary on gitoxide and omits the rest, rather than copying stock's values into a report about itself. Ten of stock's fifteen lines match. cpu, the no-build-commit line, sizeof-long, sizeof-size_t, default-ref-format and default-hash agree because they are the same facts. SHA-1: SHA1_DC and SHA-256: SHA256_BLK agree because both tokens name a backend category in hash.h and this build is in both categories: SHA1_DC is the collision-detecting one — its three alternatives all read (No collision detection) — and this build's sha1-checked is sha1collisiondetection in git's own bail-out configuration, while SHA256_BLK is the #else against SHA256_NETTLE/SHA256_GCRYPT/SHA256_OPENSSL and this build's sha2 links no crypto library either. The SHA-256 line is backed by a working object format, not just a compiled-in enum: git init --object-format=sha256 writes stock's extensions.objectformat + core.repositoryformatversion = 1 pair, and the objects, packs, index, refs, bundles, pushes and fsck walks over that repository all produce stock's bytes and stock's ids.

rust: enabled disagrees because the answers really are different here, and no honest report can close that line. zlib-rs takes the slot stock fills with zlib, naming the flate library this build does link and the version its lockfile resolved, qualified (inflate only) because deflate here is an in-tree transcription of zlib's deflate.c/trees.c rather than that crate. feature: fsmonitor--daemon, gettext, libcurl and OpenSSL are absent because no such component is present — only the client half of fsmonitor--daemon is ported, and none of the other three is linked — the same reason git's own #ifdefs drop a line. The same block is what git diagnose and git bugreport embed, through the same function git shares between them.

subtree, filter-branch and instaweb are ported directly from their stock shell scripts. subtree add, merge, pull, split, and push all produce the same commits and object ids as stock; only -S/--gpg-sign is refused. filter-branch reproduces stock's commit ids everywhere the corpus looks: every differential case it carries — --msg-filter, --tree-filter, --index-filter, --env-filter, --subdirectory-filter, --prune-empty, --tag-name-filter, --commit-filter, --original and the error paths, over the linear, branched and merged fixtures — matches stock's stdout, exit code and post-command state, where state is a set of stock probes read back from both repositories — for-each-ref, rev-parse HEAD, ls-files --stage, status --porcelain, cat-file --batch-all-objects, config --list --local among them. That is the whole of the evidence for the claim: it is a statement about those filters on those shapes, not about a repository the port has never been pointed at, and the rev-list option surface it accepts is deliberately narrow (--since, --author, --max-count, <a>...<b> and magic pathspecs are refused rather than approximated). Until v0.16.0 the claim was also plainly false in a case the corpus did not reach: the script's update-index --refresh was modelled for its read semantics but not its write, so the on-disk index kept stale mtimes, the final read-tree -u -m HEAD refused, and the run left a half-rewritten history behind.

One property of this port belongs with that claim, because it decides which git the ids come from: unlike stock, it does not prepend its own directory to PATH. The filters are shell snippets run under /bin/sh, and the script's own machinery is written the same way — the default commit filter is literally git commit-tree "$@", the empty-tree constant comes from git hash-object -t tree /dev/null, and the ident probe from git var GIT_AUTHOR_IDENT — so every one of those children, and every git a user's own filter runs, resolves through the caller's PATH. With a shim named git first on PATH, stock's filter-branch calls it zero times and this port's calls it for each of those three. Put the build you mean at the front of PATH before reading a filter-branch result as that build's.

instaweb generates the same daemon configuration as stock for whichever of lighttpd, apache2, mongoose, plackup, webrick or python is installed, and serves gitweb through it; like stock, it ships no web server of its own.

Generated cases cover mutating subcommands as well as read-only ones — the hardened environment neutralizes every interactive hook (GIT_EDITOR=true and its siblings), which is what made a command that opens an editor fuzzable at all. That coverage earns its keep: a sweep found git init --bare nested/dir failing outright, fast-import leaving a different object store than stock after a rejected command line, and cherry-pick refusing strategies git accepts — none of which any curated case reached.

What no score covers is the situation a case cannot describe. Until recently every case ran at the worktree root, so repository discovery went unmeasured and three bugs shipped behind that gap, including a process abort in any bare repository's subdirectory. A case can now name its own working directory and environment; read a green subcommand as "agrees on what was asked", never as "agrees".

[0x08] BENCHMARKS

Same repository, same commands, same machine, both binaries measured in one interleaved run so load moves them together. Regenerate with:

cargo build --release && scripts/bench.sh /path/to/some/repo

Repository: zshrs (6,376 commits) · stock git 2.50.1 · 18 cores · 12 runs after 3 warmups · release build · the machine was busy (load ~17-20), which compresses zvcs's lead rather than git's.

Commandzvcsgit
git log --stat -n 308.3 ms133.2 ms16.05x
git status20.0 ms207.4 ms10.37x
git blame README.md8.4 ms38.2 ms4.55x
git log -S return --format=%H1.858 s6.679 s3.59x
git log22.3 ms73.9 ms3.31x
git log --oneline20.5 ms65.8 ms3.21x
git log --format=%s19.0 ms55.1 ms2.90x
git log --format=%h11.5 ms26.3 ms2.29x
git log -p -n 2035.7 ms66.4 ms1.86x
git cat-file -p HEAD8.0 ms12.5 ms1.56x
git shortlog -s7.2 ms10.8 ms1.50x
git describe10.3 ms14.4 ms1.40x
git for-each-ref9.4 ms12.7 ms1.35x
git ls-files8.3 ms11.2 ms1.35x
git rev-list --count HEAD9.9 ms13.3 ms1.34x
git show HEAD10.2 ms13.3 ms1.30x
git tag -l10.5 ms13.0 ms1.24x
git diff --name-only HEAD~515.2 ms18.2 ms1.20x
git diff --stat HEAD~528.4 ms33.9 ms1.19x

Three things produce the difference, and only the first is ordinary optimization. The performance architecture page diagrams these and the two supporting levers — doing less work, and keeping cache writes off the caller's critical path:

  • Every core, not one. git's diff and log machinery is single-threaded. A blob pair is diffed in isolation and the object store cannot change while a read-only verb runs, so patches, per-file analysis, pickaxe scans and record rendering are fanned across the pool. Workers pull from a shared cursor rather than a fixed slice, because one commit that rewrites a large file outweighs a hundred that touch a line each. ZVCS_THREADS=1 forces the sequential path and produces byte-identical output.
  • A cache that remembers. An abbreviation is fixed once the object and the width core.abbrev resolves to are both known — which is why the width is part of the key, not an assumption — and a tree pair's change list and per-file line tallies are a pure function of two immutable trees. None of it can go stale, so it is computed once and read back forever after — which is what log --stat and blame are reading instead of the object store. The answers live in memory-mapped rkyv images under ~/.zvcs/cache/, so a hit is a binary search and a slice into the mapping: nothing is decoded, allocated or copied, and a short command pays for the entries it touches rather than for every one on the machine.
  • A daemon that computes it early. zvcs.precache warms those caches when a watched repo's refs move, so the work is done before anyone asks. git cannot do this at all: no part of git runs between two commands.

Cold versus warm

The table above is steady state — a repository the daemon has seen. The cold column below is the opposite extreme: a repository with no cache at all, where the cache-backed commands compute every answer from the object store and write it down for next time.

Commandzvcs coldzvcs warmgit
git log --stat -n 30121.6 ms8.2 ms142.0 ms
git log --stat -n 150432.2 ms12.0 ms544.0 ms
git blame README.md72.1 ms10.4 ms43.7 ms

Cold --stat stays ahead of git because filling a cache is not something the caller waits for: the entries are queued to a writer thread and the command returns, with one wait at the very end for whatever the writer has not already absorbed. They still land — a detached thread would be killed at exit, and a cache that never persists is just a slower uncached path.

Cold blame is the exception and is reported as measured: gix's blame walk is slower than git's, so the first blame of a file loses to git by ~1.7x. The cache is what turns that around — the second one is 4.2x faster than git, and the entry is valid in every clone holding those commits.

Cold here means the cache is deleted before every single run, which is the worst case and not one a running daemon leaves you in: it warms the newest commits on every ref change, and git zprecache does the same pass on demand (150 commits in 0.52 s).

[0x09] DOCUMENTATION

  • Docs hubhttps://menketechnologies.github.io/zvcs/
  • Design documentDESIGN.md — daemon architecture, concurrency model, autonomous behaviors, ledger/queue
  • Command listing for completionsgit --list-cmds=<group>[,<group>...] answers the same groups stock git does (builtins, main, others, nohelpers, alias, config, deprecated, list-<category>), so a completion script written against git works unchanged. The binary groups are derived from the dispatch table, so the z* verbs are listed alongside the git verbs; the list-<category> groups come from the same tables git help -a/-g print and match stock byte for byte. --list-cmds=parseopt answers empty: it names the commands that implement --git-completion-helper, and none do here yet.
  • zsh completioncompletions/_git — the stock zsh _git forked with the z* verbs; put the dir first on fpath to shadow the system _git. It is compiled into the binary, so git zshadow installs it as ~/.zvcs/completions/_git and prints the fpath line for it (put that line before compinit)
  • Plugin systemZNATIVE.mdgit znative, the store layout, the two plugin kinds, and the C ABI a native plugin is written against; examples/ holds three runnable plugins (plugin-hello, plugin-wip, plugin-todo)
  • Performance architecturehttps://menketechnologies.github.io/zvcs/#performance — diagrams of how the speedup is achieved: the worker pool over work git does single-threaded, the pickaxe rewrite, the ledger of never-stale values, daemon precompute, and the off-critical-path write queue
  • Verb referencehttps://menketechnologies.github.io/zvcs/reference.html — every superset (z*) verb with its synopsis and manual text; the same content git help <verb> opens in a terminal. Generated by perl scripts/gen_reference.pl from src/extensions/src/superset/manpage.rs, so the page can never drift from the man pages; --check fails if it is stale.
  • Engineering reporthttps://menketechnologies.github.io/zvcs/report.html
  • gitoxidehttps://github.com/GitoxideLabs/gitoxide (the ported library)
  • Sourcehttps://github.com/MenkeTechnologies/zvcs

[0xFF] LICENSE

MIT — free and open source. See LICENSE.