Tree differencing with moves (Chawathe et al. 1996, GumTree 2014) and structural merge/diff tools (Mergiraf, difftastic)
Document status:
reviewed. Canonical source.
Summary
Chawathe, Rajaraman, Garcia-Molina and Widom (SIGMOD 1996) defined the tree change-detection problem as finding a minimum-cost edit script over insert, delete, update and subtree move, split it into finding a matching and then generating a conforming script, and gave an O(ne + e²) algorithm under domain assumptions. GumTree (ASE 2014) keeps the Chawathe script generator but replaces matching with a two-phase heuristic: a greedy top-down search for the largest isomorphic subtrees, then a bottom-up phase that matches containers by Dice similarity of already matched descendants and recovers further matches with an optimal tree-edit-distance algorithm on small subtrees; worst case O(n²). Mergiraf (2024-) applies GumTree classic matching to base/left/right trees, converts them to parent-child-successor triples, merges the triple sets, and emits conflict nodes or falls back to diff3 for the affected element; it treats designated “commutative parents” specially and identifies their children by signatures. Difftastic computes a diff as a lowest-cost path with Dijkstra’s algorithm over pairs of tree positions and does not detect moves. Across these systems the expensive and heuristic step is matching; every subsequent step (edit script, three-way merge) is defined relative to a matching. When entities carry stable identifiers, the matching is the identity map and the residual problems are ordering and move conflicts.
Evidence
- Chawathe et al.: DOI 10.1145/233269.233366, SIGMOD 1996 pp. 493-504 (SIGMOD Record 25(2), PDF retrieved 2026-08-29 from https://sigmodrecord.org/1996/06/24/change-detection-in-hierarchically-structured-information/). Edit operations
INS,DEL(leaf only; interior deletion requires moving descendants first),UPD,MOVof a subtree (§3.1); an edit script conforms to a partial matching if it does not insert or delete matched nodes (§3.2); cost model with acomparefunction in[0, 2]for updates (§3.3); five phases update, align, insert, move, delete in one breadth-first scan of the new tree plus a post-order delete pass (§4.1, §4.2); LCS-based child alignment yields the minimum number of moves (§4.2); Matching Criterion 1 (leaves: equal labels andcompare ≤ f,0 ≤ f < 1), Criterion 2 (internal nodes: fraction of common leaves> t,1/2 ≤ t < 1), Assumption 1 (acyclic labels), Assumption 2 (at most one close leaf), Theorem 5.1 (unique maximal matching is the best matching), algorithm FastMatch (§5); running timeO(ne + e²)withnleaves andethe weighted edit distance (§1). - GumTree problem statement: actions
update,add,delete,move(t, tp, i)moving a subtree; the shortest script with moves is NP-hard; the best add/delete/update algorithm (RTED) isO(n³). ASE 2014 PDF §2 (retrieved 2026-08-29 from https://www.labri.fr/perso/xblanc/data/papers/ASE14.pdf). - GumTree top-down phase (Algorithm 1): height-indexed priority lists, processing nodes of equal greatest height, isomorphism by hash then exact test, ambiguous candidates ranked by
dice(parent(t1), parent(t2), M), only nodes with height greater thanminHeight. §3.1. Dice:dice(t1, t2, M) = 2·|{t1' ∈ s(t1) | (t1', t2') ∈ M}| / (|s(t1)| + |s(t2)|). - GumTree bottom-up phase (Algorithm 2): a candidate
cfor unmatched internalt1requires equal labels,cunmatched, and matched descendants; the candidate with greatest Dice is matched ifdice > minDice; when the remaining subtrees are both smaller thanmaxSizean optimal algorithmopt(RTED) recovers descendant mappings for same-label nodes. §3.2. - GumTree script generation: “RTED does not handle moves,” so the script is produced with Chawathe et al.’s algorithm from the mappings. §2 and §3.3 (Complexity Analysis).
- GumTree complexity: worst case
O(n²),n = max(|T1|, |T2|), from the Cartesian products in both phases. §3.3. Replication settings:minHeight = 2,minDice = 0.5,maxSize = 100. §5.2.3. - GumTree implementation (main branch, retrieved 2026-08-29):
core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.javausesPriorityTreeQueue,HashBasedMapper,DEFAULT_MIN_PRIORITY = 1, optionsst_minprio,st_priocalc(defaultheight);GreedySubtreeMatcher.javaresolves ambiguous mappings by sorting on maximum subtree size thenFullMappingComparator;GreedyBottomUpMatcher.javausesDEFAULT_SIM_THRESHOLD = 0.5,DEFAULT_SIZE_THRESHOLD = 1000, optionsbu_minsim,bu_minsize, andZsMatcherfor last-chance matching;actions/ChawatheScriptGenerator.javawalks the destination tree breadth-first emittingInsert,Update,Move, aligns children with LCS, computes positions withfindPos, and emitsDeletein post-order. The code defaults (min priority 1, size threshold 1000) differ from the paper’s (minHeight2,maxSize100). - GumTree README cites hyperparameter optimisation (Martinez et al., IEEE TSE 2023, DOI 10.1109/TSE.2023.3315935) and a scalable variant (Falleri and Martinez, ICSE 2024, DOI 10.1145/3597503.3639148). https://github.com/GumTreeDiff/gumtree (retrieved 2026-08-29).
- Mergiraf architecture: tree-sitter parsing with multi-line leaves split into lines; “the GumTree classic algorithm” in top-down and bottom-up phases applied to base-left, base-right and left-right; class mapping with leader preference base, left, right; conversion to parent-child-successor triples
(p, c, s)with sentinels; quadruplets tagged by revision merged into a possibly inconsistent set with base triples removed when contradicted; commutative parents merged by applying right’s deletions and appending right’s additions; signature-based duplicate detection; delete/modify conflicts decided by a covering check that distinguishes moves; fallback to diff3 on the parent element’s source; output node kindsExactTree,Conflict,LineBasedMerge,MixedTree,CommutativeChildSeparator; fast mode cannot resolve “moving edited elements”. https://mergiraf.org/architecture.html (retrieved 2026-08-29). - Mergiraf conflict classes: commutative insertions (e.g. class members) are auto-resolved; order-dependent insertions (statements in a block, function arguments) are not; duplicate signatures under commutative parents are flagged; moved-and-edited code is replayed at the new location. https://mergiraf.org/conflicts.html (retrieved 2026-08-29). Repository: Rust, GPL-3.0, 24 releases. https://codeberg.org/mergiraf/mergiraf (retrieved 2026-08-29).
- Difftastic: a diff is “a route finding problem on a directed acyclic graph”; a vertex is a pair of positions in the two trees; edges marking a node novel cost more than matching; Dijkstra’s algorithm finds the lowest-cost route with vertices constructed lazily. https://difftastic.wilfred.me.uk/diffing.html (retrieved 2026-08-29). Tricky cases: no move detection; sliders; preference for matches at the same nesting depth. https://difftastic.wilfred.me.uk/tricky_cases.html (retrieved 2026-08-29). Repository: Rust, MIT, tree-sitter parsers, no merge or patch output. https://github.com/Wilfred/difftastic (retrieved 2026-08-29).
Mechanism
Problem decomposition (Chawathe et al. §1, §3; adopted by GumTree §2):
input: T1 (old), T2 (new)
step 1: matching M ⊆ nodes(T1) × nodes(T2), partial injective, label-preserving
step 2: edit script E conforming to M, minimising Σ cost(op)
ops: INS(x, parent, k, l, v) | DEL(x) | UPD(x, v) | MOV(x, parent, k)
Script generation from a matching (Chawathe §4.1; GumTree ChawatheScriptGenerator):
for y in BFS(T2):
if y unmatched: x := INS(new, partner(parent(y)), findPos(y)); M += (x, y)
else x := partner(y):
if value(x) ≠ value(y): UPD(x, value(y))
if partner(parent(y)) ≠ parent(x): MOV(x, partner(parent(y)), findPos(y))
alignChildren(x, y): keep LCS of matched children fixed, MOV the rest
for x in postorder(T1): if x unmatched: DEL(x)
GumTree matching:
top-down: process nodes by decreasing height; isomorphic subtrees (hash, then exact) are mapped wholesale;
ambiguous candidates ranked by Dice of their parents; stop below minHeight
bottom-up: for unmatched internal t1 in post-order: candidates c with label(c)=label(t1), c unmatched,
matched descendants; match argmax dice if dice > minDice;
if |t1|,|t2| < maxSize: run optimal edit distance (RTED / Zhang-Shasha) on the residue
Three-way structured merge (Mergiraf, PCS form):
PCS(T) = {(p, c, s) | s is the immediate successor of c under p} ∪ sentinels
merge = PCS(base)^tagged ∪ PCS(left) ∪ PCS(right) minus base triples contradicted by a side
inconsistency (two successors for one (p, c), two parents for one (c, s)) → Conflict node or diff3 fallback
commutative parent: children set = left ∪ (right additions) minus (right deletions); duplicates by signature → conflict
Identity observation (NUIF interpretation): with stable entity identifiers M = {(x, y) | id(x) = id(y)}, which removes both GumTree phases and the ambiguity they resolve heuristically. The residue is exactly the class Mergiraf cannot resolve automatically: order-dependent insertions and moved-and-edited elements, i.e. sibling-order and move conflicts.
NUIF relevance
Borrow
- The Chawathe edit-operation vocabulary (insert, delete, update, move-subtree) and the conforming-script discipline: NUIF operations in nuif-protocol already mirror it (
Insert,Remove,Rename/SetExtension,Move). - Mergiraf’s commutative-parent and signature concepts: NUIF relation sets and unordered property maps are commutative parents by construction, and entity IDs are exact signatures.
- Conflict nodes embedded in the merged tree (Mergiraf
Conflict) as the representation for spec/06’s typed conflicts.
Adapt
- GumTree’s matcher is still needed at the boundary: importing documents from formats without stable IDs (SVG, Figma exports, generated code) requires a matching step, and GumTree-style heuristics with tuned thresholds are the reference for that import path.
- The LCS-based child alignment (Chawathe §4.2) should be reused to convert two child sequences into a minimal move set when NUIF diffs snapshots rather than replaying operations.
- Mergiraf’s diff3 fallback on the enclosing element corresponds to NUIF’s textual fallback for human review in the canonical text form (
nuif-text-0).
Reject
- Treating move detection as a heuristic: NUIF records moves explicitly; inferred moves are only for import.
- Difftastic’s shortest-path diff as a merge primitive: it optimises display, ignores moves, and produces no patch.
Open questions
- Which alignment cost (Chawathe’s move-minimising LCS versus a Dice-weighted variant) produces the fewest spurious
Moveoperations when diffing NUIF snapshots that differ by reordering. - Whether Mergiraf’s covering algorithm for delete/modify conflicts has an identity-based analogue that distinguishes “moved out then deleted” from “edited while deleted”.
- How to calibrate GumTree thresholds for design-document trees (wide, shallow, many identical leaves) where the paper’s defaults were tuned on Java ASTs.