3. Sync and the lockfile
This is where it clicks. You have a haw.toml from the last chapter. Now you’ll run haw sync to clone the fleet for real, read the lockfile that freezes it to exact commits,
prove it’s reproducible, and — deliberately — break something to watch haw catch drift.
Everything here clones over HTTPS with no authentication, so you can actually run every
command as you read. Keep the my-first-stack workspace from Chapter 2 open.
Sync clones the fleet; the lockfile freezes it to exact commits — the whole reproducibility trick.
🎯 In this chapter, you’ll learn to…
- Run
haw syncto clone the fleet and generatehaw.lock. - Read the fleet with
haw treeandhaw status, and understand every column. - Read the lockfile’s real fields —
rev(the resolved SHA),source-rev, andbranch. - Re-sync and confirm it’s idempotent — the lock, not the branch, is now the truth.
- Cause drift on purpose and catch it with
haw verify(exit 3), then restore.
The full compose loop you’re about to run: sync clones the fleet, then tree / status read it and the lock pins it.
🔄 1. Sync — clone everything and write the lock
One command materializes the tree:
haw sync
haw resolves each repo’s rev to an exact commit, clones it, and — because there’s no
lockfile yet — writes one. You’ll see progress per repo and a summary. Afterwards, look
around:
$ ls
haw.lock haw.toml hello-world/ spoon-knife/
There they are: two real, complete Git clones, plus a brand-new haw.lock. No
submodules, no symlinks — you could cd hello-world && git log and it’s just Git.
Tip: haw sync is idempotent. Run it again and, since the lock already pins
exact SHAs, haw just makes sure your tree matches — no surprises, safe to repeat in
scripts and CI.
🔍 2. Explore the fleet
Now the read commands. First, the shape of things:
haw tree
haw.toml
└─ site
├─ hello-world master (https://github.com/octocat/Hello-World.git)
└─ spoon-knife main (https://github.com/octocat/Spoon-Knife.git)
That’s your stack → repo tree: the stack site, the two repos under it, each with its
declared rev and origin.
Now the health check:
haw status
REPO BRANCH HEAD DIRTY DRIFT
hello-world master 7fd1a60b - -
spoon-knife main d0dd1f61 - -
Read the columns left to right: the repo, the branch it’s on, its short HEAD SHA, whether the working tree has uncommitted changes (DIRTY), and whether HEAD differs from the locked SHA (DRIFT). Right now everything is clean and in sync — the dashes mean “all good.”
Tip: On a terminal these are color-coded — cyan repo names, yellow revs, green for
clean, red for drift. Pipe the output anywhere and it falls back to plain text
automatically (it honors NO_COLOR), so it’s script-friendly by default.
🔒 3. Read the lockfile
Open haw.lock in your editor. Each repo is pinned to a full 40-character SHA in its
rev field — the exact commit, not the branch name — while source-rev records what you
asked for and branch records the branch that SHA came from:
# excerpt — your SHAs will differ
[[repo]]
name = "hello-world"
url = "https://github.com/octocat/Hello-World.git"
path = "hello-world"
rev = "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d" # the resolved commit — this is the pin
source-rev = "master" # what you declared in haw.toml
branch = "master" # the branch that SHA was resolved from
groups = ["core"]
This is the whole reproducibility trick. Your manifest said “master” (a moving target),
but the lock froze the exact commit master pointed at when you synced — that’s the SHA
now in rev. source-rev remembers your intent (master) and branch remembers where it
came from, so haw can later re-resolve if you ask. Commit haw.lock alongside
haw.toml, and anyone who clones and runs haw sync gets precisely these commits —
not whatever master happens to be today.
Don’t hand-edit it. haw.lock is generated. You commit it, but you change intent in
haw.toml and let haw lock / haw sync regenerate the lock.
♻️ 4. Prove it’s reproducible — re-run sync
haw sync
Because the lock exists, haw syncs to the pinned SHAs, not to wherever the branches
have moved. Run haw status again and you’ll see the same clean fleet. That
idempotence is the point: the lock, not the branch, is the source of truth now.
🧭 5. See drift with your own eyes
Reproducibility is only useful if you can detect when the tree wanders off the baseline. Let’s cause that on purpose. Move one repo to a different commit by hand:
cd hello-world
git checkout HEAD~1 # step one commit back — now HEAD ≠ the locked SHA
cd ..
Ask haw what it thinks:
haw status
REPO BRANCH HEAD DIRTY DRIFT
hello-world (detached) 553c2077 - YES
spoon-knife main d0dd1f61 - -
There it is — DRIFT: YES on hello-world. Checking out a specific commit also
detaches HEAD (hence (detached) in the BRANCH column), and its HEAD no longer matches
the locked SHA.
haw status flagged it, but for CI you want a command that fails on drift. That’s
verify:
haw verify
verify asserts the on-disk tree matches haw.lock and exits 3 when it doesn’t —
a clean, scriptable drift gate. Check the exit code:
haw verify; echo "exit code: $?"
✗ hello-world drift (head != lock)
verify failed: 1 repo(s) diverge from haw.lock
exit code: 3
That exit 3 is what a CI pipeline keys on: “the tree drifted from the lock — stop the
build.” (When everything matches, verify prints verified: tree matches haw.lock (2 repos) and
exits 0.)
haw verify finding drift in CI right before release.
Now put it back. haw sync restores every repo to its locked SHA:
haw sync
haw verify; echo "exit code: $?"
verified: tree matches haw.lock (2 repos)
exit code: 0
Clean again. You just watched the full loop: declare → sync → pin → detect drift → restore.
You just did the core loop. Declare intent, pin it, detect drift, and restore the baseline — the same four moves scale from two octocat repos to a hundred-repo fleet.
🙌 Your turn
Prove the lockfile really is the source of truth:
- Open
haw.lockand find each repo’s pinned SHA in itsrevfield. Confirm it’s a 40-char commit, not a branch name, and thatsource-revstill shows what you declared. - Drift one repo on purpose (
cd hello-world && git checkout HEAD~1 && cd ..), runhaw verify; echo $?, and confirm you get exit code3. - Run
haw syncto restore, thenhaw verify; echo $?again — back to exit0. That round trip is the reproducibility guarantee.
✅ Recap
haw syncclones every repo and writeshaw.lock— real Git clones, no submodules/symlinks. It’s idempotent.haw treeshows the stack→repo shape;haw statusshows branch/HEAD/dirty/drift per repo.haw.lockpins each repo to an exact 40-char SHA inrev, withsource-rev(your declared intent) andbranchalongside — commit it for byte-identical rebuilds.- Drift = HEAD differs from the lock.
haw statusflags it;haw verifyexits 3 on drift (your CI gate).haw syncrestores the baseline.
👉 Next
You can compose and pin a fleet — now let’s live in it. Meet the cockpit that drives the whole thing from the keyboard → 4. The TUI cockpit.