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