Skip to content

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.

Terminal window
graft switch main
graft --db data.sqlite merge feature/search

switch 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:

Terminal window
graft merge --continue -m "merge feature/search"

If there are conflicts:

Terminal window
graft status
graft conflicts
graft --db data.sqlite conflicts --json

This example starts with two tasks in app.sqlite:

idtitlestatus
1Write quickstartopen
2Review commandsopen

Create the repository and commit that starting state:

Terminal window
mkdir app-state-demo
cd app-state-demo
graft init
graft 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.sqlite
graft commit -m "seed tasks"

The feature branch completes task 1 and adds task 3:

Terminal window
graft switch -c feature/progress
graft sql --db app.sqlite \
"UPDATE tasks SET status = 'done' WHERE id = 1; \
INSERT INTO tasks VALUES (3, 'Add screenshots', 'open');"
graft add app.sqlite
graft commit -m "record feature progress"

Switching back materializes main before making a different set of changes:

Terminal window
graft switch main
graft sql --db app.sqlite \
"UPDATE tasks SET status = 'blocked' WHERE id = 2; \
INSERT INTO tasks VALUES (4, 'Publish docs', 'open');"
graft add app.sqlite
graft commit -m "update docs status"

The branches changed different rows: main changed rows 2 and 4, while feature/progress changed rows 1 and 3.

Terminal window
graft --db app.sqlite merge feature/progress
graft conflicts
graft diff --rows --staged app.sqlite

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

Terminal window
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:

idtitlestatus
1Write quickstartdone
2Review commandsblocked
3Add screenshotsopen
4Publish docsopen

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 ROWID tables, including STRICT and 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

Use one of --ours, --theirs, or --manual:

Terminal window
graft --db data.sqlite resolve --ours data.sqlite
graft resolve --theirs attachments/report.pdf
graft resolve --manual settings.json

For SQLite row conflicts, resolve one row:

Terminal window
graft --db data.sqlite resolve --theirs --row docs 42 data.sqlite
graft --db data.sqlite resolve --theirs --row docs '{"space_id":"space-1","id":"doc-1"}' data.sqlite

The 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:

Terminal window
graft merge --continue -m "resolve merge"

Abort a merge:

Terminal window
graft merge --abort

Use JSON output when building an application review flow:

Terminal window
graft status --json
graft --db data.sqlite conflicts --json
graft --db data.sqlite resolve --json --theirs --row docs 42 data.sqlite

Important conflict fields include:

FieldMeaning
pathRepository path in conflict.
path_kind or kindSQLite database, text file, or binary file.
storageSQLite snapshot, inline file, or external payload.
row_conflictsRowid, declared-primary-key, or semantic-key conflicts.
schema_conflictsSchema conflicts with column-level detail.
opaque_changesChanged SQLite surfaces Graft did not interpret.
blocked_reasonsWhy auto-merge did not finish.

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.