07 Hands-On Lab
The point of this lab is to predict which of worktree, index, immutable stores, and refs will change before each command, then verify the prediction.
0. Initialize an isolated repository
Section titled “0. Initialize an isolated repository”mkdir graft-book-labcd graft-book-labgraft initfind .graft -maxdepth 3 -print | sortcat .graft/HEADHEAD should name refs/heads/main, while that branch has no commit ID yet.
1. Change only the worktree
Section titled “1. Change only the worktree”graft sql --db app.sqlite \ "CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT NOT NULL);"graft sql --db app.sqlite \ "INSERT INTO notes(body) VALUES ('first');"printf '{"theme":"paper"}\n' > settings.jsongraft status --jsonStorage-engine files created during initialization do not imply a staged snapshot. Status and the index are the authorities.
2. Stage a consistent snapshot
Section titled “2. Stage a consistent snapshot”graft add app.sqlitegraft add settings.jsongraft status --jsonsed -n '1,240p' .graft/index/state.tomlfind .graft/objects -type f | sortdu -sh .graft/store/fjall .graft/store/files .graft/objectsThe index now has two stage-0 paths. SQLite names a snapshot blob, settings names an inline blob,
and main still has no repository commit.
3. Prove commit consumes the index
Section titled “3. Prove commit consumes the index”graft sql --db app.sqlite \ "INSERT INTO notes(body) VALUES ('second, not staged yet');"graft commit -m "seed app state"graft status --jsoncat .graft/HEADcat .graft/refs/heads/maingraft show --json HEADgraft diff --jsonThe commit contains the first staged snapshot. The second row remains unstaged. Export the history to verify it directly:
graft export --source HEAD --output head.sqlite app.sqlitesqlite3 head.sqlite 'SELECT * FROM notes;'sqlite3 app.sqlite 'SELECT * FROM notes;'4. Observe a page delta and repository commit
Section titled “4. Observe a page delta and repository commit”du -sk .graft/store/fjall .graft/objectsgraft add app.sqlitegraft commit -m "add second note"du -sk .graft/store/fjall .graft/objectsgraft log --jsongraft diff --json --rows HEAD~1 HEAD app.sqliteDo not expect directory size to increase by exactly one page; Fjall and indexes have overhead. The semantic observation is one inserted row and a new repository commit whose parent is the old one.
5. A branch is another ref
Section titled “5. A branch is another ref”graft branch experimentfind .graft/refs/heads -type f -maxdepth 2 -print -exec sed -n '1p' {} \;graft switch experimentcat .graft/HEADTwo branches naming one commit do not duplicate objects or SQLite pages.
graft sql --db app.sqlite \ "UPDATE notes SET body='changed on experiment' WHERE id=1;"graft add app.sqlitegraft commit -m "edit first note"graft diff --json --rows main experiment app.sqlite6. Observe materialization
Section titled “6. Observe materialization”Close long-lived application connections first:
graft switch mainsqlite3 app.sqlite 'SELECT * FROM notes ORDER BY id;'graft switch experimentsqlite3 app.sqlite 'SELECT * FROM notes ORDER BY id;'Switch reconstructs an ordinary database from the target snapshot and updates its binding. Use refs, snapshot identity, and query results—not inode stability—to identify the version.
7. Optional filesystem remote
Section titled “7. Optional filesystem remote”mkdir ../graft-book-remotegraft remote add origin "fs://$(cd ../graft-book-remote && pwd)"graft push origin experimentfind ../graft-book-remote -maxdepth 4 -type f | sort | sed -n '1,120p'The target keeps immutable data separate from mutable refs; the successful ref update makes the uploaded graph reachable.
Debugging checklist
Section titled “Debugging checklist”1. Is HEAD symbolic or detached?2. Which repository commit does the branch ref name?3. Does the index override the path or contain stages 1/2/3?4. Have worktree bytes diverged from the staged snapshot?5. Are required objects, payloads, and pages hydrated?6. Can this operation materialize, and are application handles closed?7. After failure, which ref/index/journal remains canonical?Source map
Section titled “Source map”| Question | Normative owner | Main implementation entry points |
|---|---|---|
.graft, objects, refs, index | docs/specs/graft-repository-1.0.md | crates/graft/src/repo.rs, repo/object.rs, repo/staging.rs, repo/worktree_state.rs |
| 4 KiB pages, logs, LSNs, snapshots | docs/specs/graft-storage-snapshots-1.0.md | snapshot.rs, volume_reader.rs, volume_writer.rs, local/fjall_storage.rs |
| consistent physical capture and replacement | docs/specs/graft-worktree-materialization-1.0.md | crates/graft-sqlite/src/pragma/sqlite_worktree.rs, repo_checkout.rs |
| path, row, and schema diff | docs/specs/graft-diff-1.0.md | crates/graft-sqlite/src/row_level_diff.rs |
| three-way merge and durable conflicts | docs/specs/graft-merge-1.0.md | crates/graft/src/repo/merge.rs, crates/graft-sqlite/src/row_merge.rs |
| push, fetch, pull, and remote CAS | docs/specs/graft-remote-sync-1.0.md | crates/graft/src/repo/sync.rs, repo/remote_objects.rs, packages/graft-remote |
Read the specification to learn what must hold, the entry point to learn the present implementation, and tests to find executable evidence. Do not derive a public contract from Fjall private keys or temporary filenames.