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.1

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.1
File Size Uploaded Provenance Actions
reconcile_text-0.13.1.tar.gz 76.1 MB Without provenance Details for reconcile_text-0.13.1.tar.gz

Built distributions (wheels)

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

Total release size: 77.3 MB

Release files / reconcile_text-0.13.1.tar.gz

Download URL reconcile_text-0.13.1.tar.gz
Size 76.1 MB
Tags Source
SHA-256 checksum
How to use checksums
1a5b02e247117ff6007ee3f7b7dd80c4e9f2e3d0f52748cf620f12eaf01e128d
BLAKE2b-256 checksum
How to use checksums
5555c6f5542af939c20bfc032eae44880c784c3da514f3bdd684e9ed2af9c9d4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

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

Download URL reconcile_text-0.13.1-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
a63a90df18598700c868b7a25d541f17e752fadec42baf2ff0e55a036f4efd12
BLAKE2b-256 checksum
How to use checksums
ad5e058229dbf76c89b5d7acba95ee284dbe94a7751818099d157fe1f675d750
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

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

Download URL reconcile_text-0.13.1-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
cd7c9625e2271142c03ccb79b12f7c3c3e3fc4c95ce243b53e69e3afe5779b14
BLAKE2b-256 checksum
How to use checksums
a8f10e53aacc254451c9cb87b9335861414f4088579d5666f98badceb8b37f72
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

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

Download URL reconcile_text-0.13.1-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
7e1eb2146bc29128776aa210539d01089b39f9e5f48fb8ce5a219f7f50324ea5
BLAKE2b-256 checksum
How to use checksums
12113d2878a37ef8190c66d927fe7fd6fb0197c627ce3632f94e06b68bcf9e5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

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

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

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

Download URL reconcile_text-0.13.1-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
23a5e1b57a781e6e45ce8f9bcf4d19eedd3172175d333dea7f14b043ed7d55ed
BLAKE2b-256 checksum
How to use checksums
b261bbc7606dd0cce0edbbda06d6ddf1b8d87f3df804598b007666fec5041f87
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

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

Download URL reconcile_text-0.13.1-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
22ebb2652618178d64ada1250888a4ed5dc2ac261edb9cfe1e1f8ba86738f2c6
BLAKE2b-256 checksum
How to use checksums
c1a674d963418242220468c9436e5e901b04d9e6490aea113b920a95a189e624
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.12.6

Security for reconcile-text 0.13.1

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