hav
Concepts

Capture

Every hav command except init begins the same way: capture the current workspace. Walk the tree, hash the files, and record a new version of the workspace's knot with the new snap if anything changed. This is what "the working copy is the knot" means mechanically. By the time any command runs, the snapshot already exists.

Scan-on-command

Capture is a full re-scan and re-hash of every tracked file, on every command. There is no daemon, no watcher, and no inotify, and therefore no crash window and no stale cache. The cost is measured: comfortable to a few thousand tracked files, noticeable at 10k, sluggish past 30k (see the performance envelope).

A capture that changes the snap is recorded as its own op (verb=capture) before the command's own op, so even the capture is undoable separately.

What gets captured

Files, directories, and the executable bit. Excluded:

  • symlinks, skipped with a warning (up to 5 named individually; more collapse into one skipped N symlinks line);
  • empty directories (like git);
  • .hav/ and .hav-ws (repo metadata), always;
  • anything matched by .havignore.

On Windows, where the filesystem has no exec bit, capture preserves each tracked file's bit from history and projection skips the chmod, so a repo shared across platforms never sees phantom mode changes.

.havignore is yours

Ignore rules live in .havignore at the workspace root, in gitignore syntax. The file is itself tracked.

hav ships no ignore rules of its own and never writes or auto-populates the file, because what belongs in your repository is your call. The guardrails below exist so that the absence of a .havignore is never silently expensive.

The guardrails: preview and the first-capture gate

hav preview shows what the next capture would ingest: total files and bytes, a per-directory breakdown sorted by size, and the skipped-symlink count. It uses the same walker and ignore rules as capture but reads nothing, stores nothing, and records no op. Run it before the first capture on a real project, then write .havignore from what it shows you.

The first-capture gate catches the rest. When a workspace has never captured anything, capture counts the tree first, reading, hashing and storing nothing, and prints first capture: N files (size). At ≥5000 files or ≥100 MB it stops and asks proceed? [y/N], pointing at .havignore and preview. Anything but an explicit y fails the command with nothing stored.

Later captures adding ≥5000 new files (an unignored dependency tree, almost always) warn on stderr and point at .havignore and hav undo.

If a mistake does get through: ignore the path, let the capture age out of the op journal, and hav sweep frees the space.

Capture resolves conflicts

Capture is conflict-aware. For each path stored as a conflict in the knot's snap:

  • file bytes still equal the rendered markers → still conflicted, unchanged;
  • file has no marker lines left → resolved, and the entry becomes a normal file;
  • file changed but still contains markers → still conflicted, and st shows cf-… (partially edited).

This is why there is no resolve --continue: fixing the file is the resolution, and whichever command you run next notices.

Variants route before they store

A path carrying sealed variants routes by the workspace's active crew, and encrypts before any plaintext row exists, because the routing decision happens inside the scan. You edit what you see: with a crew active, edits at variant paths update that crew's variant, and with none, they update the public variant. The first capture that would create a public variant at a sealed-only path asks first (the publish gate), and declining stores nothing and drops nothing. A variant that never materialized here is carried through rather than recorded as a deletion. It was never checked out, so its absence means nothing.

Capture triggers the cascade

When capture changes the snap of a knot that has descendants (you are editing an old knot), every unheld descendant re-merges automatically as part of the same operation. See Rewriting history.

On this page