Expand description
vcs-watch — filesystem-watch a git/jj repository and emit typed state-change
events.
A RepoWatcher watches a repository’s .git/.jj state directory (and,
optionally, the working tree), debounces the burst of writes a VCS
operation makes, re-queries the repo state through
vcs-core’s batched snapshot, and
diffs it against the previous state to yield typed RepoEvents. Each
settled change arrives as a RepoChange carrying both the new
RepoSnapshot (to render a prompt/status line) and the deltas (to react).
It’s the foundation for prompts, status bars, TUIs, and repo daemons.
Re-query-and-diff — rather than interpreting raw filesystem events — is what
makes it robust: git’s ref temp-file renames, index.lock churn, and reflog
noise all just coalesce into one “re-check the settled state” instead of being
(mis)read as events. Noise that doesn’t move observable state emits nothing,
and every emission carries the true current state, so a stray event can’t
desync the consumer.
§The surface
RepoWatcher— a live watch over one repository. Start it withRepoWatcher::watch(defaults) or theBuilder; drop it to stop the OS watch and the background task.Builder(RepoWatcher::builder) — set the watch scope and timing, thenbuild:working_treeto also watch the tree recursively,debounce(the quiet window),max_wait(the re-query ceiling under a continuous stream),requery_timeout(the per-re-query deadline). TheDEFAULT_REQUERY_TIMEOUTet al. name the defaults.RepoEvent— one typed delta, derived by diffing two snapshots:HeadMoved,BranchSwitched,BranchCreated/BranchDeleted,WorkingCopyChanged, and the upstream/ahead-behind/operation/conflict variants (#[non_exhaustive]).RepoChange— a settled change: the freshRepoSnapshot(render a status line off it) plus the non-emptyeventsvec (react to it).- Consumption — pull changes with
recv(Option<RepoChange>;Noneonce dropped), or, under thestreamfeature, poll the watcher as afutures_core::Stream. Both pull from the same channel and advancecurrent, the last-pulled snapshot. WatcherStats(stats) — lock-free health counters (re-queries run, changes emitted, skips, and the last skip’sWatcherErrorKind). Climbingskippedwith flatchangesmeans a wedged repo — poll it from a health check rather than inferring health from event silence.
§Recipes
Watch with the defaults and react to each settled change:
use vcs_core::Repo;
use vcs_watch::RepoWatcher;
let repo = Repo::open(".")?;
let mut watcher = RepoWatcher::watch(repo).await?;
while let Some(change) = watcher.recv().await {
for event in &change.events {
println!("{event:?}");
}
// `change.snapshot` is the fresh full state — render a status line off it.
}Under the stream feature the watcher is a futures_core::Stream,
so it drops into stream combinators and tokio::select! directly (needs
futures/tokio-stream’s StreamExt in scope):
use futures::StreamExt;
use vcs_core::Repo;
use vcs_watch::RepoWatcher;
let repo = Repo::open(".")?;
let mut watcher = RepoWatcher::watch(repo).await?;
while let Some(change) = watcher.next().await {
println!("{} event(s)", change.events.len());
}Runtime: unlike the rest of the toolkit (which hides tokio behind
processkit), vcs-watch uses tokio at runtime — the watch task and the
debounce timer run on the caller’s tokio runtime, so build/await it from
within one.
§Testing
The debounce → ceiling → re-query pipeline is a free function over injected
seams, so it is exercised hermetically on a paused clock (no real
filesystem or sleeps); a consumer’s own watch code tests the same way it tests
any vcs-core consumer — build the Repo over a
fake runner (processkit’s ScriptedRunner) so the re-query returns canned
state. See
vcs-testkit’s guide.
§In-depth guide
Beyond this page, this crate ships a full how-to guide — rendered on docs.rs
from docs/. See the guide module.
Modules§
- guide
- vcs-watch — repo-event stream
Structs§
- Builder
- Builder for a
RepoWatcher— set the watch scope and debounce timing, thenbuild. - Repo
Change - A batch of changes observed in one settled re-query: the new full
RepoSnapshot(ready to render a prompt/status line) plus the typedRepoEvents that produced it. ARepoWatcheronly yields aRepoChangewhen at least one event fired. - Repo
Snapshot - A one-shot snapshot of the common repository state — branch, upstream
tracking, ahead/behind, dirtiness, and operation state — gathered in one or
two process spawns instead of a call per field. The data a prompt, status
line, or TUI refresh needs. See
Repo::snapshot. - Repo
Watcher - A live watch over a repository, yielding
RepoChanges as the repo’s state changes. Dropping it stops the filesystem watch and the background task. - Watcher
Stats - A cheap point-in-time copy of the watcher’s health counters — see
RepoWatcher::stats. Lets a long-running consumer notice a watcher that is silently skipping re-queries (e.g. a permanently wedged repository) instead of inferring health from event silence.
Enums§
- Error
- An error from setting up or running a
RepoWatcher. - Operation
State - Whether the working copy is mid-operation, unified across the backends’
different models: git exposes an in-progress merge or rebase as on-disk state
(
MERGE_HEAD/ arebase-*dir), while jj has no multi-step operations — it records a conflict directly on the working-copy change. - Repo
Event - One typed change to a repository’s observable state, derived by diffing two
consecutive
RepoSnapshots (plus the branch set). - Watcher
Error Kind - What the last skipped re-query failed on (see
WatcherStats::last_error).
Constants§
- DEFAULT_
REQUERY_ TIMEOUT - Default deadline on a single re-query (
snapshot+ branch list): a wedged command (e.g. a heldindex.lockwith no client timeout configured) is killed and skipped instead of stalling the watch loop forever.