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