Skip to content

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.

Terminal window
mkdir graft-book-lab
cd graft-book-lab
graft init
find .graft -maxdepth 3 -print | sort
cat .graft/HEAD

HEAD should name refs/heads/main, while that branch has no commit ID yet.

Terminal window
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.json
graft status --json

Storage-engine files created during initialization do not imply a staged snapshot. Status and the index are the authorities.

Terminal window
graft add app.sqlite
graft add settings.json
graft status --json
sed -n '1,240p' .graft/index/state.toml
find .graft/objects -type f | sort
du -sh .graft/store/fjall .graft/store/files .graft/objects

The index now has two stage-0 paths. SQLite names a snapshot blob, settings names an inline blob, and main still has no repository commit.

Terminal window
graft sql --db app.sqlite \
"INSERT INTO notes(body) VALUES ('second, not staged yet');"
graft commit -m "seed app state"
graft status --json
cat .graft/HEAD
cat .graft/refs/heads/main
graft show --json HEAD
graft diff --json

The commit contains the first staged snapshot. The second row remains unstaged. Export the history to verify it directly:

Terminal window
graft export --source HEAD --output head.sqlite app.sqlite
sqlite3 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”
Terminal window
du -sk .graft/store/fjall .graft/objects
graft add app.sqlite
graft commit -m "add second note"
du -sk .graft/store/fjall .graft/objects
graft log --json
graft diff --json --rows HEAD~1 HEAD app.sqlite

Do 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.

Terminal window
graft branch experiment
find .graft/refs/heads -type f -maxdepth 2 -print -exec sed -n '1p' {} \;
graft switch experiment
cat .graft/HEAD

Two branches naming one commit do not duplicate objects or SQLite pages.

Terminal window
graft sql --db app.sqlite \
"UPDATE notes SET body='changed on experiment' WHERE id=1;"
graft add app.sqlite
graft commit -m "edit first note"
graft diff --json --rows main experiment app.sqlite

Close long-lived application connections first:

Terminal window
graft switch main
sqlite3 app.sqlite 'SELECT * FROM notes ORDER BY id;'
graft switch experiment
sqlite3 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.

Terminal window
mkdir ../graft-book-remote
graft remote add origin "fs://$(cd ../graft-book-remote && pwd)"
graft push origin experiment
find ../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.

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?
QuestionNormative ownerMain implementation entry points
.graft, objects, refs, indexdocs/specs/graft-repository-1.0.mdcrates/graft/src/repo.rs, repo/object.rs, repo/staging.rs, repo/worktree_state.rs
4 KiB pages, logs, LSNs, snapshotsdocs/specs/graft-storage-snapshots-1.0.mdsnapshot.rs, volume_reader.rs, volume_writer.rs, local/fjall_storage.rs
consistent physical capture and replacementdocs/specs/graft-worktree-materialization-1.0.mdcrates/graft-sqlite/src/pragma/sqlite_worktree.rs, repo_checkout.rs
path, row, and schema diffdocs/specs/graft-diff-1.0.mdcrates/graft-sqlite/src/row_level_diff.rs
three-way merge and durable conflictsdocs/specs/graft-merge-1.0.mdcrates/graft/src/repo/merge.rs, crates/graft-sqlite/src/row_merge.rs
push, fetch, pull, and remote CASdocs/specs/graft-remote-sync-1.0.mdcrates/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.