Remotes and hubs
A remote is a path to another hav repo, or a URL served by a harbor. The path case has no daemon and no network code: push and pull are one process copying rows between two SQLite files. The harbor puts the same semantics behind HTTP, and every verb on this page accepts either form.
Hubs
hav init --bare creates an empty hub: a repo with no working tree, no
workspace, and no knots, where push creates the buoys on first contact. A hub
is the rendezvous point that colleagues push to and pull from.
mkdir hub && cd hub && hav init --bare
cd ../project
hav remote origin ../hub
hav pushhav remote lists remotes, hav remote <name> <path-or-url> adds or updates
one (--token for harbor URLs, which pushing needs and pulling doesn't), and
hav remote -d <name> deletes. Remote configuration is part of the op view,
so adding and deleting are undoable. (Tokens live outside views, so undo never
touches credentials.)
What travels
Push, pull, and clone move tied history only: objects plus each knot's current version (snap, description, parents).
Never leaves the machine:
- untied WIP: drafts are yours until you tie;
- workspaces: they are local state by definition;
- the op journal: your undo history and its views are private;
- local version history of knots: only the current version travels.
Sealed crew configuration travels only where it is relevant: with content that references the crew, as updates to crews the other side already knows, on pull for crews that wrap your key, and wholesale only on clone. Pulling a fork's buoy never imports its personal crews. See Sealed crews.
Push: fast-forward only
hav push [<buoy>] copies tied history reachable from the buoy head to the
remote, then moves the remote's buoy (creating it if absent).
Push refuses non-fast-forward moves. If the remote head is unknown locally
or not an ancestor of what you're pushing, it says hav pull first. There is
no force-push. The check runs inside the remote's write transaction, so
concurrent pushers serialize and a stale check can never slip a bad move
through.
The copy itself always runs and is idempotent, so a same-head push still ships rewrites such as new descriptions and cascaded ancestors. When a rewrite reaches history the remote already had at a different snap, push goes through with a loud warning: you rewrote published history, and everyone downstream will see it.
Pull: never merges, never touches files
hav pull [<buoy>] fetches the remote head's tied history and fast-forwards
the local buoy. Two things it will never do:
- merge. On genuine divergence, the fetched history stays in your store
and pull prints the recipe:
hav tie <remote-head> <buoy>. Reconciliation uses the ordinary verbs, with conflicts as data. - touch a working tree. Tracking workspaces just see the ordinary
behind main — hav syncnotification, and files move when their owner says so.
If you're ahead, pull says hav push. A pull that changed anything is one
undoable op.
Clone
hav clone <src> <dir> copies every buoy's tied history from a path or harbor
URL into a fresh repo, sets up a workspace tracking main, projects the
files, and records <src> as remote origin.
Divergence, reconciled with ordinary verbs
Two people land on their own copies and both push, and the second is refused. The flow is always:
hav push # refused: not a fast-forward — hav pull first
hav pull # fetches; reports divergence and prints the tie recipe
hav tie <remote-head> <buoy> # ordinary merge; conflicts as data if any
hav push # fast-forward nowThere is no special reconciliation machinery, no push-time merges, and no force flags. Divergence is two histories meeting, and hav already has a verb for that.
Mixing paths and harbors
Don't write to one hub both directly by path and through a harbor, because the harbor's sequencing can't see path writers. A harbor-served root should be reachable only through the harbor. (Monotonicity checks turn the race into a clean refusal, but the protection sealed buoys get from serialized applies is lost.)