Dolt Merge Conflicts and Resolution: A Complete Guide to Three-Way Merge Handling

Dolt resolves merge conflicts through a three-way merge algorithm that compares "our," "their," and ancestor commits, storing any unresolved data, schema, or constraint conflicts in system tables like dolt_conflicts and dolt_schema_conflicts for manual or automated resolution.

Dolt, the version-controlled SQL database from DoltHub, implements Git-like branching and merging directly in the database engine. When running dolt merge or the SQL CALL DOLT_MERGE procedure, the system performs a sophisticated three-way merge that can detect and surface conflicts at multiple levels—row data, schema definitions, and constraints—allowing users to resolve them using either CLI commands or standard SQL statements.

How Dolt Performs Three-Way Merges

The Dolt merge architecture follows a pipeline from the CLI through the SQL engine to the core merge logic implemented in Go.

Entry Points: CLI and SQL Stored Procedure

When you execute dolt merge <branch> from the command line, the code in go/cmd/dolt/commands/merge.go translates your request into a SQL stored procedure call:

query, err := constructInterpolatedDoltMergeQuery(apr, cliCtx) // build CALL DOLT_MERGE(...)
_, rowIter, _, err := queryist.Queryist.Query(queryist.Context, query) // execute

This invokes the DOLT_MERGE stored procedure implemented in go/libraries/doltcore/sqle/dprocedures/dolt_merge.go, which ultimately delegates to the core MergeCommits function.

Core Merge Logic in merge.go

The heart of the merge algorithm lives in go/libraries/doltcore/merge/merge.go. The MergeCommits function resolves the common ancestor via GetCommitAncestor, loads the three root values (ourRoot, theirRoot, ancRoot), and calls MergeRoots to perform the actual three-way merge:

merger, err := NewMerger(ourRoot, theirRoot, ancRoot, theirs, ancestor, ourRoot.VRW(), ourRoot.NodeStore())

The Merger struct then processes each table individually via MergeTable, detecting conflicts and aggregating statistics into a Result struct:

type Result struct {
    Root          doltdb.RootValue
    SchemaConflicts []SchemaConflict
    Stats           map[doltdb.TableName]*MergeStats
}

Types of Dolt Merge Conflicts

During the merge process, Dolt can encounter four distinct categories of conflicts, each recorded in specific system tables or counters defined in go/libraries/doltcore/merge/merge_stats.go.

Data (Row) Conflicts

Row conflicts occur when the same primary-key row is modified on both branches in incompatible ways. These are recorded in the dolt_conflicts system table and counted in the DataConflicts field of MergeStats. Inside MergeTable in merge.go, the logic detects these clashes by comparing the base, ours, and theirs versions of each row.

Schema Conflicts

Schema conflicts arise when table definitions differ between branches—such as when one branch adds a column that the other drops, or when incompatible type changes occur. These populate the dolt_schema_conflicts system table and the SchemaConflicts slice in the Result struct. The detection logic in MergeTable appends conflicts when mergedTable.conflict.Count() > 0.

Root-Object Conflicts

Root-object conflicts involve non-row database objects like foreign-key collections, indexes, or collation settings that changed on both sides. These are tracked via RootObjectConflicts in merge_stats.go and handled by specialized logic in go/libraries/doltcore/merge/merge_artifacts.go.

Constraint Violations

Constraint violations occur when the merged result would break CHECK, NOT NULL, or foreign-key constraints. These are stored in dolt_constraint_violations and populated by functions like AddForeignKeyViolations called during MergeRoots.

Inspecting Dolt Merge Conflicts

Once a merge completes with conflicts, Dolt leaves the repository in a conflicted state and provides multiple interfaces to inspect the specific discrepancies.

System Tables Overview

After running dolt merge, query these system tables to assess the damage:

-- Data conflicts
SELECT table_name, num_conflicts FROM dolt_conflicts;

-- Schema conflicts
SELECT * FROM dolt_schema_conflicts;

-- Constraint violations
SELECT * FROM dolt_constraint_violations;

The CLI prints a summary of these statistics via the printMergeStats function in go/cmd/dolt/commands/merge.go, which calculates conflict counts from the MergeStats returned by the merge operation.

Previewing Row-Level Conflicts

For a detailed, row-by-row preview of data conflicts, use the dolt_preview_merge_conflicts table function implemented in go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts.go:

SELECT *
FROM dolt_preview_merge_conflicts('HEAD', 'feature-branch', 'orders')
ORDER BY dolt_conflict_id;

This function builds a temporary Merger and runs a three-way diff (tree.NewThreeWayDiffer) to emit columns showing base_<col>, our_<col>, and their_<col> values, along with our_diff_type and their_diff_type indicators (added, modified, removed).

Resolving Dolt Merge Conflicts

Dolt provides three primary resolution strategies once you have inspected the conflicts. You must resolve all conflicts before the merge can be committed.

Manual Resolution with SQL

Update the working set directly using standard DML statements, then stage the results:

-- Fix the row manually
UPDATE contacts SET email = 'resolved@example.com' WHERE id = 1;

-- Stage the resolved table
CALL DOLT_ADD('contacts');

Using dolt resolve Command

Choose a winning side programmatically using the CLI:


# Accept their version entirely

dolt resolve --theirs orders

# Accept our version entirely

dolt resolve --ours orders

# Resolve specific tables

dolt resolve contacts customers

This marks the conflicts as resolved in the internal metadata, allowing the merge to proceed.

Automated SQL MERGE Strategies

For complex resolution logic, write a SQL MERGE statement that incorporates data from both sides, then commit:

MERGE INTO contacts t
USING (SELECT * FROM dolt_preview_merge_conflicts('HEAD', 'dev1', 'contacts')) c
ON t.id = c.dolt_conflict_id
WHEN MATCHED THEN UPDATE SET t.email = COALESCE(c.their_email, c.base_email);

After resolution, verify no conflicts remain:

SELECT COUNT(*) FROM dolt_conflicts;
-- Should return 0

Then finalize:

dolt commit -m "Merge feature-branch with conflict resolution"

Complete Workflow Example

Here is a reproducible example demonstrating conflict creation and resolution:


# Setup: Create a conflict scenario

dolt checkout -b dev1
dolt sql -q "CREATE TABLE contacts (id INT PRIMARY KEY, email VARCHAR(50)); INSERT INTO contacts VALUES (1,'alice@example.com');"
dolt add .
dolt commit -m "Add contacts table"

dolt checkout main
dolt sql -q "INSERT INTO contacts VALUES (1,'alice@work.com');"
dolt add .
dolt commit -m "Update alice email on main"

# Execute merge (will conflict)

dolt merge dev1

# Output: Automatic merge failed; 1 table(s) are unmerged.

# Inspect via SQL

dolt sql -q "
SELECT base_id, base_email, our_email, their_email
FROM dolt_preview_merge_conflicts('HEAD', 'dev1', 'contacts');
"

# Resolve by accepting dev1's version

dolt resolve --theirs contacts
dolt add contacts
dolt commit -m 'Merge dev1, keeping dev1 version of contacts'

Summary

  • Dolt merge conflicts arise during three-way merges when row data, schema definitions, root objects, or constraints diverge between branches.
  • The merge pipeline flows from go/cmd/dolt/commands/merge.go (CLI) → DOLT_MERGE stored procedure → go/libraries/doltcore/merge/merge.go (core logic).
  • Conflicts are categorized into data conflicts (dolt_conflicts), schema conflicts (dolt_schema_conflicts), root-object conflicts, and constraint violations (dolt_constraint_violations).
  • Use dolt_preview_merge_conflicts for detailed row-level inspection before resolution.
  • Resolve conflicts via manual SQL edits, dolt resolve --ours/--theirs, or automated SQL MERGE statements, then commit to complete the merge.

Frequently Asked Questions

What causes Dolt merge conflicts?

Dolt merge conflicts occur when the three-way merge algorithm in go/libraries/doltcore/merge/merge.go detects incompatible changes between the "our," "their," and common ancestor versions of a table. This happens when the same row is modified on both branches (data conflict), when schema changes clash such as dropping a column on one side that was renamed on the other (schema conflict), or when root-level objects like foreign keys diverge (root-object conflict).

How do I view Dolt merge conflicts in SQL?

Query the dolt_conflicts system table for a summary count, or use the dolt_preview_merge_conflicts('ours_branch', 'theirs_branch', 'table_name') table function for detailed row-level diffs showing base, our, and their column values. The preview function is implemented in go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts.go and is the most accurate way to see exactly what data conflicts exist before resolving them.

What is the difference between dolt_conflicts and dolt_schema_conflicts?

dolt_conflicts stores data conflicts—rows with the same primary key that were modified differently on each branch—while dolt_schema_conflicts stores schema conflicts where the table structure itself changed incompatibly (for example, a column was added on one branch and dropped on the other). Data conflicts prevent the merge from completing until resolved, whereas schema conflicts may block the merge entirely depending on the severity of the structural change.

Can I abort a merge with conflicts in Dolt?

Yes. If you invoked the merge via the CLI with --no-commit or simply want to abandon the merge before committing, run dolt merge --abort to return the working set to the state before the merge began. This operation resets any partial merge results and clears conflict markers from the system tables, effectively canceling the MergeCommits operation initiated in go/libraries/doltcore/merge/merge.go.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →