Skip to main content

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!

Or read my blog post on it!

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

Metadata

Release files for reconcile-text 0.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reconcile-text 0.13.0
File Size Uploaded Provenance Actions
reconcile_text-0.13.0.tar.gz 76.1 MB Without provenance Details for reconcile_text-0.13.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for reconcile-text 0.13.0
File Actions
reconcile_text-0.13.0-pp311-pypy311_pp73-win_amd64.whl PyPy 3.11 PyPy 3.11 7.3 Windows x86-64 Details for reconcile_text-0.13.0-pp311-pypy311_pp73-win_amd64.whl
reconcile_text-0.13.0-cp314-cp314t-win_amd64.whl CPython 3.14 CPython 3.14 free-threading Windows x86-64 Details for reconcile_text-0.13.0-cp314-cp314t-win_amd64.whl
reconcile_text-0.13.0-cp313-cp313t-win_amd64.whl CPython 3.13 CPython 3.13 free-threading Windows x86-64 Details for reconcile_text-0.13.0-cp313-cp313t-win_amd64.whl
reconcile_text-0.13.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details for reconcile_text-0.13.0-cp39-abi3-win_amd64.whl
reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details for reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details for reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Total release size: 77.2 MB

Release files / reconcile_text-0.13.0.tar.gz

Download URL reconcile_text-0.13.0.tar.gz
Size 76.1 MB
Tags Source
SHA-256 checksum
How to use checksums
0dda2399883e6cac7a8c8313f43c8b8858e503cbee115ae8ba177cb25fbdee6e
BLAKE2b-256 checksum
How to use checksums
380635581dcd259735fc85eda12576f88826061ae5181a69e185d018181d5da9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Release files / reconcile_text-0.13.0-pp311-pypy311_pp73-win_amd64.whl

Download URL reconcile_text-0.13.0-pp311-pypy311_pp73-win_amd64.whl
Size 152.4 kB
Tags PyPy 3.11 PyPy 3.11 7.3 Windows x86-64
SHA-256 checksum
How to use checksums
92a5ad567e0ff401200c13d1729c27c2d8dc961b92af21caff7c76f82ab02464
BLAKE2b-256 checksum
How to use checksums
87a08133f03b1ad248019a02ee6d39054ac779b1afec6dd52cc0a28d9598a50a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Release files / reconcile_text-0.13.0-cp314-cp314t-win_amd64.whl

Download URL reconcile_text-0.13.0-cp314-cp314t-win_amd64.whl
Size 149.6 kB
Tags CPython 3.14 CPython 3.14 free-threading Windows x86-64
SHA-256 checksum
How to use checksums
6518e702bbc7da7bfca56896aee9fd44766a9e09ee2ec434cba7cbba76f092b6
BLAKE2b-256 checksum
How to use checksums
00b5fa33a4c91e0663e18c9f7aa46c6c94d20caaabaf9d5670b7e3060df6fa60
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Release files / reconcile_text-0.13.0-cp313-cp313t-win_amd64.whl

Download URL reconcile_text-0.13.0-cp313-cp313t-win_amd64.whl
Size 149.6 kB
Tags CPython 3.13 CPython 3.13 free-threading Windows x86-64
SHA-256 checksum
How to use checksums
85ec45430e1b9b7a4c394eaf453871b66440fbc3207cdb7edb1a39a8e22df496
BLAKE2b-256 checksum
How to use checksums
80556d332dd4a3b61f4aa8cb6e31e9e05bedc6c831fc932c590d69d3a2ea7277
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Release files / reconcile_text-0.13.0-cp39-abi3-win_amd64.whl

Download URL reconcile_text-0.13.0-cp39-abi3-win_amd64.whl
Size 153.4 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
7e382cd18276d0eb1bde5410726a07bef40dcceeaecd0b1b4590c33d6dbc5997
BLAKE2b-256 checksum
How to use checksums
6a14d4fc41524d7957f97598445ff21d079257406a76b74fbc0e6ab6f6098d8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Release files / reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 270.8 kB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
15634b8d76d4830cbedc23a73ca1563adb841b9657412a4f97ac52d199d03725
BLAKE2b-256 checksum
How to use checksums
194a02f375b9b7540e1975cf9d27cd1086e9aff94cd735db070f7ec47e882679
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Release files / reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL reconcile_text-0.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 259.2 kB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
fa3eab2faf3864adeb60d29b45254336ff8a9d4530d8bb8f6e627cf97ae15b2b
BLAKE2b-256 checksum
How to use checksums
fa9a8c7c65f33d62533d378fde1f0f77d55faa5bb2aeb1836df198b99ef1a7b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Security for reconcile-text 0.13.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page