Tie and sync
Two verbs carry hav's entire collaboration story. tie is the one merge
verb: it lands work onto buoys and joins histories. sync is the one verb
that moves your files in response to the world changing, and it only ever runs
because you asked.
Tie: the one merge verb
hav tie [<src>] [<dst>]. With no arguments, it ties the workspace's knot
into the buoy it tracks. What happens depends on what the source and
destination are:
The everyday land: an untied knot into a buoy
This is hav tie after describe. It requires the knot to be in sync:
its base chain must ground on the buoy's current head. Then each base link
becomes the real parent, the buoy moves to the knot, and history stays
linear, with no merge knot ever for parallel work on one buoy.
If the buoy advanced while you worked, tie refuses: not in sync with main — hav sync main. Sync first, bringing the advance into your tree where any
conflicts materialize for you to resolve, and then tie lands clean. The
remedy names the buoy because it may not be the one you track — tying into
main from a workspace on another buoy needs hav sync main, not a bare
hav sync.
A stack of untied knots (from split) lands
whole, bottom-up, in one tie.
After tying the workspace's own knot, the workspace automatically starts a fresh untied knot on the new head, so you just keep editing.
A tied knot into a buoy: fast-forward or merge knot
Tying already-landed history onto a buoy, such as a hotfix onto a second buoy or a release buoy onto main:
- head is an ancestor of the source → fast-forward; the buoy moves, no new knot. The hotfix is one knot id that is now the head of both buoys.
- source is an ancestor of the head →
already contains. - genuinely diverged → a merge knot, with both heads as parents, built by 3-way merge from their common ancestor. Conflicts, if any, are stored inside the merge knot's snap, and the tie itself never blocks.
Merge knots appear only here, where independent histories genuinely meet.
Knot with knot: a detached merge
hav tie <a> <b> where neither is a buoy makes a detached merge knot with
both as parents, moving no buoy. Self-ties and ancestor pairs are rejected
(nothing to tie).
Sealed history
Tying sealed knots into a public buoy is refused unless you pass --reveal.
That flag is the disclosure. See Sealed crews.
Sync: explicit, never automatic
Your workspace pins its tree. When the tracked buoy advances, whether someone
else landed, you landed from another workspace, or a pull moved it, nothing
happens to your files. hav st tells you: behind main by N knots — hav sync.
hav sync is the acknowledgment. It 3-way merges the buoy head into your knot
(base: what you forked from; ours: your tree; theirs: the head), updates your
base to the new head, and re-projects your tree. Your knot is still untied.
Sync moves your starting point forward and lands nothing.
Two properties worth internalizing:
- Conflicts land in the syncing workspace, never on the buoy. Whoever syncs resolves, in their own tree, on their own schedule. The buoy's history never carries conflict markers.
- Pin-with-notification is uniform. Buoy advances, upstream rewrites (stale bases), and pulls all follow one pattern: st notifies, sync acts. hav never rebases a tree that a build or an agent might be running against.
Any workspace can run hav sync <buoy> by name, which syncs from the named
buoy without starting to track it; a pinned workspace (one tracking no buoy)
must. Bare hav sync means the tracked buoy, so when you are landing
somewhere else — a crew buoy's work going to main — name the destination.
When sync refuses
- The workspace's knot is tied. Landed history is not a draft to move, so run
hav newfirst. - The knot sits on a fresh untied knot (a split stack's top), where advancing would cut the stack's ancestry. Tie or squash the stack instead.
Move: pointer surgery, no merge
hav move <buoy> --to <knotish> relocates a buoy with no merge and no
solidify. It is the escape hatch that can abandon history, which is why --to
is required and untied targets are refused. If you're tempted to move a buoy
backward to "undo" a land, hav undo is almost always what you
want instead.
Racing safely
Multiple processes, such as agents on sibling workspaces, can tie into one
buoy concurrently. Every decision that gates a buoy move is re-validated
inside the write transaction, so a head moved by a concurrent land turns into
a clean main moved while tying — hav sync, never a silent clobber and never
a lost update.