Perform a three-way merge of token sequences. `base` — the common ancestor's tokens `left` — one side's tokens `right` — the other side's tokens Returns [`ThreeWayResult::Merged`] when all edits are to different positions, or [`ThreeWayResult::Conflict`] when at least one token position was changed by both sides to different values.
(
base: &[MergeToken],
left: &[MergeToken],
right: &[MergeToken],
)
| 97 | /// positions, or [`ThreeWayResult::Conflict`] when at least one token |
| 98 | /// position was changed by both sides to different values. |
| 99 | pub fn three_way_merge( |
| 100 | base: &[MergeToken], |
| 101 | left: &[MergeToken], |
| 102 | right: &[MergeToken], |
| 103 | ) -> ThreeWayResult { |
| 104 | // Fast path: if left and right are identical, no conflict possible. |
| 105 | if left == right { |
| 106 | let content = reassemble_tokens(left); |
| 107 | return ThreeWayResult::Merged(content); |
| 108 | } |
| 109 | |
| 110 | // Fast path: if left is unchanged, take right. |
| 111 | if left == base { |
| 112 | let content = reassemble_tokens(right); |
| 113 | return ThreeWayResult::Merged(content); |
| 114 | } |
| 115 | |
| 116 | // Fast path: if right is unchanged, take left. |
| 117 | if right == base { |
| 118 | let content = reassemble_tokens(left); |
| 119 | return ThreeWayResult::Merged(content); |
| 120 | } |
| 121 | |
| 122 | // --- General case: LCS-based alignment --- |
| 123 | |
| 124 | let left_ops = diff_tokens(base, left); |
| 125 | let right_ops = diff_tokens(base, right); |
| 126 | |
| 127 | // Collect base indices modified by each side. |
| 128 | let left_modified = modified_indices(&left_ops); |
| 129 | let right_modified = modified_indices(&right_ops); |
| 130 | |
| 131 | // Check for overlapping modifications. |
| 132 | for idx in left_modified.intersection(&right_modified) { |
| 133 | // Both sides touched the same base index. This is only OK |
| 134 | // if they produced the *same* replacement. |
| 135 | let left_replacement = replacement_for(&left_ops, *idx); |
| 136 | let right_replacement = replacement_for(&right_ops, *idx); |
| 137 | if left_replacement != right_replacement { |
| 138 | return ThreeWayResult::Conflict; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | // Check for conflicting insertions at the same position. |
| 143 | let left_inserts = insert_positions(&left_ops); |
| 144 | let right_inserts = insert_positions(&right_ops); |
| 145 | for pos in left_inserts.keys() { |
| 146 | if let Some(right_content) = right_inserts.get(pos) { |
| 147 | let left_content = &left_inserts[pos]; |
| 148 | if left_content != right_content { |
| 149 | return ThreeWayResult::Conflict; |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | // No conflicts — merge. |
| 155 | let merged = merge_ops(base, &left_ops, &right_ops); |
| 156 | ThreeWayResult::Merged(merged) |