hav
Concepts

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 new first.
  • 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.

On this page