OXYGENHUB

Coverage / crates/oxy-core/src/graph.rs 97.0%

1407 of 1450 lines run, 205 of 222 functions · run #341 on main · run missed

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
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
/// `(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
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<'_> {
385102×
    fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
395102×
        if let Some(v) = self.known.borrow().get(&(*a, *b)) {
402635×
            return *v;
412467×
        }
422467×
        let v = self.inner.depends_on(a, b);
432467×
        self.known.borrow_mut().insert((*a, *b), v);
442467×
        v
455102×
    }
46
}
47
48
/// No change depends on any other: every pair commutes.
49
pub struct NoDeps;
50
impl DepOracle for NoDeps {
514×
    fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
524×
        a == b
534×
    }
54
}
55
56
#[derive(Default, Clone, Debug)]
57
struct AtomInfo {
58
    /// `None` until the introducing change has been applied; a line of the
59
    /// insert's shared buffer.
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
    /// Every vertex, with its direct successors.
68
    succ: HashMap<Vertex, Edges>,
69
    /// The live atoms' order, once known to be the graph's only one.
70
    order: OrderSlot,
71
}
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 {
9427014×
    fn get(&self) -> Option<std::sync::Arc<Order>> {
9527014×
        self.0.lock().unwrap_or_else(|e| e.into_inner()).clone()
9627014×
    }
971557×
    fn set(&self, o: Option<std::sync::Arc<Order>>) {
981557×
        *self.0.lock().unwrap_or_else(|e| e.into_inner()) = o;
991557×
    }
10065415×
    fn take(&mut self) -> Option<std::sync::Arc<Order>> {
10165415×
        self.0.get_mut().unwrap_or_else(|e| e.into_inner()).take()
10265415×
    }
103
}
104
105
impl Clone for OrderSlot {
1067×
    fn clone(&self) -> Self {
1077×
        OrderSlot(std::sync::Mutex::new(self.get()))
1087×
    }
109
}
110
111
impl Order {
1121547×
    fn known(atoms: Vec<AtomKey>) -> Order {
1131547×
        Order {
1141547×
            atoms,
1151547×
            unchecked: Vec::new(),
1161547×
            confirmed: std::sync::atomic::AtomicBool::new(true),
1171547×
        }
1181547×
    }
119
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.
125518×
    fn splice(
126518×
        &self,
127518×
        h: ChangeHash,
128518×
        inserts: &[(Vertex, Vertex, u32, u32)],
129518×
        deleted: &[AtomKey],
130518×
    ) -> Option<std::sync::Arc<Order>> {
131518×
        let mut want: HashSet<AtomKey> = deleted.iter().copied().collect();
132566×
        for (a, b, _, _) in inserts {
1331132×
            for v in [a, b] {
1341132×
                if let Vertex::Atom(k) = v {
135932×
                    want.insert(*k);
136932×
                }
137
            }
138
        }
139518×
        let mut pos: HashMap<AtomKey, usize> = HashMap::default();
1402812×
        for (i, k) in self.atoms.iter().enumerate() {
1412812×
            if want.contains(k) {
1421266×
                pos.insert(*k, i);
1431546×
            }
144
        }
145518×
        let len = self.atoms.len();
146518×
        let mut gone = vec![false; len];
147518×
        let mut unchecked = if self.confirmed.load(std::sync::atomic::Ordering::Relaxed) {
148518×
            Vec::new()
149
        } else {
1500×
            self.unchecked.clone()
151
        };
152725×
        for k in deleted {
153725×
            if k.0 == h {
1540×
                return None;
155725×
            }
156725×
            if let Some(&i) = pos.get(k) {
157702×
                if !gone[i] {
158702×
                    gone[i] = true;
159702×
                    unchecked.push(*k);
160702×
                }
16123×
            }
162
        }
163
        // Each run goes in the gap its anchors leave (slot `i`: before
164
        // `atoms[i]`), which must hold nothing that stays.
165518×
        let mut slots: Vec<(usize, usize, u32, u32)> = Vec::with_capacity(inserts.len());
166547×
        for &(a, b, first, count) in inserts {
167547×
            let from = match a {
168134×
                Vertex::Start => 0,
169413×
                Vertex::Atom(k) => pos.get(&k)? + 1,
1700×
                Vertex::End => return None,
171
            };
172536×
            let to = match b {
17362×
                Vertex::End => len,
174474×
                Vertex::Atom(k) => *pos.get(&k)?,
1750×
                Vertex::Start => return None,
176
            };
177529×
            if from > to || gone[from..to].iter().any(|g| !g) {
1785×
                return None;
179524×
            }
180524×
            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.
184495×
        slots.sort_unstable();
185495×
        for w in slots.windows(2) {
186159×
            let (end, next) = (w[0].1, w[1].0);
187302×
            if end >= next || !gone[end..next].iter().any(|g| !g) {
1880×
                return None;
189159×
            }
190
        }
191523×
        let added: usize = slots.iter().map(|s| s.3 as usize).sum();
192495×
        let mut atoms = Vec::with_capacity(len + added);
193495×
        let mut slots = slots.into_iter().peekable();
1943095×
        let mut runs_at = |i: usize, atoms: &mut Vec<AtomKey>| {
1953618×
            while let Some(&(from, _, first, count)) = slots.peek() {
1961770×
                if from != i {
1971247×
                    break;
198523×
                }
199771×
                atoms.extend((first..first + count).map(|j| (h, j)));
200523×
                slots.next();
201
            }
2023095×
        };
2032600×
        for (i, (k, g)) in self.atoms.iter().zip(&gone).enumerate() {
2042600×
            runs_at(i, &mut atoms);
2052600×
            if !g {
2061935×
                atoms.push(*k);
2071935×
            }
208
        }
209495×
        runs_at(len, &mut atoms);
210495×
        let confirmed = unchecked.is_empty();
211495×
        Some(std::sync::Arc::new(Order {
212495×
            atoms,
213495×
            unchecked,
214495×
            confirmed: std::sync::atomic::AtomicBool::new(confirmed),
215495×
        }))
216518×
    }
217
}
218
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>),
227
}
228
229
impl Edges {
230242606×
    fn insert(&mut self, v: Vertex) {
23131799×
        match self {
232209037×
            Edges::None => *self = Edges::One(v),
23331799×
            Edges::One(a) if *a == v => {}
23431799×
            Edges::One(a) => {
23531799×
                let a = *a;
23631799×
                *self = Edges::Many(if a < v { vec![a, v] } else { vec![v, a] });
237
            }
2381770×
            Edges::Many(list) => {
2391770×
                if let Err(i) = list.binary_search(&v) {
2401770×
                    list.insert(i, v);
2411770×
                }
242
            }
243
        }
244242606×
    }
245
246957328×
    fn as_slice(&self) -> &[Vertex] {
247957328×
        match self {
2484384×
            Edges::None => &[],
249701764×
            Edges::One(v) => std::slice::from_ref(v),
250251180×
            Edges::Many(list) => list,
251
        }
252957328×
    }
253
254696066×
    fn iter(&self) -> std::slice::Iter<'_, Vertex> {
255696066×
        self.as_slice().iter()
256696066×
    }
257
}
258
259
impl Default for FileGraph {
26025818×
    fn default() -> Self {
26125818×
        Self::new()
26225818×
    }
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.
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 {
28532970×
    pub fn is_conflicted(&self) -> bool {
28632970×
        self.segments
28732970×
            .iter()
28832970×
            .any(|s| !matches!(s, Segment::Lines(_)))
28932970×
    }
29025166×
    pub fn conflict_count(&self) -> usize {
29125166×
        self.segments
29225166×
            .iter()
29325906×
            .filter(|s| !matches!(s, Segment::Lines(_)))
29425166×
            .count()
29525166×
    }
296
    /// Every live atom in materialized order, conflict sides concatenated.
2971293×
    pub fn live_atoms(&self) -> Vec<AtomKey> {
2981293×
        let mut out = Vec::new();
2991327×
        for s in &self.segments {
3001327×
            match s {
3011283×
                Segment::Lines(a) => out.extend(a),
30230×
                Segment::Conflict(sides) => sides.iter().for_each(|x| out.extend(x)),
30329×
                Segment::Zombie { inserted, .. } => out.extend(inserted),
304
            }
305
        }
3061293×
        out
3071293×
    }
308
}
309
310
impl FileGraph {
31125888×
    pub fn new() -> Self {
31225888×
        let mut g = FileGraph {
31325888×
            atoms: HashMap::default(),
31425888×
            succ: HashMap::default(),
31525888×
            order: OrderSlot::default(),
31625888×
        };
31725888×
        g.edge(Vertex::Start, Vertex::End);
31825888×
        g
31925888×
    }
320
32130998×
    fn edge(&mut self, a: Vertex, b: Vertex) {
32230998×
        self.succ.entry(a).or_default().insert(b);
32330998×
        self.succ.entry(b).or_default();
32430998×
    }
325
326
    /// Apply an `Insert` by `change` whose first new atom has index `first_index`.
3271042×
    pub fn insert(
3281042×
        &mut self,
3291042×
        change: ChangeHash,
3301042×
        first_index: u32,
3311042×
        after: Vertex,
3321042×
        before: Vertex,
3331042×
        lines: &Lines,
3341042×
    ) {
3351042×
        self.insert_owned(change, first_index, after, before, lines.clone());
3361042×
    }
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.
34132880×
    pub fn insert_owned(
34232880×
        &mut self,
34332880×
        change: ChangeHash,
34432880×
        first_index: u32,
34532880×
        after: Vertex,
34632880×
        before: Vertex,
34732880×
        lines: Lines,
34832880×
    ) {
34932880×
        self.order.take();
35032880×
        self.atoms.reserve(lines.len());
35132880×
        self.succ.reserve(lines.len() + 1);
35232880×
        let mut prev = after;
353178728×
        for i in 0..lines.len() {
354178728×
            let key = (change, first_index + i as u32);
355178728×
            self.atoms.entry(key).or_default().content =
356178728×
                Some(LineRef::new(lines.clone(), i as u32));
357178728×
            let v = Vertex::Atom(key);
358178728×
            self.succ.entry(prev).or_default().insert(v);
359178728×
            prev = v;
360178728×
        }
36132880×
        self.succ.entry(prev).or_default().insert(before);
36232880×
        self.succ.entry(before).or_default();
36332880×
    }
364
365
    /// Apply a `Delete` of `range` by change `by`.
3665655×
    pub fn delete(&mut self, by: ChangeHash, range: &AtomRange) {
3675655×
        self.order.take();
3685966×
        for i in range.from..range.to {
3695966×
            self.atoms
3705966×
                .entry((range.change, i))
3715966×
                .or_default()
3725966×
                .deleted_by
3735966×
                .insert(by);
3745966×
        }
3755655×
    }
376
377475120×
    pub fn is_live(&self, k: &AtomKey) -> bool {
378475120×
        self.atoms
379475120×
            .get(k)
380475120×
            .is_some_and(|a| a.content.is_some() && a.deleted_by.is_empty())
381475120×
    }
382
383164518×
    pub fn content(&self, k: &AtomKey) -> Option<&[u8]> {
384164518×
        self.atoms
385164518×
            .get(k)
386164518×
            .and_then(|a| a.content.as_ref().map(LineRef::bytes))
387164518×
    }
388
389465×
    pub fn deleters(&self, k: &AtomKey) -> impl Iterator<Item = &ChangeHash> {
390465×
        self.atoms
391465×
            .get(k)
392465×
            .into_iter()
393465×
            .flat_map(|a| a.deleted_by.iter())
394465×
    }
395
396
    /// Number of atoms introduced by `change` in this graph (for index bookkeeping).
39756×
    pub fn atom_count(&self) -> usize {
39856×
        self.atoms.len()
39956×
    }
400
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.
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`.
4083×
    pub fn between(&self, first: &AtomKey, last: &AtomKey) -> Vec<AtomKey> {
4093×
        let (from, to) = (Vertex::Atom(*first), Vertex::Atom(*last));
4103×
        let mut ahead: HashSet<Vertex> = HashSet::default();
4113×
        let mut stack = vec![from];
41229×
        while let Some(v) = stack.pop() {
41326×
            if ahead.insert(v) {
41421×
                stack.extend(
41521×
                    self.succ
41621×
                        .get(&v)
41721×
                        .map(|e| e.as_slice())
41821×
                        .unwrap_or(&[])
41921×
                        .iter()
42021×
                        .copied(),
421
                );
4225×
            }
423
        }
4243×
        if !ahead.contains(&to) {
4250×
            return vec![];
4263×
        }
4273×
        let mut pred: HashMap<Vertex, Vec<Vertex>> = HashMap::default();
42828×
        for (u, es) in &self.succ {
42928×
            if ahead.contains(u) {
43023×
                for w in es.iter() {
43123×
                    pred.entry(*w).or_default().push(*u);
43223×
                }
4337×
            }
434
        }
4353×
        let mut behind: HashSet<Vertex> = HashSet::default();
4363×
        let mut stack = vec![to];
43715×
        while let Some(v) = stack.pop() {
43812×
            if behind.insert(v) {
43910×
                stack.extend(pred.get(&v).into_iter().flatten().copied());
44010×
            }
441
        }
4423×
        let mut out: Vec<AtomKey> = ahead
4433×
            .intersection(&behind)
44410×
            .filter_map(|v| match v {
44510×
                Vertex::Atom(k) if self.atoms.contains_key(k) => Some(*k),
4460×
                _ => None,
44710×
            })
4483×
            .collect();
4493×
        out.sort();
4503×
        out
4513×
    }
452
453
    /// The changes that inserted or deleted any of `atoms`.
4543×
    pub fn touching(&self, atoms: &[AtomKey]) -> BTreeSet<ChangeHash> {
4553×
        let mut out = BTreeSet::new();
45610×
        for k in atoms {
45710×
            out.insert(k.0);
45810×
            if let Some(info) = self.atoms.get(k) {
45910×
                out.extend(info.deleted_by.iter().copied());
46010×
            }
461
        }
4623×
        out
4633×
    }
464
4651192×
    pub fn live_successor_changes(&self, k: &AtomKey) -> Vec<ChangeHash> {
4661192×
        self.succ
4671192×
            .get(&Vertex::Atom(*k))
4681192×
            .map(|ss| {
4691191×
                ss.iter()
4701191×
                    .filter_map(|s| match s {
471896×
                        Vertex::Atom(y) if self.is_live(y) => Some(y.0),
472406×
                        _ => None,
4731228×
                    })
4741191×
                    .collect()
4751191×
            })
4761192×
            .unwrap_or_default()
4771192×
    }
478
479359387×
    fn is_ghost(&self, k: &AtomKey, info: &AtomInfo, deps: &dyn DepOracle) -> bool {
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.
484359387×
        self.succ.get(&Vertex::Atom(*k)).is_some_and(|ss| {
485473386×
            ss.iter().any(|s| match s {
486473386×
                Vertex::Atom(y) if self.is_live(y) => !info
4874896×
                    .deleted_by
4884896×
                    .iter()
4894935×
                    .any(|d| deps.depends_on(d, &y.0) || deps.depends_on(&y.0, d)),
490498113×
                _ => false,
491503009×
            })
492359387×
        })
493359387×
    }
494
495
    /// Successors of `v` in the graph with non-kept vertices contracted away.
4969719×
    fn reduced_succ(
4979719×
        &self,
4989719×
        v: Vertex,
4999719×
        kept: &HashSet<Vertex>,
5009719×
        memo: &mut HashMap<Vertex, BTreeSet<Vertex>>,
5019719×
    ) -> BTreeSet<Vertex> {
5029719×
        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.
50510469×
        if direct.iter().all(|s| kept.contains(s)) {
5065820×
            return direct.iter().copied().collect();
5073899×
        }
5083899×
        let mut out = BTreeSet::new();
5093899×
        let mut stack: Vec<Vertex> = direct.to_vec();
5103899×
        let mut seen = HashSet::default();
511502625×
        while let Some(s) = stack.pop() {
512498726×
            if kept.contains(&s) {
51334043×
                out.insert(s);
514464683×
            } else if seen.insert(s) {
515319659×
                if let Some(m) = memo.get(&s) {
5160×
                    out.extend(m.iter().copied());
517319659×
                } else if let Some(next) = self.succ.get(&s) {
518319659×
                    stack.extend(next.iter().copied());
519319659×
                }
520145024×
            }
521
        }
5223899×
        out
5239719×
    }
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.
53025261×
    fn as_chain(&self) -> Option<Vec<AtomKey>> {
53125261×
        if self
53225261×
            .atoms
53325261×
            .values()
534116017×
            .any(|a| a.content.is_none() || !a.deleted_by.is_empty())
535
        {
5361952×
            return None;
53723309×
        }
53823309×
        let mut order = Vec::with_capacity(self.atoms.len());
53923309×
        let mut v = Vertex::Start;
540
        loop {
541133007×
            let succ = self.succ.get(&v)?.as_slice();
542157843×
            let mut atoms = succ.iter().filter(|s| **s != Vertex::End);
543133007×
            match (atoms.next(), atoms.next()) {
54423025×
                (None, _) if succ.contains(&Vertex::End) => break,
545109698×
                (Some(Vertex::Atom(k)), None) if order.len() < self.atoms.len() => {
546109698×
                    order.push(*k);
547109698×
                    v = Vertex::Atom(*k);
548109698×
                }
549284×
                _ => return None,
550
            }
551
        }
55223025×
        (order.len() == self.atoms.len()).then_some(order)
55325261×
    }
554
555
    /// Order the live atoms and locate conflicts.
55626549×
    pub fn materialize(&self, deps: &dyn DepOracle) -> Materialized {
55726549×
        let deps = &MemoDeps {
55826549×
            inner: deps,
55926549×
            known: Default::default(),
56026549×
        };
56126549×
        let lines = |order: Vec<AtomKey>| Materialized {
56225864×
            segments: if order.is_empty() {
563198×
                vec![]
564
            } else {
56525666×
                vec![Segment::Lines(order)]
566
            },
56725864×
        };
568
        use std::sync::atomic::Ordering::Relaxed;
569
        // Which way it went, and how long it took (`OXY_TIMINGS`).
57026549×
        let started = crate::timing::enabled().then(std::time::Instant::now);
57126549×
        let took = |name: &'static str| {
57226549×
            if let Some(t) = started {
5730×
                crate::timing::add(name, t.elapsed());
57426549×
            }
57526549×
        };
57626549×
        if let Some(o) = self.order.get() {
5771304×
            let holds = o.confirmed.load(Relaxed)
578616×
                || o.unchecked.iter().all(|k| {
579616×
                    self.atoms
580616×
                        .get(k)
581616×
                        .is_none_or(|info| !self.is_ghost(k, info, deps))
582616×
                });
5831304×
            if holds {
5841294×
                o.confirmed.store(true, Relaxed);
5851294×
                took("graph: kept order");
5861294×
                return lines(o.atoms.clone());
58710×
            }
58810×
            self.order.set(None);
58925245×
        }
590
        // A chain (nothing ever deleted) is quick to walk, and a fresh tree
591
        // is all chains: no order is kept for one.
59225255×
        if let Some(order) = self.as_chain() {
59323023×
            took("graph: chain");
59423023×
            return lines(order);
5952232×
        }
5962232×
        if let Some(order) = self.as_order(deps) {
5971547×
            self.order
5981547×
                .set(Some(std::sync::Arc::new(Order::known(order.clone()))));
5991547×
            took("graph: single order");
6001547×
            return lines(order);
601685×
        }
602685×
        let m = self.materialize_general(deps);
603685×
        took("graph: general");
604685×
        m
60526549×
    }
606
607
    /// Take the kept order, to [`Order::splice`] it once a change's edits
608
    /// have applied (they drop it).
60926880×
    pub fn take_order(&mut self) -> Option<std::sync::Arc<Order>> {
61026880×
        self.order.take()
61126880×
    }
612
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.
617519×
    pub fn keep_spliced(
618519×
        &mut self,
619519×
        order: std::sync::Arc<Order>,
620519×
        h: ChangeHash,
621519×
        inserts: &[(Vertex, Vertex, u32, u32)],
622519×
        deleted: &[AtomKey],
623519×
    ) {
624519×
        let live = inserts
625519×
            .iter()
626838×
            .all(|&(_, _, first, count)| (first..first + count).all(|i| self.is_live(&(h, i))));
627519×
        let spliced = if live {
628518×
            order.splice(h, inserts, deleted)
629
        } else {
6301×
            None
631
        };
632519×
        *self.order.0.get_mut().unwrap_or_else(|e| e.into_inner()) = spliced;
633519×
    }
634
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.
6412234×
    fn as_order(&self, deps: &dyn DepOracle) -> Option<Vec<AtomKey>> {
642
        // Kept vertices by dense index: Start, End, then the live atoms.
6432234×
        let mut index: HashMap<Vertex, u32> =
6442234×
            HashMap::with_capacity_and_hasher(self.atoms.len() + 2, Default::default());
6452234×
        let mut verts: Vec<Vertex> = Vec::with_capacity(self.atoms.len() + 2);
6462234×
        verts.extend([Vertex::Start, Vertex::End]);
6472234×
        index.insert(Vertex::Start, 0);
6482234×
        index.insert(Vertex::End, 1);
649100811×
        for (k, info) in &self.atoms {
650100811×
            if info.content.is_none() {
6510×
                continue;
652100811×
            }
653100811×
            if info.deleted_by.is_empty() {
65450065×
                index.insert(Vertex::Atom(*k), verts.len() as u32);
65550065×
                verts.push(Vertex::Atom(*k));
65650746×
            } else if self.is_ghost(k, info, deps) {
657488×
                return None;
65850258×
            }
659
        }
660
        // Each kept vertex's successors, contracting the others: what a
661
        // contracted vertex reaches is worked out once (post-order).
6621746×
        let mut reach: HashMap<Vertex, Vec<u32>> = HashMap::default();
66310099×
        let contracted = |v: Vertex, reach: &mut HashMap<Vertex, Vec<u32>>| {
66410099×
            let mut stack: Vec<(Vertex, bool)> = vec![(v, false)];
66585864×
            while let Some((x, done)) = stack.pop() {
66675765×
                if reach.contains_key(&x) {
6679561×
                    continue;
66866204×
                }
66966204×
                let next = self.succ.get(&x).map(Edges::as_slice).unwrap_or_default();
67066204×
                if !done {
67133102×
                    stack.push((x, true));
67244962×
                    for s in next {
67344962×
                        if !index.contains_key(s) && !reach.contains_key(s) {
67432564×
                            stack.push((*s, false));
67532564×
                        }
676
                    }
67733102×
                    continue;
67833102×
                }
67933102×
                let mut out: Vec<u32> = Vec::new();
68044962×
                for s in next {
68144962×
                    match index.get(s) {
6825175×
                        Some(i) => out.push(*i),
68339787×
                        None => out.extend(reach.get(s).into_iter().flatten()),
684
                    }
685
                }
68633102×
                out.sort_unstable();
68733102×
                out.dedup();
68833102×
                reach.insert(x, out);
689
            }
69010099×
        };
691
        // Successors of vertex `i`: `succ[starts[i]..starts[i + 1]]`.
6921746×
        let mut succ: Vec<u32> = Vec::with_capacity(verts.len() * 2);
6931746×
        let mut starts: Vec<usize> = Vec::with_capacity(verts.len() + 1);
6941746×
        let mut indeg: Vec<u32> = vec![0; verts.len()];
69552311×
        for v in &verts {
69652311×
            let from = succ.len();
69752311×
            starts.push(from);
69860847×
            for s in self.succ.get(v).map(Edges::as_slice).unwrap_or_default() {
69960847×
                match index.get(s) {
70050748×
                    Some(i) => succ.push(*i),
70110099×
                    None => {
70210099×
                        contracted(*s, &mut reach);
70310099×
                        succ.extend(reach.get(s).into_iter().flatten());
70410099×
                    }
705
                }
706
            }
70752311×
            if succ.len() - from > 1 {
7085135×
                succ[from..].sort_unstable();
7095135×
                let mut kept = from;
71015572×
                for i in from..succ.len() {
71115572×
                    if i == from || succ[i] != succ[kept - 1] {
71210719×
                        succ[kept] = succ[i];
71310719×
                        kept += 1;
71410719×
                    }
715
                }
7165135×
                succ.truncate(kept);
71747176×
            }
71856149×
            for &w in &succ[from..] {
71956149×
                indeg[w as usize] += 1;
72056149×
            }
721
        }
7221746×
        starts.push(succ.len());
723
        // Kahn's algorithm, giving up where two vertices are ready at once.
7241746×
        let mut ready: Vec<u32> = (0..verts.len() as u32)
72552311×
            .filter(|&i| indeg[i as usize] == 0)
7261746×
            .collect();
7271746×
        let mut order: Vec<AtomKey> = Vec::with_capacity(verts.len());
7281746×
        let mut seen = 0;
72952950×
        while let Some(v) = ready.pop() {
73051402×
            if !ready.is_empty() {
7310×
                return None;
73251402×
            }
73351402×
            seen += 1;
73451402×
            if let Vertex::Atom(k) = verts[v as usize] {
73548108×
                order.push(k);
73648108×
            }
73755309×
            for &w in &succ[starts[v as usize]..starts[v as usize + 1]] {
73855309×
                indeg[w as usize] -= 1;
73955309×
                if indeg[w as usize] == 0 {
74050052×
                    ready.push(w);
74150052×
                }
742
            }
74351402×
            if ready.len() > 1 {
744198×
                return None;
74551204×
            }
746
        }
7471548×
        (seen == verts.len()).then_some(order)
7482234×
    }
749
750
    /// [`FileGraph::materialize`] for any graph: contraction, then a
751
    /// topological order with conflict regions.
7521179×
    fn materialize_general(&self, deps: &dyn DepOracle) -> Materialized {
753
        // 1. Vertices that survive contraction: live atoms, ghosts, Start, End.
7541179×
        let mut kept: HashSet<Vertex> = HashSet::default();
7551179×
        let mut ghosts: HashSet<AtomKey> = HashSet::default();
7561179×
        kept.insert(Vertex::Start);
7571179×
        kept.insert(Vertex::End);
758314789×
        for (k, info) in &self.atoms {
759314789×
            if info.content.is_none() {
7600×
                continue;
761314789×
            }
762314789×
            if info.deleted_by.is_empty() {
7636764×
                kept.insert(Vertex::Atom(*k));
764308025×
            } else if self.is_ghost(k, info, deps) {
765597×
                kept.insert(Vertex::Atom(*k));
766597×
                ghosts.insert(*k);
767307428×
            }
768
        }
769
770
        // 2. Reduced graph over kept vertices.
7711179×
        let mut memo = HashMap::default();
7721179×
        let mut rsucc: HashMap<Vertex, BTreeSet<Vertex>> = HashMap::default();
7731179×
        let mut indeg: HashMap<Vertex, usize> = HashMap::default();
7749719×
        for &u in &kept {
7759719×
            indeg.entry(u).or_insert(0);
7769719×
            let rs = self.reduced_succ(u, &kept, &mut memo);
77714679×
            for &w in &rs {
77814679×
                *indeg.entry(w).or_insert(0) += 1;
77914679×
            }
7809719×
            rsucc.insert(u, rs);
781
        }
782
783
        // 3. Kahn's algorithm with side labelling for conflict regions.
7841179×
        let mut ready: BTreeSet<Vertex> = indeg
7851179×
            .iter()
7869719×
            .filter(|(_, &d)| d == 0)
7871179×
            .map(|(v, _)| *v)
7881179×
            .collect();
7891179×
        let mut side: HashMap<Vertex, u32> = HashMap::default();
7901179×
        let mut joins: HashSet<Vertex> = HashSet::default();
7911179×
        let mut next_side = 0u32;
7921179×
        let mut plain: Vec<Vertex> = Vec::new();
7931179×
        let mut conflict: Option<BTreeMap<u32, Vec<Vertex>>> = None;
7941179×
        let mut segments: Vec<Segment> = Vec::new();
795
7961587×
        let flush_plain = |plain: &mut Vec<Vertex>, segments: &mut Vec<Segment>| {
7971587×
            let atoms: Vec<AtomKey> = plain
7981587×
                .iter()
7998674×
                .filter_map(|v| {
8008674×
                    if let Vertex::Atom(k) = v {
8016316×
                        Some(*k)
802
                    } else {
8032358×
                        None
804
                    }
8058674×
                })
8061587×
                .collect();
8071587×
            plain.clear();
8081587×
            if !atoms.is_empty() {
8091323×
                segments.extend(split_zombies(atoms, &ghosts));
8101323×
            }
8111587×
        };
8121179×
        let finish_conflict = |c: BTreeMap<u32, Vec<Vertex>>, segments: &mut Vec<Segment>| {
813408×
            let sides: Vec<Vec<AtomKey>> = c
814408×
                .into_values()
815816×
                .map(|vs| {
816816×
                    vs.into_iter()
8171045×
                        .filter_map(|v| match v {
8181045×
                            Vertex::Atom(k) if !ghosts.contains(&k) => Some(k),
81914×
                            _ => None,
8201045×
                        })
821816×
                        .collect::<Vec<_>>()
822816×
                })
823816×
                .filter(|s| !s.is_empty())
824408×
                .collect();
825408×
            match sides.len() {
8260×
                0 => {}
8270×
                1 => segments.push(Segment::Lines(sides.into_iter().next().unwrap())),
828408×
                _ => segments.push(Segment::Conflict(sides)),
829
            }
830408×
        };
831
83210898×
        while !ready.is_empty() {
8339719×
            if ready.len() >= 2 {
834480×
                if conflict.is_none() {
835408×
                    flush_plain(&mut plain, &mut segments);
836408×
                    conflict = Some(BTreeMap::new());
837408×
                }
838960×
                for r in &ready {
839960×
                    if !side.contains_key(r) {
840816×
                        side.insert(*r, next_side);
841816×
                        next_side += 1;
842816×
                    }
843
                }
8449239×
            }
8459719×
            let v = ready.pop_first().unwrap();
8469719×
            let my_side = side.get(&v).copied();
8479719×
            match (&mut conflict, my_side) {
8481045×
                (Some(c), Some(s)) => c.entry(s).or_default().push(v),
849408×
                (Some(_), None) => {
850408×
                    let c = conflict.take().unwrap();
851408×
                    finish_conflict(c, &mut segments);
852408×
                    plain.push(v);
853408×
                }
8548266×
                (None, _) => plain.push(v),
855
            }
85614679×
            for &w in &rsucc[&v] {
85714679×
                let d = indeg.get_mut(&w).unwrap();
85814679×
                *d -= 1;
85914679×
                if conflict.is_some() && !joins.contains(&w) {
8601138×
                    match (my_side, side.get(&w).copied()) {
861677×
                        (Some(s), None) => {
862677×
                            side.insert(w, s);
863677×
                        }
864461×
                        (Some(s), Some(t)) if s != t => {
865448×
                            side.remove(&w);
866448×
                            joins.insert(w);
867448×
                        }
8680×
                        (None, Some(_)) => {
8690×
                            side.remove(&w);
8700×
                            joins.insert(w);
8710×
                        }
87213×
                        _ => {}
873
                    }
87413541×
                }
87514679×
                if *d == 0 {
8768540×
                    ready.insert(w);
8778540×
                }
878
            }
879
        }
8801179×
        if let Some(c) = conflict.take() {
8810×
            finish_conflict(c, &mut segments);
8821179×
        }
8831179×
        flush_plain(&mut plain, &mut segments);
8841179×
        Materialized { segments }
8851179×
    }
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.
88925165×
    pub fn render(&self, m: &Materialized, label: &dyn Fn(&ChangeHash) -> String) -> Vec<u8> {
89025165×
        let total = m.conflict_count();
89125165×
        let mut n = 0;
89225165×
        let mut out: Vec<u8> = Vec::new();
89326686×
        let push_atoms = |out: &mut Vec<u8>, atoms: &[AtomKey]| {
894164428×
            for a in atoms {
895164428×
                if let Some(c) = self.content(a) {
896164428×
                    out.extend_from_slice(c);
897164428×
                }
898
            }
89926686×
        };
90025165×
        let newline_if_needed = |out: &mut Vec<u8>| {
9012349×
            if !out.is_empty() && *out.last().unwrap() != b'\n' {
9020×
                out.push(b'\n');
9032349×
            }
9042349×
        };
90525903×
        for seg in &m.segments {
90625903×
            match seg {
90725120×
                Segment::Lines(a) => push_atoms(&mut out, a),
908318×
                Segment::Conflict(sides) => {
909318×
                    n += 1;
910318×
                    let k = sides.len();
911636×
                    for (i, s) in sides.iter().enumerate() {
912636×
                        newline_if_needed(&mut out);
913636×
                        let who = s.first().map(|a| label(&a.0)).unwrap_or_default();
914636×
                        let marker = if i == 0 { "<<<<<<<" } else { "=======" };
915636×
                        out.extend_from_slice(
916636×
                            format!("{marker} oxy conflict {n} of {total}, side {} of {k}: change {who}\n", i + 1).as_bytes(),
917
                        );
918636×
                        push_atoms(&mut out, s);
919
                    }
920318×
                    newline_if_needed(&mut out);
921318×
                    out.extend_from_slice(
922318×
                        format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(),
923
                    );
924
                }
925465×
                Segment::Zombie { deleted, inserted } => {
926465×
                    n += 1;
927465×
                    newline_if_needed(&mut out);
928465×
                    let deleter = deleted
929465×
                        .first()
930465×
                        .and_then(|a| self.deleters(a).next().copied())
931465×
                        .map(|d| label(&d))
932465×
                        .unwrap_or_default();
933465×
                    let inserter = inserted.first().map(|a| label(&a.0)).unwrap_or_default();
934465×
                    out.extend_from_slice(
935465×
                        format!("<<<<<<< oxy conflict {n} of {total}, side 1 of 2: deleted by change {deleter}\n-------\n").as_bytes(),
936
                    );
937465×
                    push_atoms(&mut out, deleted);
938465×
                    newline_if_needed(&mut out);
939465×
                    out.extend_from_slice(
940465×
                        format!(
941465×
                            "======= oxy conflict {n} of {total}, side 2 of 2: change {inserter}\n"
942465×
                        )
943465×
                        .as_bytes(),
944
                    );
945465×
                    push_atoms(&mut out, inserted);
946465×
                    newline_if_needed(&mut out);
947465×
                    out.extend_from_slice(
948465×
                        format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(),
949
                    );
950
                }
951
            }
952
        }
95325165×
        out
95425165×
    }
955
}
956
95735228×
fn vertex_value(v: &Vertex) -> crate::cbor::Value {
958
    use crate::cbor::Value;
95935228×
    match v {
9603309×
        Vertex::Start => Value::Uint(0),
9613289×
        Vertex::End => Value::Uint(1),
96228630×
        Vertex::Atom((c, i)) => {
96328630×
            Value::Array(vec![Value::Bytes(c.0.to_vec()), Value::Uint(*i as u64)])
964
        }
965
    }
96635228×
}
967
96810220×
fn vertex_from(v: &crate::cbor::Value) -> Result<Vertex, crate::objects::ObjectError> {
969
    use crate::cbor::Value;
970
    use crate::objects::ObjectError;
9718667×
    match v {
972677×
        Value::Uint(0) => Ok(Vertex::Start),
973876×
        Value::Uint(1) => Ok(Vertex::End),
9748667×
        Value::Array(a) if a.len() == 2 => Ok(Vertex::Atom((
9758667×
            crate::objects::oref(&a[0], "vertex")?,
9768667×
            crate::objects::u32_(&a[1], "vertex")?,
977
        ))),
9780×
        _ => Err(ObjectError::Schema(
9790×
            "vertex: expected 0, 1, or [hash, index]".into(),
9800×
        )),
981
    }
98210220×
}
983
984
impl FileGraph {
985
    /// Serialize for a checkpoint: `{0: atoms [[hash, index, content|null, [deleters]]], 1: edges [[from, to]]}`.
9861453×
    pub fn to_value(&self) -> crate::cbor::Value {
987
        use crate::cbor::Value;
9881453×
        let mut atoms: Vec<(&AtomKey, &AtomInfo)> = self.atoms.iter().collect();
9891453×
        atoms.sort_by_key(|(k, _)| **k);
9901453×
        let mut edges: Vec<(Vertex, Vertex)> = self
9911453×
            .succ
9921453×
            .iter()
99317614×
            .flat_map(|(a, bs)| bs.iter().map(move |b| (*a, *b)))
9941453×
            .collect();
9951453×
        edges.sort();
9961453×
        Value::map([
997
            (
998
                0,
999
                Value::Array(
10001453×
                    atoms
10011453×
                        .into_iter()
100212902×
                        .map(|((c, i), info)| {
100312902×
                            Value::Array(vec![
100412902×
                                Value::Bytes(c.0.to_vec()),
100512902×
                                Value::Uint(*i as u64),
100612902×
                                info.content
100712902×
                                    .as_ref()
100812902×
                                    .map(|b| Value::Bytes(b.bytes().to_vec()))
100912902×
                                    .unwrap_or(Value::Null),
1010
                                Value::Array(
101112902×
                                    info.deleted_by
101212902×
                                        .iter()
101312902×
                                        .map(|d| Value::Bytes(d.0.to_vec()))
101412902×
                                        .collect(),
1015
                                ),
1016
                            ])
101712902×
                        })
10181453×
                        .collect(),
1019
                ),
1020
            ),
1021
            (
1022
                1,
1023
                Value::Array(
10241453×
                    edges
10251453×
                        .iter()
102617614×
                        .map(|(a, b)| Value::Array(vec![vertex_value(a), vertex_value(b)]))
10271453×
                        .collect(),
1028
                ),
1029
            ),
1030
        ])
10311453×
    }
1032
1033319×
    pub fn from_value(v: &crate::cbor::Value) -> Result<FileGraph, crate::objects::ObjectError> {
1034
        use crate::objects::{array, bytes, field, map, oref, u32_, ObjectError};
1035319×
        let m = map(v, "file graph")?;
1036319×
        let mut g = FileGraph {
1037319×
            atoms: HashMap::default(),
1038319×
            succ: HashMap::default(),
1039319×
            order: OrderSlot::default(),
1040319×
        };
1041319×
        let atoms = array(field(m, 0, "atoms")?, "atoms")?;
1042
        // Every atom's line in one buffer.
1043319×
        let mut texts: Vec<&[u8]> = Vec::new();
10444100×
        for a in atoms {
10454100×
            let e = array(a, "atom")?;
10464100×
            if e.len() == 4 && !e[2].is_null() {
10474100×
                texts.push(bytes(&e[2], "atom content")?);
10480×
            }
1049
        }
1050319×
        let all: Lines = texts.into_iter().collect();
1051319×
        let mut next = 0u32;
10524100×
        for a in atoms {
10534100×
            let e = array(a, "atom")?;
10544100×
            if e.len() != 4 {
10550×
                return Err(ObjectError::Schema("atom: expected 4 fields".into()));
10564100×
            }
10574100×
            let key = (oref(&e[0], "atom change")?, u32_(&e[1], "atom index")?);
10584100×
            let content = if e[2].is_null() {
10590×
                None
1060
            } else {
10614100×
                next += 1;
10624100×
                Some(LineRef::new(all.clone(), next - 1))
1063
            };
10644100×
            let deleted_by = array(&e[3], "deleters")?
10654100×
                .iter()
10664100×
                .map(|d| oref(d, "deleter"))
10674100×
                .collect::<Result<BTreeSet<_>, _>>()?;
10684100×
            g.atoms.insert(
10694100×
                key,
10704100×
                AtomInfo {
10714100×
                    content,
10724100×
                    deleted_by,
10734100×
                },
1074
            );
1075
        }
10765110×
        for e in array(field(m, 1, "edges")?, "edges")? {
10775110×
            let pair = array(e, "edge")?;
10785110×
            if pair.len() != 2 {
10790×
                return Err(ObjectError::Schema("edge: expected [from, to]".into()));
10805110×
            }
10815110×
            g.edge(vertex_from(&pair[0])?, vertex_from(&pair[1])?);
1082
        }
1083319×
        Ok(g)
1084319×
    }
1085
}
1086
1087
/// Split a plain run into `Lines` and `Zombie` segments around ghost atoms.
10881323×
fn split_zombies(atoms: Vec<AtomKey>, ghosts: &HashSet<AtomKey>) -> Vec<Segment> {
10891323×
    let mut out = Vec::new();
10901323×
    let mut lines: Vec<AtomKey> = Vec::new();
10911323×
    let mut i = 0;
10926990×
    while i < atoms.len() {
10935667×
        if !ghosts.contains(&atoms[i]) {
10945084×
            lines.push(atoms[i]);
10955084×
            i += 1;
10965084×
            continue;
1097583×
        }
1098583×
        if !lines.is_empty() {
1099323×
            out.push(Segment::Lines(std::mem::take(&mut lines)));
1100323×
        }
1101583×
        let mut deleted = Vec::new();
11021166×
        while i < atoms.len() && ghosts.contains(&atoms[i]) {
1103583×
            deleted.push(atoms[i]);
1104583×
            i += 1;
1105583×
        }
1106583×
        let mut inserted = Vec::new();
1107583×
        if i < atoms.len() {
1108544×
            let ch = atoms[i].0;
11091193×
            while i < atoms.len() && atoms[i].0 == ch && !ghosts.contains(&atoms[i]) {
1110649×
                inserted.push(atoms[i]);
1111649×
                i += 1;
1112649×
            }
111339×
        }
1114583×
        out.push(Segment::Zombie { deleted, inserted });
1115
    }
11161323×
    if !lines.is_empty() {
1117989×
        out.push(Segment::Lines(lines));
1118989×
    }
11191323×
    out
11201323×
}
1121
1122
#[cfg(test)]
1123
mod tests {
1124
    use super::*;
1125
    use crate::hash::ObjectRef;
1126
1127156×
    fn h(n: u8) -> ChangeHash {
1128156×
        ObjectRef([n; 32])
1129156×
    }
1130792×
    fn lines(v: &[&str]) -> Lines {
11311741×
        v.iter().map(|s| s.as_bytes()).collect()
1132792×
    }
113366×
    fn text(g: &FileGraph, m: &Materialized) -> String {
1134208×
        String::from_utf8(g.render(m, &|c| c.short(4))).unwrap()
113566×
    }
1136
1137
    struct Deps(HashMap<ChangeHash, Vec<ChangeHash>>);
1138
    impl DepOracle for Deps {
11391705×
        fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
11401705×
            if a == b {
11410×
                return true;
11421705×
            }
11431705×
            let mut stack = vec![*a];
11441705×
            let mut seen = HashSet::default();
114550101×
            while let Some(x) = stack.pop() {
114649787×
                if let Some(ds) = self.0.get(&x) {
11477887494×
                    for d in ds {
11487887494×
                        if d == b {
11491391×
                            return true;
11507886103×
                        }
11517886103×
                        if seen.insert(*d) {
1152362101×
                            stack.push(*d);
11537524002×
                        }
1154
                    }
1155314×
                }
1156
            }
1157314×
            false
11581705×
        }
1159
    }
1160
1161
    /// Linear history: each change depends on every earlier one.
1162
    struct Linear;
1163
    impl DepOracle for Linear {
116449×
        fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
116549×
            a.0[..2] >= b.0[..2]
116649×
        }
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.
11711×
    fn edited_file(n: usize, revs: usize, per: usize) -> FileGraph {
1172401×
        let hn = |i: usize| {
1173401×
            let mut b = [0u8; 32];
1174401×
            b[..2].copy_from_slice(&(i as u16).to_be_bytes());
1175401×
            ObjectRef(b)
1176401×
        };
11771×
        let mut g = FileGraph::new();
1178500×
        let first: Vec<String> = (0..n).map(|i| format!("line {i}\n")).collect();
11791×
        let first: Vec<&str> = first.iter().map(String::as_str).collect();
11801×
        g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first));
118110×
        for r in 2..revs + 2 {
118210×
            let order = g.materialize(&Linear).live_atoms();
118310×
            let step = order.len() / per;
1184200×
            for j in 0..per {
1185200×
                let at = j * step + (r % step.max(1));
1186200×
                let k = order[at];
1187200×
                g.delete(
1188200×
                    hn(r),
1189200×
                    &AtomRange {
1190200×
                        change: k.0,
1191200×
                        from: k.1,
1192200×
                        to: k.1 + 1,
1193200×
                    },
1194
                );
1195200×
                let after = if at == 0 {
11960×
                    Vertex::Start
1197
                } else {
1198200×
                    Vertex::Atom(order[at - 1])
1199
                };
1200200×
                let before = order
1201200×
                    .get(at + 1)
1202200×
                    .map(|k| Vertex::Atom(*k))
1203200×
                    .unwrap_or(Vertex::End);
1204200×
                g.insert(hn(r), j as u32, after, before, &lines(&["new\n"]));
1205
            }
1206
        }
12071×
        g
12081×
    }
1209
1210
    /// The single-order shortcut gives what the general algorithm gives.
1211
    #[test]
12121×
    fn the_order_shortcut_agrees_with_the_general_order() {
12131×
        let g = edited_file(500, 10, 20);
12141×
        assert!(g.as_chain().is_none());
12151×
        let fast = g.as_order(&Linear).expect("a single order");
12161×
        assert_eq!(
12171×
            Materialized {
12181×
                segments: vec![Segment::Lines(fast)]
12191×
            },
12201×
            g.materialize_general(&Linear)
1221
        );
1222
        // Two changes inserting at one place concurrently: a conflict,
1223
        // which the shortcut leaves to the general algorithm.
12241×
        let mut g = FileGraph::new();
12251×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
12261×
        let a = Vertex::Atom((h(1), 0));
12271×
        g.insert(h(2), 0, a, Vertex::End, &lines(&["b\n"]));
12281×
        g.insert(h(3), 0, a, Vertex::End, &lines(&["c\n"]));
12291×
        assert!(g.as_order(&NoDeps).is_none());
12301×
        assert!(g.materialize(&NoDeps).is_conflicted());
12311×
    }
1232
1233
    /// Applied as the state applies a change's edits: the kept order is
1234
    /// spliced by them.
1235491×
    fn apply_spliced(
1236491×
        g: &mut FileGraph,
1237491×
        h: ChangeHash,
1238491×
        inserts: &[(Vertex, Vertex, usize)],
1239491×
        deleted: &[AtomKey],
1240491×
    ) {
1241491×
        let order = g.take_order();
1242491×
        let mut runs = Vec::new();
1243491×
        let mut next = 0u32;
1244554×
        for (i, &(a, b, n)) in inserts.iter().enumerate() {
1245947×
            let text: Vec<String> = (0..n).map(|j| format!("{i}.{j}\n")).collect();
1246554×
            let text: Vec<&str> = text.iter().map(String::as_str).collect();
1247554×
            g.insert(h, next, a, b, &lines(&text));
1248554×
            runs.push((a, b, next, n as u32));
1249554×
            next += n as u32;
1250
        }
12511003×
        for k in deleted {
12521003×
            g.delete(
12531003×
                h,
12541003×
                &AtomRange {
12551003×
                    change: k.0,
12561003×
                    from: k.1,
12571003×
                    to: k.1 + 1,
12581003×
                },
12591003×
            );
12601003×
        }
1261491×
        if let Some(o) = order {
1262458×
            g.keep_spliced(o, h, &runs, deleted);
1263458×
        }
1264491×
    }
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.
1268457×
    fn random_edits(
1269457×
        l: &[AtomKey],
1270457×
        rnd: &mut impl FnMut(usize) -> usize,
1271457×
    ) -> (Vec<(Vertex, Vertex, usize)>, Vec<AtomKey>) {
1272457×
        let (mut inserts, mut deleted) = (Vec::new(), Vec::new());
1273457×
        let mut i = rnd(3);
12741247×
        while i <= l.len() {
1275790×
            let del = rnd(3).min(l.len() - i);
1276790×
            let add = rnd(3);
1277790×
            let after = if i == 0 {
1278169×
                Vertex::Start
1279
            } else {
1280621×
                Vertex::Atom(l[i - 1])
1281
            };
1282790×
            let before = l.get(i).map(|k| Vertex::Atom(*k)).unwrap_or(Vertex::End);
1283790×
            deleted.extend(&l[i..i + del]);
1284790×
            if add > 0 {
1285522×
                inserts.push((after, before, add));
1286522×
            }
1287790×
            i += del + 1 + rnd(6);
1288
        }
1289457×
        (inserts, deleted)
1290457×
    }
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]
12961×
    fn the_kept_order_follows_edits() {
1297490×
        let hn = |i: usize| {
1298490×
            let mut b = [0u8; 32];
1299490×
            b[..2].copy_from_slice(&(i as u16).to_be_bytes());
1300490×
            ObjectRef(b)
1301490×
        };
13021×
        let mut seed = 7u64;
13032827×
        let mut rnd = move |n: usize| {
13042827×
            seed = seed
13052827×
                .wrapping_mul(6364136223846793005)
13062827×
                .wrapping_add(1442695040888963407);
13072827×
            ((seed >> 33) as usize) % n.max(1)
13082827×
        };
13091×
        let mut deps = Deps(HashMap::default());
13101×
        let mut g = FileGraph::new();
131140×
        let first: Vec<String> = (0..40).map(|i| format!("{i}\n")).collect();
13121×
        let first: Vec<&str> = first.iter().map(String::as_str).collect();
13131×
        g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first));
13141×
        let mut known: Vec<ChangeHash> = vec![hn(1)];
13151×
        let mut c = 2;
13161×
        let (mut spliced, mut conflicted) = (0, 0);
1317400×
        for step in 0..400 {
1318400×
            let l = g.materialize(&deps).live_atoms();
1319
            // Now and then two changes from the same base, neither knowing
1320
            // the other.
1321400×
            let pair = step % 7 == 3;
1322400×
            let base = known.clone();
1323400×
            for _ in 0..if pair { 2 } else { 1 } {
1324457×
                let h = hn(c);
1325457×
                c += 1;
1326457×
                let (inserts, deleted) = random_edits(&l, &mut rnd);
1327457×
                deps.0.insert(h, base.clone());
1328457×
                apply_spliced(&mut g, h, &inserts, &deleted);
1329457×
                known.push(h);
1330457×
                if g.order.get().is_some() {
1331433×
                    spliced += 1;
1332433×
                }
1333457×
                let m = g.materialize(&deps);
1334457×
                assert_eq!(m, g.materialize_general(&deps), "step {step}");
1335457×
                if m.is_conflicted() {
133631×
                    conflicted += 1;
1337426×
                }
1338
            }
1339
            // A change that knows everything settles any conflict by
1340
            // rewriting the file.
1341400×
            if g.materialize(&deps).is_conflicted() {
134231×
                let h = hn(c);
134331×
                c += 1;
134431×
                let all = g.materialize(&deps).live_atoms();
134531×
                deps.0.insert(h, known.clone());
134631×
                apply_spliced(&mut g, h, &[(Vertex::Start, Vertex::End, 5)], &all);
134731×
                known.push(h);
134831×
                assert_eq!(g.materialize(&deps), g.materialize_general(&deps));
1349369×
            }
1350
        }
13511×
        assert!(
13521×
            spliced > 100 && conflicted > 0,
1353
            "{spliced} spliced, {conflicted} conflicted"
1354
        );
13551×
    }
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]
13611×
    fn the_kept_order_takes_no_line_that_arrives_deleted() {
13621×
        let mut deps = Deps(HashMap::default());
13631×
        let mut g = FileGraph::new();
13641×
        g.insert(
13651×
            h(1),
1366
            0,
13671×
            Vertex::Start,
13681×
            Vertex::End,
13691×
            &lines(&["a\n", "b\n", "c\n"]),
1370
        );
1371
        // A deletion, so the order is kept rather than walked as a chain.
13721×
        apply_spliced(&mut g, h(2), &[], &[(h(1), 2)]);
13731×
        deps.0.insert(h(2), vec![h(1)]);
13741×
        let _ = g.materialize(&deps);
13751×
        assert!(g.order.get().is_some());
13761×
        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.
13781×
        deps.0.insert(h(3), vec![h(2)]);
13791×
        deps.0.insert(h(4), vec![h(3)]);
13801×
        apply_spliced(&mut g, h(4), &[], &[(h(3), 0)]);
13811×
        apply_spliced(&mut g, h(3), &[(a, b, 1)], &[]);
13821×
        let m = g.materialize(&deps);
13831×
        assert_eq!(m, g.materialize_general(&deps));
13841×
        assert_eq!(text(&g, &m), "a\nb\n");
13851×
    }
1386
1387
    /// How long rendering a long-lived file takes (run by hand, in release).
1388
    #[test]
1389
    #[ignore]
13900×
    fn bench_materialize_edited_file() {
13910×
        let g = edited_file(20_000, 50, 100);
13920×
        let t = std::time::Instant::now();
13930×
        let mut n = 0;
13940×
        for _ in 0..20 {
13950×
            n += g.materialize(&Linear).live_atoms().len();
13960×
        }
13970×
        eprintln!(
1398
            "materialize: {:.2} ms each ({n} atoms)",
13990×
            t.elapsed().as_secs_f64() * 1000.0 / 20.0
1400
        );
14010×
    }
1402
1403
    /// The chain shortcut agrees with the general algorithm wherever it
1404
    /// applies, and stands aside wherever it does not.
1405
    #[test]
14061×
    fn the_chain_shortcut_agrees_with_the_general_order() {
14071×
        let mut g = FileGraph::new();
14081×
        g.insert(
14091×
            h(1),
1410
            0,
14111×
            Vertex::Start,
14121×
            Vertex::End,
14131×
            &lines(&["a\n", "b\n", "c\n"]),
1414
        );
14151×
        assert!(g.as_chain().is_some());
14161×
        assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps));
1417
        // Appended after the last line: still a chain.
14181×
        g.insert(
14191×
            h(2),
1420
            0,
14211×
            Vertex::Atom((h(1), 2)),
14221×
            Vertex::End,
14231×
            &lines(&["d\n"]),
1424
        );
14251×
        assert!(g.as_chain().is_some());
14261×
        assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps));
1427
        // Inserted between two lines: not a chain (a has two successors).
14281×
        g.insert(
14291×
            h(4),
1430
            0,
14311×
            Vertex::Atom((h(1), 0)),
14321×
            Vertex::Atom((h(1), 1)),
14331×
            &lines(&["x\n"]),
1434
        );
14351×
        assert!(g.as_chain().is_none());
14361×
        assert_eq!(text(&g, &g.materialize(&NoDeps)), "a\nx\nb\nc\nd\n");
1437
        // Two inserts at one anchor: a conflict, never a chain.
14381×
        let mut c = FileGraph::new();
14391×
        c.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
14401×
        c.insert(h(2), 0, Vertex::Start, Vertex::End, &lines(&["b\n"]));
14411×
        assert!(c.as_chain().is_none());
14421×
        assert!(c.materialize(&NoDeps).is_conflicted());
1443
        // A deletion: not a chain.
14441×
        let mut d = FileGraph::new();
14451×
        d.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
14461×
        d.delete(
14471×
            h(3),
14481×
            &AtomRange {
14491×
                change: h(1),
14501×
                from: 0,
14511×
                to: 1,
14521×
            },
1453
        );
14541×
        assert!(d.as_chain().is_none());
14551×
        assert_eq!(d.materialize(&NoDeps), d.materialize_general(&NoDeps));
1456
        // Empty.
14571×
        let e = FileGraph::new();
14581×
        assert_eq!(e.materialize(&NoDeps), e.materialize_general(&NoDeps));
14591×
    }
1460
1461
    #[test]
14621×
    fn sequential_inserts_and_deletes() {
14631×
        let mut g = FileGraph::new();
14641×
        g.insert(
14651×
            h(1),
1466
            0,
14671×
            Vertex::Start,
14681×
            Vertex::End,
14691×
            &lines(&["a\n", "b\n", "c\n"]),
1470
        );
14711×
        let m = g.materialize(&NoDeps);
14721×
        assert!(!m.is_conflicted());
14731×
        assert_eq!(text(&g, &m), "a\nb\nc\n");
1474
1475
        // Change 2 depends on 1, inserts between b and c and deletes a.
14761×
        g.insert(
14771×
            h(2),
1478
            0,
14791×
            Vertex::Atom((h(1), 1)),
14801×
            Vertex::Atom((h(1), 2)),
14811×
            &lines(&["x\n"]),
1482
        );
14831×
        g.delete(
14841×
            h(2),
14851×
            &AtomRange {
14861×
                change: h(1),
14871×
                from: 0,
14881×
                to: 1,
14891×
            },
1490
        );
14911×
        let deps = Deps([(h(2), vec![h(1)])].into_iter().collect());
14921×
        let m = g.materialize(&deps);
14931×
        assert!(!m.is_conflicted());
14941×
        assert_eq!(text(&g, &m), "b\nx\nc\n");
14951×
        assert_eq!(m.live_atoms(), vec![(h(1), 1), (h(2), 0), (h(1), 2)]);
14961×
    }
1497
1498
    #[test]
14991×
    fn concurrent_inserts_at_same_anchor_conflict() {
15001×
        let mut g = FileGraph::new();
15011×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
15021×
        g.insert(
15031×
            h(2),
1504
            0,
15051×
            Vertex::Atom((h(1), 0)),
15061×
            Vertex::Atom((h(1), 1)),
15071×
            &lines(&["from-2\n"]),
1508
        );
15091×
        g.insert(
15101×
            h(3),
1511
            0,
15121×
            Vertex::Atom((h(1), 0)),
15131×
            Vertex::Atom((h(1), 1)),
15141×
            &lines(&["from-3\n", "more-3\n"]),
1515
        );
15161×
        let deps = Deps(
15171×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
15181×
                .into_iter()
15191×
                .collect(),
15201×
        );
15211×
        let m = g.materialize(&deps);
15221×
        assert!(m.is_conflicted());
15231×
        assert_eq!(m.conflict_count(), 1);
15241×
        assert_eq!(
1525
            m.segments,
15261×
            vec![
15271×
                Segment::Lines(vec![(h(1), 0)]),
15281×
                Segment::Conflict(vec![vec![(h(2), 0)], vec![(h(3), 0), (h(3), 1)]]),
15291×
                Segment::Lines(vec![(h(1), 1)]),
1530
            ]
1531
        );
15321×
        let t = text(&g, &m);
15331×
        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}");
15341×
    }
1535
1536
    #[test]
15371×
    fn concurrent_inserts_at_different_anchors_do_not_conflict() {
15381×
        let mut g = FileGraph::new();
15391×
        g.insert(
15401×
            h(1),
1541
            0,
15421×
            Vertex::Start,
15431×
            Vertex::End,
15441×
            &lines(&["a\n", "b\n", "c\n"]),
1545
        );
15461×
        g.insert(
15471×
            h(2),
1548
            0,
15491×
            Vertex::Start,
15501×
            Vertex::Atom((h(1), 0)),
15511×
            &lines(&["top\n"]),
1552
        );
15531×
        g.insert(
15541×
            h(3),
1555
            0,
15561×
            Vertex::Atom((h(1), 2)),
15571×
            Vertex::End,
15581×
            &lines(&["bottom\n"]),
1559
        );
15601×
        let deps = Deps(
15611×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
15621×
                .into_iter()
15631×
                .collect(),
15641×
        );
15651×
        let m = g.materialize(&deps);
15661×
        assert!(!m.is_conflicted());
15671×
        assert_eq!(text(&g, &m), "top\na\nb\nc\nbottom\n");
15681×
    }
1569
1570
    #[test]
15711×
    fn concurrent_deletes_of_the_same_line_agree() {
15721×
        let mut g = FileGraph::new();
15731×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
15741×
        g.delete(
15751×
            h(2),
15761×
            &AtomRange {
15771×
                change: h(1),
15781×
                from: 0,
15791×
                to: 1,
15801×
            },
1581
        );
15821×
        g.delete(
15831×
            h(3),
15841×
            &AtomRange {
15851×
                change: h(1),
15861×
                from: 0,
15871×
                to: 1,
15881×
            },
1589
        );
15901×
        let deps = Deps(
15911×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
15921×
                .into_iter()
15931×
                .collect(),
15941×
        );
15951×
        let m = g.materialize(&deps);
15961×
        assert!(!m.is_conflicted());
15971×
        assert_eq!(text(&g, &m), "b\n");
15981×
    }
1599
1600
    #[test]
16011×
    fn delete_insert_conflict_is_a_zombie() {
16021×
        let mut g = FileGraph::new();
16031×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
1604
        // 2 deletes a; 3 (concurrent) inserts after a.
16051×
        g.delete(
16061×
            h(2),
16071×
            &AtomRange {
16081×
                change: h(1),
16091×
                from: 0,
16101×
                to: 1,
16111×
            },
1612
        );
16131×
        g.insert(
16141×
            h(3),
1615
            0,
16161×
            Vertex::Atom((h(1), 0)),
16171×
            Vertex::Atom((h(1), 1)),
16181×
            &lines(&["after-a\n"]),
1619
        );
16201×
        let deps = Deps(
16211×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
16221×
                .into_iter()
16231×
                .collect(),
16241×
        );
16251×
        let m = g.materialize(&deps);
16261×
        assert!(m.is_conflicted());
16271×
        assert_eq!(
1628
            m.segments,
16291×
            vec![
16301×
                Segment::Zombie {
16311×
                    deleted: vec![(h(1), 0)],
16321×
                    inserted: vec![(h(3), 0)]
16331×
                },
16341×
                Segment::Lines(vec![(h(1), 1)]),
1635
            ]
1636
        );
16371×
        let t = text(&g, &m);
16381×
        assert!(t.contains("-------\na\n======="), "{t}");
16391×
        assert!(t.contains("after-a\n>>>>>>>"), "{t}");
1640
1641
        // If the deleter saw the insert (depends on 3), there is no conflict.
16421×
        let deps = Deps(
16431×
            [(h(2), vec![h(1), h(3)]), (h(3), vec![h(1)])]
16441×
                .into_iter()
16451×
                .collect(),
16461×
        );
16471×
        let m = g.materialize(&deps);
16481×
        assert!(!m.is_conflicted());
16491×
        assert_eq!(text(&g, &m), "after-a\nb\n");
16501×
    }
1651
1652
    #[test]
16531×
    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.
16561×
        let mut g = FileGraph::new();
16571×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
16581×
        g.delete(
16591×
            h(2),
16601×
            &AtomRange {
16611×
                change: h(1),
16621×
                from: 1,
16631×
                to: 2,
16641×
            },
1665
        );
16661×
        g.insert(
16671×
            h(3),
1668
            0,
16691×
            Vertex::Atom((h(1), 1)),
16701×
            Vertex::End,
16711×
            &lines(&["c\n"]),
1672
        );
16731×
        let deps = Deps(
16741×
            [(h(2), vec![h(1)]), (h(3), vec![h(2)])]
16751×
                .into_iter()
16761×
                .collect(),
16771×
        );
16781×
        let m = g.materialize(&deps);
16791×
        assert!(!m.is_conflicted(), "{:?}", m.segments);
16801×
        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.
16831×
        let mut g = FileGraph::new();
16841×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["x\n"]));
16851×
        g.delete(
16861×
            h(1),
16871×
            &AtomRange {
16881×
                change: h(1),
16891×
                from: 0,
16901×
                to: 1,
16911×
            },
1692
        );
16931×
        g.insert(
16941×
            h(2),
1695
            0,
16961×
            Vertex::Atom((h(1), 0)),
16971×
            Vertex::End,
16981×
            &lines(&["y\n"]),
1699
        );
17001×
        let deps = Deps([(h(2), vec![h(1)])].into_iter().collect());
17011×
        let m = g.materialize(&deps);
17021×
        assert!(!m.is_conflicted());
17031×
        assert_eq!(text(&g, &m), "y\n");
17041×
    }
1705
1706
    #[test]
17071×
    fn resolution_collapses_a_conflict() {
17081×
        let mut g = FileGraph::new();
17091×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
17101×
        g.insert(
17111×
            h(2),
1712
            0,
17131×
            Vertex::Start,
17141×
            Vertex::Atom((h(1), 0)),
17151×
            &lines(&["x\n"]),
1716
        );
17171×
        g.insert(
17181×
            h(3),
1719
            0,
17201×
            Vertex::Start,
17211×
            Vertex::Atom((h(1), 0)),
17221×
            &lines(&["y\n"]),
1723
        );
17241×
        let mut deps: HashMap<_, _> = [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
17251×
            .into_iter()
17261×
            .collect();
17271×
        assert!(g.materialize(&Deps(deps.clone())).is_conflicted());
1728
        // Resolution keeps y, drops x.
17291×
        g.delete(
17301×
            h(4),
17311×
            &AtomRange {
17321×
                change: h(2),
17331×
                from: 0,
17341×
                to: 1,
17351×
            },
1736
        );
17371×
        deps.insert(h(4), vec![h(2), h(3)]);
17381×
        let m = g.materialize(&Deps(deps));
17391×
        assert!(!m.is_conflicted());
17401×
        assert_eq!(text(&g, &m), "y\na\n");
17411×
    }
1742
1743
    #[test]
17441×
    fn empty_and_unterminated_files() {
17451×
        let g = FileGraph::new();
17461×
        let m = g.materialize(&NoDeps);
17471×
        assert_eq!(m.segments, vec![]);
17481×
        assert_eq!(text(&g, &m), "");
17491×
        let mut g = FileGraph::new();
17501×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["no newline"]));
17511×
        assert_eq!(text(&g, &g.materialize(&NoDeps)), "no newline");
17521×
    }
1753
1754
    #[test]
17551×
    fn serialization_round_trip() {
17561×
        let mut g = FileGraph::new();
17571×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
17581×
        g.insert(
17591×
            h(2),
1760
            0,
17611×
            Vertex::Atom((h(1), 0)),
17621×
            Vertex::Atom((h(1), 1)),
17631×
            &lines(&["x\n"]),
1764
        );
17651×
        g.delete(
17661×
            h(3),
17671×
            &AtomRange {
17681×
                change: h(1),
17691×
                from: 1,
17701×
                to: 2,
17711×
            },
1772
        );
17731×
        let v = g.to_value();
17741×
        let bytes = crate::cbor::encode(&v);
17751×
        let back = FileGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap();
17761×
        let deps = Deps(
17771×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
17781×
                .into_iter()
17791×
                .collect(),
17801×
        );
17811×
        assert_eq!(back.materialize(&deps), g.materialize(&deps));
17821×
        assert_eq!(text(&back, &back.materialize(&deps)), "a\nx\n");
17831×
        assert_eq!(
17841×
            crate::cbor::encode(&back.to_value()),
1785
            bytes,
1786
            "deterministic"
1787
        );
17881×
    }
1789
1790
    /// Spec §7.3: the graph is a function of the set of changes, not their order.
1791
    #[test]
17921×
    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
        );
17991×
        let ops: Vec<Op> = vec![
18001×
            (
18011×
                h(1),
18021×
                vec![(0, Vertex::Start, Vertex::End, lines(&["a\n", "b\n", "c\n"]))],
18031×
                vec![],
18041×
            ),
18051×
            (
18061×
                h(2),
18071×
                vec![(
18081×
                    0,
18091×
                    Vertex::Atom((h(1), 0)),
18101×
                    Vertex::Atom((h(1), 1)),
18111×
                    lines(&["p\n"]),
18121×
                )],
18131×
                vec![],
18141×
            ),
18151×
            (
18161×
                h(3),
18171×
                vec![(
18181×
                    0,
18191×
                    Vertex::Atom((h(1), 0)),
18201×
                    Vertex::Atom((h(1), 1)),
18211×
                    lines(&["q\n", "r\n"]),
18221×
                )],
18231×
                vec![AtomRange {
18241×
                    change: h(1),
18251×
                    from: 2,
18261×
                    to: 3,
18271×
                }],
18281×
            ),
18291×
            (
18301×
                h(4),
18311×
                vec![(
18321×
                    0,
18331×
                    Vertex::Atom((h(3), 1)),
18341×
                    Vertex::Atom((h(1), 1)),
18351×
                    lines(&["s\n"]),
18361×
                )],
18371×
                vec![AtomRange {
18381×
                    change: h(1),
18391×
                    from: 1,
18401×
                    to: 2,
18411×
                }],
18421×
            ),
18431×
            (
18441×
                h(5),
18451×
                vec![(0, Vertex::Atom((h(1), 2)), Vertex::End, lines(&["z\n"]))],
18461×
                vec![],
18471×
            ),
1848
        ];
18491×
        let deps = Deps(
18501×
            [
18511×
                (h(2), vec![h(1)]),
18521×
                (h(3), vec![h(1)]),
18531×
                (h(4), vec![h(3), h(1)]),
18541×
                (h(5), vec![h(1)]),
18551×
            ]
18561×
            .into_iter()
18571×
            .collect(),
18581×
        );
185951×
        let build = |order: &[usize]| {
186051×
            let mut g = FileGraph::new();
1861255×
            for &i in order {
1862255×
                let (c, ins, dels) = &ops[i];
1863255×
                for (idx, a, b, l) in ins {
1864255×
                    g.insert(*c, *idx, *a, *b, l);
1865255×
                }
1866255×
                for d in dels {
1867102×
                    g.delete(*c, d);
1868102×
                }
1869
            }
187051×
            let m = g.materialize(&deps);
187151×
            (m.clone(), text(&g, &m))
187251×
        };
18731×
        let reference = build(&[0, 1, 2, 3, 4]);
18741×
        assert!(reference.0.is_conflicted());
18751×
        let mut rng = rand::thread_rng();
18761×
        let mut order: Vec<usize> = (0..ops.len()).collect();
18771×
        for _ in 0..50 {
187850×
            order.shuffle(&mut rng);
187950×
            assert_eq!(build(&order), reference, "order {order:?}");
1880
        }
18811×
    }
1882
}