Attempt to merge a conflict group using token-level semantics. # Algorithm 1. Extract the two competing vertices (only 2-way supported). 2. Retrieve content bytes for both from the [`ChangeStore`]. 3. Find the common ancestor vertex — the dead vertex that both competing changes deleted. 4. Retrieve content bytes for the ancestor. 5. Tokenize all three byte sequences. 6. Run a three-way diff at t
(&self, group: &ConflictGroup)
| 87 | /// |
| 88 | /// Returns [`PristineError`] on database access failure. |
| 89 | pub fn try_merge(&self, group: &ConflictGroup) -> Result<MergeOutcome, PristineError> { |
| 90 | // ── Step 1: Read content for every vertex in the group ──────── |
| 91 | // |
| 92 | // If any vertex's content is unreadable we give up immediately. |
| 93 | let mut vertex_contents: Vec<(GraphNode<NodeId>, Vec<u8>)> = Vec::new(); |
| 94 | for v in &group.vertices { |
| 95 | match self.get_vertex_content(v) { |
| 96 | Ok(c) => vertex_contents.push((*v, c)), |
| 97 | Err(e) => { |
| 98 | log::debug!("try_merge: cannot read vertex {}: {}", v, e); |
| 99 | return Ok(MergeOutcome::NoCrdtData); |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | if vertex_contents.is_empty() { |
| 104 | return Ok(MergeOutcome::NoCrdtData); |
| 105 | } |
| 106 | |
| 107 | // ── Step 2: Deduplicate identical-content vertices ──────────── |
| 108 | // |
| 109 | // Collapse vertices that share byte-identical content into a |
| 110 | // single representative. This handles the common N-way fork |
| 111 | // where multiple concurrent changes insert the same whitespace |
| 112 | // or the same boilerplate line. Indices of surviving (unique) |
| 113 | // vertices are collected in `unique`; everything else lands in |
| 114 | // `skipped` so the caller can mark them. |
| 115 | let mut unique: Vec<usize> = vec![0]; |
| 116 | let mut skipped: Vec<usize> = Vec::new(); |
| 117 | for i in 1..vertex_contents.len() { |
| 118 | if unique |
| 119 | .iter() |
| 120 | .any(|&j| vertex_contents[j].1 == vertex_contents[i].1) |
| 121 | { |
| 122 | skipped.push(i); |
| 123 | } else { |
| 124 | unique.push(i); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | // All vertices identical — no conflict at all. |
| 129 | if unique.len() == 1 && !skipped.is_empty() { |
| 130 | log::info!( |
| 131 | "try_merge: all {} vertices identical ({} bytes), deduplicating", |
| 132 | vertex_contents.len(), |
| 133 | vertex_contents[0].1.len(), |
| 134 | ); |
| 135 | return Ok(MergeOutcome::AutoMerged { |
| 136 | content: vertex_contents[0].1.clone(), |
| 137 | sources: vec![MergeSource::new(vertex_contents[0].0.change, 0)], |
| 138 | }); |
| 139 | } |
| 140 | |
| 141 | // Partial dedup: log, but continue with the unique subset. |
| 142 | if !skipped.is_empty() { |
| 143 | log::info!( |
| 144 | "try_merge: dedup reduced {}-way fork to {}-way", |
| 145 | vertex_contents.len(), |
| 146 | unique.len(), |