A Rust and TypeScript library for merging conflicting text edits without manual intervention. http://schmelczer.dev/reconcile
  • Rust 71.1%
  • TypeScript 16.3%
  • JavaScript 5.9%
  • Python 5.1%
  • Shell 1.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-28 22:36:47 +00:00
.forgejo/workflows Fix publishing 2026-10-28 08:44:15 +00:00
.vscode Fix intellisense 2026-03-12 20:25:32 +00:00
docs Fix docs mistake 2026-10-27 21:12:22 +00:00
examples Fix linting 2026-10-28 09:06:41 +00:00
reconcile-js Make optional optional 2026-10-28 19:28:49 +00:00
reconcile-python More bindings tests 2026-10-27 21:30:47 +00:00
scripts Remove version hardcoding 2026-10-28 10:44:51 +00:00
src Fix markdown tokenizing roundtrip 2026-10-28 11:43:02 +00:00
tests Fix test 2026-10-28 19:28:37 +00:00
.gitignore Add react-native support (#63) 2026-05-31 20:28:20 +01:00
.prettierrc Format files with prettier 2025-07-06 22:21:17 +01:00
Cargo.lock Bump versions to 0.13.2 2026-09-27 20:44:17 +01:00
Cargo.toml Bump versions to 0.13.2 2026-09-27 20:44:17 +01:00
LICENSE Update cargo toml metadata 2025-07-09 22:35:57 +01:00
README.md Simplify readme 2026-10-28 22:36:47 +00:00
renovate.json Add renovate.json 2026-09-19 10:41:27 +00:00
rust-toolchain.toml Use stable rust 2026-03-11 21:07:23 +00:00
rustfmt.toml Use stable rust 2026-03-11 21:07:23 +00:00

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

Merge a common parent and two edited versions into one text, without conflict markers. reconcile-text combines changes from both sides and updates any cursor positions you pass in. It is available as a library for Rust, JavaScript/TypeScript, and Python.

It is built for syncing text files when all you have are snapshots: the document before editing and the two versions it became. The merge needs no saved operation history or persistent document metadata beyond that parent text.

Try it

✨ Try the interactive demo to see it in action!

For the motivation and algorithm behind the library, read Conflict-Free Three-Way Text Merging.

Install it in your project

Quick start

Each example merges two changes to the same greeting: one side adds a word, while the other changes the greeting itself.

Rust

Install from crates.io:

cargo add reconcile-text
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 a file-merging CLI, or the Rust API documentation.

JavaScript/TypeScript

Install from 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 TypeScript guide for cursors, change provenance, and compact diffs, or the demo website source for an editor integration.

The Node.js and browser builds use the Rust library compiled to WebAssembly. For React Native, the package ships a pure-JavaScript build produced by Binaryen's wasm2js via its react-native entry point, so it can run in Hermes without a WebAssembly global.

Python

Install from PyPI with uv or pip:

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

Then use it in your application:

from reconcile_text import reconcile

parent = "Hello world"
left = "Hello beautiful world"
right = "Hi world"

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

The Python package provides native bindings to the same Rust implementation. See the merge-file example for a file-merging CLI, or the Python guide for cursors, change provenance, and compact diffs.

Why merge snapshots?

Collaborative editing presents the challenge of merging conflicting changes when multiple users edit documents simultaneously or asynchronously whilst offline. A note might be edited in Obsidian on one device and Vim on another, with a file sync service carrying changes between them. By the time those changes meet, the syncer sees two files. It cannot ask either editor for the operations that produced them.

A common ancestor gives it enough context to compare both sets of edits. Git works with the same inputs, but usually leaves ambiguous conflicts for a person to resolve. That is a useful default for source code. For notes and prose, an automatic merge can be more useful, even if the wording occasionally needs tidying up.

How it works

reconcile-text starts off similarly to diff3 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.

Choosing a tokeniser

Tokenisation affects the result, especially when edits overlap. The built-in options are:

  • Word: keeps words together; the default in JavaScript/TypeScript and Python.
  • Character: merges individual Unicode code points for finer-grained changes.
  • Line: treats each line as a unit.
  • Markdown: builds on word tokenisation, preserving newlines and attaching block prefixes such as list markers to their first word.

Rust callers choose a tokeniser explicitly and can also supply a custom function. The merge keeps tokens intact, but the Markdown tokeniser does not validate the resulting document's syntax. Try different tokenisers in the demo to see how they handle your text.

Comparison with other approaches

diff3 and Git

diff3 and Git's default text merge leave conflict markers where changes cannot be combined automatically. Git also has a union merge driver that retains lines from both sides of a conflict. Because it works with whole lines, two edits to different words in a sentence can leave two versions of the sentence. Word tokenisation lets reconcile-text combine those edits within the line.

diff-match-patch

diff-match-patch computes two-way diffs and applies patches using fuzzy matching. It is useful when you need to apply changes to text whose context may have shifted. A patch can fail, so callers need to inspect its success flags.

reconcile-text uses the common parent to derive both sides' changes before combining them. The comparison example shows how the two approaches handle overlapping replacements and a sentence inserted into a rewritten paragraph.

CRDTs and OT

Libraries based on conflict-free replicated data types (CRDTs), such as Yjs, Automerge, and Loro, maintain document structures and metadata that let replicas merge updates. They fit applications that can persist that state and integrate edits into it. Snapshot changes can also be translated into CRDT updates; using a CRDT does not require recording every keystroke.

OT libraries such as ot.js work with operations and a protocol for coordinating them. reconcile-text uses OT ideas internally, deriving the operations from each pair of snapshots at merge time.

The practical distinction is what your application keeps between merges. reconcile-text accepts three texts and merges two forks at a time. A collaborative editing system built around a CRDT or OT also manages the state and protocol needed to coordinate replicas over time.

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
  • Check lint and formatting: 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