| c07e0e7d Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 6706bdcf Andy McCall ↶ |
4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| c07e0e7d Andy McCall ↶ |
12 |
|
| 13 |
use crate::hash::ChangeHash; |
| 14 |
use crate::objects::{AtomRange, AtomRef, Change, Edit, OpRef, TreeOp}; |
| 15 |
use crate::state::State; |
| 16 |
use std::collections::{BTreeSet, HashMap}; |
| 17 |
|
| 18 |
|
| 19 |
#[derive(Clone, Debug, Default, PartialEq, Eq)] |
| 20 |
pub struct Correspondence { |
| 728da0f9 Andy McCall ↶ |
21 |
|
| 22 |
|
| 23 |
pub atoms: HashMap<(crate::ids::NodeId, u32), u32>, |
| c07e0e7d Andy McCall ↶ |
24 |
|
| 25 |
pub ops: HashMap<u32, u32>, |
| 26 |
} |
| 27 |
|
| 28 |
impl Correspondence { |
| 29 |
|
| 30 |
pub fn identity(change: &Change) -> Correspondence { |
| 31 |
let mut atoms = HashMap::new(); |
| 32 |
let mut per_node: HashMap<crate::ids::NodeId, u32> = HashMap::new(); |
| 33 |
for e in &change.edits { |
| 34 |
if let Edit::Insert { node, lines, .. } = e { |
| 35 |
let idx = per_node.entry(*node).or_default(); |
| 36 |
for _ in lines { |
| 728da0f9 Andy McCall ↶ |
37 |
atoms.insert((*node, *idx), *idx); |
| c07e0e7d Andy McCall ↶ |
38 |
*idx += 1; |
| 39 |
} |
| 40 |
} |
| 41 |
} |
| 728da0f9 Andy McCall ↶ |
42 |
let ops = (0..change.tree_ops.len() as u32).map(|i| (i, i)).collect(); |
| 43 |
Correspondence { atoms, ops } |
| 44 |
} |
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
pub fn by_content(old: &Change, new: &Change) -> Correspondence { |
| 50 |
let lines_of = |c: &Change| -> HashMap<crate::ids::NodeId, Vec<Vec<u8>>> { |
| 51 |
let mut m: HashMap<crate::ids::NodeId, Vec<Vec<u8>>> = HashMap::new(); |
| 52 |
for e in &c.edits { |
| 53 |
if let Edit::Insert { node, lines, .. } = e { |
| 54 |
m.entry(*node).or_default().extend(lines.iter().cloned()); |
| 55 |
} |
| 56 |
} |
| 57 |
m |
| 58 |
}; |
| 59 |
let (a, b) = (lines_of(old), lines_of(new)); |
| 60 |
let mut atoms = HashMap::new(); |
| 61 |
for (node, olds) in &a { |
| 62 |
let Some(news) = b.get(node) else { continue }; |
| 63 |
let mut j = 0usize; |
| 64 |
for (i, line) in olds.iter().enumerate() { |
| 65 |
while j < news.len() && news[j] != *line { |
| 66 |
j += 1; |
| 67 |
} |
| 68 |
if j < news.len() { |
| 69 |
atoms.insert((*node, i as u32), j as u32); |
| 70 |
j += 1; |
| 71 |
} |
| 72 |
} |
| 73 |
} |
| 74 |
let mut ops = HashMap::new(); |
| 75 |
for (i, op) in old.tree_ops.iter().enumerate() { |
| 75167672 Andy McCall |
76 |
if let Some(j) = new.tree_ops.iter().position(|n| { |
| 77 |
n.node() == op.node() && std::mem::discriminant(n) == std::mem::discriminant(op) |
| 78 |
}) { |
| 728da0f9 Andy McCall ↶ |
79 |
ops.insert(i as u32, j as u32); |
| 80 |
} |
| c07e0e7d Andy McCall ↶ |
81 |
} |
| 82 |
Correspondence { atoms, ops } |
| 83 |
} |
| 84 |
} |
| 85 |
|
| 86 |
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] |
| 87 |
pub enum RewriteError { |
| 88 |
#[error("change {0} is not in the state")] |
| 89 |
Missing(ChangeHash), |
| 90 |
} |
| 91 |
|
| 6706bdcf Andy McCall ↶ |
92 |
|
| 93 |
#[derive(Clone, Debug, Default)] |
| 94 |
pub struct Propagation { |
| 95 |
|
| 96 |
pub changes: Vec<(ChangeHash, Change)>, |
| 97 |
|
| 98 |
pub notes: Vec<String>, |
| 99 |
} |
| 100 |
|
| 101 |
|
| 102 |
struct Chain { |
| 728da0f9 Andy McCall ↶ |
103 |
node: crate::ids::NodeId, |
| 6706bdcf Andy McCall ↶ |
104 |
first: u32, |
| 105 |
last: u32, |
| 106 |
after: AtomRef, |
| 107 |
before: AtomRef, |
| 108 |
} |
| 109 |
|
| 110 |
fn chains_of(c: &Change) -> Vec<Chain> { |
| 111 |
let mut per_node: HashMap<crate::ids::NodeId, u32> = HashMap::new(); |
| 112 |
let mut out = Vec::new(); |
| 113 |
for e in &c.edits { |
| 75167672 Andy McCall |
114 |
if let Edit::Insert { |
| 115 |
node, |
| 116 |
after, |
| 117 |
before, |
| 118 |
lines, |
| 119 |
} = e |
| 120 |
{ |
| 6706bdcf Andy McCall ↶ |
121 |
let idx = per_node.entry(*node).or_default(); |
| 75167672 Andy McCall |
122 |
out.push(Chain { |
| 123 |
node: *node, |
| 124 |
first: *idx, |
| 125 |
last: *idx + lines.len() as u32 - 1, |
| 126 |
after: *after, |
| 127 |
before: *before, |
| 128 |
}); |
| 6706bdcf Andy McCall ↶ |
129 |
*idx += lines.len() as u32; |
| 130 |
} |
| 131 |
} |
| 132 |
out |
| 133 |
} |
| 134 |
|
| c07e0e7d Andy McCall ↶ |
135 |
|
| 136 |
#[derive(Clone, Debug)] |
| 137 |
enum Replacement { |
| 138 |
Change(ChangeHash), |
| 139 |
|
| 140 |
Removed(Vec<ChangeHash>), |
| 141 |
} |
| 142 |
|
| 143 |
|
| 144 |
|
| 6706bdcf Andy McCall ↶ |
145 |
|
| 146 |
|
| 147 |
|
| 252e8cb2 Andy McCall ↶ |
148 |
|
| 149 |
|
| 75167672 Andy McCall |
150 |
pub fn propagate( |
| 151 |
state: &State, |
| 152 |
old: ChangeHash, |
| 153 |
new: Option<&Change>, |
| 154 |
corr: &Correspondence, |
| 155 |
load: &dyn Fn(&ChangeHash) -> Option<Change>, |
| 156 |
) -> Result<Propagation, RewriteError> { |
| 6706bdcf Andy McCall ↶ |
157 |
let old_meta = state.get(&old).ok_or(RewriteError::Missing(old))?; |
| 158 |
let old_full = load(&old); |
| c07e0e7d Andy McCall ↶ |
159 |
let mut mapping: HashMap<ChangeHash, Replacement> = HashMap::new(); |
| 75167672 Andy McCall |
160 |
mapping.insert( |
| 161 |
old, |
| 162 |
match new { |
| 163 |
Some(n) => Replacement::Change(n.hash()), |
| 164 |
None => Replacement::Removed(old_meta.deps.clone()), |
| 165 |
}, |
| 166 |
); |
| c07e0e7d Andy McCall ↶ |
167 |
let mut corrs: HashMap<ChangeHash, Correspondence> = HashMap::new(); |
| 168 |
corrs.insert(old, corr.clone()); |
| 6706bdcf Andy McCall ↶ |
169 |
|
| 75167672 Andy McCall |
170 |
let chains: HashMap<ChangeHash, Vec<Chain>> = old_full |
| 171 |
.as_ref() |
| 172 |
.map(|c| (old, chains_of(c))) |
| 173 |
.into_iter() |
| 174 |
.collect(); |
| 6706bdcf Andy McCall ↶ |
175 |
let mut vanished_nodes: BTreeSet<crate::ids::NodeId> = BTreeSet::new(); |
| 176 |
if let Some(c) = &old_full { |
| 177 |
for (i, op) in c.tree_ops.iter().enumerate() { |
| 178 |
if let TreeOp::AddNode { node, .. } = op { |
| 179 |
let survives = new.is_some() && corr.ops.contains_key(&(i as u32)); |
| 180 |
if !survives { |
| 181 |
vanished_nodes.insert(*node); |
| 182 |
} |
| 183 |
} |
| 184 |
} |
| 185 |
} |
| 186 |
let mut notes = Vec::new(); |
| c07e0e7d Andy McCall ↶ |
187 |
|
| 188 |
|
| 189 |
|
| 75167672 Andy McCall |
190 |
let mut dependents: Vec<ChangeHash> = state |
| 191 |
.changes() |
| 192 |
.filter(|(h, _)| **h != old && state.closure(h).contains(&old)) |
| 193 |
.map(|(h, _)| *h) |
| 194 |
.collect(); |
| c07e0e7d Andy McCall ↶ |
195 |
dependents.sort_by_key(|h| (state.closure(h).len(), *h)); |
| 196 |
|
| 197 |
let mut out = Vec::new(); |
| 198 |
for h in dependents { |
| 252e8cb2 Andy McCall ↶ |
199 |
let c = load(&h).ok_or(RewriteError::Missing(h))?; |
| 75167672 Andy McCall |
200 |
let rewritten = rewrite_one( |
| 201 |
&h, |
| 202 |
&c, |
| 203 |
&mapping, |
| 204 |
&corrs, |
| 205 |
&chains, |
| 206 |
&vanished_nodes, |
| 207 |
&mut notes, |
| 208 |
); |
| c07e0e7d Andy McCall ↶ |
209 |
let rh = rewritten.hash(); |
| 210 |
corrs.insert(h, Correspondence::identity(&rewritten)); |
| 211 |
mapping.insert(h, Replacement::Change(rh)); |
| 212 |
out.push((h, rewritten)); |
| 213 |
} |
| 75167672 Andy McCall |
214 |
Ok(Propagation { |
| 215 |
changes: out, |
| 216 |
notes, |
| 217 |
}) |
| c07e0e7d Andy McCall ↶ |
218 |
} |
| 219 |
|
| 6706bdcf Andy McCall ↶ |
220 |
|
| 221 |
|
| 222 |
|
| 75167672 Andy McCall |
223 |
fn reanchor( |
| 224 |
change: &ChangeHash, |
| 225 |
index: u32, |
| 226 |
want_after: bool, |
| 227 |
mapping: &HashMap<ChangeHash, Replacement>, |
| 228 |
corrs: &HashMap<ChangeHash, Correspondence>, |
| 229 |
chains: &HashMap<ChangeHash, Vec<Chain>>, |
| 230 |
) -> AtomRef { |
| 6706bdcf Andy McCall ↶ |
231 |
let corr = corrs.get(change); |
| 75167672 Andy McCall |
232 |
let chain = chains |
| 233 |
.get(change) |
| 234 |
.and_then(|cs| cs.iter().find(|ch| ch.first <= index && index <= ch.last)); |
| 235 |
if let (Some(Replacement::Change(nh)), Some(corr), Some(ch)) = |
| 236 |
(mapping.get(change), corr, chain) |
| 237 |
{ |
| 238 |
let candidates: Box<dyn Iterator<Item = u32>> = if want_after { |
| 239 |
Box::new((ch.first..index).rev()) |
| 240 |
} else { |
| 241 |
Box::new(index + 1..=ch.last) |
| 242 |
}; |
| 6706bdcf Andy McCall ↶ |
243 |
for j in candidates { |
| 728da0f9 Andy McCall ↶ |
244 |
if let Some(nj) = corr.atoms.get(&(ch.node, j)) { |
| 75167672 Andy McCall |
245 |
return AtomRef::Atom { |
| 246 |
change: *nh, |
| 247 |
index: *nj, |
| 248 |
}; |
| 6706bdcf Andy McCall ↶ |
249 |
} |
| c07e0e7d Andy McCall ↶ |
250 |
} |
| 6706bdcf Andy McCall ↶ |
251 |
} |
| 252 |
match chain { |
| 75167672 Andy McCall |
253 |
Some(ch) => { |
| 254 |
if want_after { |
| 255 |
ch.after |
| 256 |
} else { |
| 257 |
ch.before |
| 258 |
} |
| 259 |
} |
| 260 |
None => { |
| 261 |
if want_after { |
| 262 |
AtomRef::Start |
| 263 |
} else { |
| 264 |
AtomRef::End |
| 265 |
} |
| 266 |
} |
| 6706bdcf Andy McCall ↶ |
267 |
} |
| 268 |
} |
| 269 |
|
| 270 |
fn rewrite_one( |
| 271 |
hash: &ChangeHash, |
| 272 |
c: &Change, |
| 273 |
mapping: &HashMap<ChangeHash, Replacement>, |
| 274 |
corrs: &HashMap<ChangeHash, Correspondence>, |
| 275 |
chains: &HashMap<ChangeHash, Vec<Chain>>, |
| 276 |
vanished_nodes: &BTreeSet<crate::ids::NodeId>, |
| 277 |
notes: &mut Vec<String>, |
| 278 |
) -> Change { |
| 279 |
let short = hash.short(12); |
| 280 |
|
| 75167672 Andy McCall |
281 |
let map_anchor = |r: AtomRef, |
| 282 |
node: &crate::ids::NodeId, |
| 283 |
want_after: bool, |
| 284 |
notes: &mut Vec<String>| |
| 285 |
-> AtomRef { |
| c07e0e7d Andy McCall ↶ |
286 |
match r { |
| 6706bdcf Andy McCall ↶ |
287 |
AtomRef::Atom { change, index } => match mapping.get(&change) { |
| 728da0f9 Andy McCall ↶ |
288 |
Some(Replacement::Change(nh)) => match corrs[&change].atoms.get(&(*node, index)) { |
| 75167672 Andy McCall |
289 |
Some(idx) => AtomRef::Atom { |
| 290 |
change: *nh, |
| 291 |
index: *idx, |
| 292 |
}, |
| 6706bdcf Andy McCall ↶ |
293 |
None => { |
| 294 |
notes.push(format!("change {short}: an anchor line of the rewritten change vanished; re-anchored to its nearest neighbour")); |
| 295 |
reanchor(&change, index, want_after, mapping, corrs, chains) |
| 296 |
} |
| 297 |
}, |
| 298 |
Some(Replacement::Removed(_)) => { |
| 299 |
notes.push(format!("change {short}: an anchor line of the removed change vanished; re-anchored to its neighbours")); |
| 300 |
reanchor(&change, index, want_after, mapping, corrs, chains) |
| c07e0e7d Andy McCall ↶ |
301 |
} |
| 6706bdcf Andy McCall ↶ |
302 |
None => r, |
| c07e0e7d Andy McCall ↶ |
303 |
}, |
| 6706bdcf Andy McCall ↶ |
304 |
other => other, |
| c07e0e7d Andy McCall ↶ |
305 |
} |
| 306 |
}; |
| 6706bdcf Andy McCall ↶ |
307 |
|
| 308 |
let map_op = |r: OpRef| -> Option<OpRef> { |
| 309 |
match mapping.get(&r.change) { |
| 75167672 Andy McCall |
310 |
Some(Replacement::Change(nh)) => corrs[&r.change].ops.get(&r.index).map(|idx| OpRef { |
| 311 |
change: *nh, |
| 312 |
index: *idx, |
| 313 |
}), |
| 6706bdcf Andy McCall ↶ |
314 |
Some(Replacement::Removed(_)) => None, |
| 315 |
None => Some(r), |
| c07e0e7d Andy McCall ↶ |
316 |
} |
| 317 |
}; |
| 318 |
let mut out = c.clone(); |
| 319 |
let mut deps: BTreeSet<ChangeHash> = BTreeSet::new(); |
| 320 |
for d in &c.deps { |
| 321 |
match mapping.get(d) { |
| 322 |
Some(Replacement::Change(nh)) => { |
| 323 |
deps.insert(*nh); |
| 324 |
} |
| 325 |
Some(Replacement::Removed(inherited)) => deps.extend(inherited.iter().copied()), |
| 326 |
None => { |
| 327 |
deps.insert(*d); |
| 328 |
} |
| 329 |
} |
| 330 |
} |
| 331 |
out.deps = deps.into_iter().collect(); |
| 6706bdcf Andy McCall ↶ |
332 |
let mut ops = Vec::new(); |
| 333 |
for op in &out.tree_ops { |
| 334 |
let node = op.node(); |
| 75167672 Andy McCall |
335 |
if vanished_nodes.contains(&node) |
| 336 |
|| matches!(op, TreeOp::AddNode { parent, .. } if vanished_nodes.contains(parent)) |
| 337 |
{ |
| 6706bdcf Andy McCall ↶ |
338 |
notes.push(format!("change {short}: an operation on a file that vanished with the rewritten change was dropped")); |
| 339 |
continue; |
| 340 |
} |
| c07e0e7d Andy McCall ↶ |
341 |
match op { |
| 6706bdcf Andy McCall ↶ |
342 |
TreeOp::RemoveNode { node, placement } => match map_op(*placement) { |
| 75167672 Andy McCall |
343 |
Some(p) => ops.push(TreeOp::RemoveNode { |
| 344 |
node: *node, |
| 345 |
placement: p, |
| 346 |
}), |
| 347 |
None => notes.push(format!( |
| 348 |
"change {short}: removal of a placement that vanished was dropped" |
| 349 |
)), |
| 6706bdcf Andy McCall ↶ |
350 |
}, |
| 75167672 Andy McCall |
351 |
TreeOp::Move { |
| 352 |
node, |
| 353 |
old, |
| 354 |
parent, |
| 355 |
name, |
| 356 |
} => match map_op(*old) { |
| 357 |
Some(o) => ops.push(TreeOp::Move { |
| 358 |
node: *node, |
| 359 |
old: o, |
| 360 |
parent: *parent, |
| 361 |
name: name.clone(), |
| 362 |
}), |
| 363 |
None => notes.push(format!( |
| 364 |
"change {short}: a move of {name} lost its origin and was dropped" |
| 365 |
)), |
| 6706bdcf Andy McCall ↶ |
366 |
}, |
| 367 |
TreeOp::SetMode { node, old, mode } => match map_op(*old) { |
| 75167672 Andy McCall |
368 |
Some(o) => ops.push(TreeOp::SetMode { |
| 369 |
node: *node, |
| 370 |
old: o, |
| 371 |
mode: *mode, |
| 372 |
}), |
| 373 |
None => notes.push(format!( |
| 374 |
"change {short}: a mode change lost its origin and was dropped" |
| 375 |
)), |
| 6706bdcf Andy McCall ↶ |
376 |
}, |
| 377 |
add @ TreeOp::AddNode { .. } => ops.push(add.clone()), |
| c07e0e7d Andy McCall ↶ |
378 |
} |
| 379 |
} |
| 6706bdcf Andy McCall ↶ |
380 |
out.tree_ops = ops; |
| 381 |
let mut edits = Vec::new(); |
| 382 |
for e in &out.edits { |
| 383 |
if vanished_nodes.contains(&e.node()) { |
| 384 |
notes.push(format!("change {short}: an edit of a file that vanished with the rewritten change was dropped")); |
| 385 |
continue; |
| 386 |
} |
| c07e0e7d Andy McCall ↶ |
387 |
match e { |
| 75167672 Andy McCall |
388 |
Edit::Insert { |
| 389 |
node, |
| 390 |
after, |
| 391 |
before, |
| 392 |
lines, |
| 393 |
} => edits.push(Edit::Insert { |
| 6706bdcf Andy McCall ↶ |
394 |
node: *node, |
| 728da0f9 Andy McCall ↶ |
395 |
after: map_anchor(*after, node, true, notes), |
| 396 |
before: map_anchor(*before, node, false, notes), |
| 6706bdcf Andy McCall ↶ |
397 |
lines: lines.clone(), |
| 398 |
}), |
| 399 |
Edit::Delete { node, atoms } => { |
| c07e0e7d Andy McCall ↶ |
400 |
let mut mapped: Vec<(ChangeHash, u32)> = Vec::new(); |
| 6706bdcf Andy McCall ↶ |
401 |
let mut dropped = 0; |
| c07e0e7d Andy McCall ↶ |
402 |
for r in atoms.iter() { |
| 403 |
for i in r.from..r.to { |
| 6706bdcf Andy McCall ↶ |
404 |
match mapping.get(&r.change) { |
| 75167672 Andy McCall |
405 |
Some(Replacement::Change(nh)) => { |
| 406 |
match corrs[&r.change].atoms.get(&(*node, i)) { |
| 407 |
Some(idx) => mapped.push((*nh, *idx)), |
| 408 |
None => dropped += 1, |
| 409 |
} |
| 410 |
} |
| 6706bdcf Andy McCall ↶ |
411 |
Some(Replacement::Removed(_)) => dropped += 1, |
| 412 |
None => mapped.push((r.change, i)), |
| c07e0e7d Andy McCall ↶ |
413 |
} |
| 414 |
} |
| 415 |
} |
| 6706bdcf Andy McCall ↶ |
416 |
if dropped > 0 { |
| 75167672 Andy McCall |
417 |
notes.push(format!( |
| 418 |
"change {short}: {dropped} deletion(s) of lines that vanished were dropped" |
| 419 |
)); |
| 6706bdcf Andy McCall ↶ |
420 |
} |
| c07e0e7d Andy McCall ↶ |
421 |
let mut ranges: Vec<AtomRange> = Vec::new(); |
| 422 |
for (ch, i) in mapped { |
| 423 |
match ranges.last_mut() { |
| 424 |
Some(r) if r.change == ch && r.to == i => r.to = i + 1, |
| 75167672 Andy McCall |
425 |
_ => ranges.push(AtomRange { |
| 426 |
change: ch, |
| 427 |
from: i, |
| 428 |
to: i + 1, |
| 429 |
}), |
| c07e0e7d Andy McCall ↶ |
430 |
} |
| 431 |
} |
| 6706bdcf Andy McCall ↶ |
432 |
if !ranges.is_empty() { |
| 75167672 Andy McCall |
433 |
edits.push(Edit::Delete { |
| 434 |
node: *node, |
| 435 |
atoms: ranges, |
| 436 |
}); |
| 6706bdcf Andy McCall ↶ |
437 |
} |
| c07e0e7d Andy McCall ↶ |
438 |
} |
| 6706bdcf Andy McCall ↶ |
439 |
other => edits.push(other.clone()), |
| c07e0e7d Andy McCall ↶ |
440 |
} |
| 441 |
} |
| 6706bdcf Andy McCall ↶ |
442 |
out.edits = edits; |
| c07e0e7d Andy McCall ↶ |
443 |
out.normalize(); |
| 6706bdcf Andy McCall ↶ |
444 |
out |
| c07e0e7d Andy McCall ↶ |
445 |
} |
| 446 |
|
| 447 |
#[cfg(test)] |
| 448 |
mod tests { |
| 449 |
use super::*; |
| 450 |
use crate::hash::ObjectRef; |
| 451 |
use crate::ids::{ChangeId, NodeId}; |
| 452 |
use crate::objects::{ChangeKind, Hlc, Mode, NodeKind}; |
| 453 |
use crate::state::FileContent; |
| 454 |
|
| 455 |
fn change(deps: Vec<ChangeHash>, tree_ops: Vec<TreeOp>, edits: Vec<Edit>, msg: &str) -> Change { |
| 75167672 Andy McCall |
456 |
let mut c = Change { |
| 6f8aeb99 Andy McCall ↶ |
457 |
version: crate::objects::CHANGE_VERSION, |
| 75167672 Andy McCall |
458 |
change_id: ChangeId::random(), |
| 459 |
deps, |
| 460 |
author: ObjectRef::ZERO, |
| 461 |
hlc: Hlc { |
| 462 |
wall_ms: 1, |
| 463 |
logical: 0, |
| 464 |
}, |
| 465 |
message: msg.into(), |
| 466 |
tree_ops, |
| 467 |
edits, |
| 468 |
kind: ChangeKind::Normal, |
| 469 |
}; |
| c07e0e7d Andy McCall ↶ |
470 |
c.normalize(); |
| 471 |
c |
| 472 |
} |
| 473 |
|
| 474 |
#[test] |
| 475 |
fn describe_propagates_through_dependents() { |
| 476 |
let mut st = State::new(); |
| 477 |
let f = NodeId::random(); |
| 75167672 Andy McCall |
478 |
let c1 = change( |
| 479 |
vec![], |
| 480 |
vec![TreeOp::AddNode { |
| 481 |
node: f, |
| 482 |
kind: NodeKind::File, |
| 483 |
mode: Mode::TEXT, |
| 484 |
parent: NodeId::ROOT, |
| 485 |
name: "f".into(), |
| 486 |
}], |
| 487 |
vec![Edit::Insert { |
| 488 |
node: f, |
| 489 |
after: AtomRef::Start, |
| 490 |
before: AtomRef::End, |
| 491 |
lines: vec![b"a\n".to_vec(), b"b\n".to_vec()], |
| 492 |
}], |
| 493 |
"one", |
| 494 |
); |
| c07e0e7d Andy McCall ↶ |
495 |
let h1 = st.add(c1.clone()).unwrap(); |
| 75167672 Andy McCall |
496 |
let c2 = change( |
| 497 |
vec![h1], |
| 498 |
vec![], |
| 499 |
vec![ |
| 500 |
Edit::Delete { |
| 501 |
node: f, |
| 502 |
atoms: vec![AtomRange { |
| 503 |
change: h1, |
| 504 |
from: 0, |
| 505 |
to: 1, |
| 506 |
}], |
| 507 |
}, |
| 508 |
Edit::Insert { |
| 509 |
node: f, |
| 510 |
after: AtomRef::Atom { |
| 511 |
change: h1, |
| 512 |
index: 1, |
| 513 |
}, |
| 514 |
before: AtomRef::End, |
| 515 |
lines: vec![b"c\n".to_vec()], |
| 516 |
}, |
| 517 |
], |
| 518 |
"two", |
| 519 |
); |
| c07e0e7d Andy McCall ↶ |
520 |
let h2 = st.add(c2.clone()).unwrap(); |
| 75167672 Andy McCall |
521 |
let c3 = change( |
| 522 |
vec![h2], |
| 523 |
vec![TreeOp::SetMode { |
| 524 |
node: f, |
| 525 |
old: OpRef { |
| 526 |
change: h1, |
| 527 |
index: 0, |
| 528 |
}, |
| 529 |
mode: Mode { |
| 530 |
exec: true, |
| 531 |
opaque: false, |
| 532 |
}, |
| 533 |
}], |
| 534 |
vec![Edit::Insert { |
| 535 |
node: f, |
| 536 |
after: AtomRef::Atom { |
| 537 |
change: h2, |
| 538 |
index: 0, |
| 539 |
}, |
| 540 |
before: AtomRef::End, |
| 541 |
lines: vec![b"d\n".to_vec()], |
| 542 |
}], |
| 543 |
"three", |
| 544 |
); |
| c07e0e7d Andy McCall ↶ |
545 |
let h3 = st.add(c3.clone()).unwrap(); |
| 546 |
let before = st.materialize(); |
| 547 |
|
| 548 |
|
| 549 |
let mut n1 = c1.clone(); |
| 550 |
n1.message = "one, described".into(); |
| 75167672 Andy McCall |
551 |
let all: HashMap<ChangeHash, Change> = |
| 552 |
[(h1, c1.clone()), (h2, c2.clone()), (h3, c3.clone())] |
| 553 |
.into_iter() |
| 554 |
.collect(); |
| 252e8cb2 Andy McCall ↶ |
555 |
let load = |h: &ChangeHash| all.get(h).cloned(); |
| 75167672 Andy McCall |
556 |
let rewritten = propagate(&st, h1, Some(&n1), &Correspondence::identity(&c1), &load) |
| 557 |
.unwrap() |
| 558 |
.changes; |
| 559 |
assert_eq!( |
| 560 |
rewritten.iter().map(|(h, _)| *h).collect::<Vec<_>>(), |
| 561 |
vec![h2, h3] |
| 562 |
); |
| c07e0e7d Andy McCall ↶ |
563 |
let mut st2 = State::new(); |
| 564 |
st2.add(n1.clone()).unwrap(); |
| 565 |
for (_, c) in &rewritten { |
| 566 |
st2.add(c.clone()).unwrap(); |
| 567 |
} |
| 568 |
assert_eq!(st2.len(), 3); |
| 569 |
let after = st2.materialize(); |
| 570 |
assert_eq!(after.files["f"].mode, before.files["f"].mode); |
| 571 |
match (&after.files["f"].content, &before.files["f"].content) { |
| 75167672 Andy McCall |
572 |
(FileContent::Text { bytes: a, .. }, FileContent::Text { bytes: b, .. }) => { |
| 573 |
assert_eq!(a, b) |
| 574 |
} |
| c07e0e7d Andy McCall ↶ |
575 |
_ => panic!(), |
| 576 |
} |
| 75167672 Andy McCall |
577 |
assert!( |
| 578 |
matches!(&after.files["f"].content, FileContent::Text { bytes, .. } if bytes == b"b\nc\nd\n") |
| 579 |
); |
| c07e0e7d Andy McCall ↶ |
580 |
|
| 581 |
assert_eq!(rewritten[0].1.change_id, c2.change_id); |
| 582 |
assert_eq!(rewritten[0].1.deps, vec![n1.hash()]); |
| 583 |
assert_ne!(rewritten[0].1.hash(), h2); |
| 584 |
assert_eq!(rewritten[1].1.deps, vec![rewritten[0].1.hash()]); |
| 585 |
} |
| 586 |
|
| 587 |
#[test] |
| 6706bdcf Andy McCall ↶ |
588 |
fn missing_counterpart_is_reanchored() { |
| c07e0e7d Andy McCall ↶ |
589 |
let mut st = State::new(); |
| 590 |
let f = NodeId::random(); |
| 75167672 Andy McCall |
591 |
let c1 = change( |
| 592 |
vec![], |
| 593 |
vec![TreeOp::AddNode { |
| 594 |
node: f, |
| 595 |
kind: NodeKind::File, |
| 596 |
mode: Mode::TEXT, |
| 597 |
parent: NodeId::ROOT, |
| 598 |
name: "f".into(), |
| 599 |
}], |
| 600 |
vec![Edit::Insert { |
| 601 |
node: f, |
| 602 |
after: AtomRef::Start, |
| 603 |
before: AtomRef::End, |
| 604 |
lines: vec![b"a\n".to_vec(), b"b\n".to_vec()], |
| 605 |
}], |
| 606 |
"one", |
| 607 |
); |
| c07e0e7d Andy McCall ↶ |
608 |
let h1 = st.add(c1.clone()).unwrap(); |
| 75167672 Andy McCall |
609 |
let c2 = change( |
| 610 |
vec![h1], |
| 611 |
vec![], |
| 612 |
vec![Edit::Insert { |
| 613 |
node: f, |
| 614 |
after: AtomRef::Atom { |
| 615 |
change: h1, |
| 616 |
index: 1, |
| 617 |
}, |
| 618 |
before: AtomRef::End, |
| 619 |
lines: vec![b"c\n".to_vec()], |
| 620 |
}], |
| 621 |
"two", |
| 622 |
); |
| 252e8cb2 Andy McCall ↶ |
623 |
let h2 = st.add(c2.clone()).unwrap(); |
| 624 |
let all: HashMap<ChangeHash, Change> = [(h1, c1.clone()), (h2, c2)].into_iter().collect(); |
| 625 |
let load = |h: &ChangeHash| all.get(h).cloned(); |
| 6706bdcf Andy McCall ↶ |
626 |
|
| 627 |
|
| c07e0e7d Andy McCall ↶ |
628 |
let mut n1 = c1.clone(); |
| 75167672 Andy McCall |
629 |
n1.edits = vec![Edit::Insert { |
| 630 |
node: f, |
| 631 |
after: AtomRef::Start, |
| 632 |
before: AtomRef::End, |
| 633 |
lines: vec![b"a\n".to_vec()], |
| 634 |
}]; |
| 635 |
let corr = Correspondence { |
| 636 |
atoms: [((f, 0), 0)].into_iter().collect(), |
| 637 |
ops: [(0, 0)].into_iter().collect(), |
| 638 |
}; |
| 6706bdcf Andy McCall ↶ |
639 |
let p = propagate(&st, h1, Some(&n1), &corr, &load).unwrap(); |
| 640 |
assert_eq!(p.notes.len(), 1, "{:?}", p.notes); |
| 641 |
let n1h = n1.hash(); |
| 75167672 Andy McCall |
642 |
assert!( |
| 643 |
matches!(p.changes[0].1.edits[0], Edit::Insert { after: AtomRef::Atom { change, index: 0 }, before: AtomRef::End, .. } if change == n1h) |
| 644 |
); |
| 6706bdcf Andy McCall ↶ |
645 |
let mut st2 = State::new(); |
| 646 |
st2.add(n1.clone()).unwrap(); |
| 647 |
st2.add(p.changes[0].1.clone()).unwrap(); |
| 648 |
let m = st2.materialize(); |
| 649 |
assert!(!m.is_conflicted()); |
| 75167672 Andy McCall |
650 |
assert!( |
| 651 |
matches!(&m.files["f"].content, FileContent::Text { bytes, .. } if bytes == b"a\nc\n") |
| 652 |
); |
| 653 |
assert_eq!( |
| 654 |
propagate(&st, ObjectRef([9; 32]), Some(&n1), &corr, &load).unwrap_err(), |
| 655 |
RewriteError::Missing(ObjectRef([9; 32])) |
| 656 |
); |
| 6706bdcf Andy McCall ↶ |
657 |
|
| 658 |
|
| 659 |
let p = propagate(&st, h1, None, &Correspondence::default(), &load).unwrap(); |
| 660 |
assert_eq!(p.notes.len(), 1, "{:?}", p.notes); |
| 661 |
assert!(p.changes[0].1.edits.is_empty() && p.changes[0].1.deps.is_empty()); |
| 662 |
|
| 663 |
|
| 664 |
|
| 665 |
let mut n2 = c1.clone(); |
| 666 |
n2.edits = vec![]; |
| 75167672 Andy McCall |
667 |
let corr = Correspondence { |
| 668 |
atoms: HashMap::new(), |
| 669 |
ops: [(0, 0)].into_iter().collect(), |
| 670 |
}; |
| 6706bdcf Andy McCall ↶ |
671 |
let p = propagate(&st, h1, Some(&n2), &corr, &load).unwrap(); |
| 75167672 Andy McCall |
672 |
assert!(matches!( |
| 673 |
p.changes[0].1.edits[0], |
| 674 |
Edit::Insert { |
| 675 |
after: AtomRef::Start, |
| 676 |
before: AtomRef::End, |
| 677 |
.. |
| 678 |
} |
| 679 |
)); |
| 6706bdcf Andy McCall ↶ |
680 |
let mut st3 = State::new(); |
| 681 |
st3.add(n2.clone()).unwrap(); |
| 682 |
st3.add(p.changes[0].1.clone()).unwrap(); |
| 683 |
let m = st3.materialize(); |
| 684 |
assert!(!m.is_conflicted()); |
| 75167672 Andy McCall |
685 |
assert!( |
| 686 |
matches!(&m.files["f"].content, FileContent::Text { bytes, .. } if bytes == b"c\n") |
| 687 |
); |
| c07e0e7d Andy McCall ↶ |
688 |
} |
| 689 |
|
| 690 |
#[test] |
| 691 |
fn removal_rewires_dependencies_or_fails_on_references() { |
| 692 |
let mut st = State::new(); |
| 693 |
let f = NodeId::random(); |
| 694 |
let g = NodeId::random(); |
| 75167672 Andy McCall |
695 |
let c1 = change( |
| 696 |
vec![], |
| 697 |
vec![TreeOp::AddNode { |
| 698 |
node: f, |
| 699 |
kind: NodeKind::File, |
| 700 |
mode: Mode::TEXT, |
| 701 |
parent: NodeId::ROOT, |
| 702 |
name: "f".into(), |
| 703 |
}], |
| 704 |
vec![], |
| 705 |
"one", |
| 706 |
); |
| 252e8cb2 Andy McCall ↶ |
707 |
let h1 = st.add(c1.clone()).unwrap(); |
| c07e0e7d Andy McCall ↶ |
708 |
|
| 75167672 Andy McCall |
709 |
let c2 = change( |
| 710 |
vec![h1], |
| 711 |
vec![TreeOp::AddNode { |
| 712 |
node: g, |
| 713 |
kind: NodeKind::File, |
| 714 |
mode: Mode::TEXT, |
| 715 |
parent: NodeId::ROOT, |
| 716 |
name: "g".into(), |
| 717 |
}], |
| 718 |
vec![], |
| 719 |
"two", |
| 720 |
); |
| c07e0e7d Andy McCall ↶ |
721 |
let h2 = st.add(c2.clone()).unwrap(); |
| 75167672 Andy McCall |
722 |
let c3 = change( |
| 723 |
vec![h2], |
| 724 |
vec![], |
| 725 |
vec![Edit::Insert { |
| 726 |
node: g, |
| 727 |
after: AtomRef::Start, |
| 728 |
before: AtomRef::End, |
| 729 |
lines: vec![b"x\n".to_vec()], |
| 730 |
}], |
| 731 |
"three", |
| 732 |
); |
| 252e8cb2 Andy McCall ↶ |
733 |
let h3 = st.add(c3.clone()).unwrap(); |
| 75167672 Andy McCall |
734 |
let mut all: HashMap<ChangeHash, Change> = |
| 735 |
[(h1, c1), (h2, c2), (h3, c3)].into_iter().collect(); |
| 252e8cb2 Andy McCall ↶ |
736 |
let load_all = all.clone(); |
| 737 |
let load = move |h: &ChangeHash| load_all.get(h).cloned(); |
| c07e0e7d Andy McCall ↶ |
738 |
|
| 75167672 Andy McCall |
739 |
let out = propagate(&st, h2, None, &Correspondence::default(), &load) |
| 740 |
.unwrap() |
| 741 |
.changes; |
| c07e0e7d Andy McCall ↶ |
742 |
assert_eq!(out.len(), 1); |
| 743 |
assert_eq!(out[0].0, h3); |
| 744 |
assert_eq!(out[0].1.deps, vec![h1]); |
| 745 |
|
| 746 |
|
| 75167672 Andy McCall |
747 |
let out = propagate(&st, h1, None, &Correspondence::default(), &load) |
| 748 |
.unwrap() |
| 749 |
.changes; |
| c07e0e7d Andy McCall ↶ |
750 |
assert!(out.iter().any(|(h, c)| *h == h2 && c.deps.is_empty())); |
| 6706bdcf Andy McCall ↶ |
751 |
|
| 752 |
|
| 75167672 Andy McCall |
753 |
let c4 = change( |
| 754 |
vec![h2], |
| 755 |
vec![TreeOp::RemoveNode { |
| 756 |
node: g, |
| 757 |
placement: OpRef { |
| 758 |
change: h2, |
| 759 |
index: 0, |
| 760 |
}, |
| 761 |
}], |
| 762 |
vec![], |
| 763 |
"four", |
| 764 |
); |
| 252e8cb2 Andy McCall ↶ |
765 |
let h4 = st.add(c4.clone()).unwrap(); |
| 766 |
all.insert(h4, c4); |
| 767 |
let load2 = |h: &ChangeHash| all.get(h).cloned(); |
| 6706bdcf Andy McCall ↶ |
768 |
let p = propagate(&st, h2, None, &Correspondence::default(), &load2).unwrap(); |
| 769 |
assert_eq!(p.notes.len(), 2, "{:?}", p.notes); |
| 770 |
for (_, c) in &p.changes { |
| 771 |
assert!(c.edits.is_empty() && c.tree_ops.is_empty()); |
| 772 |
} |
| c07e0e7d Andy McCall ↶ |
773 |
} |
| 774 |
} |