| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | |
|
| 6 | |
|
| 7 | | use crate::fasthash::{FastMap as HashMap, FastSet as HashSet}; |
| 8 | | use crate::hash::ChangeHash; |
| 9 | | use crate::lines::{LineRef, Lines}; |
| 10 | | use crate::objects::AtomRange; |
| 11 | | use std::collections::{BTreeMap, BTreeSet}; |
| 12 | |
|
| 13 | |
|
| 14 | | pub type AtomKey = (ChangeHash, u32); |
| 15 | |
|
| 16 | | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] |
| 17 | | pub enum Vertex { |
| 18 | | Start, |
| 19 | | Atom(AtomKey), |
| 20 | | End, |
| 21 | | } |
| 22 | |
|
| 23 | |
|
| 24 | | pub trait DepOracle { |
| 25 | | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool; |
| 26 | | } |
| 27 | |
|
| 28 | |
|
| 29 | | pub struct NoDeps; |
| 30 | | impl DepOracle for NoDeps { |
| 31 | 6× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 32 | 6× | a == b |
| 33 | 6× | } |
| 34 | | } |
| 35 | |
|
| 36 | | #[derive(Default, Clone, Debug)] |
| 37 | | struct AtomInfo { |
| 38 | | |
| 39 | | |
| 40 | | content: Option<LineRef>, |
| 41 | | deleted_by: BTreeSet<ChangeHash>, |
| 42 | | } |
| 43 | |
|
| 44 | | #[derive(Clone, Debug)] |
| 45 | | pub struct FileGraph { |
| 46 | | atoms: HashMap<AtomKey, AtomInfo>, |
| 47 | | |
| 48 | | succ: HashMap<Vertex, Edges>, |
| 49 | | |
| 50 | | order: OrderSlot, |
| 51 | | } |
| 52 | |
|
| 53 | |
|
| 54 | |
|
| 55 | |
|
| 56 | |
|
| 57 | |
|
| 58 | | #[derive(Debug)] |
| 59 | | pub struct Order { |
| 60 | | atoms: Vec<AtomKey>, |
| 61 | | |
| 62 | | |
| 63 | | |
| 64 | | unchecked: Vec<AtomKey>, |
| 65 | | confirmed: std::sync::atomic::AtomicBool, |
| 66 | | } |
| 67 | |
|
| 68 | |
|
| 69 | |
|
| 70 | | #[derive(Debug, Default)] |
| 71 | | struct OrderSlot(std::sync::Mutex<Option<std::sync::Arc<Order>>>); |
| 72 | |
|
| 73 | | impl OrderSlot { |
| 74 | 25721× | fn get(&self) -> Option<std::sync::Arc<Order>> { |
| 75 | 25721× | self.0.lock().unwrap_or_else(|e| e.into_inner()).clone() |
| 76 | 25721× | } |
| 77 | 1563× | fn set(&self, o: Option<std::sync::Arc<Order>>) { |
| 78 | 1563× | *self.0.lock().unwrap_or_else(|e| e.into_inner()) = o; |
| 79 | 1563× | } |
| 80 | 62439× | fn take(&mut self) -> Option<std::sync::Arc<Order>> { |
| 81 | 62439× | self.0.get_mut().unwrap_or_else(|e| e.into_inner()).take() |
| 82 | 62439× | } |
| 83 | | } |
| 84 | |
|
| 85 | | impl Clone for OrderSlot { |
| 86 | 7× | fn clone(&self) -> Self { |
| 87 | 7× | OrderSlot(std::sync::Mutex::new(self.get())) |
| 88 | 7× | } |
| 89 | | } |
| 90 | |
|
| 91 | | impl Order { |
| 92 | 1553× | fn known(atoms: Vec<AtomKey>) -> Order { |
| 93 | 1553× | Order { |
| 94 | 1553× | atoms, |
| 95 | 1553× | unchecked: Vec::new(), |
| 96 | 1553× | confirmed: std::sync::atomic::AtomicBool::new(true), |
| 97 | 1553× | } |
| 98 | 1553× | } |
| 99 | |
|
| 100 | | |
| 101 | | |
| 102 | | |
| 103 | | |
| 104 | | |
| 105 | 526× | fn splice( |
| 106 | 526× | &self, |
| 107 | 526× | h: ChangeHash, |
| 108 | 526× | inserts: &[(Vertex, Vertex, u32, u32)], |
| 109 | 526× | deleted: &[AtomKey], |
| 110 | 526× | ) -> Option<std::sync::Arc<Order>> { |
| 111 | 526× | let mut want: HashSet<AtomKey> = deleted.iter().copied().collect(); |
| 112 | 576× | for (a, b, _, _) in inserts { |
| 113 | 1152× | for v in [a, b] { |
| 114 | 1152× | if let Vertex::Atom(k) = v { |
| 115 | 935× | want.insert(*k); |
| 116 | 935× | } |
| 117 | | } |
| 118 | | } |
| 119 | 526× | let mut pos: HashMap<AtomKey, usize> = HashMap::default(); |
| 120 | 2841× | for (i, k) in self.atoms.iter().enumerate() { |
| 121 | 2841× | if want.contains(k) { |
| 122 | 1268× | pos.insert(*k, i); |
| 123 | 1573× | } |
| 124 | | } |
| 125 | 526× | let len = self.atoms.len(); |
| 126 | 526× | let mut gone = vec![false; len]; |
| 127 | 526× | let mut unchecked = if self.confirmed.load(std::sync::atomic::Ordering::Relaxed) { |
| 128 | 526× | Vec::new() |
| 129 | | } else { |
| 130 | 0× | self.unchecked.clone() |
| 131 | | }; |
| 132 | 722× | for k in deleted { |
| 133 | 722× | if k.0 == h { |
| 134 | 0× | return None; |
| 135 | 722× | } |
| 136 | 722× | if let Some(&i) = pos.get(k) { |
| 137 | 699× | if !gone[i] { |
| 138 | 699× | gone[i] = true; |
| 139 | 699× | unchecked.push(*k); |
| 140 | 699× | } |
| 141 | 23× | } |
| 142 | | } |
| 143 | | |
| 144 | | |
| 145 | 526× | let mut slots: Vec<(usize, usize, u32, u32)> = Vec::with_capacity(inserts.len()); |
| 146 | 557× | for &(a, b, first, count) in inserts { |
| 147 | 557× | let from = match a { |
| 148 | 141× | Vertex::Start => 0, |
| 149 | 416× | Vertex::Atom(k) => pos.get(&k)? + 1, |
| 150 | 0× | Vertex::End => return None, |
| 151 | | }; |
| 152 | 546× | let to = match b { |
| 153 | 72× | Vertex::End => len, |
| 154 | 474× | Vertex::Atom(k) => *pos.get(&k)?, |
| 155 | 0× | Vertex::Start => return None, |
| 156 | | }; |
| 157 | 539× | if from > to || gone[from..to].iter().any(|g| !g) { |
| 158 | 5× | return None; |
| 159 | 534× | } |
| 160 | 534× | slots.push((from, to, first, count)); |
| 161 | | } |
| 162 | | |
| 163 | | |
| 164 | 503× | slots.sort_unstable(); |
| 165 | 503× | for w in slots.windows(2) { |
| 166 | 160× | let (end, next) = (w[0].1, w[1].0); |
| 167 | 303× | if end >= next || !gone[end..next].iter().any(|g| !g) { |
| 168 | 0× | return None; |
| 169 | 160× | } |
| 170 | | } |
| 171 | 533× | let added: usize = slots.iter().map(|s| s.3 as usize).sum(); |
| 172 | 503× | let mut atoms = Vec::with_capacity(len + added); |
| 173 | 503× | let mut slots = slots.into_iter().peekable(); |
| 174 | 3132× | let mut runs_at = |i: usize, atoms: &mut Vec<AtomKey>| { |
| 175 | 3665× | while let Some(&(from, _, first, count)) = slots.peek() { |
| 176 | 1790× | if from != i { |
| 177 | 1257× | break; |
| 178 | 533× | } |
| 179 | 780× | atoms.extend((first..first + count).map(|j| (h, j))); |
| 180 | 533× | slots.next(); |
| 181 | | } |
| 182 | 3132× | }; |
| 183 | 2629× | for (i, (k, g)) in self.atoms.iter().zip(&gone).enumerate() { |
| 184 | 2629× | runs_at(i, &mut atoms); |
| 185 | 2629× | if !g { |
| 186 | 1967× | atoms.push(*k); |
| 187 | 1967× | } |
| 188 | | } |
| 189 | 503× | runs_at(len, &mut atoms); |
| 190 | 503× | let confirmed = unchecked.is_empty(); |
| 191 | 503× | Some(std::sync::Arc::new(Order { |
| 192 | 503× | atoms, |
| 193 | 503× | unchecked, |
| 194 | 503× | confirmed: std::sync::atomic::AtomicBool::new(confirmed), |
| 195 | 503× | })) |
| 196 | 526× | } |
| 197 | | } |
| 198 | |
|
| 199 | |
|
| 200 | |
|
| 201 | | #[derive(Clone, Debug, Default, PartialEq, Eq)] |
| 202 | | enum Edges { |
| 203 | | #[default] |
| 204 | | None, |
| 205 | | One(Vertex), |
| 206 | | Many(Vec<Vertex>), |
| 207 | | } |
| 208 | |
|
| 209 | | impl Edges { |
| 210 | 238548× | fn insert(&mut self, v: Vertex) { |
| 211 | 30524× | match self { |
| 212 | 206344× | Edges::None => *self = Edges::One(v), |
| 213 | 30524× | Edges::One(a) if *a == v => {} |
| 214 | 30524× | Edges::One(a) => { |
| 215 | 30524× | let a = *a; |
| 216 | 30524× | *self = Edges::Many(if a < v { vec![a, v] } else { vec![v, a] }); |
| 217 | | } |
| 218 | 1680× | Edges::Many(list) => { |
| 219 | 1680× | if let Err(i) = list.binary_search(&v) { |
| 220 | 1680× | list.insert(i, v); |
| 221 | 1680× | } |
| 222 | | } |
| 223 | | } |
| 224 | 238548× | } |
| 225 | |
|
| 226 | 953185× | fn as_slice(&self) -> &[Vertex] { |
| 227 | 953185× | match self { |
| 228 | 4303× | Edges::None => &[], |
| 229 | 699766× | Edges::One(v) => std::slice::from_ref(v), |
| 230 | 249116× | Edges::Many(list) => list, |
| 231 | | } |
| 232 | 953185× | } |
| 233 | |
|
| 234 | 695594× | fn iter(&self) -> std::slice::Iter<'_, Vertex> { |
| 235 | 695594× | self.as_slice().iter() |
| 236 | 695594× | } |
| 237 | | } |
| 238 | |
|
| 239 | | impl Default for FileGraph { |
| 240 | 24771× | fn default() -> Self { |
| 241 | 24771× | Self::new() |
| 242 | 24771× | } |
| 243 | | } |
| 244 | |
|
| 245 | |
|
| 246 | | #[derive(Clone, Debug, PartialEq, Eq)] |
| 247 | | pub enum Segment { |
| 248 | | Lines(Vec<AtomKey>), |
| 249 | | |
| 250 | | Conflict(Vec<Vec<AtomKey>>), |
| 251 | | |
| 252 | | |
| 253 | | Zombie { |
| 254 | | deleted: Vec<AtomKey>, |
| 255 | | inserted: Vec<AtomKey>, |
| 256 | | }, |
| 257 | | } |
| 258 | |
|
| 259 | | #[derive(Clone, Debug, PartialEq, Eq, Default)] |
| 260 | | pub struct Materialized { |
| 261 | | pub segments: Vec<Segment>, |
| 262 | | } |
| 263 | |
|
| 264 | | impl Materialized { |
| 265 | 31472× | pub fn is_conflicted(&self) -> bool { |
| 266 | 31472× | self.segments |
| 267 | 31472× | .iter() |
| 268 | 31472× | .any(|s| !matches!(s, Segment::Lines(_))) |
| 269 | 31472× | } |
| 270 | 23873× | pub fn conflict_count(&self) -> usize { |
| 271 | 23873× | self.segments |
| 272 | 23873× | .iter() |
| 273 | 24294× | .filter(|s| !matches!(s, Segment::Lines(_))) |
| 274 | 23873× | .count() |
| 275 | 23873× | } |
| 276 | | |
| 277 | 1213× | pub fn live_atoms(&self) -> Vec<AtomKey> { |
| 278 | 1213× | let mut out = Vec::new(); |
| 279 | 1248× | for s in &self.segments { |
| 280 | 1248× | match s { |
| 281 | 1204× | Segment::Lines(a) => out.extend(a), |
| 282 | 30× | Segment::Conflict(sides) => sides.iter().for_each(|x| out.extend(x)), |
| 283 | 29× | Segment::Zombie { inserted, .. } => out.extend(inserted), |
| 284 | | } |
| 285 | | } |
| 286 | 1213× | out |
| 287 | 1213× | } |
| 288 | | } |
| 289 | |
|
| 290 | | impl FileGraph { |
| 291 | 24841× | pub fn new() -> Self { |
| 292 | 24841× | let mut g = FileGraph { |
| 293 | 24841× | atoms: HashMap::default(), |
| 294 | 24841× | succ: HashMap::default(), |
| 295 | 24841× | order: OrderSlot::default(), |
| 296 | 24841× | }; |
| 297 | 24841× | g.edge(Vertex::Start, Vertex::End); |
| 298 | 24841× | g |
| 299 | 24841× | } |
| 300 | |
|
| 301 | 29965× | fn edge(&mut self, a: Vertex, b: Vertex) { |
| 302 | 29965× | self.succ.entry(a).or_default().insert(b); |
| 303 | 29965× | self.succ.entry(b).or_default(); |
| 304 | 29965× | } |
| 305 | |
|
| 306 | | |
| 307 | 1042× | pub fn insert( |
| 308 | 1042× | &mut self, |
| 309 | 1042× | change: ChangeHash, |
| 310 | 1042× | first_index: u32, |
| 311 | 1042× | after: Vertex, |
| 312 | 1042× | before: Vertex, |
| 313 | 1042× | lines: &Lines, |
| 314 | 1042× | ) { |
| 315 | 1042× | self.insert_owned(change, first_index, after, before, lines.clone()); |
| 316 | 1042× | } |
| 317 | |
|
| 318 | | |
| 319 | | |
| 320 | | |
| 321 | 31514× | pub fn insert_owned( |
| 322 | 31514× | &mut self, |
| 323 | 31514× | change: ChangeHash, |
| 324 | 31514× | first_index: u32, |
| 325 | 31514× | after: Vertex, |
| 326 | 31514× | before: Vertex, |
| 327 | 31514× | lines: Lines, |
| 328 | 31514× | ) { |
| 329 | 31514× | self.order.take(); |
| 330 | 31514× | self.atoms.reserve(lines.len()); |
| 331 | 31514× | self.succ.reserve(lines.len() + 1); |
| 332 | 31514× | let mut prev = after; |
| 333 | 177069× | for i in 0..lines.len() { |
| 334 | 177069× | let key = (change, first_index + i as u32); |
| 335 | 177069× | self.atoms.entry(key).or_default().content = |
| 336 | 177069× | Some(LineRef::new(lines.clone(), i as u32)); |
| 337 | 177069× | let v = Vertex::Atom(key); |
| 338 | 177069× | self.succ.entry(prev).or_default().insert(v); |
| 339 | 177069× | prev = v; |
| 340 | 177069× | } |
| 341 | 31514× | self.succ.entry(prev).or_default().insert(before); |
| 342 | 31514× | self.succ.entry(before).or_default(); |
| 343 | 31514× | } |
| 344 | |
|
| 345 | | |
| 346 | 5259× | pub fn delete(&mut self, by: ChangeHash, range: &AtomRange) { |
| 347 | 5259× | self.order.take(); |
| 348 | 5496× | for i in range.from..range.to { |
| 349 | 5496× | self.atoms |
| 350 | 5496× | .entry((range.change, i)) |
| 351 | 5496× | .or_default() |
| 352 | 5496× | .deleted_by |
| 353 | 5496× | .insert(by); |
| 354 | 5496× | } |
| 355 | 5259× | } |
| 356 | |
|
| 357 | 473928× | pub fn is_live(&self, k: &AtomKey) -> bool { |
| 358 | 473928× | self.atoms |
| 359 | 473928× | .get(k) |
| 360 | 473928× | .is_some_and(|a| a.content.is_some() && a.deleted_by.is_empty()) |
| 361 | 473928× | } |
| 362 | |
|
| 363 | 162517× | pub fn content(&self, k: &AtomKey) -> Option<&[u8]> { |
| 364 | 162517× | self.atoms |
| 365 | 162517× | .get(k) |
| 366 | 162517× | .and_then(|a| a.content.as_ref().map(LineRef::bytes)) |
| 367 | 162517× | } |
| 368 | |
|
| 369 | 216× | pub fn deleters(&self, k: &AtomKey) -> impl Iterator<Item = &ChangeHash> { |
| 370 | 216× | self.atoms |
| 371 | 216× | .get(k) |
| 372 | 216× | .into_iter() |
| 373 | 216× | .flat_map(|a| a.deleted_by.iter()) |
| 374 | 216× | } |
| 375 | |
|
| 376 | | |
| 377 | 53× | pub fn atom_count(&self) -> usize { |
| 378 | 53× | self.atoms.len() |
| 379 | 53× | } |
| 380 | |
|
| 381 | | |
| 382 | | |
| 383 | | |
| 384 | | |
| 385 | | |
| 386 | | |
| 387 | | |
| 388 | 3× | pub fn between(&self, first: &AtomKey, last: &AtomKey) -> Vec<AtomKey> { |
| 389 | 3× | let (from, to) = (Vertex::Atom(*first), Vertex::Atom(*last)); |
| 390 | 3× | let mut ahead: HashSet<Vertex> = HashSet::default(); |
| 391 | 3× | let mut stack = vec![from]; |
| 392 | 29× | while let Some(v) = stack.pop() { |
| 393 | 26× | if ahead.insert(v) { |
| 394 | 21× | stack.extend( |
| 395 | 21× | self.succ |
| 396 | 21× | .get(&v) |
| 397 | 21× | .map(|e| e.as_slice()) |
| 398 | 21× | .unwrap_or(&[]) |
| 399 | 21× | .iter() |
| 400 | 21× | .copied(), |
| 401 | | ); |
| 402 | 5× | } |
| 403 | | } |
| 404 | 3× | if !ahead.contains(&to) { |
| 405 | 0× | return vec![]; |
| 406 | 3× | } |
| 407 | 3× | let mut pred: HashMap<Vertex, Vec<Vertex>> = HashMap::default(); |
| 408 | 28× | for (u, es) in &self.succ { |
| 409 | 28× | if ahead.contains(u) { |
| 410 | 23× | for w in es.iter() { |
| 411 | 23× | pred.entry(*w).or_default().push(*u); |
| 412 | 23× | } |
| 413 | 7× | } |
| 414 | | } |
| 415 | 3× | let mut behind: HashSet<Vertex> = HashSet::default(); |
| 416 | 3× | let mut stack = vec![to]; |
| 417 | 15× | while let Some(v) = stack.pop() { |
| 418 | 12× | if behind.insert(v) { |
| 419 | 10× | stack.extend(pred.get(&v).into_iter().flatten().copied()); |
| 420 | 10× | } |
| 421 | | } |
| 422 | 3× | let mut out: Vec<AtomKey> = ahead |
| 423 | 3× | .intersection(&behind) |
| 424 | 10× | .filter_map(|v| match v { |
| 425 | 10× | Vertex::Atom(k) if self.atoms.contains_key(k) => Some(*k), |
| 426 | 0× | _ => None, |
| 427 | 10× | }) |
| 428 | 3× | .collect(); |
| 429 | 3× | out.sort(); |
| 430 | 3× | out |
| 431 | 3× | } |
| 432 | |
|
| 433 | | |
| 434 | 3× | pub fn touching(&self, atoms: &[AtomKey]) -> BTreeSet<ChangeHash> { |
| 435 | 3× | let mut out = BTreeSet::new(); |
| 436 | 10× | for k in atoms { |
| 437 | 10× | out.insert(k.0); |
| 438 | 10× | if let Some(info) = self.atoms.get(k) { |
| 439 | 10× | out.extend(info.deleted_by.iter().copied()); |
| 440 | 10× | } |
| 441 | | } |
| 442 | 3× | out |
| 443 | 3× | } |
| 444 | |
|
| 445 | 1098× | pub fn live_successor_changes(&self, k: &AtomKey) -> Vec<ChangeHash> { |
| 446 | 1098× | self.succ |
| 447 | 1098× | .get(&Vertex::Atom(*k)) |
| 448 | 1098× | .map(|ss| { |
| 449 | 1097× | ss.iter() |
| 450 | 1097× | .filter_map(|s| match s { |
| 451 | 850× | Vertex::Atom(y) if self.is_live(y) => Some(y.0), |
| 452 | 354× | _ => None, |
| 453 | 1138× | }) |
| 454 | 1097× | .collect() |
| 455 | 1097× | }) |
| 456 | 1098× | .unwrap_or_default() |
| 457 | 1098× | } |
| 458 | |
|
| 459 | 359064× | fn is_ghost(&self, k: &AtomKey, info: &AtomInfo, deps: &dyn DepOracle) -> bool { |
| 460 | | |
| 461 | | |
| 462 | | |
| 463 | | |
| 464 | 359064× | self.succ.get(&Vertex::Atom(*k)).is_some_and(|ss| { |
| 465 | 472231× | ss.iter().any(|s| match s { |
| 466 | 472231× | Vertex::Atom(y) if self.is_live(y) => !info |
| 467 | 4362× | .deleted_by |
| 468 | 4362× | .iter() |
| 469 | 4402× | .any(|d| deps.depends_on(d, &y.0) || deps.depends_on(&y.0, d)), |
| 470 | 497791× | _ => false, |
| 471 | 502153× | }) |
| 472 | 359064× | }) |
| 473 | 359064× | } |
| 474 | |
|
| 475 | | |
| 476 | 8202× | fn reduced_succ( |
| 477 | 8202× | &self, |
| 478 | 8202× | v: Vertex, |
| 479 | 8202× | kept: &HashSet<Vertex>, |
| 480 | 8202× | memo: &mut HashMap<Vertex, BTreeSet<Vertex>>, |
| 481 | 8202× | ) -> BTreeSet<Vertex> { |
| 482 | 8202× | let direct = self.succ.get(&v).map(Edges::as_slice).unwrap_or_default(); |
| 483 | | |
| 484 | | |
| 485 | 8418× | if direct.iter().all(|s| kept.contains(s)) { |
| 486 | 4601× | return direct.iter().copied().collect(); |
| 487 | 3601× | } |
| 488 | 3601× | let mut out = BTreeSet::new(); |
| 489 | 3601× | let mut stack: Vec<Vertex> = direct.to_vec(); |
| 490 | 3601× | let mut seen = HashSet::default(); |
| 491 | 501482× | while let Some(s) = stack.pop() { |
| 492 | 497881× | if kept.contains(&s) { |
| 493 | 33566× | out.insert(s); |
| 494 | 464315× | } else if seen.insert(s) { |
| 495 | 319318× | if let Some(m) = memo.get(&s) { |
| 496 | 0× | out.extend(m.iter().copied()); |
| 497 | 319318× | } else if let Some(next) = self.succ.get(&s) { |
| 498 | 319318× | stack.extend(next.iter().copied()); |
| 499 | 319318× | } |
| 500 | 144997× | } |
| 501 | | } |
| 502 | 3601× | out |
| 503 | 8202× | } |
| 504 | |
|
| 505 | | |
| 506 | | |
| 507 | | |
| 508 | | |
| 509 | | |
| 510 | 24036× | fn as_chain(&self) -> Option<Vec<AtomKey>> { |
| 511 | 24036× | if self |
| 512 | 24036× | .atoms |
| 513 | 24036× | .values() |
| 514 | 121772× | .any(|a| a.content.is_none() || !a.deleted_by.is_empty()) |
| 515 | | { |
| 516 | 1734× | return None; |
| 517 | 22302× | } |
| 518 | 22302× | let mut order = Vec::with_capacity(self.atoms.len()); |
| 519 | 22302× | let mut v = Vertex::Start; |
| 520 | | loop { |
| 521 | 130734× | let succ = self.succ.get(&v)?.as_slice(); |
| 522 | 154519× | let mut atoms = succ.iter().filter(|s| **s != Vertex::End); |
| 523 | 130734× | match (atoms.next(), atoms.next()) { |
| 524 | 21995× | (None, _) if succ.contains(&Vertex::End) => break, |
| 525 | 108432× | (Some(Vertex::Atom(k)), None) if order.len() < self.atoms.len() => { |
| 526 | 108432× | order.push(*k); |
| 527 | 108432× | v = Vertex::Atom(*k); |
| 528 | 108432× | } |
| 529 | 307× | _ => return None, |
| 530 | | } |
| 531 | | } |
| 532 | 21995× | (order.len() == self.atoms.len()).then_some(order) |
| 533 | 24036× | } |
| 534 | |
|
| 535 | | |
| 536 | 25256× | pub fn materialize(&self, deps: &dyn DepOracle) -> Materialized { |
| 537 | 25256× | let lines = |order: Vec<AtomKey>| Materialized { |
| 538 | 24772× | segments: if order.is_empty() { |
| 539 | 176× | vec![] |
| 540 | | } else { |
| 541 | 24596× | vec![Segment::Lines(order)] |
| 542 | | }, |
| 543 | 24772× | }; |
| 544 | | use std::sync::atomic::Ordering::Relaxed; |
| 545 | | |
| 546 | 25256× | let started = crate::timing::enabled().then(std::time::Instant::now); |
| 547 | 25256× | let took = |name: &'static str| { |
| 548 | 25256× | if let Some(t) = started { |
| 549 | 0× | crate::timing::add(name, t.elapsed()); |
| 550 | 25256× | } |
| 551 | 25256× | }; |
| 552 | 25256× | if let Some(o) = self.order.get() { |
| 553 | 1236× | let holds = o.confirmed.load(Relaxed) |
| 554 | 616× | || o.unchecked.iter().all(|k| { |
| 555 | 616× | self.atoms |
| 556 | 616× | .get(k) |
| 557 | 616× | .is_none_or(|info| !self.is_ghost(k, info, deps)) |
| 558 | 616× | }); |
| 559 | 1236× | if holds { |
| 560 | 1226× | o.confirmed.store(true, Relaxed); |
| 561 | 1226× | took("graph: kept order"); |
| 562 | 1226× | return lines(o.atoms.clone()); |
| 563 | 10× | } |
| 564 | 10× | self.order.set(None); |
| 565 | 24020× | } |
| 566 | | |
| 567 | | |
| 568 | 24030× | if let Some(order) = self.as_chain() { |
| 569 | 21993× | took("graph: chain"); |
| 570 | 21993× | return lines(order); |
| 571 | 2037× | } |
| 572 | 2037× | if let Some(order) = self.as_order(deps) { |
| 573 | 1553× | self.order |
| 574 | 1553× | .set(Some(std::sync::Arc::new(Order::known(order.clone())))); |
| 575 | 1553× | took("graph: single order"); |
| 576 | 1553× | return lines(order); |
| 577 | 484× | } |
| 578 | 484× | let m = self.materialize_general(deps); |
| 579 | 484× | took("graph: general"); |
| 580 | 484× | m |
| 581 | 25256× | } |
| 582 | |
|
| 583 | | |
| 584 | | |
| 585 | 25666× | pub fn take_order(&mut self) -> Option<std::sync::Arc<Order>> { |
| 586 | 25666× | self.order.take() |
| 587 | 25666× | } |
| 588 | |
|
| 589 | | |
| 590 | | |
| 591 | | |
| 592 | | |
| 593 | 527× | pub fn keep_spliced( |
| 594 | 527× | &mut self, |
| 595 | 527× | order: std::sync::Arc<Order>, |
| 596 | 527× | h: ChangeHash, |
| 597 | 527× | inserts: &[(Vertex, Vertex, u32, u32)], |
| 598 | 527× | deleted: &[AtomKey], |
| 599 | 527× | ) { |
| 600 | 527× | let live = inserts |
| 601 | 527× | .iter() |
| 602 | 847× | .all(|&(_, _, first, count)| (first..first + count).all(|i| self.is_live(&(h, i)))); |
| 603 | 527× | let spliced = if live { |
| 604 | 526× | order.splice(h, inserts, deleted) |
| 605 | | } else { |
| 606 | 1× | None |
| 607 | | }; |
| 608 | 527× | *self.order.0.get_mut().unwrap_or_else(|e| e.into_inner()) = spliced; |
| 609 | 527× | } |
| 610 | |
|
| 611 | | |
| 612 | | |
| 613 | | |
| 614 | | |
| 615 | | |
| 616 | | |
| 617 | 2039× | fn as_order(&self, deps: &dyn DepOracle) -> Option<Vec<AtomKey>> { |
| 618 | | |
| 619 | 2039× | let mut index: HashMap<Vertex, u32> = |
| 620 | 2039× | HashMap::with_capacity_and_hasher(self.atoms.len() + 2, Default::default()); |
| 621 | 2039× | let mut verts: Vec<Vertex> = Vec::with_capacity(self.atoms.len() + 2); |
| 622 | 2039× | verts.extend([Vertex::Start, Vertex::End]); |
| 623 | 2039× | index.insert(Vertex::Start, 0); |
| 624 | 2039× | index.insert(Vertex::End, 1); |
| 625 | 100546× | for (k, info) in &self.atoms { |
| 626 | 100546× | if info.content.is_none() { |
| 627 | 0× | continue; |
| 628 | 100546× | } |
| 629 | 100546× | if info.deleted_by.is_empty() { |
| 630 | 49737× | index.insert(Vertex::Atom(*k), verts.len() as u32); |
| 631 | 49737× | verts.push(Vertex::Atom(*k)); |
| 632 | 50809× | } else if self.is_ghost(k, info, deps) { |
| 633 | 316× | return None; |
| 634 | 50493× | } |
| 635 | | } |
| 636 | | |
| 637 | | |
| 638 | 1723× | let mut reach: HashMap<Vertex, Vec<u32>> = HashMap::default(); |
| 639 | 9981× | let contracted = |v: Vertex, reach: &mut HashMap<Vertex, Vec<u32>>| { |
| 640 | 9981× | let mut stack: Vec<(Vertex, bool)> = vec![(v, false)]; |
| 641 | 85707× | while let Some((x, done)) = stack.pop() { |
| 642 | 75726× | if reach.contains_key(&x) { |
| 643 | 9582× | continue; |
| 644 | 66144× | } |
| 645 | 66144× | let next = self.succ.get(&x).map(Edges::as_slice).unwrap_or_default(); |
| 646 | 66144× | if !done { |
| 647 | 33072× | stack.push((x, true)); |
| 648 | 44960× | for s in next { |
| 649 | 44960× | if !index.contains_key(s) && !reach.contains_key(s) { |
| 650 | 32673× | stack.push((*s, false)); |
| 651 | 32673× | } |
| 652 | | } |
| 653 | 33072× | continue; |
| 654 | 33072× | } |
| 655 | 33072× | let mut out: Vec<u32> = Vec::new(); |
| 656 | 44960× | for s in next { |
| 657 | 44960× | match index.get(s) { |
| 658 | 5152× | Some(i) => out.push(*i), |
| 659 | 39808× | None => out.extend(reach.get(s).into_iter().flatten()), |
| 660 | | } |
| 661 | | } |
| 662 | 33072× | out.sort_unstable(); |
| 663 | 33072× | out.dedup(); |
| 664 | 33072× | reach.insert(x, out); |
| 665 | | } |
| 666 | 9981× | }; |
| 667 | | |
| 668 | 1723× | let mut succ: Vec<u32> = Vec::with_capacity(verts.len() * 2); |
| 669 | 1723× | let mut starts: Vec<usize> = Vec::with_capacity(verts.len() + 1); |
| 670 | 1723× | let mut indeg: Vec<u32> = vec![0; verts.len()]; |
| 671 | 52490× | for v in &verts { |
| 672 | 52490× | let from = succ.len(); |
| 673 | 52490× | starts.push(from); |
| 674 | 61162× | for s in self.succ.get(v).map(Edges::as_slice).unwrap_or_default() { |
| 675 | 61162× | match index.get(s) { |
| 676 | 51181× | Some(i) => succ.push(*i), |
| 677 | 9981× | None => { |
| 678 | 9981× | contracted(*s, &mut reach); |
| 679 | 9981× | succ.extend(reach.get(s).into_iter().flatten()); |
| 680 | 9981× | } |
| 681 | | } |
| 682 | | } |
| 683 | 52490× | if succ.len() - from > 1 { |
| 684 | 5163× | succ[from..].sort_unstable(); |
| 685 | 5163× | let mut kept = from; |
| 686 | 15760× | for i in from..succ.len() { |
| 687 | 15760× | if i == from || succ[i] != succ[kept - 1] { |
| 688 | 10882× | succ[kept] = succ[i]; |
| 689 | 10882× | kept += 1; |
| 690 | 10882× | } |
| 691 | | } |
| 692 | 5163× | succ.truncate(kept); |
| 693 | 47327× | } |
| 694 | 56486× | for &w in &succ[from..] { |
| 695 | 56486× | indeg[w as usize] += 1; |
| 696 | 56486× | } |
| 697 | | } |
| 698 | 1723× | starts.push(succ.len()); |
| 699 | | |
| 700 | 1723× | let mut ready: Vec<u32> = (0..verts.len() as u32) |
| 701 | 52490× | .filter(|&i| indeg[i as usize] == 0) |
| 702 | 1723× | .collect(); |
| 703 | 1723× | let mut order: Vec<AtomKey> = Vec::with_capacity(verts.len()); |
| 704 | 1723× | let mut seen = 0; |
| 705 | 53340× | while let Some(v) = ready.pop() { |
| 706 | 51786× | if !ready.is_empty() { |
| 707 | 0× | return None; |
| 708 | 51786× | } |
| 709 | 51786× | seen += 1; |
| 710 | 51786× | if let Vertex::Atom(k) = verts[v as usize] { |
| 711 | 48509× | order.push(k); |
| 712 | 48509× | } |
| 713 | 55835× | for &w in &succ[starts[v as usize]..starts[v as usize + 1]] { |
| 714 | 55835× | indeg[w as usize] -= 1; |
| 715 | 55835× | if indeg[w as usize] == 0 { |
| 716 | 50401× | ready.push(w); |
| 717 | 50401× | } |
| 718 | | } |
| 719 | 51786× | if ready.len() > 1 { |
| 720 | 169× | return None; |
| 721 | 51617× | } |
| 722 | | } |
| 723 | 1554× | (seen == verts.len()).then_some(order) |
| 724 | 2039× | } |
| 725 | |
|
| 726 | | |
| 727 | | |
| 728 | 978× | fn materialize_general(&self, deps: &dyn DepOracle) -> Materialized { |
| 729 | | |
| 730 | 978× | let mut kept: HashSet<Vertex> = HashSet::default(); |
| 731 | 978× | let mut ghosts: HashSet<AtomKey> = HashSet::default(); |
| 732 | 978× | kept.insert(Vertex::Start); |
| 733 | 978× | kept.insert(Vertex::End); |
| 734 | 313511× | for (k, info) in &self.atoms { |
| 735 | 313511× | if info.content.is_none() { |
| 736 | 0× | continue; |
| 737 | 313511× | } |
| 738 | 313511× | if info.deleted_by.is_empty() { |
| 739 | 5872× | kept.insert(Vertex::Atom(*k)); |
| 740 | 307639× | } else if self.is_ghost(k, info, deps) { |
| 741 | 374× | kept.insert(Vertex::Atom(*k)); |
| 742 | 374× | ghosts.insert(*k); |
| 743 | 307265× | } |
| 744 | | } |
| 745 | |
|
| 746 | | |
| 747 | 978× | let mut memo = HashMap::default(); |
| 748 | 978× | let mut rsucc: HashMap<Vertex, BTreeSet<Vertex>> = HashMap::default(); |
| 749 | 978× | let mut indeg: HashMap<Vertex, usize> = HashMap::default(); |
| 750 | 8202× | for &u in &kept { |
| 751 | 8202× | indeg.entry(u).or_insert(0); |
| 752 | 8202× | let rs = self.reduced_succ(u, &kept, &mut memo); |
| 753 | 12579× | for &w in &rs { |
| 754 | 12579× | *indeg.entry(w).or_insert(0) += 1; |
| 755 | 12579× | } |
| 756 | 8202× | rsucc.insert(u, rs); |
| 757 | | } |
| 758 | |
|
| 759 | | |
| 760 | 978× | let mut ready: BTreeSet<Vertex> = indeg |
| 761 | 978× | .iter() |
| 762 | 8202× | .filter(|(_, &d)| d == 0) |
| 763 | 978× | .map(|(v, _)| *v) |
| 764 | 978× | .collect(); |
| 765 | 978× | let mut side: HashMap<Vertex, u32> = HashMap::default(); |
| 766 | 978× | let mut joins: HashSet<Vertex> = HashSet::default(); |
| 767 | 978× | let mut next_side = 0u32; |
| 768 | 978× | let mut plain: Vec<Vertex> = Vec::new(); |
| 769 | 978× | let mut conflict: Option<BTreeMap<u32, Vec<Vertex>>> = None; |
| 770 | 978× | let mut segments: Vec<Segment> = Vec::new(); |
| 771 | |
|
| 772 | 1306× | let flush_plain = |plain: &mut Vec<Vertex>, segments: &mut Vec<Segment>| { |
| 773 | 1306× | let atoms: Vec<AtomKey> = plain |
| 774 | 1306× | .iter() |
| 775 | 7343× | .filter_map(|v| { |
| 776 | 7343× | if let Vertex::Atom(k) = v { |
| 777 | 5387× | Some(*k) |
| 778 | | } else { |
| 779 | 1956× | None |
| 780 | | } |
| 781 | 7343× | }) |
| 782 | 1306× | .collect(); |
| 783 | 1306× | plain.clear(); |
| 784 | 1306× | if !atoms.is_empty() { |
| 785 | 1046× | segments.extend(split_zombies(atoms, &ghosts)); |
| 786 | 1046× | } |
| 787 | 1306× | }; |
| 788 | 978× | let finish_conflict = |c: BTreeMap<u32, Vec<Vertex>>, segments: &mut Vec<Segment>| { |
| 789 | 328× | let sides: Vec<Vec<AtomKey>> = c |
| 790 | 328× | .into_values() |
| 791 | 656× | .map(|vs| { |
| 792 | 656× | vs.into_iter() |
| 793 | 859× | .filter_map(|v| match v { |
| 794 | 859× | Vertex::Atom(k) if !ghosts.contains(&k) => Some(k), |
| 795 | 40× | _ => None, |
| 796 | 859× | }) |
| 797 | 656× | .collect::<Vec<_>>() |
| 798 | 656× | }) |
| 799 | 656× | .filter(|s| !s.is_empty()) |
| 800 | 328× | .collect(); |
| 801 | 328× | match sides.len() { |
| 802 | 0× | 0 => {} |
| 803 | 0× | 1 => segments.push(Segment::Lines(sides.into_iter().next().unwrap())), |
| 804 | 328× | _ => segments.push(Segment::Conflict(sides)), |
| 805 | | } |
| 806 | 328× | }; |
| 807 | |
|
| 808 | 9180× | while !ready.is_empty() { |
| 809 | 8202× | if ready.len() >= 2 { |
| 810 | 386× | if conflict.is_none() { |
| 811 | 328× | flush_plain(&mut plain, &mut segments); |
| 812 | 328× | conflict = Some(BTreeMap::new()); |
| 813 | 328× | } |
| 814 | 772× | for r in &ready { |
| 815 | 772× | if !side.contains_key(r) { |
| 816 | 656× | side.insert(*r, next_side); |
| 817 | 656× | next_side += 1; |
| 818 | 656× | } |
| 819 | | } |
| 820 | 7816× | } |
| 821 | 8202× | let v = ready.pop_first().unwrap(); |
| 822 | 8202× | let my_side = side.get(&v).copied(); |
| 823 | 8202× | match (&mut conflict, my_side) { |
| 824 | 859× | (Some(c), Some(s)) => c.entry(s).or_default().push(v), |
| 825 | 328× | (Some(_), None) => { |
| 826 | 328× | let c = conflict.take().unwrap(); |
| 827 | 328× | finish_conflict(c, &mut segments); |
| 828 | 328× | plain.push(v); |
| 829 | 328× | } |
| 830 | 7015× | (None, _) => plain.push(v), |
| 831 | | } |
| 832 | 12579× | for &w in &rsucc[&v] { |
| 833 | 12579× | let d = indeg.get_mut(&w).unwrap(); |
| 834 | 12579× | *d -= 1; |
| 835 | 12579× | if conflict.is_some() && !joins.contains(&w) { |
| 836 | 965× | match (my_side, side.get(&w).copied()) { |
| 837 | 571× | (Some(s), None) => { |
| 838 | 571× | side.insert(w, s); |
| 839 | 571× | } |
| 840 | 394× | (Some(s), Some(t)) if s != t => { |
| 841 | 368× | side.remove(&w); |
| 842 | 368× | joins.insert(w); |
| 843 | 368× | } |
| 844 | 0× | (None, Some(_)) => { |
| 845 | 0× | side.remove(&w); |
| 846 | 0× | joins.insert(w); |
| 847 | 0× | } |
| 848 | 26× | _ => {} |
| 849 | | } |
| 850 | 11614× | } |
| 851 | 12579× | if *d == 0 { |
| 852 | 7224× | ready.insert(w); |
| 853 | 7224× | } |
| 854 | | } |
| 855 | | } |
| 856 | 978× | if let Some(c) = conflict.take() { |
| 857 | 0× | finish_conflict(c, &mut segments); |
| 858 | 978× | } |
| 859 | 978× | flush_plain(&mut plain, &mut segments); |
| 860 | 978× | Materialized { segments } |
| 861 | 978× | } |
| 862 | |
|
| 863 | | |
| 864 | | |
| 865 | 23872× | pub fn render(&self, m: &Materialized, label: &dyn Fn(&ChangeHash) -> String) -> Vec<u8> { |
| 866 | 23872× | let total = m.conflict_count(); |
| 867 | 23872× | let mut n = 0; |
| 868 | 23872× | let mut out: Vec<u8> = Vec::new(); |
| 869 | 24745× | let push_atoms = |out: &mut Vec<u8>, atoms: &[AtomKey]| { |
| 870 | 162428× | for a in atoms { |
| 871 | 162428× | if let Some(c) = self.content(a) { |
| 872 | 162428× | out.extend_from_slice(c); |
| 873 | 162428× | } |
| 874 | | } |
| 875 | 24745× | }; |
| 876 | 23872× | let newline_if_needed = |out: &mut Vec<u8>| { |
| 877 | 1362× | if !out.is_empty() && *out.last().unwrap() != b'\n' { |
| 878 | 0× | out.push(b'\n'); |
| 879 | 1362× | } |
| 880 | 1362× | }; |
| 881 | 24291× | for seg in &m.segments { |
| 882 | 24291× | match seg { |
| 883 | 23837× | Segment::Lines(a) => push_atoms(&mut out, a), |
| 884 | 238× | Segment::Conflict(sides) => { |
| 885 | 238× | n += 1; |
| 886 | 238× | let k = sides.len(); |
| 887 | 476× | for (i, s) in sides.iter().enumerate() { |
| 888 | 476× | newline_if_needed(&mut out); |
| 889 | 476× | let who = s.first().map(|a| label(&a.0)).unwrap_or_default(); |
| 890 | 476× | let marker = if i == 0 { "<<<<<<<" } else { "=======" }; |
| 891 | 476× | out.extend_from_slice( |
| 892 | 476× | format!("{marker} oxy conflict {n} of {total}, side {} of {k}: change {who}\n", i + 1).as_bytes(), |
| 893 | | ); |
| 894 | 476× | push_atoms(&mut out, s); |
| 895 | | } |
| 896 | 238× | newline_if_needed(&mut out); |
| 897 | 238× | out.extend_from_slice( |
| 898 | 238× | format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(), |
| 899 | | ); |
| 900 | | } |
| 901 | 216× | Segment::Zombie { deleted, inserted } => { |
| 902 | 216× | n += 1; |
| 903 | 216× | newline_if_needed(&mut out); |
| 904 | 216× | let deleter = deleted |
| 905 | 216× | .first() |
| 906 | 216× | .and_then(|a| self.deleters(a).next().copied()) |
| 907 | 216× | .map(|d| label(&d)) |
| 908 | 216× | .unwrap_or_default(); |
| 909 | 216× | let inserter = inserted.first().map(|a| label(&a.0)).unwrap_or_default(); |
| 910 | 216× | out.extend_from_slice( |
| 911 | 216× | format!("<<<<<<< oxy conflict {n} of {total}, side 1 of 2: deleted by change {deleter}\n-------\n").as_bytes(), |
| 912 | | ); |
| 913 | 216× | push_atoms(&mut out, deleted); |
| 914 | 216× | newline_if_needed(&mut out); |
| 915 | 216× | out.extend_from_slice( |
| 916 | 216× | format!( |
| 917 | 216× | "======= oxy conflict {n} of {total}, side 2 of 2: change {inserter}\n" |
| 918 | 216× | ) |
| 919 | 216× | .as_bytes(), |
| 920 | | ); |
| 921 | 216× | push_atoms(&mut out, inserted); |
| 922 | 216× | newline_if_needed(&mut out); |
| 923 | 216× | out.extend_from_slice( |
| 924 | 216× | format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(), |
| 925 | | ); |
| 926 | | } |
| 927 | | } |
| 928 | | } |
| 929 | 23872× | out |
| 930 | 23872× | } |
| 931 | | } |
| 932 | |
|
| 933 | 35536× | fn vertex_value(v: &Vertex) -> crate::cbor::Value { |
| 934 | | use crate::cbor::Value; |
| 935 | 35536× | match v { |
| 936 | 3650× | Vertex::Start => Value::Uint(0), |
| 937 | 3641× | Vertex::End => Value::Uint(1), |
| 938 | 28245× | Vertex::Atom((c, i)) => { |
| 939 | 28245× | Value::Array(vec![Value::Bytes(c.0.to_vec()), Value::Uint(*i as u64)]) |
| 940 | | } |
| 941 | | } |
| 942 | 35536× | } |
| 943 | |
|
| 944 | 10248× | fn vertex_from(v: &crate::cbor::Value) -> Result<Vertex, crate::objects::ObjectError> { |
| 945 | | use crate::cbor::Value; |
| 946 | | use crate::objects::ObjectError; |
| 947 | 8632× | match v { |
| 948 | 708× | Value::Uint(0) => Ok(Vertex::Start), |
| 949 | 908× | Value::Uint(1) => Ok(Vertex::End), |
| 950 | 8632× | Value::Array(a) if a.len() == 2 => Ok(Vertex::Atom(( |
| 951 | 8632× | crate::objects::oref(&a[0], "vertex")?, |
| 952 | 8632× | crate::objects::u32_(&a[1], "vertex")?, |
| 953 | | ))), |
| 954 | 0× | _ => Err(ObjectError::Schema( |
| 955 | 0× | "vertex: expected 0, 1, or [hash, index]".into(), |
| 956 | 0× | )), |
| 957 | | } |
| 958 | 10248× | } |
| 959 | |
|
| 960 | | impl FileGraph { |
| 961 | | |
| 962 | 1596× | pub fn to_value(&self) -> crate::cbor::Value { |
| 963 | | use crate::cbor::Value; |
| 964 | 1596× | let mut atoms: Vec<(&AtomKey, &AtomInfo)> = self.atoms.iter().collect(); |
| 965 | 1596× | atoms.sort_by_key(|(k, _)| **k); |
| 966 | 1596× | let mut edges: Vec<(Vertex, Vertex)> = self |
| 967 | 1596× | .succ |
| 968 | 1596× | .iter() |
| 969 | 17768× | .flat_map(|(a, bs)| bs.iter().map(move |b| (*a, *b))) |
| 970 | 1596× | .collect(); |
| 971 | 1596× | edges.sort(); |
| 972 | 1596× | Value::map([ |
| 973 | | ( |
| 974 | | 0, |
| 975 | | Value::Array( |
| 976 | 1596× | atoms |
| 977 | 1596× | .into_iter() |
| 978 | 12902× | .map(|((c, i), info)| { |
| 979 | 12902× | Value::Array(vec![ |
| 980 | 12902× | Value::Bytes(c.0.to_vec()), |
| 981 | 12902× | Value::Uint(*i as u64), |
| 982 | 12902× | info.content |
| 983 | 12902× | .as_ref() |
| 984 | 12902× | .map(|b| Value::Bytes(b.bytes().to_vec())) |
| 985 | 12902× | .unwrap_or(Value::Null), |
| 986 | | Value::Array( |
| 987 | 12902× | info.deleted_by |
| 988 | 12902× | .iter() |
| 989 | 12902× | .map(|d| Value::Bytes(d.0.to_vec())) |
| 990 | 12902× | .collect(), |
| 991 | | ), |
| 992 | | ]) |
| 993 | 12902× | }) |
| 994 | 1596× | .collect(), |
| 995 | | ), |
| 996 | | ), |
| 997 | | ( |
| 998 | | 1, |
| 999 | | Value::Array( |
| 1000 | 1596× | edges |
| 1001 | 1596× | .iter() |
| 1002 | 17768× | .map(|(a, b)| Value::Array(vec![vertex_value(a), vertex_value(b)])) |
| 1003 | 1596× | .collect(), |
| 1004 | | ), |
| 1005 | | ), |
| 1006 | | ]) |
| 1007 | 1596× | } |
| 1008 | |
|
| 1009 | 332× | pub fn from_value(v: &crate::cbor::Value) -> Result<FileGraph, crate::objects::ObjectError> { |
| 1010 | | use crate::objects::{array, bytes, field, map, oref, u32_, ObjectError}; |
| 1011 | 332× | let m = map(v, "file graph")?; |
| 1012 | 332× | let mut g = FileGraph { |
| 1013 | 332× | atoms: HashMap::default(), |
| 1014 | 332× | succ: HashMap::default(), |
| 1015 | 332× | order: OrderSlot::default(), |
| 1016 | 332× | }; |
| 1017 | 332× | let atoms = array(field(m, 0, "atoms")?, "atoms")?; |
| 1018 | | |
| 1019 | 332× | let mut texts: Vec<&[u8]> = Vec::new(); |
| 1020 | 4100× | for a in atoms { |
| 1021 | 4100× | let e = array(a, "atom")?; |
| 1022 | 4100× | if e.len() == 4 && !e[2].is_null() { |
| 1023 | 4100× | texts.push(bytes(&e[2], "atom content")?); |
| 1024 | 0× | } |
| 1025 | | } |
| 1026 | 332× | let all: Lines = texts.into_iter().collect(); |
| 1027 | 332× | let mut next = 0u32; |
| 1028 | 4100× | for a in atoms { |
| 1029 | 4100× | let e = array(a, "atom")?; |
| 1030 | 4100× | if e.len() != 4 { |
| 1031 | 0× | return Err(ObjectError::Schema("atom: expected 4 fields".into())); |
| 1032 | 4100× | } |
| 1033 | 4100× | let key = (oref(&e[0], "atom change")?, u32_(&e[1], "atom index")?); |
| 1034 | 4100× | let content = if e[2].is_null() { |
| 1035 | 0× | None |
| 1036 | | } else { |
| 1037 | 4100× | next += 1; |
| 1038 | 4100× | Some(LineRef::new(all.clone(), next - 1)) |
| 1039 | | }; |
| 1040 | 4100× | let deleted_by = array(&e[3], "deleters")? |
| 1041 | 4100× | .iter() |
| 1042 | 4100× | .map(|d| oref(d, "deleter")) |
| 1043 | 4100× | .collect::<Result<BTreeSet<_>, _>>()?; |
| 1044 | 4100× | g.atoms.insert( |
| 1045 | 4100× | key, |
| 1046 | 4100× | AtomInfo { |
| 1047 | 4100× | content, |
| 1048 | 4100× | deleted_by, |
| 1049 | 4100× | }, |
| 1050 | | ); |
| 1051 | | } |
| 1052 | 5124× | for e in array(field(m, 1, "edges")?, "edges")? { |
| 1053 | 5124× | let pair = array(e, "edge")?; |
| 1054 | 5124× | if pair.len() != 2 { |
| 1055 | 0× | return Err(ObjectError::Schema("edge: expected [from, to]".into())); |
| 1056 | 5124× | } |
| 1057 | 5124× | g.edge(vertex_from(&pair[0])?, vertex_from(&pair[1])?); |
| 1058 | | } |
| 1059 | 332× | Ok(g) |
| 1060 | 332× | } |
| 1061 | | } |
| 1062 | |
|
| 1063 | |
|
| 1064 | 1046× | fn split_zombies(atoms: Vec<AtomKey>, ghosts: &HashSet<AtomKey>) -> Vec<Segment> { |
| 1065 | 1046× | let mut out = Vec::new(); |
| 1066 | 1046× | let mut lines: Vec<AtomKey> = Vec::new(); |
| 1067 | 1046× | let mut i = 0; |
| 1068 | 6036× | while i < atoms.len() { |
| 1069 | 4990× | if !ghosts.contains(&atoms[i]) { |
| 1070 | 4656× | lines.push(atoms[i]); |
| 1071 | 4656× | i += 1; |
| 1072 | 4656× | continue; |
| 1073 | 334× | } |
| 1074 | 334× | if !lines.is_empty() { |
| 1075 | 204× | out.push(Segment::Lines(std::mem::take(&mut lines))); |
| 1076 | 204× | } |
| 1077 | 334× | let mut deleted = Vec::new(); |
| 1078 | 668× | while i < atoms.len() && ghosts.contains(&atoms[i]) { |
| 1079 | 334× | deleted.push(atoms[i]); |
| 1080 | 334× | i += 1; |
| 1081 | 334× | } |
| 1082 | 334× | let mut inserted = Vec::new(); |
| 1083 | 334× | if i < atoms.len() { |
| 1084 | 320× | let ch = atoms[i].0; |
| 1085 | 717× | while i < atoms.len() && atoms[i].0 == ch && !ghosts.contains(&atoms[i]) { |
| 1086 | 397× | inserted.push(atoms[i]); |
| 1087 | 397× | i += 1; |
| 1088 | 397× | } |
| 1089 | 14× | } |
| 1090 | 334× | out.push(Segment::Zombie { deleted, inserted }); |
| 1091 | | } |
| 1092 | 1046× | if !lines.is_empty() { |
| 1093 | 895× | out.push(Segment::Lines(lines)); |
| 1094 | 895× | } |
| 1095 | 1046× | out |
| 1096 | 1046× | } |
| 1097 | |
|
| 1098 | | #[cfg(test)] |
| 1099 | | mod tests { |
| 1100 | | use super::*; |
| 1101 | | use crate::hash::ObjectRef; |
| 1102 | |
|
| 1103 | 156× | fn h(n: u8) -> ChangeHash { |
| 1104 | 156× | ObjectRef([n; 32]) |
| 1105 | 156× | } |
| 1106 | 792× | fn lines(v: &[&str]) -> Lines { |
| 1107 | 1741× | v.iter().map(|s| s.as_bytes()).collect() |
| 1108 | 792× | } |
| 1109 | 66× | fn text(g: &FileGraph, m: &Materialized) -> String { |
| 1110 | 208× | String::from_utf8(g.render(m, &|c| c.short(4))).unwrap() |
| 1111 | 66× | } |
| 1112 | |
|
| 1113 | | struct Deps(HashMap<ChangeHash, Vec<ChangeHash>>); |
| 1114 | | impl DepOracle for Deps { |
| 1115 | 2237× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 1116 | 2237× | if a == b { |
| 1117 | 0× | return true; |
| 1118 | 2237× | } |
| 1119 | 2237× | let mut stack = vec![*a]; |
| 1120 | 2237× | let mut seen = HashSet::default(); |
| 1121 | 93569× | while let Some(x) = stack.pop() { |
| 1122 | 92957× | if let Some(ds) = self.0.get(&x) { |
| 1123 | 14480864× | for d in ds { |
| 1124 | 14480864× | if d == b { |
| 1125 | 1625× | return true; |
| 1126 | 14479239× | } |
| 1127 | 14479239× | if seen.insert(*d) { |
| 1128 | 455761× | stack.push(*d); |
| 1129 | 14023478× | } |
| 1130 | | } |
| 1131 | 612× | } |
| 1132 | | } |
| 1133 | 612× | false |
| 1134 | 2237× | } |
| 1135 | | } |
| 1136 | |
|
| 1137 | | |
| 1138 | | struct Linear; |
| 1139 | | impl DepOracle for Linear { |
| 1140 | 220× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 1141 | 220× | a.0[..2] >= b.0[..2] |
| 1142 | 220× | } |
| 1143 | | } |
| 1144 | |
|
| 1145 | | |
| 1146 | | |
| 1147 | 1× | fn edited_file(n: usize, revs: usize, per: usize) -> FileGraph { |
| 1148 | 401× | let hn = |i: usize| { |
| 1149 | 401× | let mut b = [0u8; 32]; |
| 1150 | 401× | b[..2].copy_from_slice(&(i as u16).to_be_bytes()); |
| 1151 | 401× | ObjectRef(b) |
| 1152 | 401× | }; |
| 1153 | 1× | let mut g = FileGraph::new(); |
| 1154 | 500× | let first: Vec<String> = (0..n).map(|i| format!("line {i}\n")).collect(); |
| 1155 | 1× | let first: Vec<&str> = first.iter().map(String::as_str).collect(); |
| 1156 | 1× | g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first)); |
| 1157 | 10× | for r in 2..revs + 2 { |
| 1158 | 10× | let order = g.materialize(&Linear).live_atoms(); |
| 1159 | 10× | let step = order.len() / per; |
| 1160 | 200× | for j in 0..per { |
| 1161 | 200× | let at = j * step + (r % step.max(1)); |
| 1162 | 200× | let k = order[at]; |
| 1163 | 200× | g.delete( |
| 1164 | 200× | hn(r), |
| 1165 | 200× | &AtomRange { |
| 1166 | 200× | change: k.0, |
| 1167 | 200× | from: k.1, |
| 1168 | 200× | to: k.1 + 1, |
| 1169 | 200× | }, |
| 1170 | | ); |
| 1171 | 200× | let after = if at == 0 { |
| 1172 | 0× | Vertex::Start |
| 1173 | | } else { |
| 1174 | 200× | Vertex::Atom(order[at - 1]) |
| 1175 | | }; |
| 1176 | 200× | let before = order |
| 1177 | 200× | .get(at + 1) |
| 1178 | 200× | .map(|k| Vertex::Atom(*k)) |
| 1179 | 200× | .unwrap_or(Vertex::End); |
| 1180 | 200× | g.insert(hn(r), j as u32, after, before, &lines(&["new\n"])); |
| 1181 | | } |
| 1182 | | } |
| 1183 | 1× | g |
| 1184 | 1× | } |
| 1185 | |
|
| 1186 | | |
| 1187 | | #[test] |
| 1188 | 1× | fn the_order_shortcut_agrees_with_the_general_order() { |
| 1189 | 1× | let g = edited_file(500, 10, 20); |
| 1190 | 1× | assert!(g.as_chain().is_none()); |
| 1191 | 1× | let fast = g.as_order(&Linear).expect("a single order"); |
| 1192 | 1× | assert_eq!( |
| 1193 | 1× | Materialized { |
| 1194 | 1× | segments: vec![Segment::Lines(fast)] |
| 1195 | 1× | }, |
| 1196 | 1× | g.materialize_general(&Linear) |
| 1197 | | ); |
| 1198 | | |
| 1199 | | |
| 1200 | 1× | let mut g = FileGraph::new(); |
| 1201 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"])); |
| 1202 | 1× | let a = Vertex::Atom((h(1), 0)); |
| 1203 | 1× | g.insert(h(2), 0, a, Vertex::End, &lines(&["b\n"])); |
| 1204 | 1× | g.insert(h(3), 0, a, Vertex::End, &lines(&["c\n"])); |
| 1205 | 1× | assert!(g.as_order(&NoDeps).is_none()); |
| 1206 | 1× | assert!(g.materialize(&NoDeps).is_conflicted()); |
| 1207 | 1× | } |
| 1208 | |
|
| 1209 | | |
| 1210 | | |
| 1211 | 491× | fn apply_spliced( |
| 1212 | 491× | g: &mut FileGraph, |
| 1213 | 491× | h: ChangeHash, |
| 1214 | 491× | inserts: &[(Vertex, Vertex, usize)], |
| 1215 | 491× | deleted: &[AtomKey], |
| 1216 | 491× | ) { |
| 1217 | 491× | let order = g.take_order(); |
| 1218 | 491× | let mut runs = Vec::new(); |
| 1219 | 491× | let mut next = 0u32; |
| 1220 | 554× | for (i, &(a, b, n)) in inserts.iter().enumerate() { |
| 1221 | 947× | let text: Vec<String> = (0..n).map(|j| format!("{i}.{j}\n")).collect(); |
| 1222 | 554× | let text: Vec<&str> = text.iter().map(String::as_str).collect(); |
| 1223 | 554× | g.insert(h, next, a, b, &lines(&text)); |
| 1224 | 554× | runs.push((a, b, next, n as u32)); |
| 1225 | 554× | next += n as u32; |
| 1226 | | } |
| 1227 | 1003× | for k in deleted { |
| 1228 | 1003× | g.delete( |
| 1229 | 1003× | h, |
| 1230 | 1003× | &AtomRange { |
| 1231 | 1003× | change: k.0, |
| 1232 | 1003× | from: k.1, |
| 1233 | 1003× | to: k.1 + 1, |
| 1234 | 1003× | }, |
| 1235 | 1003× | ); |
| 1236 | 1003× | } |
| 1237 | 491× | if let Some(o) = order { |
| 1238 | 458× | g.keep_spliced(o, h, &runs, deleted); |
| 1239 | 458× | } |
| 1240 | 491× | } |
| 1241 | |
|
| 1242 | | |
| 1243 | | |
| 1244 | 457× | fn random_edits( |
| 1245 | 457× | l: &[AtomKey], |
| 1246 | 457× | rnd: &mut impl FnMut(usize) -> usize, |
| 1247 | 457× | ) -> (Vec<(Vertex, Vertex, usize)>, Vec<AtomKey>) { |
| 1248 | 457× | let (mut inserts, mut deleted) = (Vec::new(), Vec::new()); |
| 1249 | 457× | let mut i = rnd(3); |
| 1250 | 1247× | while i <= l.len() { |
| 1251 | 790× | let del = rnd(3).min(l.len() - i); |
| 1252 | 790× | let add = rnd(3); |
| 1253 | 790× | let after = if i == 0 { |
| 1254 | 169× | Vertex::Start |
| 1255 | | } else { |
| 1256 | 621× | Vertex::Atom(l[i - 1]) |
| 1257 | | }; |
| 1258 | 790× | let before = l.get(i).map(|k| Vertex::Atom(*k)).unwrap_or(Vertex::End); |
| 1259 | 790× | deleted.extend(&l[i..i + del]); |
| 1260 | 790× | if add > 0 { |
| 1261 | 522× | inserts.push((after, before, add)); |
| 1262 | 522× | } |
| 1263 | 790× | i += del + 1 + rnd(6); |
| 1264 | | } |
| 1265 | 457× | (inserts, deleted) |
| 1266 | 457× | } |
| 1267 | |
|
| 1268 | | |
| 1269 | | |
| 1270 | | |
| 1271 | | #[test] |
| 1272 | 1× | fn the_kept_order_follows_edits() { |
| 1273 | 490× | let hn = |i: usize| { |
| 1274 | 490× | let mut b = [0u8; 32]; |
| 1275 | 490× | b[..2].copy_from_slice(&(i as u16).to_be_bytes()); |
| 1276 | 490× | ObjectRef(b) |
| 1277 | 490× | }; |
| 1278 | 1× | let mut seed = 7u64; |
| 1279 | 2827× | let mut rnd = move |n: usize| { |
| 1280 | 2827× | seed = seed |
| 1281 | 2827× | .wrapping_mul(6364136223846793005) |
| 1282 | 2827× | .wrapping_add(1442695040888963407); |
| 1283 | 2827× | ((seed >> 33) as usize) % n.max(1) |
| 1284 | 2827× | }; |
| 1285 | 1× | let mut deps = Deps(HashMap::default()); |
| 1286 | 1× | let mut g = FileGraph::new(); |
| 1287 | 40× | let first: Vec<String> = (0..40).map(|i| format!("{i}\n")).collect(); |
| 1288 | 1× | let first: Vec<&str> = first.iter().map(String::as_str).collect(); |
| 1289 | 1× | g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first)); |
| 1290 | 1× | let mut known: Vec<ChangeHash> = vec![hn(1)]; |
| 1291 | 1× | let mut c = 2; |
| 1292 | 1× | let (mut spliced, mut conflicted) = (0, 0); |
| 1293 | 400× | for step in 0..400 { |
| 1294 | 400× | let l = g.materialize(&deps).live_atoms(); |
| 1295 | | |
| 1296 | | |
| 1297 | 400× | let pair = step % 7 == 3; |
| 1298 | 400× | let base = known.clone(); |
| 1299 | 400× | for _ in 0..if pair { 2 } else { 1 } { |
| 1300 | 457× | let h = hn(c); |
| 1301 | 457× | c += 1; |
| 1302 | 457× | let (inserts, deleted) = random_edits(&l, &mut rnd); |
| 1303 | 457× | deps.0.insert(h, base.clone()); |
| 1304 | 457× | apply_spliced(&mut g, h, &inserts, &deleted); |
| 1305 | 457× | known.push(h); |
| 1306 | 457× | if g.order.get().is_some() { |
| 1307 | 433× | spliced += 1; |
| 1308 | 433× | } |
| 1309 | 457× | let m = g.materialize(&deps); |
| 1310 | 457× | assert_eq!(m, g.materialize_general(&deps), "step {step}"); |
| 1311 | 457× | if m.is_conflicted() { |
| 1312 | 31× | conflicted += 1; |
| 1313 | 426× | } |
| 1314 | | } |
| 1315 | | |
| 1316 | | |
| 1317 | 400× | if g.materialize(&deps).is_conflicted() { |
| 1318 | 31× | let h = hn(c); |
| 1319 | 31× | c += 1; |
| 1320 | 31× | let all = g.materialize(&deps).live_atoms(); |
| 1321 | 31× | deps.0.insert(h, known.clone()); |
| 1322 | 31× | apply_spliced(&mut g, h, &[(Vertex::Start, Vertex::End, 5)], &all); |
| 1323 | 31× | known.push(h); |
| 1324 | 31× | assert_eq!(g.materialize(&deps), g.materialize_general(&deps)); |
| 1325 | 369× | } |
| 1326 | | } |
| 1327 | 1× | assert!( |
| 1328 | 1× | spliced > 100 && conflicted > 0, |
| 1329 | | "{spliced} spliced, {conflicted} conflicted" |
| 1330 | | ); |
| 1331 | 1× | } |
| 1332 | |
|
| 1333 | | |
| 1334 | | |
| 1335 | | |
| 1336 | | #[test] |
| 1337 | 1× | fn the_kept_order_takes_no_line_that_arrives_deleted() { |
| 1338 | 1× | let mut deps = Deps(HashMap::default()); |
| 1339 | 1× | let mut g = FileGraph::new(); |
| 1340 | 1× | g.insert( |
| 1341 | 1× | h(1), |
| 1342 | | 0, |
| 1343 | 1× | Vertex::Start, |
| 1344 | 1× | Vertex::End, |
| 1345 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1346 | | ); |
| 1347 | | |
| 1348 | 1× | apply_spliced(&mut g, h(2), &[], &[(h(1), 2)]); |
| 1349 | 1× | deps.0.insert(h(2), vec![h(1)]); |
| 1350 | 1× | let _ = g.materialize(&deps); |
| 1351 | 1× | assert!(g.order.get().is_some()); |
| 1352 | 1× | let (a, b) = (Vertex::Atom((h(1), 0)), Vertex::Atom((h(1), 1))); |
| 1353 | | |
| 1354 | 1× | deps.0.insert(h(3), vec![h(2)]); |
| 1355 | 1× | deps.0.insert(h(4), vec![h(3)]); |
| 1356 | 1× | apply_spliced(&mut g, h(4), &[], &[(h(3), 0)]); |
| 1357 | 1× | apply_spliced(&mut g, h(3), &[(a, b, 1)], &[]); |
| 1358 | 1× | let m = g.materialize(&deps); |
| 1359 | 1× | assert_eq!(m, g.materialize_general(&deps)); |
| 1360 | 1× | assert_eq!(text(&g, &m), "a\nb\n"); |
| 1361 | 1× | } |
| 1362 | |
|
| 1363 | | |
| 1364 | | #[test] |
| 1365 | | #[ignore] |
| 1366 | 0× | fn bench_materialize_edited_file() { |
| 1367 | 0× | let g = edited_file(20_000, 50, 100); |
| 1368 | 0× | let t = std::time::Instant::now(); |
| 1369 | 0× | let mut n = 0; |
| 1370 | 0× | for _ in 0..20 { |
| 1371 | 0× | n += g.materialize(&Linear).live_atoms().len(); |
| 1372 | 0× | } |
| 1373 | 0× | eprintln!( |
| 1374 | | "materialize: {:.2} ms each ({n} atoms)", |
| 1375 | 0× | t.elapsed().as_secs_f64() * 1000.0 / 20.0 |
| 1376 | | ); |
| 1377 | 0× | } |
| 1378 | |
|
| 1379 | | |
| 1380 | | |
| 1381 | | #[test] |
| 1382 | 1× | fn the_chain_shortcut_agrees_with_the_general_order() { |
| 1383 | 1× | let mut g = FileGraph::new(); |
| 1384 | 1× | g.insert( |
| 1385 | 1× | h(1), |
| 1386 | | 0, |
| 1387 | 1× | Vertex::Start, |
| 1388 | 1× | Vertex::End, |
| 1389 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1390 | | ); |
| 1391 | 1× | assert!(g.as_chain().is_some()); |
| 1392 | 1× | assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps)); |
| 1393 | | |
| 1394 | 1× | g.insert( |
| 1395 | 1× | h(2), |
| 1396 | | 0, |
| 1397 | 1× | Vertex::Atom((h(1), 2)), |
| 1398 | 1× | Vertex::End, |
| 1399 | 1× | &lines(&["d\n"]), |
| 1400 | | ); |
| 1401 | 1× | assert!(g.as_chain().is_some()); |
| 1402 | 1× | assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps)); |
| 1403 | | |
| 1404 | 1× | g.insert( |
| 1405 | 1× | h(4), |
| 1406 | | 0, |
| 1407 | 1× | Vertex::Atom((h(1), 0)), |
| 1408 | 1× | Vertex::Atom((h(1), 1)), |
| 1409 | 1× | &lines(&["x\n"]), |
| 1410 | | ); |
| 1411 | 1× | assert!(g.as_chain().is_none()); |
| 1412 | 1× | assert_eq!(text(&g, &g.materialize(&NoDeps)), "a\nx\nb\nc\nd\n"); |
| 1413 | | |
| 1414 | 1× | let mut c = FileGraph::new(); |
| 1415 | 1× | c.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"])); |
| 1416 | 1× | c.insert(h(2), 0, Vertex::Start, Vertex::End, &lines(&["b\n"])); |
| 1417 | 1× | assert!(c.as_chain().is_none()); |
| 1418 | 1× | assert!(c.materialize(&NoDeps).is_conflicted()); |
| 1419 | | |
| 1420 | 1× | let mut d = FileGraph::new(); |
| 1421 | 1× | d.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1422 | 1× | d.delete( |
| 1423 | 1× | h(3), |
| 1424 | 1× | &AtomRange { |
| 1425 | 1× | change: h(1), |
| 1426 | 1× | from: 0, |
| 1427 | 1× | to: 1, |
| 1428 | 1× | }, |
| 1429 | | ); |
| 1430 | 1× | assert!(d.as_chain().is_none()); |
| 1431 | 1× | assert_eq!(d.materialize(&NoDeps), d.materialize_general(&NoDeps)); |
| 1432 | | |
| 1433 | 1× | let e = FileGraph::new(); |
| 1434 | 1× | assert_eq!(e.materialize(&NoDeps), e.materialize_general(&NoDeps)); |
| 1435 | 1× | } |
| 1436 | |
|
| 1437 | | #[test] |
| 1438 | 1× | fn sequential_inserts_and_deletes() { |
| 1439 | 1× | let mut g = FileGraph::new(); |
| 1440 | 1× | g.insert( |
| 1441 | 1× | h(1), |
| 1442 | | 0, |
| 1443 | 1× | Vertex::Start, |
| 1444 | 1× | Vertex::End, |
| 1445 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1446 | | ); |
| 1447 | 1× | let m = g.materialize(&NoDeps); |
| 1448 | 1× | assert!(!m.is_conflicted()); |
| 1449 | 1× | assert_eq!(text(&g, &m), "a\nb\nc\n"); |
| 1450 | |
|
| 1451 | | |
| 1452 | 1× | g.insert( |
| 1453 | 1× | h(2), |
| 1454 | | 0, |
| 1455 | 1× | Vertex::Atom((h(1), 1)), |
| 1456 | 1× | Vertex::Atom((h(1), 2)), |
| 1457 | 1× | &lines(&["x\n"]), |
| 1458 | | ); |
| 1459 | 1× | g.delete( |
| 1460 | 1× | h(2), |
| 1461 | 1× | &AtomRange { |
| 1462 | 1× | change: h(1), |
| 1463 | 1× | from: 0, |
| 1464 | 1× | to: 1, |
| 1465 | 1× | }, |
| 1466 | | ); |
| 1467 | 1× | let deps = Deps([(h(2), vec![h(1)])].into_iter().collect()); |
| 1468 | 1× | let m = g.materialize(&deps); |
| 1469 | 1× | assert!(!m.is_conflicted()); |
| 1470 | 1× | assert_eq!(text(&g, &m), "b\nx\nc\n"); |
| 1471 | 1× | assert_eq!(m.live_atoms(), vec![(h(1), 1), (h(2), 0), (h(1), 2)]); |
| 1472 | 1× | } |
| 1473 | |
|
| 1474 | | #[test] |
| 1475 | 1× | fn concurrent_inserts_at_same_anchor_conflict() { |
| 1476 | 1× | let mut g = FileGraph::new(); |
| 1477 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1478 | 1× | g.insert( |
| 1479 | 1× | h(2), |
| 1480 | | 0, |
| 1481 | 1× | Vertex::Atom((h(1), 0)), |
| 1482 | 1× | Vertex::Atom((h(1), 1)), |
| 1483 | 1× | &lines(&["from-2\n"]), |
| 1484 | | ); |
| 1485 | 1× | g.insert( |
| 1486 | 1× | h(3), |
| 1487 | | 0, |
| 1488 | 1× | Vertex::Atom((h(1), 0)), |
| 1489 | 1× | Vertex::Atom((h(1), 1)), |
| 1490 | 1× | &lines(&["from-3\n", "more-3\n"]), |
| 1491 | | ); |
| 1492 | 1× | let deps = Deps( |
| 1493 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1494 | 1× | .into_iter() |
| 1495 | 1× | .collect(), |
| 1496 | 1× | ); |
| 1497 | 1× | let m = g.materialize(&deps); |
| 1498 | 1× | assert!(m.is_conflicted()); |
| 1499 | 1× | assert_eq!(m.conflict_count(), 1); |
| 1500 | 1× | assert_eq!( |
| 1501 | | m.segments, |
| 1502 | 1× | vec![ |
| 1503 | 1× | Segment::Lines(vec![(h(1), 0)]), |
| 1504 | 1× | Segment::Conflict(vec![vec![(h(2), 0)], vec![(h(3), 0), (h(3), 1)]]), |
| 1505 | 1× | Segment::Lines(vec![(h(1), 1)]), |
| 1506 | | ] |
| 1507 | | ); |
| 1508 | 1× | let t = text(&g, &m); |
| 1509 | 1× | assert!(t.starts_with("a\n<<<<<<< oxy conflict 1 of 1, side 1 of 2: change 0202\nfrom-2\n======= oxy conflict 1 of 1, side 2 of 2: change 0303\nfrom-3\nmore-3\n>>>>>>> oxy conflict 1 of 1 end\nb\n"), "{t}"); |
| 1510 | 1× | } |
| 1511 | |
|
| 1512 | | #[test] |
| 1513 | 1× | fn concurrent_inserts_at_different_anchors_do_not_conflict() { |
| 1514 | 1× | let mut g = FileGraph::new(); |
| 1515 | 1× | g.insert( |
| 1516 | 1× | h(1), |
| 1517 | | 0, |
| 1518 | 1× | Vertex::Start, |
| 1519 | 1× | Vertex::End, |
| 1520 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1521 | | ); |
| 1522 | 1× | g.insert( |
| 1523 | 1× | h(2), |
| 1524 | | 0, |
| 1525 | 1× | Vertex::Start, |
| 1526 | 1× | Vertex::Atom((h(1), 0)), |
| 1527 | 1× | &lines(&["top\n"]), |
| 1528 | | ); |
| 1529 | 1× | g.insert( |
| 1530 | 1× | h(3), |
| 1531 | | 0, |
| 1532 | 1× | Vertex::Atom((h(1), 2)), |
| 1533 | 1× | Vertex::End, |
| 1534 | 1× | &lines(&["bottom\n"]), |
| 1535 | | ); |
| 1536 | 1× | let deps = Deps( |
| 1537 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1538 | 1× | .into_iter() |
| 1539 | 1× | .collect(), |
| 1540 | 1× | ); |
| 1541 | 1× | let m = g.materialize(&deps); |
| 1542 | 1× | assert!(!m.is_conflicted()); |
| 1543 | 1× | assert_eq!(text(&g, &m), "top\na\nb\nc\nbottom\n"); |
| 1544 | 1× | } |
| 1545 | |
|
| 1546 | | #[test] |
| 1547 | 1× | fn concurrent_deletes_of_the_same_line_agree() { |
| 1548 | 1× | let mut g = FileGraph::new(); |
| 1549 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1550 | 1× | g.delete( |
| 1551 | 1× | h(2), |
| 1552 | 1× | &AtomRange { |
| 1553 | 1× | change: h(1), |
| 1554 | 1× | from: 0, |
| 1555 | 1× | to: 1, |
| 1556 | 1× | }, |
| 1557 | | ); |
| 1558 | 1× | g.delete( |
| 1559 | 1× | h(3), |
| 1560 | 1× | &AtomRange { |
| 1561 | 1× | change: h(1), |
| 1562 | 1× | from: 0, |
| 1563 | 1× | to: 1, |
| 1564 | 1× | }, |
| 1565 | | ); |
| 1566 | 1× | let deps = Deps( |
| 1567 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1568 | 1× | .into_iter() |
| 1569 | 1× | .collect(), |
| 1570 | 1× | ); |
| 1571 | 1× | let m = g.materialize(&deps); |
| 1572 | 1× | assert!(!m.is_conflicted()); |
| 1573 | 1× | assert_eq!(text(&g, &m), "b\n"); |
| 1574 | 1× | } |
| 1575 | |
|
| 1576 | | #[test] |
| 1577 | 1× | fn delete_insert_conflict_is_a_zombie() { |
| 1578 | 1× | let mut g = FileGraph::new(); |
| 1579 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1580 | | |
| 1581 | 1× | g.delete( |
| 1582 | 1× | h(2), |
| 1583 | 1× | &AtomRange { |
| 1584 | 1× | change: h(1), |
| 1585 | 1× | from: 0, |
| 1586 | 1× | to: 1, |
| 1587 | 1× | }, |
| 1588 | | ); |
| 1589 | 1× | g.insert( |
| 1590 | 1× | h(3), |
| 1591 | | 0, |
| 1592 | 1× | Vertex::Atom((h(1), 0)), |
| 1593 | 1× | Vertex::Atom((h(1), 1)), |
| 1594 | 1× | &lines(&["after-a\n"]), |
| 1595 | | ); |
| 1596 | 1× | let deps = Deps( |
| 1597 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1598 | 1× | .into_iter() |
| 1599 | 1× | .collect(), |
| 1600 | 1× | ); |
| 1601 | 1× | let m = g.materialize(&deps); |
| 1602 | 1× | assert!(m.is_conflicted()); |
| 1603 | 1× | assert_eq!( |
| 1604 | | m.segments, |
| 1605 | 1× | vec![ |
| 1606 | 1× | Segment::Zombie { |
| 1607 | 1× | deleted: vec![(h(1), 0)], |
| 1608 | 1× | inserted: vec![(h(3), 0)] |
| 1609 | 1× | }, |
| 1610 | 1× | Segment::Lines(vec![(h(1), 1)]), |
| 1611 | | ] |
| 1612 | | ); |
| 1613 | 1× | let t = text(&g, &m); |
| 1614 | 1× | assert!(t.contains("-------\na\n======="), "{t}"); |
| 1615 | 1× | assert!(t.contains("after-a\n>>>>>>>"), "{t}"); |
| 1616 | |
|
| 1617 | | |
| 1618 | 1× | let deps = Deps( |
| 1619 | 1× | [(h(2), vec![h(1), h(3)]), (h(3), vec![h(1)])] |
| 1620 | 1× | .into_iter() |
| 1621 | 1× | .collect(), |
| 1622 | 1× | ); |
| 1623 | 1× | let m = g.materialize(&deps); |
| 1624 | 1× | assert!(!m.is_conflicted()); |
| 1625 | 1× | assert_eq!(text(&g, &m), "after-a\nb\n"); |
| 1626 | 1× | } |
| 1627 | |
|
| 1628 | | #[test] |
| 1629 | 1× | fn insert_after_a_line_deleted_by_a_dependency_is_not_a_conflict() { |
| 1630 | | |
| 1631 | | |
| 1632 | 1× | let mut g = FileGraph::new(); |
| 1633 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1634 | 1× | g.delete( |
| 1635 | 1× | h(2), |
| 1636 | 1× | &AtomRange { |
| 1637 | 1× | change: h(1), |
| 1638 | 1× | from: 1, |
| 1639 | 1× | to: 2, |
| 1640 | 1× | }, |
| 1641 | | ); |
| 1642 | 1× | g.insert( |
| 1643 | 1× | h(3), |
| 1644 | | 0, |
| 1645 | 1× | Vertex::Atom((h(1), 1)), |
| 1646 | 1× | Vertex::End, |
| 1647 | 1× | &lines(&["c\n"]), |
| 1648 | | ); |
| 1649 | 1× | let deps = Deps( |
| 1650 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(2)])] |
| 1651 | 1× | .into_iter() |
| 1652 | 1× | .collect(), |
| 1653 | 1× | ); |
| 1654 | 1× | let m = g.materialize(&deps); |
| 1655 | 1× | assert!(!m.is_conflicted(), "{:?}", m.segments); |
| 1656 | 1× | assert_eq!(text(&g, &m), "a\nc\n"); |
| 1657 | | |
| 1658 | | |
| 1659 | 1× | let mut g = FileGraph::new(); |
| 1660 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["x\n"])); |
| 1661 | 1× | g.delete( |
| 1662 | 1× | h(1), |
| 1663 | 1× | &AtomRange { |
| 1664 | 1× | change: h(1), |
| 1665 | 1× | from: 0, |
| 1666 | 1× | to: 1, |
| 1667 | 1× | }, |
| 1668 | | ); |
| 1669 | 1× | g.insert( |
| 1670 | 1× | h(2), |
| 1671 | | 0, |
| 1672 | 1× | Vertex::Atom((h(1), 0)), |
| 1673 | 1× | Vertex::End, |
| 1674 | 1× | &lines(&["y\n"]), |
| 1675 | | ); |
| 1676 | 1× | let deps = Deps([(h(2), vec![h(1)])].into_iter().collect()); |
| 1677 | 1× | let m = g.materialize(&deps); |
| 1678 | 1× | assert!(!m.is_conflicted()); |
| 1679 | 1× | assert_eq!(text(&g, &m), "y\n"); |
| 1680 | 1× | } |
| 1681 | |
|
| 1682 | | #[test] |
| 1683 | 1× | fn resolution_collapses_a_conflict() { |
| 1684 | 1× | let mut g = FileGraph::new(); |
| 1685 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"])); |
| 1686 | 1× | g.insert( |
| 1687 | 1× | h(2), |
| 1688 | | 0, |
| 1689 | 1× | Vertex::Start, |
| 1690 | 1× | Vertex::Atom((h(1), 0)), |
| 1691 | 1× | &lines(&["x\n"]), |
| 1692 | | ); |
| 1693 | 1× | g.insert( |
| 1694 | 1× | h(3), |
| 1695 | | 0, |
| 1696 | 1× | Vertex::Start, |
| 1697 | 1× | Vertex::Atom((h(1), 0)), |
| 1698 | 1× | &lines(&["y\n"]), |
| 1699 | | ); |
| 1700 | 1× | let mut deps: HashMap<_, _> = [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1701 | 1× | .into_iter() |
| 1702 | 1× | .collect(); |
| 1703 | 1× | assert!(g.materialize(&Deps(deps.clone())).is_conflicted()); |
| 1704 | | |
| 1705 | 1× | g.delete( |
| 1706 | 1× | h(4), |
| 1707 | 1× | &AtomRange { |
| 1708 | 1× | change: h(2), |
| 1709 | 1× | from: 0, |
| 1710 | 1× | to: 1, |
| 1711 | 1× | }, |
| 1712 | | ); |
| 1713 | 1× | deps.insert(h(4), vec![h(2), h(3)]); |
| 1714 | 1× | let m = g.materialize(&Deps(deps)); |
| 1715 | 1× | assert!(!m.is_conflicted()); |
| 1716 | 1× | assert_eq!(text(&g, &m), "y\na\n"); |
| 1717 | 1× | } |
| 1718 | |
|
| 1719 | | #[test] |
| 1720 | 1× | fn empty_and_unterminated_files() { |
| 1721 | 1× | let g = FileGraph::new(); |
| 1722 | 1× | let m = g.materialize(&NoDeps); |
| 1723 | 1× | assert_eq!(m.segments, vec![]); |
| 1724 | 1× | assert_eq!(text(&g, &m), ""); |
| 1725 | 1× | let mut g = FileGraph::new(); |
| 1726 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["no newline"])); |
| 1727 | 1× | assert_eq!(text(&g, &g.materialize(&NoDeps)), "no newline"); |
| 1728 | 1× | } |
| 1729 | |
|
| 1730 | | #[test] |
| 1731 | 1× | fn serialization_round_trip() { |
| 1732 | 1× | let mut g = FileGraph::new(); |
| 1733 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1734 | 1× | g.insert( |
| 1735 | 1× | h(2), |
| 1736 | | 0, |
| 1737 | 1× | Vertex::Atom((h(1), 0)), |
| 1738 | 1× | Vertex::Atom((h(1), 1)), |
| 1739 | 1× | &lines(&["x\n"]), |
| 1740 | | ); |
| 1741 | 1× | g.delete( |
| 1742 | 1× | h(3), |
| 1743 | 1× | &AtomRange { |
| 1744 | 1× | change: h(1), |
| 1745 | 1× | from: 1, |
| 1746 | 1× | to: 2, |
| 1747 | 1× | }, |
| 1748 | | ); |
| 1749 | 1× | let v = g.to_value(); |
| 1750 | 1× | let bytes = crate::cbor::encode(&v); |
| 1751 | 1× | let back = FileGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap(); |
| 1752 | 1× | let deps = Deps( |
| 1753 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1754 | 1× | .into_iter() |
| 1755 | 1× | .collect(), |
| 1756 | 1× | ); |
| 1757 | 1× | assert_eq!(back.materialize(&deps), g.materialize(&deps)); |
| 1758 | 1× | assert_eq!(text(&back, &back.materialize(&deps)), "a\nx\n"); |
| 1759 | 1× | assert_eq!( |
| 1760 | 1× | crate::cbor::encode(&back.to_value()), |
| 1761 | | bytes, |
| 1762 | | "deterministic" |
| 1763 | | ); |
| 1764 | 1× | } |
| 1765 | |
|
| 1766 | | |
| 1767 | | #[test] |
| 1768 | 1× | fn application_order_is_irrelevant() { |
| 1769 | | use rand::seq::SliceRandom; |
| 1770 | | type Op = ( |
| 1771 | | ChangeHash, |
| 1772 | | Vec<(u32, Vertex, Vertex, Lines)>, |
| 1773 | | Vec<AtomRange>, |
| 1774 | | ); |
| 1775 | 1× | let ops: Vec<Op> = vec![ |
| 1776 | 1× | ( |
| 1777 | 1× | h(1), |
| 1778 | 1× | vec![(0, Vertex::Start, Vertex::End, lines(&["a\n", "b\n", "c\n"]))], |
| 1779 | 1× | vec![], |
| 1780 | 1× | ), |
| 1781 | 1× | ( |
| 1782 | 1× | h(2), |
| 1783 | 1× | vec![( |
| 1784 | 1× | 0, |
| 1785 | 1× | Vertex::Atom((h(1), 0)), |
| 1786 | 1× | Vertex::Atom((h(1), 1)), |
| 1787 | 1× | lines(&["p\n"]), |
| 1788 | 1× | )], |
| 1789 | 1× | vec![], |
| 1790 | 1× | ), |
| 1791 | 1× | ( |
| 1792 | 1× | h(3), |
| 1793 | 1× | vec![( |
| 1794 | 1× | 0, |
| 1795 | 1× | Vertex::Atom((h(1), 0)), |
| 1796 | 1× | Vertex::Atom((h(1), 1)), |
| 1797 | 1× | lines(&["q\n", "r\n"]), |
| 1798 | 1× | )], |
| 1799 | 1× | vec![AtomRange { |
| 1800 | 1× | change: h(1), |
| 1801 | 1× | from: 2, |
| 1802 | 1× | to: 3, |
| 1803 | 1× | }], |
| 1804 | 1× | ), |
| 1805 | 1× | ( |
| 1806 | 1× | h(4), |
| 1807 | 1× | vec![( |
| 1808 | 1× | 0, |
| 1809 | 1× | Vertex::Atom((h(3), 1)), |
| 1810 | 1× | Vertex::Atom((h(1), 1)), |
| 1811 | 1× | lines(&["s\n"]), |
| 1812 | 1× | )], |
| 1813 | 1× | vec![AtomRange { |
| 1814 | 1× | change: h(1), |
| 1815 | 1× | from: 1, |
| 1816 | 1× | to: 2, |
| 1817 | 1× | }], |
| 1818 | 1× | ), |
| 1819 | 1× | ( |
| 1820 | 1× | h(5), |
| 1821 | 1× | vec![(0, Vertex::Atom((h(1), 2)), Vertex::End, lines(&["z\n"]))], |
| 1822 | 1× | vec![], |
| 1823 | 1× | ), |
| 1824 | | ]; |
| 1825 | 1× | let deps = Deps( |
| 1826 | 1× | [ |
| 1827 | 1× | (h(2), vec![h(1)]), |
| 1828 | 1× | (h(3), vec![h(1)]), |
| 1829 | 1× | (h(4), vec![h(3), h(1)]), |
| 1830 | 1× | (h(5), vec![h(1)]), |
| 1831 | 1× | ] |
| 1832 | 1× | .into_iter() |
| 1833 | 1× | .collect(), |
| 1834 | 1× | ); |
| 1835 | 51× | let build = |order: &[usize]| { |
| 1836 | 51× | let mut g = FileGraph::new(); |
| 1837 | 255× | for &i in order { |
| 1838 | 255× | let (c, ins, dels) = &ops[i]; |
| 1839 | 255× | for (idx, a, b, l) in ins { |
| 1840 | 255× | g.insert(*c, *idx, *a, *b, l); |
| 1841 | 255× | } |
| 1842 | 255× | for d in dels { |
| 1843 | 102× | g.delete(*c, d); |
| 1844 | 102× | } |
| 1845 | | } |
| 1846 | 51× | let m = g.materialize(&deps); |
| 1847 | 51× | (m.clone(), text(&g, &m)) |
| 1848 | 51× | }; |
| 1849 | 1× | let reference = build(&[0, 1, 2, 3, 4]); |
| 1850 | 1× | assert!(reference.0.is_conflicted()); |
| 1851 | 1× | let mut rng = rand::thread_rng(); |
| 1852 | 1× | let mut order: Vec<usize> = (0..ops.len()).collect(); |
| 1853 | 1× | for _ in 0..50 { |
| 1854 | 50× | order.shuffle(&mut rng); |
| 1855 | 50× | assert_eq!(build(&order), reference, "order {order:?}"); |
| 1856 | | } |
| 1857 | 1× | } |
| 1858 | | } |