Skip to main content

Intelligent 3-way text merging with automated conflict resolution

Project description

reconcile-text: conflict-free 3-way text merging

A Rust, TypeScript, and Python library for merging conflicting text edits without manual intervention. Unlike traditional 3-way merge tools that produce conflict markers, reconcile-text automatically resolves conflicts by applying both sets of changes (while updating cursor positions) using an algorithm inspired by Operational Transformation.

Try it

Try the interactive demo to see it in action!

Install it in your project

Key features

  • No conflict markers - Clean, merged output without Git's <<<<<<< markers
  • Cursor tracking - Automatically repositions cursors and selections throughout the merging process
  • Flexible tokenisation - Word-level (default), character-level, line-level, or custom tokenisation strategies
  • Unicode support - Full UTF-8 support with proper handling of complex scripts and grapheme clusters
  • Cross-platform - Native Rust performance with WebAssembly bindings for JavaScript and native bindings for Python

Quick start

Rust

Install via crates.io:

cargo add reconcile-text

Alternatively, add reconcile-text to your Cargo.toml:

[dependencies]
reconcile-text = "0.8"

Then start merging:

use reconcile_text::{reconcile, BuiltinTokenizer};

// Start with the original text
let parent = "Hello world";
// Two users edit simultaneously
let left = "Hello beautiful world";  // Added "beautiful"
let right = "Hi world";              // Changed "Hello" to "Hi"

// Reconcile combines both changes
let result = reconcile(parent, &left.into(), &right.into(), &*BuiltinTokenizer::Word);
assert_eq!(result.apply().text(), "Hi beautiful world");

See the merge-file example for another example, or the library's documentation.

JavaScript/TypeScript

Install via NPM:

npm install reconcile-text

Then use it in your application:

import { reconcile } from 'reconcile-text';

// Start with the original text
const parent = 'Hello world';
// Two users edit simultaneously
const left = 'Hello beautiful world';
const right = 'Hi world';

const result = reconcile(parent, left, right);
console.log(result.text); // "Hi beautiful world"

See the example website source for a more complex example, or the advanced examples document.

React Native (Hermes)

React Native's default engine, Hermes, does not expose a runtime WebAssembly global, so the WebAssembly build cannot run there. For React Native, the package ships a pure-JavaScript build produced by Binaryen's wasm2js via its react-native entry point.

Python

Install via uv or pip:

uv add reconcile-text
# or: pip install reconcile-text

Then use it in your application:

from reconcile_text import reconcile

# Start with the original text
parent = "Hello world"
# Two users edit simultaneously
left = "Hello beautiful world"
right = "Hi world"

result = reconcile(parent, left, right)
print(result["text"])  # "Hi beautiful world"

See the merge-file example for a file-merging CLI, or the advanced examples document for cursor tracking, change provenance, and compact diffs.

Motivation

Collaborative editing presents the challenge of merging conflicting changes when multiple users edit documents simultaneously or asynchronously whilst offline. Traditional solutions like Conflict-free Replicated Data Types (CRDTs) or Operational Transformation (OT) work well when you control the complete editing infrastructure and can capture every individual operation (1). However, many workflows involve users editing with various tools, for example, Obsidian users editing Markdown files with various editors ranging from Vim to VS Code.

This creates Differential Synchronisation scenarios (2, 3): we only know the final state of each document, not the sequence of operations that produced it. This is the same challenge Git addresses, but Git requires manual conflict resolution. The key insight is that while incorrect merges in source code can introduce bugs, human text is more forgiving: a slightly imperfect sentence is often preferable to conflict markers interrupting the flow.

Note: Some text domains require more careful handling. Legal contracts, for instance, could have unintended meaning changes from conflicting edits that create double negations. At the same time, semantic conflicts can still arise when merging code, even in the absence of syntactic conflicts.

Differential sync is implemented by universal-sync, and it requires a merging tool that creates conflict-free results for the best user experience.

How it works

reconcile-text starts off similarly to diff3 (4, 5) but adds automated conflict resolution. Given a parent document and two modified versions (left and right), the following happens:

  1. Tokenisation - Input texts are split into meaningful units (words, characters, etc.) for granular merging
  2. Diff computation - Myers' algorithm calculates differences between (parent ↔ left) and (parent ↔ right)
  3. Diff optimisation - Operations are reordered and consolidated to maximise chained changes
  4. Operational Transformation - Edits are woven together using OT principles, preserving all modifications and updating cursors

Whilst the primary goal of reconcile-text isn't to implement OT, it provides an elegant way to merge Myers' diff outputs. (For a dedicated Rust OT implementation, see operational-transform-rs.) The same could be achieved with CRDTs, which many libraries implement well for text (see Loro, cola, and automerge).

However, when only the end result of concurrent changes is observable, merge quality depends entirely on the quality of the underlying 2-way diffs. For instance, move operations cannot be supported because Myers' algorithm decomposes them into separate insert and delete operations, regardless of the merging algorithm used.

Comparison with other approaches

Traditional 3-way merge (diff3, Git)

Tools like diff3 (4) and Git produce conflict markers (<<<<<<< / ======= / >>>>>>>) when both sides modify the same region. This works for source code where a human must verify correctness, but breaks the reading flow for prose. reconcile-text uses the same diff3-like foundation but adds an OT-inspired resolution step that eliminates conflict markers entirely. Libraries like diffy, merge3 (Rust), and node-diff3 (JavaScript) all fall into this category.

diff-match-patch

diff-match-patch is a widely-used library created by Neil Fraser at Google in 2006, providing character-level diffing (Myers' algorithm), fuzzy string matching (Bitap algorithm), and patch application. It powers Fraser's Differential Synchronisation protocol (2): compute a diff between two texts, apply the patch to a third text that may have drifted, and repeat until convergence. If a patch fails, the failure self-corrects in the next sync cycle.

The key differences from reconcile-text:

  • 2-way vs 3-way - diff-match-patch diffs two texts and applies the result as a patch. It has no concept of a common ancestor and cannot reason about "left changes" vs "right changes". reconcile-text performs true 3-way merging, understanding the intent behind each side's edits.

  • Character-level only - Word-level and line-level diffs require encoding tokens as single Unicode characters before diffing (7). reconcile-text supports word, character, line, and custom tokenisation natively.

  • Patches can fail - patch_apply returns a boolean array indicating success per patch; failed patches are silently dropped. In Differential Synchronisation, failures self-correct in the next cycle, but for one-shot merges edits can be lost. reconcile-text always produces a complete merged result.

  • No cursor tracking or change provenance - diff-match-patch does not reposition cursors or track which side made which edit. reconcile-text does both automatically.

See the comparison example for concrete cases where diff-match-patch garbles adjacent edits and silently drops an entire sentence, while reconcile-text merges both users' changes correctly.

When to use diff-match-patch instead: when you don't have a common ancestor, for example synchronising texts that have diverged through an unknown sequence of edits. If you have a common ancestor (as in most version control and collaborative editing scenarios), reconcile-text produces more reliable results.

CRDTs (Yjs, Automerge, Loro, diamond-types)

Conflict-free Replicated Data Types guarantee convergence by mathematical construction: every operation commutes, so the order of application doesn't matter. Libraries like Yjs (and its Rust port Yrs), Automerge, Loro, cola, and diamond-types implement this approach.

CRDTs capture every individual keystroke or operation, assigning each a unique identity. This makes them ideal when you control the complete editing infrastructure: the editor, the transport layer, and the storage format. They work peer-to-peer, handle arbitrary numbers of concurrent editors, and never lose an edit.

The trade-off is that CRDTs require maintaining document state over time - an operation log or internal data structure that grows with the document's edit history. You cannot simply hand a CRDT library three plain strings and get a merged result. This makes them unsuitable for Differential Synchronisation scenarios where you only observe the final state of each document, which is exactly the niche reconcile-text fills.

When to use CRDTs instead: if you control the complete editing stack and can capture every operation as it happens, CRDTs provide stronger convergence guarantees. They also support more than two concurrent editors naturally, whereas reconcile-text merges exactly two forks at a time (though merges can be chained).

Operational Transformation (OT)

OT libraries like ot.js and ShareJS transform concurrent operations against each other so that applying them in any order produces the same result. Like CRDTs, they capture individual operations and require infrastructure to coordinate them, typically a central server that determines the canonical operation order.

reconcile-text borrows the concept of OT (transforming one side's edits against the other) but applies it to a different problem. Instead of transforming individual keystrokes in real time, it transforms the consolidated diff output of two complete edits. This means it doesn't need a server, doesn't need to capture operations as they happen, and works entirely offline.

When to use OT instead: if you need real-time collaboration with sub-second latency and can run a coordination server, dedicated OT libraries handle this well. reconcile-text is designed for merge points, not live keystroke-by-keystroke synchronisation.

Development

Contributions are welcome!

Environment

Python setup

Install uv and build the extension for development:

cd reconcile-python
uv run maturin develop

Node.js setup

  1. Install nvm:
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
    
  2. Install and use Node 22:
    nvm install 22 && nvm use 22
    
  3. Optionally, set as default:
    nvm alias default 22
    

Rust toolchain

Install rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Scripts

  • Run tests: scripts/test.sh
  • Lint and format: scripts/lint.sh
  • Develop demo website: scripts/dev-website.sh
  • Build demo website: scripts/build-website.sh
  • Publish new version: scripts/bump-version.sh patch

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reconcile_text-0.12.1.tar.gz (76.1 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

reconcile_text-0.12.1-pp311-pypy311_pp73-win_amd64.whl (154.2 kB view details)

Uploaded PyPyWindows x86-64

reconcile_text-0.12.1-cp314-cp314t-win_amd64.whl (151.4 kB view details)

Uploaded CPython 3.14tWindows x86-64

reconcile_text-0.12.1-cp313-cp313t-win_amd64.whl (151.3 kB view details)

Uploaded CPython 3.13tWindows x86-64

reconcile_text-0.12.1-cp39-abi3-win_amd64.whl (155.5 kB view details)

Uploaded CPython 3.9+Windows x86-64

reconcile_text-0.12.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (273.0 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

reconcile_text-0.12.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (261.1 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

File details

Details for the file reconcile_text-0.12.1.tar.gz.

File metadata

  • Download URL: reconcile_text-0.12.1.tar.gz
  • Upload date:
  • Size: 76.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.12.6

File hashes

Hashes for reconcile_text-0.12.1.tar.gz
Algorithm Hash digest
SHA256 d4a0b94b7696aa7006229234f95458d6e9e7d59d17aab1df8a1302bed9ebbf60
MD5 f02794cd8436d58eda1ca11484c068a1
BLAKE2b-256 4576c00509f7fcfe7f9c972fa8ba3e2bf0517f337391ef93b92ca0e15c059592

See more details on using hashes here.

File details

Details for the file reconcile_text-0.12.1-pp311-pypy311_pp73-win_amd64.whl.

File metadata

File hashes

Hashes for reconcile_text-0.12.1-pp311-pypy311_pp73-win_amd64.whl
Algorithm Hash digest
SHA256 00327e23957126a6fad3284508c11ccab058bc77fecae61cdc96ab6a4f86d278
MD5 cd9703d22341b893e3906a4a7f8d71c9
BLAKE2b-256 e9edb449f2311df311dd9401365b776c7a7dc9ffbcd5f452eb9865bdf6534b88

See more details on using hashes here.

File details

Details for the file reconcile_text-0.12.1-cp314-cp314t-win_amd64.whl.

File metadata

File hashes

Hashes for reconcile_text-0.12.1-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 04516590857d1ae7ea576e20c7221a4a681c355f2317be36684def026d322a21
MD5 55e39082b6c48a766a98a90cda88bb20
BLAKE2b-256 d4d782e356d83a32eae1d10caa6b2866a2abc4c71da3a73de6baf4dd711227b4

See more details on using hashes here.

File details

Details for the file reconcile_text-0.12.1-cp313-cp313t-win_amd64.whl.

File metadata

File hashes

Hashes for reconcile_text-0.12.1-cp313-cp313t-win_amd64.whl
Algorithm Hash digest
SHA256 36d69f80e72d9753bd938a00737cb446c280cb39f26e138c88f8f7a7f5327a5a
MD5 dc67b4ebb278525cdde0243bc38fbe98
BLAKE2b-256 cfef9a2d4e1d17d546458829f7bfc0a87abaab6dcaa8f8e4acba18459715e89e

See more details on using hashes here.

File details

Details for the file reconcile_text-0.12.1-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for reconcile_text-0.12.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 dbd7460687dd0b2a225dd2e2ba629e22f5625fca34466658d6abab710bca8a28
MD5 5246ea1f138e843f811c08335e4e9bfe
BLAKE2b-256 3cf83bd9dc6781ab8dd2fc9d21ccc12967c4c921e9add0a6715b80c4d7549fb9

See more details on using hashes here.

File details

Details for the file reconcile_text-0.12.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for reconcile_text-0.12.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 70544b67fae81dfa7a4fe141f812e552ea051f5cd60e2c40db4bc64335428f8a
MD5 8ff674d6d49462001150c0fe9c871c21
BLAKE2b-256 dc6ae11ee22e17a93439422853d1b21ea61590eb96ea158cf9395d4d1608f4db

See more details on using hashes here.

File details

Details for the file reconcile_text-0.12.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for reconcile_text-0.12.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 6f25b420b729fc78c789a9a4f4ca3fec8781a21782f540eaa2e229fa36e6d9f6
MD5 2d76aeefff6d87c74a0a0c82192aa473
BLAKE2b-256 6d4920a8d584614b75c407158db51ae40c918ac20ad80aa1694c4a1096cbe4a7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page