hav
Reference

Command reference

Every hav verb, grouped by area. All id arguments accept unique prefixes (ambiguity errors list the candidates), and <knotish> means a knot id prefix or a buoy name. Ids follow the display grammar. Every command's output is single-line-per-fact, and errors are one line naming the remedy, with a non-zero exit.

Except where noted, every mutating command is one undoable op, and every command begins by capturing the workspace.


Repo and status

hav init [--name <s>]

Create .hav/repo.db in the current directory, buoy main, and register the directory as workspace ws-1 on a fresh untied knot. Writes no other files, since .havignore is yours to create.

hav init --bare

Create an empty push/pull hub: no working tree, no workspace, no knots. Push creates buoys on first contact. Workspace commands in a bare repo error with a one-line explanation.

hav st

Capture, then show: the workspace knot and its description, its base (on main@sn-…, plus · using <slug> when a crew is active), the tracked diff (one line per changed variant: M/A/D path +a -d with path@N hunk tags for text files, a readable sealed variant tagged [slug] with path[slug]@N tags, an opaque one an M path [slug · sealed] stub with no counts), outstanding conflicts (conflicted variants tagged [slug]), the behind-notification (behind main by N knots — hav sync) when the tracked buoy has advanced, and the undo cursor.

hav preview

What the next capture would ingest: total files and bytes plus a per-top-level-directory breakdown, sorted by size, and the skipped-symlink count. Same walker and ignore rules as capture. Reads nothing, stores nothing, records no op. The tool for writing .havignore before a first capture on a real project.


Naming and starting work

hav describe -m <msg>

Set the workspace knot's description: one line saying what the work is.

hav new [-m <msg>]

Start a fresh untied knot on top of the current one. The current knot keeps its state, and the workspace moves to a new knot based on it, with an empty diff.

hav discard

Abandon the workspace's untied draft: revert the tree to the draft's base, start a fresh empty draft, retire the abandoned knot. Refuses tied history (use hav new or edit instead, since dropping landed history is move, pointer surgery) and drafts with knots stacked on them. No-op when the draft is empty. Undoable: hav undo restores the work on disk.


Inspecting history

hav log [<knotish>] [-n N] [--buoys]

Walk knot ancestry newest-first from a knot or buoy (default: the workspace knot), showing id, timestamp, description, snap, any buoys pointing there, and any workspace holding it. The workspace's knot is marked @. Targeting a buoy reviews history you don't have checked out. --buoys lists buoys and their heads instead.

hav show [<knotish>]

One knot's identity: id, description, snap, created timestamp, the buoys/crew/workspace on it, its parents (or base (untied)), and its diff vs its first parent. The read a reviewer runs before tying an incoming knot. Read-only.

hav diff [<knotish>] [--from <knotish> --to <knotish>]

Unified diff, one section per changed variant (a sealed variant suffixes [slug] in its ---/+++ headers), hunks tagged path@N / path[slug]@N (the tags split --take accepts). Members see real unified diffs, and an opaque variant renders a stable one-line token per version ([variant · slug · <commitment-prefix>]). Default: the workspace knot vs its base/first parent.

hav conflicts

List conflicted variants in the workspace knot: id, path, [slug] for a crew's variant, shape (ours+theirs, base).


Buoys

hav buoy

List buoys with their head knot ids.

hav buoy <name> [--at <knotish>]

Create a buoy (default: at the workspace knot). Buoys reference tied knots only, so pointing one at a draft is refused (tie first). Slashes in names are fine (release/1.4).

hav buoy -d <name>

Delete a buoy. Workspaces tracking it become pinned. Undo restores both the buoy and the tracking.

hav move <buoy> --to <knotish>

Pure pointer move, with no merge and no solidify. --to is required because this can abandon history. Tied targets only: tie is move-plus-solidify, and move is pointer surgery over tied history.


Landing and syncing

hav tie [<src>] [<dst>] [--reveal]

The one merge verb. Defaults: the workspace knot into its tracked buoy. Semantics by destination, with the full story in Tie and sync:

  • untied knot → buoy: the everyday land. Requires the knot's base chain to ground on the buoy head (else not in sync with <buoy> — hav sync <buoy>, naming the destination rather than the tracked buoy). Bases become parents, the buoy moves, and history stays linear. Split stacks land whole, bottom-up. The workspace then auto-starts a fresh draft on the new head.
  • tied knot → buoy: fast-forward when possible, already contains when redundant, otherwise a merge knot (genuinely diverged histories only) with conflicts stored inside its snap.
  • knot ↔ knot: a detached merge knot; self and ancestor pairs rejected.
  • Sealed knots landing on a public buoy are refused without --reveal, since that flag is the disclosure.

Reports K conflicts stored — hav conflicts when the result carries any.

hav sync [<buoy>]

Pull a buoy's advances into the workspace knot, explicit and never automatic. 3-way merge, conflicts materialize in this working tree (the buoy never carries them), and the knot stays untied. A stale base (the knot beneath you was rewritten by a cascade) refreshes in place. Default: the tracked buoy, and pinned workspaces must name one (naming doesn't start tracking). Errors on a tied knot (hav new first).

hav resolve <path> --take ours|theirs

Write the chosen side of a conflicted path, acting on the variant the workspace materializes (the active crew's conflicted variant if it has one, else the public conflict). Convenience only, since editing the file marker-free and running any command resolves it too.


Rewriting history

See Rewriting history for semantics; all four cascade to descendants where applicable.

hav edit <knotish>

Move this workspace onto an existing knot to change it in place. Each snap-changing capture appends to that knot and auto-rebases its tied descendants. One workspace per knot, ever (refused if another holds it). Pins the workspace and re-projects. Refuses retired knots.

hav squash [<knot>]

Fold the workspace knot (or the named one) into its parent/base: the parent takes the child's snap, descriptions concatenate, the child retires. The workspace moves to the parent, and sibling descendants cascade.

hav split --take <path>[@N]... --into-new -m <msg>

Additive split: move the selected whole files, or selected hunks (path@N / path[slug]@N tags from st and diff; sealed-variant hunks need membership), into a new knot beneath the current one, and the remainder stays. The result is an untied stack, and a later bare tie lands it whole.

hav absorb

Route the workspace knot's edits into the untied ancestors that own the touched lines (per-line blame). Never guesses: ambiguous ownership, conflicted files, and anything owned by tied history stay put, and whole-file adds/deletes always stay. Working-tree bytes never change, and only attribution moves. Records nothing when nothing moves.


Workspaces

hav ws new <path> [--on <buoy> | --at <knotish>]

Create a new workspace: directory, .hav-ws pointer, projected files, fresh untied knot based on the buoy head (tracking) or pinned knot. <path> must be its own directory, so the repo root or an existing workspace is refused.

hav ws list

List workspaces: id, path, knot, base. Bare hav ws does the same.

hav ws -d <id>

Forget a workspace: drop its row and .hav-ws pointer, leaving the projected files and the directory. The held knot becomes an unheld draft. Refuses the current workspace, and asks for confirmation when the knot holds untied work no buoy reaches. Undoable (undo restores the row; rerun ws new if you need the directory reprojected).


Undo and the journal

hav undo / hav redo

Move the op cursor: restore the recorded view and re-project the files on disk. Output always names what changed and whether redo is available. See Undo and the op journal.

hav op log [-n N]

The numbered op list (op-412 capture sn-19ac (+2 ~1)), cursor marked @.

hav op limit [<n>]

Show or set journal retention (default 50; 0 = unlimited). Undo reaches back exactly this far. Lowering below what's retained discards history and asks for an explicit y. The configured limit is standing consent for routine pruning. Records no op.

hav sweep

Drop drafts and objects nothing retained can reach, dissolve packs that held dropped members, and compact the database. This is the physical complement of op limit. Explicit, workspace repos only, and refuses while any sealed crew is locked (run it where your keys are). Records no op.


Remotes

See Remotes and hubs.

hav remote [<name> <path-or-url> [--token <t>] | -d <name>]

List, add/update, or delete remotes. A remote is a path to another repo or a harbor URL. The token gates pushes only, since harbor reads are anonymous. Add and delete are undoable, and tokens live outside op views.

hav clone <src> <dir> [--token <t>]

Copy every buoy's tied history from a path or harbor URL into a fresh repo at <dir> (refused if it exists), set up ws-1 tracking main, project files, record origin. No token is needed to clone, and --token stores the push credential for later.

hav push [<buoy>] [--remote <name>]

Send tied history to the remote and fast-forward its buoy (created if absent). Fast-forward only, refused when the remote is ahead or unknown (hav pull first), with the check running inside the remote's write transaction. Untied WIP, workspaces, and the op journal never travel. Rewrites reaching published history go through with a loud warning. Defaults: tracked buoy, remote origin. Records no local op.

hav pull [<buoy>] [--remote <name>]

Fetch the remote head's tied history and fast-forward the local buoy (created if absent). Never merges, never touches the working tree. Tracking workspaces get the ordinary behind — hav sync notification. On divergence the fetched data stays and pull prints the recipe (hav tie <remote-head> <buoy>). One op when anything changed.


Keys and crews

See Sealed crews.

hav key [gen]

Show this machine's public key, or create the identity keyfile (~/.config/hav/identity; $HAV_IDENTITY overrides). Once per person, and private keys never move. Back the keyfile up, because losing it permanently loses all sealed-crew content.

If the keyfile resolves to a path inside a worktree, capture refuses rather than publishing your private key. Move it out of the repo or add it to .havignore.

hav key rotate [--finish]

Swap this machine's key with a two-key overlap: rotate mints a fresh primary while the old key stays on a second line of the keyfile, still unwrapping everything it could, so there is no lockout window. Get the new pk added to every crew the old key is in (hav crew <slug> --add <new-pk>, which any member can do, this machine included, in every repo that has the crew), remove the old pk, then --finish drops the old line (it asks y/N first, and declining drops nothing). hav key lists the old key while the rotation is in flight, and a second rotate is refused until you finish.

Identity is per-person rather than per-repo: rotation is not an op, and hav cannot enumerate which crews across which repos still need the new key. That inventory is yours. A crew still wrapped only to the old key when you finish becomes unreadable from this machine, forever.

hav crew

List crews. Members see the sealed display name; others see slug and counts.

hav crew <slug> --name <s>

Create a crew, or rename its display name. The slug is immutable and public, and the display name is sealed. Buoys live under private-<slug>/….

hav crew <slug> --add <pk> / --remove <pk>

Add a member (wrap the crew key to their public key) or remove one (rotate to a fresh generation for everyone who stays; any member can, the creator included). Removing the last member warns: the sealed history becomes permanently unreadable (hav undo restores it while the op is retained). All undoable ops.

hav crew use <slug> / hav crew use --none

Set or clear this workspace's active crew (membership required; local state, one undoable op). The active crew's variants materialize on disk and receive your edits at variant paths, and with none active you see and edit the public variants. use and none are reserved words, never valid slugs. Fresh workspaces and clones start with none. Re-running the current setting rematerializes in place (the repair for an interrupted swap). On a sealed knot the setting applies only when it names the knot's own crew; another crew's setting records but stays inert (the output and st say so), its variants carried untouched.

hav crew -d <slug>

Delete a crew from this copy: its config (row, wraps, key chain) and its sealed buoys, one undoable op. No membership needed, since whoever holds a copy is sovereign over it. Asks y/N, naming what dies. The crew's sealed history becomes unreadable here (hav undo restores it while the op is retained, and hav sweep frees the ciphertext once nothing retained reaches it). Refused while any workspace has the crew active or sits on one of its knots. Local only: remotes are untouched, and a later pull that carries the crew's content, or wraps your key, brings the config back. The slug is immediately reusable here, but the recreation is a brand-new crew (identity, not name), so copies that knew the old one will never accept the new one under that slug.

hav seal <path> [--keep-public]

From this capture on, the file at <path> stores as the active crew's sealed variant (variant sets). Drops the public variant going forward unless --keep-public, and already-published versions stay public history forever. There is no rule and no pattern: the variant in the snap is the routing. On a sealed knot only the knot's own crew can seal (the variant outlives the knot's reveal); another crew's active setting is refused there.

hav unseal <path>

Publish the active crew's variant at <path> as the public variant and drop the sealed one going forward. A disclosure: names what it publishes and asks y/N. Sealed history stays sealed. Inside the crew's own sealed buoy the "public" slot is still the knot's ciphertext, so the content goes plaintext only when the knot is revealed.


The harbor

See The harbor.

hav harbor serve <root> [--addr host:port] [--token <t>]

Serve every bare repo under <root> at http://<addr>/<name>. Same push/pull/clone verbs, URLs in place of paths. Reads (pull and clone) are anonymous, and the token (--token or HAV_HARBOR_TOKEN, required) gates every push.


Storage

hav pack

Compact loose objects into two-regime storage: large blobs content-defined chunked and deduped, everything else into solid zstd packs. Ids and reads are unchanged, and packs expand back to whole objects on the wire. Runs on a workspace repo or a bare hub. Records no op.

On this page