OXYGENHUB

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

1395 of 1438 lines run, 203 of 220 functions · run #252 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
/// No change depends on any other: every pair commutes.
29
pub struct NoDeps;
30
impl DepOracle for NoDeps {
316×
    fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
326×
        a == b
336×
    }
34
}
35
36
#[derive(Default, Clone, Debug)]
37
struct AtomInfo {
38
    /// `None` until the introducing change has been applied; a line of the
39
    /// insert's shared buffer.
40
    content: Option<LineRef>,
41
    deleted_by: BTreeSet<ChangeHash>,
42
}
43
44
#[derive(Clone, Debug)]
45
pub struct FileGraph {
46
    atoms: HashMap<AtomKey, AtomInfo>,
47
    /// Every vertex, with its direct successors.
48
    succ: HashMap<Vertex, Edges>,
49
    /// The live atoms' order, once known to be the graph's only one.
50
    order: OrderSlot,
51
}
52
53
/// A file graph's live atoms in order, when the graph orders them one way
54
/// only: found by [`FileGraph::materialize`] and brought up to date by
55
/// [`Order::splice`] as a change's edits apply, so a long-lived file
56
/// renders in time that follows its live lines rather than every line it
57
/// ever had. Any other change to the graph drops it.
58
#[derive(Debug)]
59
pub struct Order {
60
    atoms: Vec<AtomKey>,
61
    /// Atoms deleted since the order was last confirmed: it holds while
62
    /// none of them is a ghost (spec §9.1), which only a render can ask,
63
    /// since it needs to know how the changes depend on each other.
64
    unchecked: Vec<AtomKey>,
65
    confirmed: std::sync::atomic::AtomicBool,
66
}
67
68
/// Holds a graph's [`Order`]; filled in by a render, which only reads the
69
/// graph.
70
#[derive(Debug, Default)]
71
struct OrderSlot(std::sync::Mutex<Option<std::sync::Arc<Order>>>);
72
73
impl OrderSlot {
7425628×
    fn get(&self) -> Option<std::sync::Arc<Order>> {
7525628×
        self.0.lock().unwrap_or_else(|e| e.into_inner()).clone()
7625628×
    }
771539×
    fn set(&self, o: Option<std::sync::Arc<Order>>) {
781539×
        *self.0.lock().unwrap_or_else(|e| e.into_inner()) = o;
791539×
    }
8063182×
    fn take(&mut self) -> Option<std::sync::Arc<Order>> {
8163182×
        self.0.get_mut().unwrap_or_else(|e| e.into_inner()).take()
8263182×
    }
83
}
84
85
impl Clone for OrderSlot {
867×
    fn clone(&self) -> Self {
877×
        OrderSlot(std::sync::Mutex::new(self.get()))
887×
    }
89
}
90
91
impl Order {
921529×
    fn known(atoms: Vec<AtomKey>) -> Order {
931529×
        Order {
941529×
            atoms,
951529×
            unchecked: Vec::new(),
961529×
            confirmed: std::sync::atomic::AtomicBool::new(true),
971529×
        }
981529×
    }
99
100
    /// The order after change `h` inserts the runs `inserts` (after,
101
    /// before, first index, count) and deletes `deleted`, as its edits
102
    /// apply; `None` where that is not certain to be the graph's only
103
    /// order: an anchor not in the order, live lines between an insert's
104
    /// anchors, or two inserts with nothing kept between them.
105533×
    fn splice(
106533×
        &self,
107533×
        h: ChangeHash,
108533×
        inserts: &[(Vertex, Vertex, u32, u32)],
109533×
        deleted: &[AtomKey],
110533×
    ) -> Option<std::sync::Arc<Order>> {
111533×
        let mut want: HashSet<AtomKey> = deleted.iter().copied().collect();
112581×
        for (a, b, _, _) in inserts {
1131162×
            for v in [a, b] {
1141162×
                if let Vertex::Atom(k) = v {
115952×
                    want.insert(*k);
116952×
                }
117
            }
118
        }
119533×
        let mut pos: HashMap<AtomKey, usize> = HashMap::default();
1202873×
        for (i, k) in self.atoms.iter().enumerate() {
1212873×
            if want.contains(k) {
1221293×
                pos.insert(*k, i);
1231580×
            }
124
        }
125533×
        let len = self.atoms.len();
126533×
        let mut gone = vec![false; len];
127533×
        let mut unchecked = if self.confirmed.load(std::sync::atomic::Ordering::Relaxed) {
128533×
            Vec::new()
129
        } else {
1300×
            self.unchecked.clone()
131
        };
132732×
        for k in deleted {
133732×
            if k.0 == h {
1340×
                return None;
135732×
            }
136732×
            if let Some(&i) = pos.get(k) {
137709×
                if !gone[i] {
138709×
                    gone[i] = true;
139709×
                    unchecked.push(*k);
140709×
                }
14123×
            }
142
        }
143
        // Each run goes in the gap its anchors leave (slot `i`: before
144
        // `atoms[i]`), which must hold nothing that stays.
145533×
        let mut slots: Vec<(usize, usize, u32, u32)> = Vec::with_capacity(inserts.len());
146562×
        for &(a, b, first, count) in inserts {
147562×
            let from = match a {
148136×
                Vertex::Start => 0,
149426×
                Vertex::Atom(k) => pos.get(&k)? + 1,
1500×
                Vertex::End => return None,
151
            };
152551×
            let to = match b {
15370×
                Vertex::End => len,
154481×
                Vertex::Atom(k) => *pos.get(&k)?,
1550×
                Vertex::Start => return None,
156
            };
157544×
            if from > to || gone[from..to].iter().any(|g| !g) {
1585×
                return None;
159539×
            }
160539×
            slots.push((from, to, first, count));
161
        }
162
        // Runs one after another need a kept line between them, or the
163
        // graph would not say which comes first.
164510×
        slots.sort_unstable();
165510×
        for w in slots.windows(2) {
166161×
            let (end, next) = (w[0].1, w[1].0);
167306×
            if end >= next || !gone[end..next].iter().any(|g| !g) {
1680×
                return None;
169161×
            }
170
        }
171538×
        let added: usize = slots.iter().map(|s| s.3 as usize).sum();
172510×
        let mut atoms = Vec::with_capacity(len + added);
173510×
        let mut slots = slots.into_iter().peekable();
1743171×
        let mut runs_at = |i: usize, atoms: &mut Vec<AtomKey>| {
1753709×
            while let Some(&(from, _, first, count)) = slots.peek() {
1761816×
                if from != i {
1771278×
                    break;
178538×
                }
179789×
                atoms.extend((first..first + count).map(|j| (h, j)));
180538×
                slots.next();
181
            }
1823171×
        };
1832661×
        for (i, (k, g)) in self.atoms.iter().zip(&gone).enumerate() {
1842661×
            runs_at(i, &mut atoms);
1852661×
            if !g {
1861989×
                atoms.push(*k);
1871989×
            }
188
        }
189510×
        runs_at(len, &mut atoms);
190510×
        let confirmed = unchecked.is_empty();
191510×
        Some(std::sync::Arc::new(Order {
192510×
            atoms,
193510×
            unchecked,
194510×
            confirmed: std::sync::atomic::AtomicBool::new(confirmed),
195510×
        }))
196533×
    }
197
}
198
199
/// A vertex's direct successors, sorted and without repeats. Nearly every
200
/// vertex has one, which is kept inline without an allocation.
201
#[derive(Clone, Debug, Default, PartialEq, Eq)]
202
enum Edges {
203
    #[default]
204
    None,
205
    One(Vertex),
206
    Many(Vec<Vertex>),
207
}
208
209
impl Edges {
210239016×
    fn insert(&mut self, v: Vertex) {
21130640×
        match self {
212206588×
            Edges::None => *self = Edges::One(v),
21330640×
            Edges::One(a) if *a == v => {}
21430640×
            Edges::One(a) => {
21530640×
                let a = *a;
21630640×
                *self = Edges::Many(if a < v { vec![a, v] } else { vec![v, a] });
217
            }
2181788×
            Edges::Many(list) => {
2191788×
                if let Err(i) = list.binary_search(&v) {
2201788×
                    list.insert(i, v);
2211788×
                }
222
            }
223
        }
224239016×
    }
225
226955627×
    fn as_slice(&self) -> &[Vertex] {
227955627×
        match self {
2284274×
            Edges::None => &[],
229701287×
            Edges::One(v) => std::slice::from_ref(v),
230250066×
            Edges::Many(list) => list,
231
        }
232955627×
    }
233
234697096×
    fn iter(&self) -> std::slice::Iter<'_, Vertex> {
235697096×
        self.as_slice().iter()
236697096×
    }
237
}
238
239
impl Default for FileGraph {
24024634×
    fn default() -> Self {
24124634×
        Self::new()
24224634×
    }
243
}
244
245
/// One piece of a materialized file.
246
#[derive(Clone, Debug, PartialEq, Eq)]
247
pub enum Segment {
248
    Lines(Vec<AtomKey>),
249
    /// Order conflict: alternatives that cannot be ordered against each other.
250
    Conflict(Vec<Vec<AtomKey>>),
251
    /// Delete/insert conflict: `inserted` was anchored to `deleted` by a change
252
    /// that commutes with the deletion.
253
    Zombie {
254
        deleted: Vec<AtomKey>,
255
        inserted: Vec<AtomKey>,
256
    },
257
}
258
259
#[derive(Clone, Debug, PartialEq, Eq, Default)]
260
pub struct Materialized {
261
    pub segments: Vec<Segment>,
262
}
263
264
impl Materialized {
26531637×
    pub fn is_conflicted(&self) -> bool {
26631637×
        self.segments
26731637×
            .iter()
26831637×
            .any(|s| !matches!(s, Segment::Lines(_)))
26931637×
    }
27023780×
    pub fn conflict_count(&self) -> usize {
27123780×
        self.segments
27223780×
            .iter()
27324387×
            .filter(|s| !matches!(s, Segment::Lines(_)))
27423780×
            .count()
27523780×
    }
276
    /// Every live atom in materialized order, conflict sides concatenated.
2771240×
    pub fn live_atoms(&self) -> Vec<AtomKey> {
2781240×
        let mut out = Vec::new();
2791276×
        for s in &self.segments {
2801276×
            match s {
2811232×
                Segment::Lines(a) => out.extend(a),
28230×
                Segment::Conflict(sides) => sides.iter().for_each(|x| out.extend(x)),
28329×
                Segment::Zombie { inserted, .. } => out.extend(inserted),
284
            }
285
        }
2861240×
        out
2871240×
    }
288
}
289
290
impl FileGraph {
29124704×
    pub fn new() -> Self {
29224704×
        let mut g = FileGraph {
29324704×
            atoms: HashMap::default(),
29424704×
            succ: HashMap::default(),
29524704×
            order: OrderSlot::default(),
29624704×
        };
29724704×
        g.edge(Vertex::Start, Vertex::End);
29824704×
        g
29924704×
    }
300
30129833×
    fn edge(&mut self, a: Vertex, b: Vertex) {
30229833×
        self.succ.entry(a).or_default().insert(b);
30329833×
        self.succ.entry(b).or_default();
30429833×
    }
305
306
    /// Apply an `Insert` by `change` whose first new atom has index `first_index`.
3071042×
    pub fn insert(
3081042×
        &mut self,
3091042×
        change: ChangeHash,
3101042×
        first_index: u32,
3111042×
        after: Vertex,
3121042×
        before: Vertex,
3131042×
        lines: &Lines,
3141042×
    ) {
3151042×
        self.insert_owned(change, first_index, after, before, lines.clone());
3161042×
    }
317
318
    /// [`FileGraph::insert`] taking the lines, so loading a state moves
319
    /// them in rather than copying. Each new atom's entry is made once, as
320
    /// the source of the next edge, and the chain's ends are ensured last.
32131729×
    pub fn insert_owned(
32231729×
        &mut self,
32331729×
        change: ChangeHash,
32431729×
        first_index: u32,
32531729×
        after: Vertex,
32631729×
        before: Vertex,
32731729×
        lines: Lines,
32831729×
    ) {
32931729×
        self.order.take();
33031729×
        self.atoms.reserve(lines.len());
33131729×
        self.succ.reserve(lines.len() + 1);
33231729×
        let mut prev = after;
333177454×
        for i in 0..lines.len() {
334177454×
            let key = (change, first_index + i as u32);
335177454×
            self.atoms.entry(key).or_default().content =
336177454×
                Some(LineRef::new(lines.clone(), i as u32));
337177454×
            let v = Vertex::Atom(key);
338177454×
            self.succ.entry(prev).or_default().insert(v);
339177454×
            prev = v;
340177454×
        }
34131729×
        self.succ.entry(prev).or_default().insert(before);
34231729×
        self.succ.entry(before).or_default();
34331729×
    }
344
345
    /// Apply a `Delete` of `range` by change `by`.
3465617×
    pub fn delete(&mut self, by: ChangeHash, range: &AtomRange) {
3475617×
        self.order.take();
3485881×
        for i in range.from..range.to {
3495881×
            self.atoms
3505881×
                .entry((range.change, i))
3515881×
                .or_default()
3525881×
                .deleted_by
3535881×
                .insert(by);
3545881×
        }
3555617×
    }
356
357476471×
    pub fn is_live(&self, k: &AtomKey) -> bool {
358476471×
        self.atoms
359476471×
            .get(k)
360476471×
            .is_some_and(|a| a.content.is_some() && a.deleted_by.is_empty())
361476471×
    }
362
363162832×
    pub fn content(&self, k: &AtomKey) -> Option<&[u8]> {
364162832×
        self.atoms
365162832×
            .get(k)
366162832×
            .and_then(|a| a.content.as_ref().map(LineRef::bytes))
367162832×
    }
368
369292×
    pub fn deleters(&self, k: &AtomKey) -> impl Iterator<Item = &ChangeHash> {
370292×
        self.atoms
371292×
            .get(k)
372292×
            .into_iter()
373292×
            .flat_map(|a| a.deleted_by.iter())
374292×
    }
375
376
    /// Number of atoms introduced by `change` in this graph (for index bookkeeping).
37748×
    pub fn atom_count(&self) -> usize {
37848×
        self.atoms.len()
37948×
    }
380
381
    /// The changes that inserted a live atom directly after `k`. A change
382
    /// that deletes `k` must depend on them (spec §8.1), or `is_ghost`
383
    /// reads the deletion as concurrent with those insertions.
384
    /// Every atom on some path from `first` to `last`, both included, live
385
    /// or deleted (spec §8): the atoms a range of lines is made of, and
386
    /// those that were deleted from between them. Empty when `last` does
387
    /// not follow `first`.
3883×
    pub fn between(&self, first: &AtomKey, last: &AtomKey) -> Vec<AtomKey> {
3893×
        let (from, to) = (Vertex::Atom(*first), Vertex::Atom(*last));
3903×
        let mut ahead: HashSet<Vertex> = HashSet::default();
3913×
        let mut stack = vec![from];
39229×
        while let Some(v) = stack.pop() {
39326×
            if ahead.insert(v) {
39421×
                stack.extend(
39521×
                    self.succ
39621×
                        .get(&v)
39721×
                        .map(|e| e.as_slice())
39821×
                        .unwrap_or(&[])
39921×
                        .iter()
40021×
                        .copied(),
401
                );
4025×
            }
403
        }
4043×
        if !ahead.contains(&to) {
4050×
            return vec![];
4063×
        }
4073×
        let mut pred: HashMap<Vertex, Vec<Vertex>> = HashMap::default();
40828×
        for (u, es) in &self.succ {
40928×
            if ahead.contains(u) {
41023×
                for w in es.iter() {
41123×
                    pred.entry(*w).or_default().push(*u);
41223×
                }
4137×
            }
414
        }
4153×
        let mut behind: HashSet<Vertex> = HashSet::default();
4163×
        let mut stack = vec![to];
41715×
        while let Some(v) = stack.pop() {
41812×
            if behind.insert(v) {
41910×
                stack.extend(pred.get(&v).into_iter().flatten().copied());
42010×
            }
421
        }
4223×
        let mut out: Vec<AtomKey> = ahead
4233×
            .intersection(&behind)
42410×
            .filter_map(|v| match v {
42510×
                Vertex::Atom(k) if self.atoms.contains_key(k) => Some(*k),
4260×
                _ => None,
42710×
            })
4283×
            .collect();
4293×
        out.sort();
4303×
        out
4313×
    }
432
433
    /// The changes that inserted or deleted any of `atoms`.
4343×
    pub fn touching(&self, atoms: &[AtomKey]) -> BTreeSet<ChangeHash> {
4353×
        let mut out = BTreeSet::new();
43610×
        for k in atoms {
43710×
            out.insert(k.0);
43810×
            if let Some(info) = self.atoms.get(k) {
43910×
                out.extend(info.deleted_by.iter().copied());
44010×
            }
441
        }
4423×
        out
4433×
    }
444
4451148×
    pub fn live_successor_changes(&self, k: &AtomKey) -> Vec<ChangeHash> {
4461148×
        self.succ
4471148×
            .get(&Vertex::Atom(*k))
4481148×
            .map(|ss| {
4491147×
                ss.iter()
4501147×
                    .filter_map(|s| match s {
451908×
                        Vertex::Atom(y) if self.is_live(y) => Some(y.0),
452364×
                        _ => None,
4531196×
                    })
4541147×
                    .collect()
4551147×
            })
4561148×
            .unwrap_or_default()
4571148×
    }
458
459360435×
    fn is_ghost(&self, k: &AtomKey, info: &AtomInfo, deps: &dyn DepOracle) -> bool {
460
        // A deleted atom is a ghost when a live direct successor was inserted
461
        // by a change that commutes with every deleter: neither depends on
462
        // the other (spec §9.1, delete/insert). If the inserter came after the
463
        // deletion, or the deleter saw the insertion, there is no conflict.
464360435×
        self.succ.get(&Vertex::Atom(*k)).is_some_and(|ss| {
465474707×
            ss.iter().any(|s| match s {
466474707×
                Vertex::Atom(y) if self.is_live(y) => !info
4674711×
                    .deleted_by
4684711×
                    .iter()
4694789×
                    .any(|d| deps.depends_on(d, &y.0) || deps.depends_on(&y.0, d)),
470499796×
                _ => false,
471504507×
            })
472360435×
        })
473360435×
    }
474
475
    /// Successors of `v` in the graph with non-kept vertices contracted away.
4769031×
    fn reduced_succ(
4779031×
        &self,
4789031×
        v: Vertex,
4799031×
        kept: &HashSet<Vertex>,
4809031×
        memo: &mut HashMap<Vertex, BTreeSet<Vertex>>,
4819031×
    ) -> BTreeSet<Vertex> {
4829031×
        let direct = self.succ.get(&v).map(Edges::as_slice).unwrap_or_default();
483
        // The common case, a successor that is itself kept: nothing to
484
        // contract.
4859613×
        if direct.iter().all(|s| kept.contains(s)) {
4865213×
            return direct.iter().copied().collect();
4873818×
        }
4883818×
        let mut out = BTreeSet::new();
4893818×
        let mut stack: Vec<Vertex> = direct.to_vec();
4903818×
        let mut seen = HashSet::default();
491502365×
        while let Some(s) = stack.pop() {
492498547×
            if kept.contains(&s) {
49333962×
                out.insert(s);
494464585×
            } else if seen.insert(s) {
495319575×
                if let Some(m) = memo.get(&s) {
4960×
                    out.extend(m.iter().copied());
497319575×
                } else if let Some(next) = self.succ.get(&s) {
498319575×
                    stack.extend(next.iter().copied());
499319575×
                }
500145010×
            }
501
        }
5023818×
        out
5039031×
    }
504
505
    /// The atoms in order when the graph is one chain from Start to End with
506
    /// nothing deleted: a file only ever inserted into in sequence, as every
507
    /// file of a fresh commit or a clone is. Such a graph has no conflict.
508
    /// An edge to End besides the chain's next atom (Start's own, or an
509
    /// append's anchor) orders nothing, since End is last anyway.
51023943×
    fn as_chain(&self) -> Option<Vec<AtomKey>> {
51123943×
        if self
51223943×
            .atoms
51323943×
            .values()
514138674×
            .any(|a| a.content.is_none() || !a.deleted_by.is_empty())
515
        {
5161848×
            return None;
51722095×
        }
51822095×
        let mut order = Vec::with_capacity(self.atoms.len());
51922095×
        let mut v = Vertex::Start;
520
        loop {
521130262×
            let succ = self.succ.get(&v)?.as_slice();
522153803×
            let mut atoms = succ.iter().filter(|s| **s != Vertex::End);
523130262×
            match (atoms.next(), atoms.next()) {
52421830×
                (None, _) if succ.contains(&Vertex::End) => break,
525108167×
                (Some(Vertex::Atom(k)), None) if order.len() < self.atoms.len() => {
526108167×
                    order.push(*k);
527108167×
                    v = Vertex::Atom(*k);
528108167×
                }
529265×
                _ => return None,
530
            }
531
        }
53221830×
        (order.len() == self.atoms.len()).then_some(order)
53323943×
    }
534
535
    /// Order the live atoms and locate conflicts.
53625163×
    pub fn materialize(&self, deps: &dyn DepOracle) -> Materialized {
53725163×
        let lines = |order: Vec<AtomKey>| Materialized {
53824583×
            segments: if order.is_empty() {
539165×
                vec![]
540
            } else {
54124418×
                vec![Segment::Lines(order)]
542
            },
54324583×
        };
544
        use std::sync::atomic::Ordering::Relaxed;
545
        // Which way it went, and how long it took (`OXY_TIMINGS`).
54625163×
        let started = crate::timing::enabled().then(std::time::Instant::now);
54725163×
        let took = |name: &'static str| {
54825163×
            if let Some(t) = started {
5490×
                crate::timing::add(name, t.elapsed());
55025163×
            }
55125163×
        };
55225163×
        if let Some(o) = self.order.get() {
5531236×
            let holds = o.confirmed.load(Relaxed)
554616×
                || o.unchecked.iter().all(|k| {
555616×
                    self.atoms
556616×
                        .get(k)
557616×
                        .is_none_or(|info| !self.is_ghost(k, info, deps))
558616×
                });
5591236×
            if holds {
5601226×
                o.confirmed.store(true, Relaxed);
5611226×
                took("graph: kept order");
5621226×
                return lines(o.atoms.clone());
56310×
            }
56410×
            self.order.set(None);
56523927×
        }
566
        // A chain (nothing ever deleted) is quick to walk, and a fresh tree
567
        // is all chains: no order is kept for one.
56823937×
        if let Some(order) = self.as_chain() {
56921828×
            took("graph: chain");
57021828×
            return lines(order);
5712109×
        }
5722109×
        if let Some(order) = self.as_order(deps) {
5731529×
            self.order
5741529×
                .set(Some(std::sync::Arc::new(Order::known(order.clone()))));
5751529×
            took("graph: single order");
5761529×
            return lines(order);
577580×
        }
578580×
        let m = self.materialize_general(deps);
579580×
        took("graph: general");
580580×
        m
58125163×
    }
582
583
    /// Take the kept order, to [`Order::splice`] it once a change's edits
584
    /// have applied (they drop it).
58525836×
    pub fn take_order(&mut self) -> Option<std::sync::Arc<Order>> {
58625836×
        self.order.take()
58725836×
    }
588
589
    /// Keep `order` (taken before change `h`'s edits applied) spliced by
590
    /// them: `inserts` (after, before, first index, count) and `deleted`.
591
    /// Changes may apply in any order, so a line may arrive already
592
    /// deleted by a change applied before it: then the order is dropped.
593534×
    pub fn keep_spliced(
594534×
        &mut self,
595534×
        order: std::sync::Arc<Order>,
596534×
        h: ChangeHash,
597534×
        inserts: &[(Vertex, Vertex, u32, u32)],
598534×
        deleted: &[AtomKey],
599534×
    ) {
600534×
        let live = inserts
601534×
            .iter()
602856×
            .all(|&(_, _, first, count)| (first..first + count).all(|i| self.is_live(&(h, i))));
603534×
        let spliced = if live {
604533×
            order.splice(h, inserts, deleted)
605
        } else {
6061×
            None
607
        };
608534×
        *self.order.0.get_mut().unwrap_or_else(|e| e.into_inner()) = spliced;
609534×
    }
610
611
    /// The live atoms in order when, with the dead ones contracted away,
612
    /// the graph orders them one way only: no deleted atom is a ghost, and
613
    /// at every step of the topological sort one vertex is next. A file
614
    /// edited without concurrent edits is such a graph, however long its
615
    /// history. `None` otherwise, for [`FileGraph::materialize_general`]
616
    /// to find the conflicts; where this answers, it gives the same order.
6172111×
    fn as_order(&self, deps: &dyn DepOracle) -> Option<Vec<AtomKey>> {
618
        // Kept vertices by dense index: Start, End, then the live atoms.
6192111×
        let mut index: HashMap<Vertex, u32> =
6202111×
            HashMap::with_capacity_and_hasher(self.atoms.len() + 2, Default::default());
6212111×
        let mut verts: Vec<Vertex> = Vec::with_capacity(self.atoms.len() + 2);
6222111×
        verts.extend([Vertex::Start, Vertex::End]);
6232111×
        index.insert(Vertex::Start, 0);
6242111×
        index.insert(Vertex::End, 1);
625102157×
        for (k, info) in &self.atoms {
626102157×
            if info.content.is_none() {
6270×
                continue;
628102157×
            }
629102157×
            if info.deleted_by.is_empty() {
63050108×
                index.insert(Vertex::Atom(*k), verts.len() as u32);
63150108×
                verts.push(Vertex::Atom(*k));
63252049×
            } else if self.is_ghost(k, info, deps) {
633381×
                return None;
63451668×
            }
635
        }
636
        // Each kept vertex's successors, contracting the others: what a
637
        // contracted vertex reaches is worked out once (post-order).
6381730×
        let mut reach: HashMap<Vertex, Vec<u32>> = HashMap::default();
63910216×
        let contracted = |v: Vertex, reach: &mut HashMap<Vertex, Vec<u32>>| {
64010216×
            let mut stack: Vec<(Vertex, bool)> = vec![(v, false)];
64186462×
            while let Some((x, done)) = stack.pop() {
64276246×
                if reach.contains_key(&x) {
6439658×
                    continue;
64466588×
                }
64566588×
                let next = self.succ.get(&x).map(Edges::as_slice).unwrap_or_default();
64666588×
                if !done {
64733294×
                    stack.push((x, true));
64845227×
                    for s in next {
64945227×
                        if !index.contains_key(s) && !reach.contains_key(s) {
65032736×
                            stack.push((*s, false));
65132736×
                        }
652
                    }
65333294×
                    continue;
65433294×
                }
65533294×
                let mut out: Vec<u32> = Vec::new();
65645227×
                for s in next {
65745227×
                    match index.get(s) {
6585230×
                        Some(i) => out.push(*i),
65939997×
                        None => out.extend(reach.get(s).into_iter().flatten()),
660
                    }
661
                }
66233294×
                out.sort_unstable();
66333294×
                out.dedup();
66433294×
                reach.insert(x, out);
665
            }
66610216×
        };
667
        // Successors of vertex `i`: `succ[starts[i]..starts[i + 1]]`.
6681730×
        let mut succ: Vec<u32> = Vec::with_capacity(verts.len() * 2);
6691730×
        let mut starts: Vec<usize> = Vec::with_capacity(verts.len() + 1);
6701730×
        let mut indeg: Vec<u32> = vec![0; verts.len()];
67152629×
        for v in &verts {
67252629×
            let from = succ.len();
67352629×
            starts.push(from);
67461462×
            for s in self.succ.get(v).map(Edges::as_slice).unwrap_or_default() {
67561462×
                match index.get(s) {
67651246×
                    Some(i) => succ.push(*i),
67710216×
                    None => {
67810216×
                        contracted(*s, &mut reach);
67910216×
                        succ.extend(reach.get(s).into_iter().flatten());
68010216×
                    }
681
                }
682
            }
68352629×
            if succ.len() - from > 1 {
6845289×
                succ[from..].sort_unstable();
6855289×
                let mut kept = from;
68616085×
                for i in from..succ.len() {
68716085×
                    if i == from || succ[i] != succ[kept - 1] {
68811187×
                        succ[kept] = succ[i];
68911187×
                        kept += 1;
69011187×
                    }
691
                }
6925289×
                succ.truncate(kept);
69347340×
            }
69456797×
            for &w in &succ[from..] {
69556797×
                indeg[w as usize] += 1;
69656797×
            }
697
        }
6981730×
        starts.push(succ.len());
699
        // Kahn's algorithm, giving up where two vertices are ready at once.
7001730×
        let mut ready: Vec<u32> = (0..verts.len() as u32)
70152629×
            .filter(|&i| indeg[i as usize] == 0)
7021730×
            .collect();
7031730×
        let mut order: Vec<AtomKey> = Vec::with_capacity(verts.len());
7041730×
        let mut seen = 0;
70553262×
        while let Some(v) = ready.pop() {
70651732×
            if !ready.is_empty() {
7070×
                return None;
70851732×
            }
70951732×
            seen += 1;
71051732×
            if let Vertex::Atom(k) = verts[v as usize] {
71148472×
                order.push(k);
71248472×
            }
71355968×
            for &w in &succ[starts[v as usize]..starts[v as usize + 1]] {
71455968×
                indeg[w as usize] -= 1;
71555968×
                if indeg[w as usize] == 0 {
71650441×
                    ready.push(w);
71750441×
                }
718
            }
71951732×
            if ready.len() > 1 {
720200×
                return None;
72151532×
            }
722
        }
7231530×
        (seen == verts.len()).then_some(order)
7242111×
    }
725
726
    /// [`FileGraph::materialize`] for any graph: contraction, then a
727
    /// topological order with conflict regions.
7281074×
    fn materialize_general(&self, deps: &dyn DepOracle) -> Materialized {
729
        // 1. Vertices that survive contraction: live atoms, ghosts, Start, End.
7301074×
        let mut kept: HashSet<Vertex> = HashSet::default();
7311074×
        let mut ghosts: HashSet<AtomKey> = HashSet::default();
7321074×
        kept.insert(Vertex::Start);
7331074×
        kept.insert(Vertex::End);
734314230×
        for (k, info) in &self.atoms {
735314230×
            if info.content.is_none() {
7360×
                continue;
737314230×
            }
738314230×
            if info.deleted_by.is_empty() {
7396460×
                kept.insert(Vertex::Atom(*k));
740307770×
            } else if self.is_ghost(k, info, deps) {
741423×
                kept.insert(Vertex::Atom(*k));
742423×
                ghosts.insert(*k);
743307347×
            }
744
        }
745
746
        // 2. Reduced graph over kept vertices.
7471074×
        let mut memo = HashMap::default();
7481074×
        let mut rsucc: HashMap<Vertex, BTreeSet<Vertex>> = HashMap::default();
7491074×
        let mut indeg: HashMap<Vertex, usize> = HashMap::default();
7509031×
        for &u in &kept {
7519031×
            indeg.entry(u).or_insert(0);
7529031×
            let rs = self.reduced_succ(u, &kept, &mut memo);
75313771×
            for &w in &rs {
75413771×
                *indeg.entry(w).or_insert(0) += 1;
75513771×
            }
7569031×
            rsucc.insert(u, rs);
757
        }
758
759
        // 3. Kahn's algorithm with side labelling for conflict regions.
7601074×
        let mut ready: BTreeSet<Vertex> = indeg
7611074×
            .iter()
7629031×
            .filter(|(_, &d)| d == 0)
7631074×
            .map(|(v, _)| *v)
7641074×
            .collect();
7651074×
        let mut side: HashMap<Vertex, u32> = HashMap::default();
7661074×
        let mut joins: HashSet<Vertex> = HashSet::default();
7671074×
        let mut next_side = 0u32;
7681074×
        let mut plain: Vec<Vertex> = Vec::new();
7691074×
        let mut conflict: Option<BTreeMap<u32, Vec<Vertex>>> = None;
7701074×
        let mut segments: Vec<Segment> = Vec::new();
771
7721444×
        let flush_plain = |plain: &mut Vec<Vertex>, segments: &mut Vec<Segment>| {
7731444×
            let atoms: Vec<AtomKey> = plain
7741444×
                .iter()
7757954×
                .filter_map(|v| {
7767954×
                    if let Vertex::Atom(k) = v {
7775806×
                        Some(*k)
778
                    } else {
7792148×
                        None
780
                    }
7817954×
                })
7821444×
                .collect();
7831444×
            plain.clear();
7841444×
            if !atoms.is_empty() {
7851182×
                segments.extend(split_zombies(atoms, &ghosts));
7861182×
            }
7871444×
        };
7881074×
        let finish_conflict = |c: BTreeMap<u32, Vec<Vertex>>, segments: &mut Vec<Segment>| {
789370×
            let sides: Vec<Vec<AtomKey>> = c
790370×
                .into_values()
791792×
                .map(|vs| {
792792×
                    vs.into_iter()
7931077×
                        .filter_map(|v| match v {
7941077×
                            Vertex::Atom(k) if !ghosts.contains(&k) => Some(k),
79513×
                            _ => None,
7961077×
                        })
797792×
                        .collect::<Vec<_>>()
798792×
                })
799792×
                .filter(|s| !s.is_empty())
800370×
                .collect();
801370×
            match sides.len() {
8020×
                0 => {}
8030×
                1 => segments.push(Segment::Lines(sides.into_iter().next().unwrap())),
804370×
                _ => segments.push(Segment::Conflict(sides)),
805
            }
806370×
        };
807
80810105×
        while !ready.is_empty() {
8099031×
            if ready.len() >= 2 {
810549×
                if conflict.is_none() {
811370×
                    flush_plain(&mut plain, &mut segments);
812370×
                    conflict = Some(BTreeMap::new());
813370×
                }
8141176×
                for r in &ready {
8151176×
                    if !side.contains_key(r) {
816792×
                        side.insert(*r, next_side);
817792×
                        next_side += 1;
818792×
                    }
819
                }
8208482×
            }
8219031×
            let v = ready.pop_first().unwrap();
8229031×
            let my_side = side.get(&v).copied();
8239031×
            match (&mut conflict, my_side) {
8241077×
                (Some(c), Some(s)) => c.entry(s).or_default().push(v),
825370×
                (Some(_), None) => {
826370×
                    let c = conflict.take().unwrap();
827370×
                    finish_conflict(c, &mut segments);
828370×
                    plain.push(v);
829370×
                }
8307584×
                (None, _) => plain.push(v),
831
            }
83213771×
            for &w in &rsucc[&v] {
83313771×
                let d = indeg.get_mut(&w).unwrap();
83413771×
                *d -= 1;
83513771×
                if conflict.is_some() && !joins.contains(&w) {
8361119×
                    match (my_side, side.get(&w).copied()) {
837695×
                        (Some(s), None) => {
838695×
                            side.insert(w, s);
839695×
                        }
840424×
                        (Some(s), Some(t)) if s != t => {
841410×
                            side.remove(&w);
842410×
                            joins.insert(w);
843410×
                        }
8440×
                        (None, Some(_)) => {
8450×
                            side.remove(&w);
8460×
                            joins.insert(w);
8470×
                        }
84814×
                        _ => {}
849
                    }
85012652×
                }
85113771×
                if *d == 0 {
8527957×
                    ready.insert(w);
8537957×
                }
854
            }
855
        }
8561074×
        if let Some(c) = conflict.take() {
8570×
            finish_conflict(c, &mut segments);
8581074×
        }
8591074×
        flush_plain(&mut plain, &mut segments);
8601074×
        Materialized { segments }
8611074×
    }
862
863
    /// Render a materialization to bytes with conflict markers (spec §9.5).
864
    /// `label` gives the short description of a change for marker headers.
86523779×
    pub fn render(&self, m: &Materialized, label: &dyn Fn(&ChangeHash) -> String) -> Vec<u8> {
86623779×
        let total = m.conflict_count();
86723779×
        let mut n = 0;
86823779×
        let mut out: Vec<u8> = Vec::new();
86925008×
        let push_atoms = |out: &mut Vec<u8>, atoms: &[AtomKey]| {
870162743×
            for a in atoms {
871162743×
                if let Some(c) = self.content(a) {
872162743×
                    out.extend_from_slice(c);
873162743×
                }
874
            }
87525008×
        };
87623779×
        let newline_if_needed = |out: &mut Vec<u8>| {
8771768×
            if !out.is_empty() && *out.last().unwrap() != b'\n' {
8780×
                out.push(b'\n');
8791768×
            }
8801768×
        };
88124384×
        for seg in &m.segments {
88224384×
            match seg {
88323812×
                Segment::Lines(a) => push_atoms(&mut out, a),
884280×
                Segment::Conflict(sides) => {
885280×
                    n += 1;
886280×
                    let k = sides.len();
887612×
                    for (i, s) in sides.iter().enumerate() {
888612×
                        newline_if_needed(&mut out);
889612×
                        let who = s.first().map(|a| label(&a.0)).unwrap_or_default();
890612×
                        let marker = if i == 0 { "<<<<<<<" } else { "=======" };
891612×
                        out.extend_from_slice(
892612×
                            format!("{marker} oxy conflict {n} of {total}, side {} of {k}: change {who}\n", i + 1).as_bytes(),
893
                        );
894612×
                        push_atoms(&mut out, s);
895
                    }
896280×
                    newline_if_needed(&mut out);
897280×
                    out.extend_from_slice(
898280×
                        format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(),
899
                    );
900
                }
901292×
                Segment::Zombie { deleted, inserted } => {
902292×
                    n += 1;
903292×
                    newline_if_needed(&mut out);
904292×
                    let deleter = deleted
905292×
                        .first()
906292×
                        .and_then(|a| self.deleters(a).next().copied())
907292×
                        .map(|d| label(&d))
908292×
                        .unwrap_or_default();
909292×
                    let inserter = inserted.first().map(|a| label(&a.0)).unwrap_or_default();
910292×
                    out.extend_from_slice(
911292×
                        format!("<<<<<<< oxy conflict {n} of {total}, side 1 of 2: deleted by change {deleter}\n-------\n").as_bytes(),
912
                    );
913292×
                    push_atoms(&mut out, deleted);
914292×
                    newline_if_needed(&mut out);
915292×
                    out.extend_from_slice(
916292×
                        format!(
917292×
                            "======= oxy conflict {n} of {total}, side 2 of 2: change {inserter}\n"
918292×
                        )
919292×
                        .as_bytes(),
920
                    );
921292×
                    push_atoms(&mut out, inserted);
922292×
                    newline_if_needed(&mut out);
923292×
                    out.extend_from_slice(
924292×
                        format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(),
925
                    );
926
                }
927
            }
928
        }
92923779×
        out
93023779×
    }
931
}
932
93335646×
fn vertex_value(v: &Vertex) -> crate::cbor::Value {
934
    use crate::cbor::Value;
93535646×
    match v {
9363386×
        Vertex::Start => Value::Uint(0),
9373399×
        Vertex::End => Value::Uint(1),
93828861×
        Vertex::Atom((c, i)) => {
93928861×
            Value::Array(vec![Value::Bytes(c.0.to_vec()), Value::Uint(*i as u64)])
940
        }
941
    }
94235646×
}
943
94410258×
fn vertex_from(v: &crate::cbor::Value) -> Result<Vertex, crate::objects::ObjectError> {
945
    use crate::cbor::Value;
946
    use crate::objects::ObjectError;
9478688×
    match v {
948684×
        Value::Uint(0) => Ok(Vertex::Start),
949886×
        Value::Uint(1) => Ok(Vertex::End),
9508688×
        Value::Array(a) if a.len() == 2 => Ok(Vertex::Atom((
9518688×
            crate::objects::oref(&a[0], "vertex")?,
9528688×
            crate::objects::u32_(&a[1], "vertex")?,
953
        ))),
9540×
        _ => Err(ObjectError::Schema(
9550×
            "vertex: expected 0, 1, or [hash, index]".into(),
9560×
        )),
957
    }
95810258×
}
959
960
impl FileGraph {
961
    /// Serialize for a checkpoint: `{0: atoms [[hash, index, content|null, [deleters]]], 1: edges [[from, to]]}`.
9621464×
    pub fn to_value(&self) -> crate::cbor::Value {
963
        use crate::cbor::Value;
9641464×
        let mut atoms: Vec<(&AtomKey, &AtomInfo)> = self.atoms.iter().collect();
9651464×
        atoms.sort_by_key(|(k, _)| **k);
9661464×
        let mut edges: Vec<(Vertex, Vertex)> = self
9671464×
            .succ
9681464×
            .iter()
96917823×
            .flat_map(|(a, bs)| bs.iter().map(move |b| (*a, *b)))
9701464×
            .collect();
9711464×
        edges.sort();
9721464×
        Value::map([
973
            (
974
                0,
975
                Value::Array(
9761464×
                    atoms
9771464×
                        .into_iter()
97812990×
                        .map(|((c, i), info)| {
97912990×
                            Value::Array(vec![
98012990×
                                Value::Bytes(c.0.to_vec()),
98112990×
                                Value::Uint(*i as u64),
98212990×
                                info.content
98312990×
                                    .as_ref()
98412990×
                                    .map(|b| Value::Bytes(b.bytes().to_vec()))
98512990×
                                    .unwrap_or(Value::Null),
986
                                Value::Array(
98712990×
                                    info.deleted_by
98812990×
                                        .iter()
98912990×
                                        .map(|d| Value::Bytes(d.0.to_vec()))
99012990×
                                        .collect(),
991
                                ),
992
                            ])
99312990×
                        })
9941464×
                        .collect(),
995
                ),
996
            ),
997
            (
998
                1,
999
                Value::Array(
10001464×
                    edges
10011464×
                        .iter()
100217823×
                        .map(|(a, b)| Value::Array(vec![vertex_value(a), vertex_value(b)]))
10031464×
                        .collect(),
1004
                ),
1005
            ),
1006
        ])
10071464×
    }
1008
1009320×
    pub fn from_value(v: &crate::cbor::Value) -> Result<FileGraph, crate::objects::ObjectError> {
1010
        use crate::objects::{array, bytes, field, map, oref, u32_, ObjectError};
1011320×
        let m = map(v, "file graph")?;
1012320×
        let mut g = FileGraph {
1013320×
            atoms: HashMap::default(),
1014320×
            succ: HashMap::default(),
1015320×
            order: OrderSlot::default(),
1016320×
        };
1017320×
        let atoms = array(field(m, 0, "atoms")?, "atoms")?;
1018
        // Every atom's line in one buffer.
1019320×
        let mut texts: Vec<&[u8]> = Vec::new();
10204108×
        for a in atoms {
10214108×
            let e = array(a, "atom")?;
10224108×
            if e.len() == 4 && !e[2].is_null() {
10234108×
                texts.push(bytes(&e[2], "atom content")?);
10240×
            }
1025
        }
1026320×
        let all: Lines = texts.into_iter().collect();
1027320×
        let mut next = 0u32;
10284108×
        for a in atoms {
10294108×
            let e = array(a, "atom")?;
10304108×
            if e.len() != 4 {
10310×
                return Err(ObjectError::Schema("atom: expected 4 fields".into()));
10324108×
            }
10334108×
            let key = (oref(&e[0], "atom change")?, u32_(&e[1], "atom index")?);
10344108×
            let content = if e[2].is_null() {
10350×
                None
1036
            } else {
10374108×
                next += 1;
10384108×
                Some(LineRef::new(all.clone(), next - 1))
1039
            };
10404108×
            let deleted_by = array(&e[3], "deleters")?
10414108×
                .iter()
10424108×
                .map(|d| oref(d, "deleter"))
10434108×
                .collect::<Result<BTreeSet<_>, _>>()?;
10444108×
            g.atoms.insert(
10454108×
                key,
10464108×
                AtomInfo {
10474108×
                    content,
10484108×
                    deleted_by,
10494108×
                },
1050
            );
1051
        }
10525129×
        for e in array(field(m, 1, "edges")?, "edges")? {
10535129×
            let pair = array(e, "edge")?;
10545129×
            if pair.len() != 2 {
10550×
                return Err(ObjectError::Schema("edge: expected [from, to]".into()));
10565129×
            }
10575129×
            g.edge(vertex_from(&pair[0])?, vertex_from(&pair[1])?);
1058
        }
1059320×
        Ok(g)
1060320×
    }
1061
}
1062
1063
/// Split a plain run into `Lines` and `Zombie` segments around ghost atoms.
10641182×
fn split_zombies(atoms: Vec<AtomKey>, ghosts: &HashSet<AtomKey>) -> Vec<Segment> {
10651182×
    let mut out = Vec::new();
10661182×
    let mut lines: Vec<AtomKey> = Vec::new();
10671182×
    let mut i = 0;
10686553×
    while i < atoms.len() {
10695371×
        if !ghosts.contains(&atoms[i]) {
10704961×
            lines.push(atoms[i]);
10714961×
            i += 1;
10724961×
            continue;
1073410×
        }
1074410×
        if !lines.is_empty() {
1075297×
            out.push(Segment::Lines(std::mem::take(&mut lines)));
1076297×
        }
1077410×
        let mut deleted = Vec::new();
1078820×
        while i < atoms.len() && ghosts.contains(&atoms[i]) {
1079410×
            deleted.push(atoms[i]);
1080410×
            i += 1;
1081410×
        }
1082410×
        let mut inserted = Vec::new();
1083410×
        if i < atoms.len() {
1084358×
            let ch = atoms[i].0;
1085793×
            while i < atoms.len() && atoms[i].0 == ch && !ghosts.contains(&atoms[i]) {
1086435×
                inserted.push(atoms[i]);
1087435×
                i += 1;
1088435×
            }
108952×
        }
1090410×
        out.push(Segment::Zombie { deleted, inserted });
1091
    }
10921182×
    if !lines.is_empty() {
1093955×
        out.push(Segment::Lines(lines));
1094955×
    }
10951182×
    out
10961182×
}
1097
1098
#[cfg(test)]
1099
mod tests {
1100
    use super::*;
1101
    use crate::hash::ObjectRef;
1102
1103156×
    fn h(n: u8) -> ChangeHash {
1104156×
        ObjectRef([n; 32])
1105156×
    }
1106792×
    fn lines(v: &[&str]) -> Lines {
11071741×
        v.iter().map(|s| s.as_bytes()).collect()
1108792×
    }
110966×
    fn text(g: &FileGraph, m: &Materialized) -> String {
1110208×
        String::from_utf8(g.render(m, &|c| c.short(4))).unwrap()
111166×
    }
1112
1113
    struct Deps(HashMap<ChangeHash, Vec<ChangeHash>>);
1114
    impl DepOracle for Deps {
11152225×
        fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
11162225×
            if a == b {
11170×
                return true;
11182225×
            }
11192225×
            let mut stack = vec![*a];
11202225×
            let mut seen = HashSet::default();
112193557×
            while let Some(x) = stack.pop() {
112292945×
                if let Some(ds) = self.0.get(&x) {
112314479313×
                    for d in ds {
112414479313×
                        if d == b {
11251613×
                            return true;
112614477700×
                        }
112714477700×
                        if seen.insert(*d) {
1128454222×
                            stack.push(*d);
112914023478×
                        }
1130
                    }
1131612×
                }
1132
            }
1133612×
            false
11342225×
        }
1135
    }
1136
1137
    /// Linear history: each change depends on every earlier one.
1138
    struct Linear;
1139
    impl DepOracle for Linear {
1140220×
        fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
1141220×
            a.0[..2] >= b.0[..2]
1142220×
        }
1143
    }
1144
1145
    /// A file of `n` lines edited `revs` times, `per` lines replaced each
1146
    /// time, as a long-lived file is: most atoms dead, one live chain.
11471×
    fn edited_file(n: usize, revs: usize, per: usize) -> FileGraph {
1148401×
        let hn = |i: usize| {
1149401×
            let mut b = [0u8; 32];
1150401×
            b[..2].copy_from_slice(&(i as u16).to_be_bytes());
1151401×
            ObjectRef(b)
1152401×
        };
11531×
        let mut g = FileGraph::new();
1154500×
        let first: Vec<String> = (0..n).map(|i| format!("line {i}\n")).collect();
11551×
        let first: Vec<&str> = first.iter().map(String::as_str).collect();
11561×
        g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first));
115710×
        for r in 2..revs + 2 {
115810×
            let order = g.materialize(&Linear).live_atoms();
115910×
            let step = order.len() / per;
1160200×
            for j in 0..per {
1161200×
                let at = j * step + (r % step.max(1));
1162200×
                let k = order[at];
1163200×
                g.delete(
1164200×
                    hn(r),
1165200×
                    &AtomRange {
1166200×
                        change: k.0,
1167200×
                        from: k.1,
1168200×
                        to: k.1 + 1,
1169200×
                    },
1170
                );
1171200×
                let after = if at == 0 {
11720×
                    Vertex::Start
1173
                } else {
1174200×
                    Vertex::Atom(order[at - 1])
1175
                };
1176200×
                let before = order
1177200×
                    .get(at + 1)
1178200×
                    .map(|k| Vertex::Atom(*k))
1179200×
                    .unwrap_or(Vertex::End);
1180200×
                g.insert(hn(r), j as u32, after, before, &lines(&["new\n"]));
1181
            }
1182
        }
11831×
        g
11841×
    }
1185
1186
    /// The single-order shortcut gives what the general algorithm gives.
1187
    #[test]
11881×
    fn the_order_shortcut_agrees_with_the_general_order() {
11891×
        let g = edited_file(500, 10, 20);
11901×
        assert!(g.as_chain().is_none());
11911×
        let fast = g.as_order(&Linear).expect("a single order");
11921×
        assert_eq!(
11931×
            Materialized {
11941×
                segments: vec![Segment::Lines(fast)]
11951×
            },
11961×
            g.materialize_general(&Linear)
1197
        );
1198
        // Two changes inserting at one place concurrently: a conflict,
1199
        // which the shortcut leaves to the general algorithm.
12001×
        let mut g = FileGraph::new();
12011×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
12021×
        let a = Vertex::Atom((h(1), 0));
12031×
        g.insert(h(2), 0, a, Vertex::End, &lines(&["b\n"]));
12041×
        g.insert(h(3), 0, a, Vertex::End, &lines(&["c\n"]));
12051×
        assert!(g.as_order(&NoDeps).is_none());
12061×
        assert!(g.materialize(&NoDeps).is_conflicted());
12071×
    }
1208
1209
    /// Applied as the state applies a change's edits: the kept order is
1210
    /// spliced by them.
1211491×
    fn apply_spliced(
1212491×
        g: &mut FileGraph,
1213491×
        h: ChangeHash,
1214491×
        inserts: &[(Vertex, Vertex, usize)],
1215491×
        deleted: &[AtomKey],
1216491×
    ) {
1217491×
        let order = g.take_order();
1218491×
        let mut runs = Vec::new();
1219491×
        let mut next = 0u32;
1220554×
        for (i, &(a, b, n)) in inserts.iter().enumerate() {
1221947×
            let text: Vec<String> = (0..n).map(|j| format!("{i}.{j}\n")).collect();
1222554×
            let text: Vec<&str> = text.iter().map(String::as_str).collect();
1223554×
            g.insert(h, next, a, b, &lines(&text));
1224554×
            runs.push((a, b, next, n as u32));
1225554×
            next += n as u32;
1226
        }
12271003×
        for k in deleted {
12281003×
            g.delete(
12291003×
                h,
12301003×
                &AtomRange {
12311003×
                    change: k.0,
12321003×
                    from: k.1,
12331003×
                    to: k.1 + 1,
12341003×
                },
12351003×
            );
12361003×
        }
1237491×
        if let Some(o) = order {
1238458×
            g.keep_spliced(o, h, &runs, deleted);
1239458×
        }
1240491×
    }
1241
1242
    /// Edits as a diff makes them from the order `l`: hunks with a kept
1243
    /// line between them, each deleting some lines and inserting some.
1244457×
    fn random_edits(
1245457×
        l: &[AtomKey],
1246457×
        rnd: &mut impl FnMut(usize) -> usize,
1247457×
    ) -> (Vec<(Vertex, Vertex, usize)>, Vec<AtomKey>) {
1248457×
        let (mut inserts, mut deleted) = (Vec::new(), Vec::new());
1249457×
        let mut i = rnd(3);
12501247×
        while i <= l.len() {
1251790×
            let del = rnd(3).min(l.len() - i);
1252790×
            let add = rnd(3);
1253790×
            let after = if i == 0 {
1254169×
                Vertex::Start
1255
            } else {
1256621×
                Vertex::Atom(l[i - 1])
1257
            };
1258790×
            let before = l.get(i).map(|k| Vertex::Atom(*k)).unwrap_or(Vertex::End);
1259790×
            deleted.extend(&l[i..i + del]);
1260790×
            if add > 0 {
1261522×
                inserts.push((after, before, add));
1262522×
            }
1263790×
            i += del + 1 + rnd(6);
1264
        }
1265457×
        (inserts, deleted)
1266457×
    }
1267
1268
    /// The kept order, spliced change by change, is what the general
1269
    /// algorithm finds, through sequential edits and through concurrent
1270
    /// ones that leave conflicts and ghosts (where it must stand aside).
1271
    #[test]
12721×
    fn the_kept_order_follows_edits() {
1273490×
        let hn = |i: usize| {
1274490×
            let mut b = [0u8; 32];
1275490×
            b[..2].copy_from_slice(&(i as u16).to_be_bytes());
1276490×
            ObjectRef(b)
1277490×
        };
12781×
        let mut seed = 7u64;
12792827×
        let mut rnd = move |n: usize| {
12802827×
            seed = seed
12812827×
                .wrapping_mul(6364136223846793005)
12822827×
                .wrapping_add(1442695040888963407);
12832827×
            ((seed >> 33) as usize) % n.max(1)
12842827×
        };
12851×
        let mut deps = Deps(HashMap::default());
12861×
        let mut g = FileGraph::new();
128740×
        let first: Vec<String> = (0..40).map(|i| format!("{i}\n")).collect();
12881×
        let first: Vec<&str> = first.iter().map(String::as_str).collect();
12891×
        g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first));
12901×
        let mut known: Vec<ChangeHash> = vec![hn(1)];
12911×
        let mut c = 2;
12921×
        let (mut spliced, mut conflicted) = (0, 0);
1293400×
        for step in 0..400 {
1294400×
            let l = g.materialize(&deps).live_atoms();
1295
            // Now and then two changes from the same base, neither knowing
1296
            // the other.
1297400×
            let pair = step % 7 == 3;
1298400×
            let base = known.clone();
1299400×
            for _ in 0..if pair { 2 } else { 1 } {
1300457×
                let h = hn(c);
1301457×
                c += 1;
1302457×
                let (inserts, deleted) = random_edits(&l, &mut rnd);
1303457×
                deps.0.insert(h, base.clone());
1304457×
                apply_spliced(&mut g, h, &inserts, &deleted);
1305457×
                known.push(h);
1306457×
                if g.order.get().is_some() {
1307433×
                    spliced += 1;
1308433×
                }
1309457×
                let m = g.materialize(&deps);
1310457×
                assert_eq!(m, g.materialize_general(&deps), "step {step}");
1311457×
                if m.is_conflicted() {
131231×
                    conflicted += 1;
1313426×
                }
1314
            }
1315
            // A change that knows everything settles any conflict by
1316
            // rewriting the file.
1317400×
            if g.materialize(&deps).is_conflicted() {
131831×
                let h = hn(c);
131931×
                c += 1;
132031×
                let all = g.materialize(&deps).live_atoms();
132131×
                deps.0.insert(h, known.clone());
132231×
                apply_spliced(&mut g, h, &[(Vertex::Start, Vertex::End, 5)], &all);
132331×
                known.push(h);
132431×
                assert_eq!(g.materialize(&deps), g.materialize_general(&deps));
1325369×
            }
1326
        }
13271×
        assert!(
13281×
            spliced > 100 && conflicted > 0,
1329
            "{spliced} spliced, {conflicted} conflicted"
1330
        );
13311×
    }
1332
1333
    /// A change applied before the one whose line it deletes (as a merge
1334
    /// adds the other side, newest first): the line arrives dead, and the
1335
    /// kept order must not take it in.
1336
    #[test]
13371×
    fn the_kept_order_takes_no_line_that_arrives_deleted() {
13381×
        let mut deps = Deps(HashMap::default());
13391×
        let mut g = FileGraph::new();
13401×
        g.insert(
13411×
            h(1),
1342
            0,
13431×
            Vertex::Start,
13441×
            Vertex::End,
13451×
            &lines(&["a\n", "b\n", "c\n"]),
1346
        );
1347
        // A deletion, so the order is kept rather than walked as a chain.
13481×
        apply_spliced(&mut g, h(2), &[], &[(h(1), 2)]);
13491×
        deps.0.insert(h(2), vec![h(1)]);
13501×
        let _ = g.materialize(&deps);
13511×
        assert!(g.order.get().is_some());
13521×
        let (a, b) = (Vertex::Atom((h(1), 0)), Vertex::Atom((h(1), 1)));
1353
        // h(3) inserts x between a and b; h(4) deletes it. h(4) first.
13541×
        deps.0.insert(h(3), vec![h(2)]);
13551×
        deps.0.insert(h(4), vec![h(3)]);
13561×
        apply_spliced(&mut g, h(4), &[], &[(h(3), 0)]);
13571×
        apply_spliced(&mut g, h(3), &[(a, b, 1)], &[]);
13581×
        let m = g.materialize(&deps);
13591×
        assert_eq!(m, g.materialize_general(&deps));
13601×
        assert_eq!(text(&g, &m), "a\nb\n");
13611×
    }
1362
1363
    /// How long rendering a long-lived file takes (run by hand, in release).
1364
    #[test]
1365
    #[ignore]
13660×
    fn bench_materialize_edited_file() {
13670×
        let g = edited_file(20_000, 50, 100);
13680×
        let t = std::time::Instant::now();
13690×
        let mut n = 0;
13700×
        for _ in 0..20 {
13710×
            n += g.materialize(&Linear).live_atoms().len();
13720×
        }
13730×
        eprintln!(
1374
            "materialize: {:.2} ms each ({n} atoms)",
13750×
            t.elapsed().as_secs_f64() * 1000.0 / 20.0
1376
        );
13770×
    }
1378
1379
    /// The chain shortcut agrees with the general algorithm wherever it
1380
    /// applies, and stands aside wherever it does not.
1381
    #[test]
13821×
    fn the_chain_shortcut_agrees_with_the_general_order() {
13831×
        let mut g = FileGraph::new();
13841×
        g.insert(
13851×
            h(1),
1386
            0,
13871×
            Vertex::Start,
13881×
            Vertex::End,
13891×
            &lines(&["a\n", "b\n", "c\n"]),
1390
        );
13911×
        assert!(g.as_chain().is_some());
13921×
        assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps));
1393
        // Appended after the last line: still a chain.
13941×
        g.insert(
13951×
            h(2),
1396
            0,
13971×
            Vertex::Atom((h(1), 2)),
13981×
            Vertex::End,
13991×
            &lines(&["d\n"]),
1400
        );
14011×
        assert!(g.as_chain().is_some());
14021×
        assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps));
1403
        // Inserted between two lines: not a chain (a has two successors).
14041×
        g.insert(
14051×
            h(4),
1406
            0,
14071×
            Vertex::Atom((h(1), 0)),
14081×
            Vertex::Atom((h(1), 1)),
14091×
            &lines(&["x\n"]),
1410
        );
14111×
        assert!(g.as_chain().is_none());
14121×
        assert_eq!(text(&g, &g.materialize(&NoDeps)), "a\nx\nb\nc\nd\n");
1413
        // Two inserts at one anchor: a conflict, never a chain.
14141×
        let mut c = FileGraph::new();
14151×
        c.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
14161×
        c.insert(h(2), 0, Vertex::Start, Vertex::End, &lines(&["b\n"]));
14171×
        assert!(c.as_chain().is_none());
14181×
        assert!(c.materialize(&NoDeps).is_conflicted());
1419
        // A deletion: not a chain.
14201×
        let mut d = FileGraph::new();
14211×
        d.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
14221×
        d.delete(
14231×
            h(3),
14241×
            &AtomRange {
14251×
                change: h(1),
14261×
                from: 0,
14271×
                to: 1,
14281×
            },
1429
        );
14301×
        assert!(d.as_chain().is_none());
14311×
        assert_eq!(d.materialize(&NoDeps), d.materialize_general(&NoDeps));
1432
        // Empty.
14331×
        let e = FileGraph::new();
14341×
        assert_eq!(e.materialize(&NoDeps), e.materialize_general(&NoDeps));
14351×
    }
1436
1437
    #[test]
14381×
    fn sequential_inserts_and_deletes() {
14391×
        let mut g = FileGraph::new();
14401×
        g.insert(
14411×
            h(1),
1442
            0,
14431×
            Vertex::Start,
14441×
            Vertex::End,
14451×
            &lines(&["a\n", "b\n", "c\n"]),
1446
        );
14471×
        let m = g.materialize(&NoDeps);
14481×
        assert!(!m.is_conflicted());
14491×
        assert_eq!(text(&g, &m), "a\nb\nc\n");
1450
1451
        // Change 2 depends on 1, inserts between b and c and deletes a.
14521×
        g.insert(
14531×
            h(2),
1454
            0,
14551×
            Vertex::Atom((h(1), 1)),
14561×
            Vertex::Atom((h(1), 2)),
14571×
            &lines(&["x\n"]),
1458
        );
14591×
        g.delete(
14601×
            h(2),
14611×
            &AtomRange {
14621×
                change: h(1),
14631×
                from: 0,
14641×
                to: 1,
14651×
            },
1466
        );
14671×
        let deps = Deps([(h(2), vec![h(1)])].into_iter().collect());
14681×
        let m = g.materialize(&deps);
14691×
        assert!(!m.is_conflicted());
14701×
        assert_eq!(text(&g, &m), "b\nx\nc\n");
14711×
        assert_eq!(m.live_atoms(), vec![(h(1), 1), (h(2), 0), (h(1), 2)]);
14721×
    }
1473
1474
    #[test]
14751×
    fn concurrent_inserts_at_same_anchor_conflict() {
14761×
        let mut g = FileGraph::new();
14771×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
14781×
        g.insert(
14791×
            h(2),
1480
            0,
14811×
            Vertex::Atom((h(1), 0)),
14821×
            Vertex::Atom((h(1), 1)),
14831×
            &lines(&["from-2\n"]),
1484
        );
14851×
        g.insert(
14861×
            h(3),
1487
            0,
14881×
            Vertex::Atom((h(1), 0)),
14891×
            Vertex::Atom((h(1), 1)),
14901×
            &lines(&["from-3\n", "more-3\n"]),
1491
        );
14921×
        let deps = Deps(
14931×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
14941×
                .into_iter()
14951×
                .collect(),
14961×
        );
14971×
        let m = g.materialize(&deps);
14981×
        assert!(m.is_conflicted());
14991×
        assert_eq!(m.conflict_count(), 1);
15001×
        assert_eq!(
1501
            m.segments,
15021×
            vec![
15031×
                Segment::Lines(vec![(h(1), 0)]),
15041×
                Segment::Conflict(vec![vec![(h(2), 0)], vec![(h(3), 0), (h(3), 1)]]),
15051×
                Segment::Lines(vec![(h(1), 1)]),
1506
            ]
1507
        );
15081×
        let t = text(&g, &m);
15091×
        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}");
15101×
    }
1511
1512
    #[test]
15131×
    fn concurrent_inserts_at_different_anchors_do_not_conflict() {
15141×
        let mut g = FileGraph::new();
15151×
        g.insert(
15161×
            h(1),
1517
            0,
15181×
            Vertex::Start,
15191×
            Vertex::End,
15201×
            &lines(&["a\n", "b\n", "c\n"]),
1521
        );
15221×
        g.insert(
15231×
            h(2),
1524
            0,
15251×
            Vertex::Start,
15261×
            Vertex::Atom((h(1), 0)),
15271×
            &lines(&["top\n"]),
1528
        );
15291×
        g.insert(
15301×
            h(3),
1531
            0,
15321×
            Vertex::Atom((h(1), 2)),
15331×
            Vertex::End,
15341×
            &lines(&["bottom\n"]),
1535
        );
15361×
        let deps = Deps(
15371×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
15381×
                .into_iter()
15391×
                .collect(),
15401×
        );
15411×
        let m = g.materialize(&deps);
15421×
        assert!(!m.is_conflicted());
15431×
        assert_eq!(text(&g, &m), "top\na\nb\nc\nbottom\n");
15441×
    }
1545
1546
    #[test]
15471×
    fn concurrent_deletes_of_the_same_line_agree() {
15481×
        let mut g = FileGraph::new();
15491×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
15501×
        g.delete(
15511×
            h(2),
15521×
            &AtomRange {
15531×
                change: h(1),
15541×
                from: 0,
15551×
                to: 1,
15561×
            },
1557
        );
15581×
        g.delete(
15591×
            h(3),
15601×
            &AtomRange {
15611×
                change: h(1),
15621×
                from: 0,
15631×
                to: 1,
15641×
            },
1565
        );
15661×
        let deps = Deps(
15671×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
15681×
                .into_iter()
15691×
                .collect(),
15701×
        );
15711×
        let m = g.materialize(&deps);
15721×
        assert!(!m.is_conflicted());
15731×
        assert_eq!(text(&g, &m), "b\n");
15741×
    }
1575
1576
    #[test]
15771×
    fn delete_insert_conflict_is_a_zombie() {
15781×
        let mut g = FileGraph::new();
15791×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
1580
        // 2 deletes a; 3 (concurrent) inserts after a.
15811×
        g.delete(
15821×
            h(2),
15831×
            &AtomRange {
15841×
                change: h(1),
15851×
                from: 0,
15861×
                to: 1,
15871×
            },
1588
        );
15891×
        g.insert(
15901×
            h(3),
1591
            0,
15921×
            Vertex::Atom((h(1), 0)),
15931×
            Vertex::Atom((h(1), 1)),
15941×
            &lines(&["after-a\n"]),
1595
        );
15961×
        let deps = Deps(
15971×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
15981×
                .into_iter()
15991×
                .collect(),
16001×
        );
16011×
        let m = g.materialize(&deps);
16021×
        assert!(m.is_conflicted());
16031×
        assert_eq!(
1604
            m.segments,
16051×
            vec![
16061×
                Segment::Zombie {
16071×
                    deleted: vec![(h(1), 0)],
16081×
                    inserted: vec![(h(3), 0)]
16091×
                },
16101×
                Segment::Lines(vec![(h(1), 1)]),
1611
            ]
1612
        );
16131×
        let t = text(&g, &m);
16141×
        assert!(t.contains("-------\na\n======="), "{t}");
16151×
        assert!(t.contains("after-a\n>>>>>>>"), "{t}");
1616
1617
        // If the deleter saw the insert (depends on 3), there is no conflict.
16181×
        let deps = Deps(
16191×
            [(h(2), vec![h(1), h(3)]), (h(3), vec![h(1)])]
16201×
                .into_iter()
16211×
                .collect(),
16221×
        );
16231×
        let m = g.materialize(&deps);
16241×
        assert!(!m.is_conflicted());
16251×
        assert_eq!(text(&g, &m), "after-a\nb\n");
16261×
    }
1627
1628
    #[test]
16291×
    fn insert_after_a_line_deleted_by_a_dependency_is_not_a_conflict() {
1630
        // 1 inserts a,b; 2 deletes b (depends on 1); 3 depends on 2 yet anchors
1631
        // after the dead b (a rewritten change may do this): no conflict.
16321×
        let mut g = FileGraph::new();
16331×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
16341×
        g.delete(
16351×
            h(2),
16361×
            &AtomRange {
16371×
                change: h(1),
16381×
                from: 1,
16391×
                to: 2,
16401×
            },
1641
        );
16421×
        g.insert(
16431×
            h(3),
1644
            0,
16451×
            Vertex::Atom((h(1), 1)),
16461×
            Vertex::End,
16471×
            &lines(&["c\n"]),
1648
        );
16491×
        let deps = Deps(
16501×
            [(h(2), vec![h(1)]), (h(3), vec![h(2)])]
16511×
                .into_iter()
16521×
                .collect(),
16531×
        );
16541×
        let m = g.materialize(&deps);
16551×
        assert!(!m.is_conflicted(), "{:?}", m.segments);
16561×
        assert_eq!(text(&g, &m), "a\nc\n");
1657
        // A change that deletes a line it introduced itself, with a dependent
1658
        // anchoring after it, is likewise fine.
16591×
        let mut g = FileGraph::new();
16601×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["x\n"]));
16611×
        g.delete(
16621×
            h(1),
16631×
            &AtomRange {
16641×
                change: h(1),
16651×
                from: 0,
16661×
                to: 1,
16671×
            },
1668
        );
16691×
        g.insert(
16701×
            h(2),
1671
            0,
16721×
            Vertex::Atom((h(1), 0)),
16731×
            Vertex::End,
16741×
            &lines(&["y\n"]),
1675
        );
16761×
        let deps = Deps([(h(2), vec![h(1)])].into_iter().collect());
16771×
        let m = g.materialize(&deps);
16781×
        assert!(!m.is_conflicted());
16791×
        assert_eq!(text(&g, &m), "y\n");
16801×
    }
1681
1682
    #[test]
16831×
    fn resolution_collapses_a_conflict() {
16841×
        let mut g = FileGraph::new();
16851×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
16861×
        g.insert(
16871×
            h(2),
1688
            0,
16891×
            Vertex::Start,
16901×
            Vertex::Atom((h(1), 0)),
16911×
            &lines(&["x\n"]),
1692
        );
16931×
        g.insert(
16941×
            h(3),
1695
            0,
16961×
            Vertex::Start,
16971×
            Vertex::Atom((h(1), 0)),
16981×
            &lines(&["y\n"]),
1699
        );
17001×
        let mut deps: HashMap<_, _> = [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
17011×
            .into_iter()
17021×
            .collect();
17031×
        assert!(g.materialize(&Deps(deps.clone())).is_conflicted());
1704
        // Resolution keeps y, drops x.
17051×
        g.delete(
17061×
            h(4),
17071×
            &AtomRange {
17081×
                change: h(2),
17091×
                from: 0,
17101×
                to: 1,
17111×
            },
1712
        );
17131×
        deps.insert(h(4), vec![h(2), h(3)]);
17141×
        let m = g.materialize(&Deps(deps));
17151×
        assert!(!m.is_conflicted());
17161×
        assert_eq!(text(&g, &m), "y\na\n");
17171×
    }
1718
1719
    #[test]
17201×
    fn empty_and_unterminated_files() {
17211×
        let g = FileGraph::new();
17221×
        let m = g.materialize(&NoDeps);
17231×
        assert_eq!(m.segments, vec![]);
17241×
        assert_eq!(text(&g, &m), "");
17251×
        let mut g = FileGraph::new();
17261×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["no newline"]));
17271×
        assert_eq!(text(&g, &g.materialize(&NoDeps)), "no newline");
17281×
    }
1729
1730
    #[test]
17311×
    fn serialization_round_trip() {
17321×
        let mut g = FileGraph::new();
17331×
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
17341×
        g.insert(
17351×
            h(2),
1736
            0,
17371×
            Vertex::Atom((h(1), 0)),
17381×
            Vertex::Atom((h(1), 1)),
17391×
            &lines(&["x\n"]),
1740
        );
17411×
        g.delete(
17421×
            h(3),
17431×
            &AtomRange {
17441×
                change: h(1),
17451×
                from: 1,
17461×
                to: 2,
17471×
            },
1748
        );
17491×
        let v = g.to_value();
17501×
        let bytes = crate::cbor::encode(&v);
17511×
        let back = FileGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap();
17521×
        let deps = Deps(
17531×
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
17541×
                .into_iter()
17551×
                .collect(),
17561×
        );
17571×
        assert_eq!(back.materialize(&deps), g.materialize(&deps));
17581×
        assert_eq!(text(&back, &back.materialize(&deps)), "a\nx\n");
17591×
        assert_eq!(
17601×
            crate::cbor::encode(&back.to_value()),
1761
            bytes,
1762
            "deterministic"
1763
        );
17641×
    }
1765
1766
    /// Spec §7.3: the graph is a function of the set of changes, not their order.
1767
    #[test]
17681×
    fn application_order_is_irrelevant() {
1769
        use rand::seq::SliceRandom;
1770
        type Op = (
1771
            ChangeHash,
1772
            Vec<(u32, Vertex, Vertex, Lines)>,
1773
            Vec<AtomRange>,
1774
        );
17751×
        let ops: Vec<Op> = vec![
17761×
            (
17771×
                h(1),
17781×
                vec![(0, Vertex::Start, Vertex::End, lines(&["a\n", "b\n", "c\n"]))],
17791×
                vec![],
17801×
            ),
17811×
            (
17821×
                h(2),
17831×
                vec![(
17841×
                    0,
17851×
                    Vertex::Atom((h(1), 0)),
17861×
                    Vertex::Atom((h(1), 1)),
17871×
                    lines(&["p\n"]),
17881×
                )],
17891×
                vec![],
17901×
            ),
17911×
            (
17921×
                h(3),
17931×
                vec![(
17941×
                    0,
17951×
                    Vertex::Atom((h(1), 0)),
17961×
                    Vertex::Atom((h(1), 1)),
17971×
                    lines(&["q\n", "r\n"]),
17981×
                )],
17991×
                vec![AtomRange {
18001×
                    change: h(1),
18011×
                    from: 2,
18021×
                    to: 3,
18031×
                }],
18041×
            ),
18051×
            (
18061×
                h(4),
18071×
                vec![(
18081×
                    0,
18091×
                    Vertex::Atom((h(3), 1)),
18101×
                    Vertex::Atom((h(1), 1)),
18111×
                    lines(&["s\n"]),
18121×
                )],
18131×
                vec![AtomRange {
18141×
                    change: h(1),
18151×
                    from: 1,
18161×
                    to: 2,
18171×
                }],
18181×
            ),
18191×
            (
18201×
                h(5),
18211×
                vec![(0, Vertex::Atom((h(1), 2)), Vertex::End, lines(&["z\n"]))],
18221×
                vec![],
18231×
            ),
1824
        ];
18251×
        let deps = Deps(
18261×
            [
18271×
                (h(2), vec![h(1)]),
18281×
                (h(3), vec![h(1)]),
18291×
                (h(4), vec![h(3), h(1)]),
18301×
                (h(5), vec![h(1)]),
18311×
            ]
18321×
            .into_iter()
18331×
            .collect(),
18341×
        );
183551×
        let build = |order: &[usize]| {
183651×
            let mut g = FileGraph::new();
1837255×
            for &i in order {
1838255×
                let (c, ins, dels) = &ops[i];
1839255×
                for (idx, a, b, l) in ins {
1840255×
                    g.insert(*c, *idx, *a, *b, l);
1841255×
                }
1842255×
                for d in dels {
1843102×
                    g.delete(*c, d);
1844102×
                }
1845
            }
184651×
            let m = g.materialize(&deps);
184751×
            (m.clone(), text(&g, &m))
184851×
        };
18491×
        let reference = build(&[0, 1, 2, 3, 4]);
18501×
        assert!(reference.0.is_conflicted());
18511×
        let mut rng = rand::thread_rng();
18521×
        let mut order: Vec<usize> = (0..ops.len()).collect();
18531×
        for _ in 0..50 {
185450×
            order.shuffle(&mut rng);
185550×
            assert_eq!(build(&order), reference, "order {order:?}");
1856
        }
18571×
    }
1858
}