How to Merge Branches

Open inAnthropic

What you'll achieve

By the end of this guide, you will know how to merge branches, detect conflicts, resolve them, and roll back failed merges in TerminusDB.

Merging in TerminusDB applies changes from one branch onto another — like git merge. TerminusDB uses a three-way merge that detects conflicts at the field level. If both branches modified the same field on the same document, the merge fails with a precise conflict report — no silent data loss.

No silent overwrites

TerminusDB never silently resolves conflicts. If two branches changed the same field, the merge fails and reports exactly what conflicted. You decide which value wins.

Prerequisites

  • TerminusDB running on localhost:6363
  • A database with at least two branches. See How to Branch to create one.
  • Examples use admin/mydb with branches main and feature.

1. Merge a branch into main

Apply all changes from feature onto main. This is the most common merge operation.

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/apply/admin/mydb/local/branch/main \
  -H "Content-Type: application/json" \
  -d '{
    "before_commit": "admin/mydb/local/branch/main",
    "after_commit": "admin/mydb/local/branch/feature",
    "commit_info": {
      "author": "alice@example.com",
      "message": "Merge feature into main"
    }
  }'

Expected response (success):

Example: JSON
{"@type": "api:ApplyResponse", "api:status": "api:success"}

TypeScript

Example: TypeScript
import TerminusClient from "@terminusdb/terminusdb-client";

const client = new TerminusClient.WOQLClient("http://localhost:6363", {
  user: "admin",
  key: "root",
  organization: "admin",
  db: "mydb",
});

await client.apply(
  "admin/mydb/local/branch/main",    // target branch
  "admin/mydb/local/branch/main",    // before (common ancestor)
  "admin/mydb/local/branch/feature", // after (source of changes)
  { author: "alice@example.com", message: "Merge feature into main" }
);

Python

Example: Python
from terminusdb_client import Client

client = Client("http://localhost:6363")
client.connect(user="admin", key="root", db="mydb")

client.apply(
    before_commit="admin/mydb/local/branch/main",
    after_commit="admin/mydb/local/branch/feature",
    commit_msg="Merge feature into main",
    author="alice@example.com"
)

2. Preview changes before merging (diff)

Always review what will change before merging. The diff shows exactly which documents and fields were modified.

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/diff/admin/mydb \
  -H "Content-Type: application/json" \
  -d '{"before_data_version": "main", "after_data_version": "feature"}'

Expected response:

Example: JSON
[
  {"@id": "terminusdb:///data/Product/Widget", "price": {"@op": "SwapValue", "@before": 9.99, "@after": 14.99}},
  {"@id": "terminusdb:///data/Product/Gadget", "@op": "Insert"}
]

Operations explained:

  • SwapValue — a field value changed (shows before and after)
  • Insert — a new document exists on the source branch but not on the target
  • Delete — a document exists on the target but not on the source branch

3. Handle merge conflicts

A conflict occurs when both branches changed the same field on the same document. TerminusDB detects this and rejects the merge.

Conflict response

Example: JSON
{
  "@type": "api:ApplyResponse",
  "api:status": "api:conflict",
  "api:witnesses": [
    {
      "@id": "terminusdb:///data/Product/Widget",
      "price": {
        "@op": "SwapValue",
        "@before": 9.99,
        "@after_left": 12.99,
        "@after_right": 14.99
      }
    }
  ]
}

This tells you:

  • Document: Product/Widget
  • Field: price
  • Original value: 9.99
  • main changed it to: 12.99 (@after_left)
  • feature changed it to: 14.99 (@after_right)

Resolving conflicts

To resolve, you must decide which value wins and apply it manually:

# Option A: Accept the feature branch value (14.99)
curl -u admin:root -X PUT \
  "http://localhost:6363/api/document/admin/mydb/local/branch/main?author=alice&message=Resolve+conflict:+accept+feature+price" \
  -H "Content-Type: application/json" \
  -d '{"@id": "Product/Widget", "@type": "Product", "name": "Widget", "price": 14.99}'

# Then retry the merge (remaining non-conflicting changes will apply)
# Option B: Keep main's value (12.99) — no action needed on main
# Just update the feature branch to match main before re-merging:
curl -u admin:root -X PUT \
  "http://localhost:6363/api/document/admin/mydb/local/branch/feature?author=alice&message=Resolve+conflict:+accept+main+price" \
  -H "Content-Type: application/json" \
  -d '{"@id": "Product/Widget", "@type": "Product", "name": "Widget", "price": 12.99}'

4. Squash before merging

Collapse all commits on a branch into a single commit — useful for cleaning up history before merging.

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/squash/admin/mydb/local/branch/feature \
  -H "Content-Type: application/json" \
  -d '{"commit_info": {"author": "alice@example.com", "message": "Squash feature branch"}}'

Expected response:

Example: JSON
{"@type": "api:SquashResponse", "api:status": "api:success", "api:commit": "system:data/admin/mydb/local/branch/feature/commit/abc123"}

After squashing, the feature branch has one clean commit that you can merge into main.


5. Merge from main into feature (rebase pattern)

Keep your feature branch up-to-date with main by merging main's changes into your branch:

HTTP API

curl -u admin:root -X POST http://localhost:6363/api/apply/admin/mydb/local/branch/feature \
  -H "Content-Type: application/json" \
  -d '{
    "before_commit": "admin/mydb/local/branch/feature",
    "after_commit": "admin/mydb/local/branch/main",
    "commit_info": {
      "author": "alice@example.com",
      "message": "Merge latest main into feature"
    }
  }'

This brings your feature branch up-to-date with main's latest changes, reducing the chance of conflicts when you later merge back.


Complete merge workflow

# 1. Check what changed on feature vs main
curl -u admin:root -X POST http://localhost:6363/api/diff/admin/mydb \
  -H "Content-Type: application/json" \
  -d '{"before_data_version": "main", "after_data_version": "feature"}'

# 2. Squash feature branch for clean history
curl -u admin:root -X POST http://localhost:6363/api/squash/admin/mydb/local/branch/feature \
  -H "Content-Type: application/json" \
  -d '{"commit_info": {"author": "alice", "message": "Feature: add Widget product"}}'

# 3. Merge into main
curl -u admin:root -X POST http://localhost:6363/api/apply/admin/mydb/local/branch/main \
  -H "Content-Type: application/json" \
  -d '{"before_commit": "admin/mydb/local/branch/main", "after_commit": "admin/mydb/local/branch/feature", "commit_info": {"author": "alice", "message": "Merge feature: add Widget product"}}'

# 4. Clean up the branch
curl -u admin:root -X DELETE http://localhost:6363/api/branch/admin/mydb/local/branch/feature

Next steps

Was this helpful?