Merge Conflicts
Graft starts merge from repository paths. When a modified path is a SQLite database, it can inspect base, ours, and theirs snapshots and attempt a row-aware merge. Files use conservative file-level handling.
Merge A Branch
Section titled “Merge A Branch”graft switch maingraft --db data.sqlite merge feature/searchswitch operates on the repository discovered from the current directory.
merge uses --db to open data.sqlite for row-aware analysis. If the merge
is clean, Graft stages the result. Finish it:
graft merge --continue -m "merge feature/search"If there are conflicts:
graft statusgraft conflictsgraft --db data.sqlite conflicts --jsonExample: Merge Changes To Different Rows
Section titled “Example: Merge Changes To Different Rows”This example starts with two tasks in app.sqlite:
| id | title | status |
|---|---|---|
| 1 | Write quickstart | open |
| 2 | Review commands | open |
Create the repository and commit that starting state:
mkdir app-state-democd app-state-demograft initgraft sql --db app.sqlite \ "CREATE TABLE tasks(id INTEGER PRIMARY KEY, title TEXT, status TEXT); \ INSERT INTO tasks VALUES (1, 'Write quickstart', 'open'), \ (2, 'Review commands', 'open');"graft add app.sqlitegraft commit -m "seed tasks"1. Change Row 1 On A Feature Branch
Section titled “1. Change Row 1 On A Feature Branch”The feature branch completes task 1 and adds task 3:
graft switch -c feature/progressgraft sql --db app.sqlite \ "UPDATE tasks SET status = 'done' WHERE id = 1; \ INSERT INTO tasks VALUES (3, 'Add screenshots', 'open');"graft add app.sqlitegraft commit -m "record feature progress"2. Change Row 2 On Main
Section titled “2. Change Row 2 On Main”Switching back materializes main before making a different set of changes:
graft switch maingraft sql --db app.sqlite \ "UPDATE tasks SET status = 'blocked' WHERE id = 2; \ INSERT INTO tasks VALUES (4, 'Publish docs', 'open');"graft add app.sqlitegraft commit -m "update docs status"The branches changed different rows: main changed rows 2 and 4, while
feature/progress changed rows 1 and 3.
3. Merge And Review The Staged Result
Section titled “3. Merge And Review The Staged Result”graft --db app.sqlite merge feature/progressgraft conflictsgraft diff --rows --staged app.sqlitegraft conflicts reports no unresolved paths. The staged row diff contains the
feature branch changes to rows 1 and 3; the committed main changes to rows 2
and 4 remain in place.
4. Finish The Merge
Section titled “4. Finish The Merge”graft merge --continue -m "merge feature progress"graft sql --db app.sqlite \ "SELECT id, title, status FROM tasks ORDER BY id;"The merged database contains both branches:
| id | title | status |
|---|---|---|
| 1 | Write quickstart | done |
| 2 | Review commands | blocked |
| 3 | Add screenshots | open |
| 4 | Publish docs | open |
What Can Auto-Merge
Section titled “What Can Auto-Merge”Graft can auto-merge compatible SQLite changes when the merge policy can interpret them safely:
- disjoint row changes in supported rowid tables and declared-primary-key
WITHOUT ROWIDtables, includingSTRICTand composite-key schemas - compatible schema additions
- configured SQLite internal changes such as sequence or index rebuilds
- changes that pass validation after applying the merge plan
Graft leaves conflicts when it cannot merge confidently:
- both sides edit the same logical row incompatibly
- schema changes conflict
- one side deletes a path while the other modifies it
- a changed SQLite surface is unsupported or opaque
- file artifacts require an explicit side or manual replacement
Resolve A Path
Section titled “Resolve A Path”Use one of --ours, --theirs, or --manual:
graft --db data.sqlite resolve --ours data.sqlitegraft resolve --theirs attachments/report.pdfgraft resolve --manual settings.jsonFor SQLite row conflicts, resolve one row:
graft --db data.sqlite resolve --theirs --row docs 42 data.sqlitegraft --db data.sqlite resolve --theirs --row docs '{"space_id":"space-1","id":"doc-1"}' data.sqliteThe identity is an integer rowid for an ordinary rowid table, or a JSON object
containing every declared primary-key column for a WITHOUT ROWID table.
Continue after resolving all conflicts:
graft merge --continue -m "resolve merge"Abort a merge:
graft merge --abortJSON For Merge UIs
Section titled “JSON For Merge UIs”Use JSON output when building an application review flow:
graft status --jsongraft --db data.sqlite conflicts --jsongraft --db data.sqlite resolve --json --theirs --row docs 42 data.sqliteImportant conflict fields include:
| Field | Meaning |
|---|---|
path | Repository path in conflict. |
path_kind or kind | SQLite database, text file, or binary file. |
storage | SQLite snapshot, inline file, or external payload. |
row_conflicts | Rowid, declared-primary-key, or semantic-key conflicts. |
schema_conflicts | Schema conflicts with column-level detail. |
opaque_changes | Changed SQLite surfaces Graft did not interpret. |
blocked_reasons | Why auto-merge did not finish. |
Configure Merge Policy
Section titled “Configure Merge Policy”Applications can configure semantic keys, generated columns, schema resolvers,
and internal SQLite resolvers in .graft/config.toml.
See Merge Policy for supported settings and Row Diffs And Merge Policy for the mental model.