version control

DoltGres is Postgres that branches, commits, and merges like Git — but the unit of versioning is the whole database, not a file. Every operation is a SQL function you call with SELECT.

the model: working → staged → committed

Like Git, DoltGres tracks three tiers of change. A normal INSERT/UPDATE/DELETE lands in the working set — your uncommitted edits. You stage the changes you want to keep, then commit them to permanent, named history.

  • working set — uncommitted, unstaged edits. Each branch has its own working set, so changes stay isolated.
  • staged — changes added with dolt_add, queued for the next commit.
  • committed — a permanent, hashed snapshot of the entire database state, linked to its parent commit.

gotcha: a Dolt commit is NOT a SQL transaction COMMIT

These are two different things that both happen to be called “commit”. A SQL COMMIT just ends a database transaction — it makes your writes durable in the working set. A Dolt commit (SELECT dolt_commit(...)) creates a versioned snapshot in history with an author, message, and content hash. You can run a thousand SQL transactions and still have zero Dolt commits. To capture a point in history, you must take a Dolt commit explicitly.

the golden rule: SELECT, never CALL

DoltGres speaks the PostgreSQL dialect, and Postgres allows side-effects inside a SELECT (the same way nextval() does). So every mutating version-control operation is a function invoked with SELECT:

SELECT dolt_commit('-a', '-m', 'add notes table');   -- DoltGres (Postgres)

If you have seen MySQL-flavored Dolt, this is the one thing to unlearn: MySQL Dolt uses CALL dolt_commit(...). DoltGres does not — it is always SELECT dolt_xxx(...).

the mutating functions

Each returns a small result — a status, and/or a commit hash and message. The core set:

functionexamplereturns
dolt_addSELECT dolt_add('-A');status
dolt_commitSELECT dolt_commit('-a','-m','msg');hash
dolt_checkoutSELECT dolt_checkout('-b','feat');status, message
dolt_branchSELECT dolt_branch('-c','main','feat');status
dolt_mergeSELECT dolt_merge('feat','--no-ff','-m','msg');hash, fast_forward, conflicts, message
dolt_resetSELECT dolt_reset('--hard','<hash>');status
dolt_tagSELECT dolt_tag('v1','HEAD');status

The same SELECT dolt_xxx(...) convention covers the rest of the surface too — dolt_revert, dolt_cherry_pick, dolt_rebase, dolt_stash, dolt_clean, and dolt_verify_constraints.

end-to-end: branch → change → commit → merge

A realistic workflow. Make a feature branch, edit data on it, commit the snapshot, switch back to main, and merge the branch in.

-- 1. create a feature branch off main and switch to it
SELECT dolt_checkout('-b', 'add-welcome-note');

-- 2. make changes (ordinary SQL — these land in the working set)
INSERT INTO notes (id, body) VALUES ('n_001', 'welcome to briven');

-- 3. stage everything and commit a snapshot of the whole database
SELECT dolt_add('-A');
SELECT dolt_commit('-m', 'seed welcome note');   -- returns the new commit hash

-- 4. go back to main
SELECT dolt_checkout('main');

-- 5. merge the feature branch in (--no-ff forces a real merge commit)
SELECT dolt_merge('add-welcome-note', '--no-ff', '-m', 'merge welcome note');

dolt_merge returns a row with hash, fast_forward, conflicts, and message. If conflicts is non-zero, the merge stopped and is waiting for you to resolve them (see below). A fast-forward merge — where main had no new commits since the branch point — moves the pointer forward and creates no merge commit unless you pass --no-ff.

reading state: system tables

Writes are functions; reads are tables. DoltGres exposes version-control state as tables in the dolt schema, each with a dolt_-prefixed alias you can use unqualified. This dolt. schema namespace is the main DoltGres-specific divergence from MySQL Dolt (which only has the dolt_ prefixed names).

-- what is staged / unstaged right now
SELECT * FROM dolt.status;

-- every branch, its head commit, latest committer + message
SELECT * FROM dolt.branches;

-- commit history reachable from the current HEAD
SELECT * FROM dolt.commits WHERE date < '2026-01-01';

-- the same tables are reachable via the dolt_ aliases:
SELECT * FROM dolt_status;
SELECT * FROM dolt_branches;

conflicts

DoltGres merges cell-by-cell. A conflict happens when both branches changed the same cell to different values. Unlike Git, conflicts are not written as inline <<<<< markers in a file — they are recorded in a per-table system table: dolt_conflicts_<table>. Each conflicted cell shows three values:

  • base — the original common-ancestor value
  • ours — the current branch's value
  • theirs — the merging branch's value
-- inspect the conflicts on a specific table
SELECT * FROM dolt_conflicts_notes;

-- resolve by keeping the current branch's version of every conflicted row
SELECT dolt_conflicts_resolve('--ours', 'notes');

-- ...or keep the incoming branch's version
SELECT dolt_conflicts_resolve('--theirs', 'notes');

resolving conflicts does not guarantee a valid merge

Merges happen at the storage layer, so combining two branches can still produce a database that violates a constraint (for example a foreign-key reference that only existed on one side). “Zero conflicts” is not the same as “valid merge.” After resolving, run SELECT dolt_verify_constraints(); to surface any constraint violations the merge introduced before you commit it.

info functions

Read-only helpers for answering “where am I?” and “how do these two branches relate?”

-- which branch is this session on?
SELECT active_branch();

-- the common-ancestor commit of two branches (their merge base)
SELECT dolt_merge_base('main', 'add-welcome-note');

what to read next