OXYGENHUB
# 01a47cd7f0c8 oxygen / crates / oxy-core / src / graph.rs
38.1 KiB · Rust · last changed Keep an insert's lines in one shared buffer 1 month ago source blame history raw
ff0af1ee unknown 1
//! The per-file atom graph (spec §7.3), its materialization, and text conflict
2
//! detection (spec §9.1, §9.5).
3
//!
4
//! The graph is a pure function of the set of changes applied to it: inserts
5
//! add vertices and edges, deletes set a flag, and nothing is ever removed.
6
0bdc3f91 Andy McCall ↶ 7
use crate::fasthash::{FastMap as HashMap, FastSet as HashSet};
ff0af1ee unknown 8
use crate::hash::ChangeHash;
01a47cd7 Andy McCall ↶ 9
use crate::lines::{LineRef, Lines};
ff0af1ee unknown 10
use crate::objects::AtomRange;
0bdc3f91 Andy McCall ↶ 11
use std::collections::{BTreeMap, BTreeSet};
ff0af1ee unknown 12
13
/// `(introducing change, index within that change for this node)`.
14
pub type AtomKey = (ChangeHash, u32);
15
16
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
17
pub enum Vertex {
18
    Start,
19
    Atom(AtomKey),
20
    End,
21
}
22
23
/// Transitive dependency test between changes; `depends_on(a, a)` is true.
24
pub trait DepOracle {
25
    fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool;
26
}
27
28
/// No change depends on any other: every pair commutes.
29
pub struct NoDeps;
30
impl DepOracle for NoDeps {
31
    fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
32
        a == b
33
    }
34
}
35
36
#[derive(Default, Clone, Debug)]
37
struct AtomInfo {
01a47cd7 Andy McCall ↶ 38
    /// `None` until the introducing change has been applied; a line of the
39
    /// insert's shared buffer.
40
    content: Option<LineRef>,
ff0af1ee unknown 41
    deleted_by: BTreeSet<ChangeHash>,
42
}
43
44
#[derive(Clone, Debug)]
45
pub struct FileGraph {
46
    atoms: HashMap<AtomKey, AtomInfo>,
0bdc3f91 Andy McCall ↶ 47
    /// Every vertex, with its direct successors.
48
    succ: HashMap<Vertex, Edges>,
49
}
50
51
/// A vertex's direct successors, sorted and without repeats. Nearly every
52
/// vertex has one, which is kept inline without an allocation.
53
#[derive(Clone, Debug, Default, PartialEq, Eq)]
54
enum Edges {
55
    #[default]
56
    None,
57
    One(Vertex),
58
    Many(Vec<Vertex>),
ff0af1ee unknown 59
}
60
0bdc3f91 Andy McCall ↶ 61
impl Edges {
62
    fn insert(&mut self, v: Vertex) {
63
        match self {
64
            Edges::None => *self = Edges::One(v),
65
            Edges::One(a) if *a == v => {}
66
            Edges::One(a) => {
67
                let a = *a;
68
                *self = Edges::Many(if a < v { vec![a, v] } else { vec![v, a] });
69
            }
70
            Edges::Many(list) => {
71
                if let Err(i) = list.binary_search(&v) {
72
                    list.insert(i, v);
73
                }
74
            }
75
        }
76
    }
77
78
    fn as_slice(&self) -> &[Vertex] {
79
        match self {
80
            Edges::None => &[],
81
            Edges::One(v) => std::slice::from_ref(v),
82
            Edges::Many(list) => list,
83
        }
84
    }
85
86
    fn iter(&self) -> std::slice::Iter<'_, Vertex> {
87
        self.as_slice().iter()
88
    }
89
}
90
ff0af1ee unknown 91
impl Default for FileGraph {
92
    fn default() -> Self {
93
        Self::new()
94
    }
95
}
96
97
/// One piece of a materialized file.
98
#[derive(Clone, Debug, PartialEq, Eq)]
99
pub enum Segment {
100
    Lines(Vec<AtomKey>),
101
    /// Order conflict: alternatives that cannot be ordered against each other.
102
    Conflict(Vec<Vec<AtomKey>>),
103
    /// Delete/insert conflict: `inserted` was anchored to `deleted` by a change
104
    /// that commutes with the deletion.
75167672 Andy McCall 105
    Zombie {
106
        deleted: Vec<AtomKey>,
107
        inserted: Vec<AtomKey>,
108
    },
ff0af1ee unknown 109
}
110
111
#[derive(Clone, Debug, PartialEq, Eq, Default)]
112
pub struct Materialized {
113
    pub segments: Vec<Segment>,
114
}
115
116
impl Materialized {
117
    pub fn is_conflicted(&self) -> bool {
75167672 Andy McCall 118
        self.segments
119
            .iter()
120
            .any(|s| !matches!(s, Segment::Lines(_)))
ff0af1ee unknown 121
    }
122
    pub fn conflict_count(&self) -> usize {
75167672 Andy McCall 123
        self.segments
124
            .iter()
125
            .filter(|s| !matches!(s, Segment::Lines(_)))
126
            .count()
ff0af1ee unknown 127
    }
128
    /// Every live atom in materialized order, conflict sides concatenated.
129
    pub fn live_atoms(&self) -> Vec<AtomKey> {
130
        let mut out = Vec::new();
131
        for s in &self.segments {
132
            match s {
133
                Segment::Lines(a) => out.extend(a),
134
                Segment::Conflict(sides) => sides.iter().for_each(|x| out.extend(x)),
135
                Segment::Zombie { inserted, .. } => out.extend(inserted),
136
            }
137
        }
138
        out
139
    }
140
}
141
142
impl FileGraph {
143
    pub fn new() -> Self {
75167672 Andy McCall 144
        let mut g = FileGraph {
0bdc3f91 Andy McCall ↶ 145
            atoms: HashMap::default(),
146
            succ: HashMap::default(),
75167672 Andy McCall 147
        };
ff0af1ee unknown 148
        g.edge(Vertex::Start, Vertex::End);
149
        g
150
    }
151
152
    fn edge(&mut self, a: Vertex, b: Vertex) {
153
        self.succ.entry(a).or_default().insert(b);
154
        self.succ.entry(b).or_default();
155
    }
156
157
    /// Apply an `Insert` by `change` whose first new atom has index `first_index`.
75167672 Andy McCall 158
    pub fn insert(
159
        &mut self,
160
        change: ChangeHash,
161
        first_index: u32,
162
        after: Vertex,
163
        before: Vertex,
01a47cd7 Andy McCall ↶ 164
        lines: &Lines,
0bdc3f91 Andy McCall ↶ 165
    ) {
01a47cd7 Andy McCall ↶ 166
        self.insert_owned(change, first_index, after, before, lines.clone());
0bdc3f91 Andy McCall ↶ 167
    }
168
169
    /// [`FileGraph::insert`] taking the lines, so loading a state moves
170
    /// them in rather than copying. Each new atom's entry is made once, as
171
    /// the source of the next edge, and the chain's ends are ensured last.
172
    pub fn insert_owned(
173
        &mut self,
174
        change: ChangeHash,
175
        first_index: u32,
176
        after: Vertex,
177
        before: Vertex,
01a47cd7 Andy McCall ↶ 178
        lines: Lines,
75167672 Andy McCall 179
    ) {
0bdc3f91 Andy McCall ↶ 180
        self.atoms.reserve(lines.len());
181
        self.succ.reserve(lines.len() + 1);
ff0af1ee unknown 182
        let mut prev = after;
01a47cd7 Andy McCall ↶ 183
        for i in 0..lines.len() {
ff0af1ee unknown 184
            let key = (change, first_index + i as u32);
01a47cd7 Andy McCall ↶ 185
            self.atoms.entry(key).or_default().content =
186
                Some(LineRef::new(lines.clone(), i as u32));
ff0af1ee unknown 187
            let v = Vertex::Atom(key);
0bdc3f91 Andy McCall ↶ 188
            self.succ.entry(prev).or_default().insert(v);
ff0af1ee unknown 189
            prev = v;
190
        }
0bdc3f91 Andy McCall ↶ 191
        self.succ.entry(prev).or_default().insert(before);
192
        self.succ.entry(before).or_default();
ff0af1ee unknown 193
    }
194
195
    /// Apply a `Delete` of `range` by change `by`.
196
    pub fn delete(&mut self, by: ChangeHash, range: &AtomRange) {
197
        for i in range.from..range.to {
75167672 Andy McCall 198
            self.atoms
199
                .entry((range.change, i))
200
                .or_default()
201
                .deleted_by
202
                .insert(by);
ff0af1ee unknown 203
        }
204
    }
205
206
    pub fn is_live(&self, k: &AtomKey) -> bool {
75167672 Andy McCall 207
        self.atoms
208
            .get(k)
209
            .is_some_and(|a| a.content.is_some() && a.deleted_by.is_empty())
ff0af1ee unknown 210
    }
211
212
    pub fn content(&self, k: &AtomKey) -> Option<&[u8]> {
01a47cd7 Andy McCall ↶ 213
        self.atoms
214
            .get(k)
215
            .and_then(|a| a.content.as_ref().map(LineRef::bytes))
ff0af1ee unknown 216
    }
217
218
    pub fn deleters(&self, k: &AtomKey) -> impl Iterator<Item = &ChangeHash> {
75167672 Andy McCall 219
        self.atoms
220
            .get(k)
221
            .into_iter()
222
            .flat_map(|a| a.deleted_by.iter())
ff0af1ee unknown 223
    }
224
225
    /// Number of atoms introduced by `change` in this graph (for index bookkeeping).
226
    pub fn atom_count(&self) -> usize {
227
        self.atoms.len()
228
    }
229
62794ecc Andy McCall ↶ 230
    /// The changes that inserted a live atom directly after `k`. A change
231
    /// that deletes `k` must depend on them (spec §8.1), or `is_ghost`
232
    /// reads the deletion as concurrent with those insertions.
83d61e80 Andy McCall ↶ 233
    /// Every atom on some path from `first` to `last`, both included, live
234
    /// or deleted (spec §8): the atoms a range of lines is made of, and
235
    /// those that were deleted from between them. Empty when `last` does
236
    /// not follow `first`.
237
    pub fn between(&self, first: &AtomKey, last: &AtomKey) -> Vec<AtomKey> {
238
        let (from, to) = (Vertex::Atom(*first), Vertex::Atom(*last));
239
        let mut ahead: HashSet<Vertex> = HashSet::default();
240
        let mut stack = vec![from];
241
        while let Some(v) = stack.pop() {
242
            if ahead.insert(v) {
243
                stack.extend(
244
                    self.succ
245
                        .get(&v)
246
                        .map(|e| e.as_slice())
247
                        .unwrap_or(&[])
248
                        .iter()
249
                        .copied(),
250
                );
251
            }
252
        }
253
        if !ahead.contains(&to) {
254
            return vec![];
255
        }
256
        let mut pred: HashMap<Vertex, Vec<Vertex>> = HashMap::default();
257
        for (u, es) in &self.succ {
258
            if ahead.contains(u) {
259
                for w in es.iter() {
260
                    pred.entry(*w).or_default().push(*u);
261
                }
262
            }
263
        }
264
        let mut behind: HashSet<Vertex> = HashSet::default();
265
        let mut stack = vec![to];
266
        while let Some(v) = stack.pop() {
267
            if behind.insert(v) {
268
                stack.extend(pred.get(&v).into_iter().flatten().copied());
269
            }
270
        }
271
        let mut out: Vec<AtomKey> = ahead
272
            .intersection(&behind)
273
            .filter_map(|v| match v {
274
                Vertex::Atom(k) if self.atoms.contains_key(k) => Some(*k),
275
                _ => None,
276
            })
277
            .collect();
278
        out.sort();
279
        out
280
    }
281
282
    /// The changes that inserted or deleted any of `atoms`.
283
    pub fn touching(&self, atoms: &[AtomKey]) -> BTreeSet<ChangeHash> {
284
        let mut out = BTreeSet::new();
285
        for k in atoms {
286
            out.insert(k.0);
287
            if let Some(info) = self.atoms.get(k) {
288
                out.extend(info.deleted_by.iter().copied());
289
            }
290
        }
291
        out
292
    }
293
62794ecc Andy McCall ↶ 294
    pub fn live_successor_changes(&self, k: &AtomKey) -> Vec<ChangeHash> {
295
        self.succ
296
            .get(&Vertex::Atom(*k))
297
            .map(|ss| {
298
                ss.iter()
299
                    .filter_map(|s| match s {
300
                        Vertex::Atom(y) if self.is_live(y) => Some(y.0),
301
                        _ => None,
302
                    })
303
                    .collect()
304
            })
305
            .unwrap_or_default()
306
    }
307
ff0af1ee unknown 308
    fn is_ghost(&self, k: &AtomKey, info: &AtomInfo, deps: &dyn DepOracle) -> bool {
23b217d7 Andy McCall ↶ 309
        // A deleted atom is a ghost when a live direct successor was inserted
310
        // by a change that commutes with every deleter: neither depends on
311
        // the other (spec §9.1, delete/insert). If the inserter came after the
312
        // deletion, or the deleter saw the insertion, there is no conflict.
ff0af1ee unknown 313
        self.succ.get(&Vertex::Atom(*k)).is_some_and(|ss| {
314
            ss.iter().any(|s| match s {
75167672 Andy McCall 315
                Vertex::Atom(y) if self.is_live(y) => !info
316
                    .deleted_by
317
                    .iter()
318
                    .any(|d| deps.depends_on(d, &y.0) || deps.depends_on(&y.0, d)),
ff0af1ee unknown 319
                _ => false,
320
            })
321
        })
322
    }
323
324
    /// Successors of `v` in the graph with non-kept vertices contracted away.
75167672 Andy McCall 325
    fn reduced_succ(
326
        &self,
327
        v: Vertex,
328
        kept: &HashSet<Vertex>,
329
        memo: &mut HashMap<Vertex, BTreeSet<Vertex>>,
330
    ) -> BTreeSet<Vertex> {
ff0af1ee unknown 331
        let mut out = BTreeSet::new();
75167672 Andy McCall 332
        let mut stack: Vec<Vertex> = self
333
            .succ
334
            .get(&v)
335
            .map(|s| s.iter().copied().collect())
336
            .unwrap_or_default();
0bdc3f91 Andy McCall ↶ 337
        let mut seen = HashSet::default();
ff0af1ee unknown 338
        while let Some(s) = stack.pop() {
339
            if kept.contains(&s) {
340
                out.insert(s);
341
            } else if seen.insert(s) {
342
                if let Some(m) = memo.get(&s) {
343
                    out.extend(m.iter().copied());
344
                } else if let Some(next) = self.succ.get(&s) {
345
                    stack.extend(next.iter().copied());
346
                }
347
            }
348
        }
349
        out
350
    }
351
352
    /// Order the live atoms and locate conflicts.
353
    pub fn materialize(&self, deps: &dyn DepOracle) -> Materialized {
354
        // 1. Vertices that survive contraction: live atoms, ghosts, Start, End.
0bdc3f91 Andy McCall ↶ 355
        let mut kept: HashSet<Vertex> = HashSet::default();
356
        let mut ghosts: HashSet<AtomKey> = HashSet::default();
ff0af1ee unknown 357
        kept.insert(Vertex::Start);
358
        kept.insert(Vertex::End);
359
        for (k, info) in &self.atoms {
360
            if info.content.is_none() {
361
                continue;
362
            }
363
            if info.deleted_by.is_empty() {
364
                kept.insert(Vertex::Atom(*k));
365
            } else if self.is_ghost(k, info, deps) {
366
                kept.insert(Vertex::Atom(*k));
367
                ghosts.insert(*k);
368
            }
369
        }
370
371
        // 2. Reduced graph over kept vertices.
0bdc3f91 Andy McCall ↶ 372
        let mut memo = HashMap::default();
373
        let mut rsucc: HashMap<Vertex, BTreeSet<Vertex>> = HashMap::default();
374
        let mut indeg: HashMap<Vertex, usize> = HashMap::default();
ff0af1ee unknown 375
        for &u in &kept {
376
            indeg.entry(u).or_insert(0);
377
            let rs = self.reduced_succ(u, &kept, &mut memo);
378
            for &w in &rs {
379
                *indeg.entry(w).or_insert(0) += 1;
380
            }
381
            rsucc.insert(u, rs);
382
        }
383
384
        // 3. Kahn's algorithm with side labelling for conflict regions.
75167672 Andy McCall 385
        let mut ready: BTreeSet<Vertex> = indeg
386
            .iter()
387
            .filter(|(_, &d)| d == 0)
388
            .map(|(v, _)| *v)
389
            .collect();
0bdc3f91 Andy McCall ↶ 390
        let mut side: HashMap<Vertex, u32> = HashMap::default();
391
        let mut joins: HashSet<Vertex> = HashSet::default();
ff0af1ee unknown 392
        let mut next_side = 0u32;
393
        let mut plain: Vec<Vertex> = Vec::new();
394
        let mut conflict: Option<BTreeMap<u32, Vec<Vertex>>> = None;
395
        let mut segments: Vec<Segment> = Vec::new();
396
397
        let flush_plain = |plain: &mut Vec<Vertex>, segments: &mut Vec<Segment>| {
75167672 Andy McCall 398
            let atoms: Vec<AtomKey> = plain
399
                .iter()
400
                .filter_map(|v| {
401
                    if let Vertex::Atom(k) = v {
402
                        Some(*k)
403
                    } else {
404
                        None
405
                    }
406
                })
407
                .collect();
ff0af1ee unknown 408
            plain.clear();
409
            if !atoms.is_empty() {
410
                segments.extend(split_zombies(atoms, &ghosts));
411
            }
412
        };
413
        let finish_conflict = |c: BTreeMap<u32, Vec<Vertex>>, segments: &mut Vec<Segment>| {
414
            let sides: Vec<Vec<AtomKey>> = c
415
                .into_values()
416
                .map(|vs| {
417
                    vs.into_iter()
418
                        .filter_map(|v| match v {
419
                            Vertex::Atom(k) if !ghosts.contains(&k) => Some(k),
420
                            _ => None,
421
                        })
422
                        .collect::<Vec<_>>()
423
                })
424
                .filter(|s| !s.is_empty())
425
                .collect();
426
            match sides.len() {
427
                0 => {}
428
                1 => segments.push(Segment::Lines(sides.into_iter().next().unwrap())),
429
                _ => segments.push(Segment::Conflict(sides)),
430
            }
431
        };
432
433
        while !ready.is_empty() {
434
            if ready.len() >= 2 {
435
                if conflict.is_none() {
436
                    flush_plain(&mut plain, &mut segments);
437
                    conflict = Some(BTreeMap::new());
438
                }
439
                for r in &ready {
440
                    if !side.contains_key(r) {
441
                        side.insert(*r, next_side);
442
                        next_side += 1;
443
                    }
444
                }
445
            }
446
            let v = ready.pop_first().unwrap();
447
            let my_side = side.get(&v).copied();
448
            match (&mut conflict, my_side) {
449
                (Some(c), Some(s)) => c.entry(s).or_default().push(v),
450
                (Some(_), None) => {
451
                    let c = conflict.take().unwrap();
452
                    finish_conflict(c, &mut segments);
453
                    plain.push(v);
454
                }
455
                (None, _) => plain.push(v),
456
            }
457
            for &w in &rsucc[&v] {
458
                let d = indeg.get_mut(&w).unwrap();
459
                *d -= 1;
460
                if conflict.is_some() && !joins.contains(&w) {
461
                    match (my_side, side.get(&w).copied()) {
462
                        (Some(s), None) => {
463
                            side.insert(w, s);
464
                        }
465
                        (Some(s), Some(t)) if s != t => {
466
                            side.remove(&w);
467
                            joins.insert(w);
468
                        }
469
                        (None, Some(_)) => {
470
                            side.remove(&w);
471
                            joins.insert(w);
472
                        }
473
                        _ => {}
474
                    }
475
                }
476
                if *d == 0 {
477
                    ready.insert(w);
478
                }
479
            }
480
        }
481
        if let Some(c) = conflict.take() {
482
            finish_conflict(c, &mut segments);
483
        }
484
        flush_plain(&mut plain, &mut segments);
485
        Materialized { segments }
486
    }
487
488
    /// Render a materialization to bytes with conflict markers (spec §9.5).
489
    /// `label` gives the short description of a change for marker headers.
490
    pub fn render(&self, m: &Materialized, label: &dyn Fn(&ChangeHash) -> String) -> Vec<u8> {
491
        let total = m.conflict_count();
492
        let mut n = 0;
493
        let mut out: Vec<u8> = Vec::new();
494
        let push_atoms = |out: &mut Vec<u8>, atoms: &[AtomKey]| {
495
            for a in atoms {
496
                if let Some(c) = self.content(a) {
497
                    out.extend_from_slice(c);
498
                }
499
            }
500
        };
501
        let newline_if_needed = |out: &mut Vec<u8>| {
502
            if !out.is_empty() && *out.last().unwrap() != b'\n' {
503
                out.push(b'\n');
504
            }
505
        };
506
        for seg in &m.segments {
507
            match seg {
508
                Segment::Lines(a) => push_atoms(&mut out, a),
509
                Segment::Conflict(sides) => {
510
                    n += 1;
511
                    let k = sides.len();
512
                    for (i, s) in sides.iter().enumerate() {
513
                        newline_if_needed(&mut out);
514
                        let who = s.first().map(|a| label(&a.0)).unwrap_or_default();
515
                        let marker = if i == 0 { "<<<<<<<" } else { "=======" };
516
                        out.extend_from_slice(
517
                            format!("{marker} oxy conflict {n} of {total}, side {} of {k}: change {who}\n", i + 1).as_bytes(),
518
                        );
519
                        push_atoms(&mut out, s);
520
                    }
521
                    newline_if_needed(&mut out);
75167672 Andy McCall 522
                    out.extend_from_slice(
523
                        format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(),
524
                    );
ff0af1ee unknown 525
                }
526
                Segment::Zombie { deleted, inserted } => {
527
                    n += 1;
528
                    newline_if_needed(&mut out);
529
                    let deleter = deleted
530
                        .first()
531
                        .and_then(|a| self.deleters(a).next().copied())
532
                        .map(|d| label(&d))
533
                        .unwrap_or_default();
534
                    let inserter = inserted.first().map(|a| label(&a.0)).unwrap_or_default();
535
                    out.extend_from_slice(
536
                        format!("<<<<<<< oxy conflict {n} of {total}, side 1 of 2: deleted by change {deleter}\n-------\n").as_bytes(),
537
                    );
538
                    push_atoms(&mut out, deleted);
539
                    newline_if_needed(&mut out);
75167672 Andy McCall 540
                    out.extend_from_slice(
541
                        format!(
542
                            "======= oxy conflict {n} of {total}, side 2 of 2: change {inserter}\n"
543
                        )
544
                        .as_bytes(),
545
                    );
ff0af1ee unknown 546
                    push_atoms(&mut out, inserted);
547
                    newline_if_needed(&mut out);
75167672 Andy McCall 548
                    out.extend_from_slice(
549
                        format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(),
550
                    );
ff0af1ee unknown 551
                }
552
            }
553
        }
554
        out
555
    }
556
}
557
252e8cb2 Andy McCall ↶ 558
fn vertex_value(v: &Vertex) -> crate::cbor::Value {
559
    use crate::cbor::Value;
560
    match v {
561
        Vertex::Start => Value::Uint(0),
562
        Vertex::End => Value::Uint(1),
75167672 Andy McCall 563
        Vertex::Atom((c, i)) => {
564
            Value::Array(vec![Value::Bytes(c.0.to_vec()), Value::Uint(*i as u64)])
565
        }
252e8cb2 Andy McCall ↶ 566
    }
567
}
568
569
fn vertex_from(v: &crate::cbor::Value) -> Result<Vertex, crate::objects::ObjectError> {
570
    use crate::cbor::Value;
571
    use crate::objects::ObjectError;
572
    match v {
573
        Value::Uint(0) => Ok(Vertex::Start),
574
        Value::Uint(1) => Ok(Vertex::End),
75167672 Andy McCall 575
        Value::Array(a) if a.len() == 2 => Ok(Vertex::Atom((
576
            crate::objects::oref(&a[0], "vertex")?,
577
            crate::objects::u32_(&a[1], "vertex")?,
578
        ))),
579
        _ => Err(ObjectError::Schema(
580
            "vertex: expected 0, 1, or [hash, index]".into(),
581
        )),
252e8cb2 Andy McCall ↶ 582
    }
583
}
584
585
impl FileGraph {
586
    /// Serialize for a checkpoint: `{0: atoms [[hash, index, content|null, [deleters]]], 1: edges [[from, to]]}`.
587
    pub fn to_value(&self) -> crate::cbor::Value {
588
        use crate::cbor::Value;
589
        let mut atoms: Vec<(&AtomKey, &AtomInfo)> = self.atoms.iter().collect();
590
        atoms.sort_by_key(|(k, _)| **k);
75167672 Andy McCall 591
        let mut edges: Vec<(Vertex, Vertex)> = self
592
            .succ
593
            .iter()
594
            .flat_map(|(a, bs)| bs.iter().map(move |b| (*a, *b)))
595
            .collect();
252e8cb2 Andy McCall ↶ 596
        edges.sort();
597
        Value::map([
598
            (
599
                0,
600
                Value::Array(
601
                    atoms
602
                        .into_iter()
603
                        .map(|((c, i), info)| {
604
                            Value::Array(vec![
605
                                Value::Bytes(c.0.to_vec()),
606
                                Value::Uint(*i as u64),
75167672 Andy McCall 607
                                info.content
608
                                    .as_ref()
01a47cd7 Andy McCall ↶ 609
                                    .map(|b| Value::Bytes(b.bytes().to_vec()))
75167672 Andy McCall 610
                                    .unwrap_or(Value::Null),
611
                                Value::Array(
612
                                    info.deleted_by
613
                                        .iter()
614
                                        .map(|d| Value::Bytes(d.0.to_vec()))
615
                                        .collect(),
616
                                ),
252e8cb2 Andy McCall ↶ 617
                            ])
618
                        })
619
                        .collect(),
620
                ),
621
            ),
75167672 Andy McCall 622
            (
623
                1,
624
                Value::Array(
625
                    edges
626
                        .iter()
627
                        .map(|(a, b)| Value::Array(vec![vertex_value(a), vertex_value(b)]))
628
                        .collect(),
629
                ),
630
            ),
252e8cb2 Andy McCall ↶ 631
        ])
632
    }
633
634
    pub fn from_value(v: &crate::cbor::Value) -> Result<FileGraph, crate::objects::ObjectError> {
635
        use crate::objects::{array, bytes, field, map, oref, u32_, ObjectError};
636
        let m = map(v, "file graph")?;
75167672 Andy McCall 637
        let mut g = FileGraph {
0bdc3f91 Andy McCall ↶ 638
            atoms: HashMap::default(),
639
            succ: HashMap::default(),
75167672 Andy McCall 640
        };
01a47cd7 Andy McCall ↶ 641
        let atoms = array(field(m, 0, "atoms")?, "atoms")?;
642
        // Every atom's line in one buffer.
643
        let mut texts: Vec<&[u8]> = Vec::new();
644
        for a in atoms {
645
            let e = array(a, "atom")?;
646
            if e.len() == 4 && !e[2].is_null() {
647
                texts.push(bytes(&e[2], "atom content")?);
648
            }
649
        }
650
        let all: Lines = texts.into_iter().collect();
651
        let mut next = 0u32;
652
        for a in atoms {
252e8cb2 Andy McCall ↶ 653
            let e = array(a, "atom")?;
654
            if e.len() != 4 {
655
                return Err(ObjectError::Schema("atom: expected 4 fields".into()));
656
            }
657
            let key = (oref(&e[0], "atom change")?, u32_(&e[1], "atom index")?);
75167672 Andy McCall 658
            let content = if e[2].is_null() {
659
                None
660
            } else {
01a47cd7 Andy McCall ↶ 661
                next += 1;
662
                Some(LineRef::new(all.clone(), next - 1))
75167672 Andy McCall 663
            };
664
            let deleted_by = array(&e[3], "deleters")?
665
                .iter()
666
                .map(|d| oref(d, "deleter"))
667
                .collect::<Result<BTreeSet<_>, _>>()?;
668
            g.atoms.insert(
669
                key,
670
                AtomInfo {
671
                    content,
672
                    deleted_by,
673
                },
674
            );
252e8cb2 Andy McCall ↶ 675
        }
676
        for e in array(field(m, 1, "edges")?, "edges")? {
677
            let pair = array(e, "edge")?;
678
            if pair.len() != 2 {
679
                return Err(ObjectError::Schema("edge: expected [from, to]".into()));
680
            }
681
            g.edge(vertex_from(&pair[0])?, vertex_from(&pair[1])?);
682
        }
683
        Ok(g)
684
    }
685
}
686
ff0af1ee unknown 687
/// Split a plain run into `Lines` and `Zombie` segments around ghost atoms.
688
fn split_zombies(atoms: Vec<AtomKey>, ghosts: &HashSet<AtomKey>) -> Vec<Segment> {
689
    let mut out = Vec::new();
690
    let mut lines: Vec<AtomKey> = Vec::new();
691
    let mut i = 0;
692
    while i < atoms.len() {
693
        if !ghosts.contains(&atoms[i]) {
694
            lines.push(atoms[i]);
695
            i += 1;
696
            continue;
697
        }
698
        if !lines.is_empty() {
699
            out.push(Segment::Lines(std::mem::take(&mut lines)));
700
        }
701
        let mut deleted = Vec::new();
702
        while i < atoms.len() && ghosts.contains(&atoms[i]) {
703
            deleted.push(atoms[i]);
704
            i += 1;
705
        }
706
        let mut inserted = Vec::new();
707
        if i < atoms.len() {
708
            let ch = atoms[i].0;
709
            while i < atoms.len() && atoms[i].0 == ch && !ghosts.contains(&atoms[i]) {
710
                inserted.push(atoms[i]);
711
                i += 1;
712
            }
713
        }
714
        out.push(Segment::Zombie { deleted, inserted });
715
    }
716
    if !lines.is_empty() {
717
        out.push(Segment::Lines(lines));
718
    }
719
    out
720
}
721
722
#[cfg(test)]
723
mod tests {
724
    use super::*;
725
    use crate::hash::ObjectRef;
726
727
    fn h(n: u8) -> ChangeHash {
728
        ObjectRef([n; 32])
729
    }
01a47cd7 Andy McCall ↶ 730
    fn lines(v: &[&str]) -> Lines {
731
        v.iter().map(|s| s.as_bytes()).collect()
ff0af1ee unknown 732
    }
733
    fn text(g: &FileGraph, m: &Materialized) -> String {
734
        String::from_utf8(g.render(m, &|c| c.short(4))).unwrap()
735
    }
736
737
    struct Deps(HashMap<ChangeHash, Vec<ChangeHash>>);
738
    impl DepOracle for Deps {
739
        fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool {
740
            if a == b {
741
                return true;
742
            }
743
            let mut stack = vec![*a];
0bdc3f91 Andy McCall ↶ 744
            let mut seen = HashSet::default();
ff0af1ee unknown 745
            while let Some(x) = stack.pop() {
746
                if let Some(ds) = self.0.get(&x) {
747
                    for d in ds {
748
                        if d == b {
749
                            return true;
750
                        }
751
                        if seen.insert(*d) {
752
                            stack.push(*d);
753
                        }
754
                    }
755
                }
756
            }
757
            false
758
        }
759
    }
760
761
    #[test]
762
    fn sequential_inserts_and_deletes() {
763
        let mut g = FileGraph::new();
75167672 Andy McCall 764
        g.insert(
765
            h(1),
766
            0,
767
            Vertex::Start,
768
            Vertex::End,
769
            &lines(&["a\n", "b\n", "c\n"]),
770
        );
ff0af1ee unknown 771
        let m = g.materialize(&NoDeps);
772
        assert!(!m.is_conflicted());
773
        assert_eq!(text(&g, &m), "a\nb\nc\n");
774
775
        // Change 2 depends on 1, inserts between b and c and deletes a.
75167672 Andy McCall 776
        g.insert(
777
            h(2),
778
            0,
779
            Vertex::Atom((h(1), 1)),
780
            Vertex::Atom((h(1), 2)),
781
            &lines(&["x\n"]),
782
        );
783
        g.delete(
784
            h(2),
785
            &AtomRange {
786
                change: h(1),
787
                from: 0,
788
                to: 1,
789
            },
790
        );
ff0af1ee unknown 791
        let deps = Deps([(h(2), vec![h(1)])].into_iter().collect());
792
        let m = g.materialize(&deps);
793
        assert!(!m.is_conflicted());
794
        assert_eq!(text(&g, &m), "b\nx\nc\n");
795
        assert_eq!(m.live_atoms(), vec![(h(1), 1), (h(2), 0), (h(1), 2)]);
796
    }
797
798
    #[test]
799
    fn concurrent_inserts_at_same_anchor_conflict() {
800
        let mut g = FileGraph::new();
801
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
75167672 Andy McCall 802
        g.insert(
803
            h(2),
804
            0,
805
            Vertex::Atom((h(1), 0)),
806
            Vertex::Atom((h(1), 1)),
807
            &lines(&["from-2\n"]),
808
        );
809
        g.insert(
810
            h(3),
811
            0,
812
            Vertex::Atom((h(1), 0)),
813
            Vertex::Atom((h(1), 1)),
814
            &lines(&["from-3\n", "more-3\n"]),
815
        );
816
        let deps = Deps(
817
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
818
                .into_iter()
819
                .collect(),
820
        );
ff0af1ee unknown 821
        let m = g.materialize(&deps);
822
        assert!(m.is_conflicted());
823
        assert_eq!(m.conflict_count(), 1);
824
        assert_eq!(
825
            m.segments,
826
            vec![
827
                Segment::Lines(vec![(h(1), 0)]),
828
                Segment::Conflict(vec![vec![(h(2), 0)], vec![(h(3), 0), (h(3), 1)]]),
829
                Segment::Lines(vec![(h(1), 1)]),
830
            ]
831
        );
832
        let t = text(&g, &m);
833
        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}");
834
    }
835
836
    #[test]
837
    fn concurrent_inserts_at_different_anchors_do_not_conflict() {
838
        let mut g = FileGraph::new();
75167672 Andy McCall 839
        g.insert(
840
            h(1),
841
            0,
842
            Vertex::Start,
843
            Vertex::End,
844
            &lines(&["a\n", "b\n", "c\n"]),
845
        );
846
        g.insert(
847
            h(2),
848
            0,
849
            Vertex::Start,
850
            Vertex::Atom((h(1), 0)),
851
            &lines(&["top\n"]),
852
        );
853
        g.insert(
854
            h(3),
855
            0,
856
            Vertex::Atom((h(1), 2)),
857
            Vertex::End,
858
            &lines(&["bottom\n"]),
859
        );
860
        let deps = Deps(
861
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
862
                .into_iter()
863
                .collect(),
864
        );
ff0af1ee unknown 865
        let m = g.materialize(&deps);
866
        assert!(!m.is_conflicted());
867
        assert_eq!(text(&g, &m), "top\na\nb\nc\nbottom\n");
868
    }
869
870
    #[test]
871
    fn concurrent_deletes_of_the_same_line_agree() {
872
        let mut g = FileGraph::new();
873
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
75167672 Andy McCall 874
        g.delete(
875
            h(2),
876
            &AtomRange {
877
                change: h(1),
878
                from: 0,
879
                to: 1,
880
            },
881
        );
882
        g.delete(
883
            h(3),
884
            &AtomRange {
885
                change: h(1),
886
                from: 0,
887
                to: 1,
888
            },
889
        );
890
        let deps = Deps(
891
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
892
                .into_iter()
893
                .collect(),
894
        );
ff0af1ee unknown 895
        let m = g.materialize(&deps);
896
        assert!(!m.is_conflicted());
897
        assert_eq!(text(&g, &m), "b\n");
898
    }
899
900
    #[test]
901
    fn delete_insert_conflict_is_a_zombie() {
902
        let mut g = FileGraph::new();
903
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
904
        // 2 deletes a; 3 (concurrent) inserts after a.
75167672 Andy McCall 905
        g.delete(
906
            h(2),
907
            &AtomRange {
908
                change: h(1),
909
                from: 0,
910
                to: 1,
911
            },
912
        );
913
        g.insert(
914
            h(3),
915
            0,
916
            Vertex::Atom((h(1), 0)),
917
            Vertex::Atom((h(1), 1)),
918
            &lines(&["after-a\n"]),
919
        );
920
        let deps = Deps(
921
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
922
                .into_iter()
923
                .collect(),
924
        );
ff0af1ee unknown 925
        let m = g.materialize(&deps);
926
        assert!(m.is_conflicted());
927
        assert_eq!(
928
            m.segments,
929
            vec![
75167672 Andy McCall 930
                Segment::Zombie {
931
                    deleted: vec![(h(1), 0)],
932
                    inserted: vec![(h(3), 0)]
933
                },
ff0af1ee unknown 934
                Segment::Lines(vec![(h(1), 1)]),
935
            ]
936
        );
937
        let t = text(&g, &m);
938
        assert!(t.contains("-------\na\n======="), "{t}");
939
        assert!(t.contains("after-a\n>>>>>>>"), "{t}");
940
941
        // If the deleter saw the insert (depends on 3), there is no conflict.
75167672 Andy McCall 942
        let deps = Deps(
943
            [(h(2), vec![h(1), h(3)]), (h(3), vec![h(1)])]
944
                .into_iter()
945
                .collect(),
946
        );
ff0af1ee unknown 947
        let m = g.materialize(&deps);
948
        assert!(!m.is_conflicted());
949
        assert_eq!(text(&g, &m), "after-a\nb\n");
950
    }
951
952
    #[test]
23b217d7 Andy McCall ↶ 953
    fn insert_after_a_line_deleted_by_a_dependency_is_not_a_conflict() {
954
        // 1 inserts a,b; 2 deletes b (depends on 1); 3 depends on 2 yet anchors
955
        // after the dead b (a rewritten change may do this): no conflict.
956
        let mut g = FileGraph::new();
957
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
75167672 Andy McCall 958
        g.delete(
959
            h(2),
960
            &AtomRange {
961
                change: h(1),
962
                from: 1,
963
                to: 2,
964
            },
965
        );
966
        g.insert(
967
            h(3),
968
            0,
969
            Vertex::Atom((h(1), 1)),
970
            Vertex::End,
971
            &lines(&["c\n"]),
972
        );
973
        let deps = Deps(
974
            [(h(2), vec![h(1)]), (h(3), vec![h(2)])]
975
                .into_iter()
976
                .collect(),
977
        );
23b217d7 Andy McCall ↶ 978
        let m = g.materialize(&deps);
979
        assert!(!m.is_conflicted(), "{:?}", m.segments);
980
        assert_eq!(text(&g, &m), "a\nc\n");
981
        // A change that deletes a line it introduced itself, with a dependent
982
        // anchoring after it, is likewise fine.
983
        let mut g = FileGraph::new();
984
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["x\n"]));
75167672 Andy McCall 985
        g.delete(
986
            h(1),
987
            &AtomRange {
988
                change: h(1),
989
                from: 0,
990
                to: 1,
991
            },
992
        );
993
        g.insert(
994
            h(2),
995
            0,
996
            Vertex::Atom((h(1), 0)),
997
            Vertex::End,
998
            &lines(&["y\n"]),
999
        );
23b217d7 Andy McCall ↶ 1000
        let deps = Deps([(h(2), vec![h(1)])].into_iter().collect());
1001
        let m = g.materialize(&deps);
1002
        assert!(!m.is_conflicted());
1003
        assert_eq!(text(&g, &m), "y\n");
1004
    }
1005
1006
    #[test]
ff0af1ee unknown 1007
    fn resolution_collapses_a_conflict() {
1008
        let mut g = FileGraph::new();
1009
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"]));
75167672 Andy McCall 1010
        g.insert(
1011
            h(2),
1012
            0,
1013
            Vertex::Start,
1014
            Vertex::Atom((h(1), 0)),
1015
            &lines(&["x\n"]),
1016
        );
1017
        g.insert(
1018
            h(3),
1019
            0,
1020
            Vertex::Start,
1021
            Vertex::Atom((h(1), 0)),
1022
            &lines(&["y\n"]),
1023
        );
1024
        let mut deps: HashMap<_, _> = [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
1025
            .into_iter()
1026
            .collect();
ff0af1ee unknown 1027
        assert!(g.materialize(&Deps(deps.clone())).is_conflicted());
1028
        // Resolution keeps y, drops x.
75167672 Andy McCall 1029
        g.delete(
1030
            h(4),
1031
            &AtomRange {
1032
                change: h(2),
1033
                from: 0,
1034
                to: 1,
1035
            },
1036
        );
ff0af1ee unknown 1037
        deps.insert(h(4), vec![h(2), h(3)]);
1038
        let m = g.materialize(&Deps(deps));
1039
        assert!(!m.is_conflicted());
1040
        assert_eq!(text(&g, &m), "y\na\n");
1041
    }
1042
1043
    #[test]
1044
    fn empty_and_unterminated_files() {
1045
        let g = FileGraph::new();
1046
        let m = g.materialize(&NoDeps);
1047
        assert_eq!(m.segments, vec![]);
1048
        assert_eq!(text(&g, &m), "");
1049
        let mut g = FileGraph::new();
1050
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["no newline"]));
1051
        assert_eq!(text(&g, &g.materialize(&NoDeps)), "no newline");
1052
    }
1053
252e8cb2 Andy McCall ↶ 1054
    #[test]
1055
    fn serialization_round_trip() {
1056
        let mut g = FileGraph::new();
1057
        g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"]));
75167672 Andy McCall 1058
        g.insert(
1059
            h(2),
1060
            0,
1061
            Vertex::Atom((h(1), 0)),
1062
            Vertex::Atom((h(1), 1)),
1063
            &lines(&["x\n"]),
1064
        );
1065
        g.delete(
1066
            h(3),
1067
            &AtomRange {
1068
                change: h(1),
1069
                from: 1,
1070
                to: 2,
1071
            },
1072
        );
252e8cb2 Andy McCall ↶ 1073
        let v = g.to_value();
1074
        let bytes = crate::cbor::encode(&v);
1075
        let back = FileGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap();
75167672 Andy McCall 1076
        let deps = Deps(
1077
            [(h(2), vec![h(1)]), (h(3), vec![h(1)])]
1078
                .into_iter()
1079
                .collect(),
1080
        );
252e8cb2 Andy McCall ↶ 1081
        assert_eq!(back.materialize(&deps), g.materialize(&deps));
1082
        assert_eq!(text(&back, &back.materialize(&deps)), "a\nx\n");
75167672 Andy McCall 1083
        assert_eq!(
1084
            crate::cbor::encode(&back.to_value()),
1085
            bytes,
1086
            "deterministic"
1087
        );
252e8cb2 Andy McCall ↶ 1088
    }
1089
ff0af1ee unknown 1090
    /// Spec §7.3: the graph is a function of the set of changes, not their order.
1091
    #[test]
1092
    fn application_order_is_irrelevant() {
1093
        use rand::seq::SliceRandom;
75167672 Andy McCall 1094
        type Op = (
1095
            ChangeHash,
01a47cd7 Andy McCall ↶ 1096
            Vec<(u32, Vertex, Vertex, Lines)>,
75167672 Andy McCall 1097
            Vec<AtomRange>,
1098
        );
ff0af1ee unknown 1099
        let ops: Vec<Op> = vec![
75167672 Andy McCall 1100
            (
1101
                h(1),
1102
                vec![(0, Vertex::Start, Vertex::End, lines(&["a\n", "b\n", "c\n"]))],
1103
                vec![],
1104
            ),
1105
            (
1106
                h(2),
1107
                vec![(
1108
                    0,
1109
                    Vertex::Atom((h(1), 0)),
1110
                    Vertex::Atom((h(1), 1)),
1111
                    lines(&["p\n"]),
1112
                )],
1113
                vec![],
1114
            ),
1115
            (
1116
                h(3),
1117
                vec![(
1118
                    0,
1119
                    Vertex::Atom((h(1), 0)),
1120
                    Vertex::Atom((h(1), 1)),
1121
                    lines(&["q\n", "r\n"]),
1122
                )],
1123
                vec![AtomRange {
1124
                    change: h(1),
1125
                    from: 2,
1126
                    to: 3,
1127
                }],
1128
            ),
1129
            (
1130
                h(4),
1131
                vec![(
1132
                    0,
1133
                    Vertex::Atom((h(3), 1)),
1134
                    Vertex::Atom((h(1), 1)),
1135
                    lines(&["s\n"]),
1136
                )],
1137
                vec![AtomRange {
1138
                    change: h(1),
1139
                    from: 1,
1140
                    to: 2,
1141
                }],
1142
            ),
1143
            (
1144
                h(5),
1145
                vec![(0, Vertex::Atom((h(1), 2)), Vertex::End, lines(&["z\n"]))],
1146
                vec![],
1147
            ),
ff0af1ee unknown 1148
        ];
1149
        let deps = Deps(
75167672 Andy McCall 1150
            [
1151
                (h(2), vec![h(1)]),
1152
                (h(3), vec![h(1)]),
1153
                (h(4), vec![h(3), h(1)]),
1154
                (h(5), vec![h(1)]),
1155
            ]
1156
            .into_iter()
1157
            .collect(),
ff0af1ee unknown 1158
        );
1159
        let build = |order: &[usize]| {
1160
            let mut g = FileGraph::new();
1161
            for &i in order {
1162
                let (c, ins, dels) = &ops[i];
1163
                for (idx, a, b, l) in ins {
1164
                    g.insert(*c, *idx, *a, *b, l);
1165
                }
1166
                for d in dels {
1167
                    g.delete(*c, d);
1168
                }
1169
            }
1170
            let m = g.materialize(&deps);
1171
            (m.clone(), text(&g, &m))
1172
        };
1173
        let reference = build(&[0, 1, 2, 3, 4]);
1174
        assert!(reference.0.is_conflicted());
1175
        let mut rng = rand::thread_rng();
1176
        let mut order: Vec<usize> = (0..ops.len()).collect();
1177
        for _ in 0..50 {
1178
            order.shuffle(&mut rng);
1179
            assert_eq!(build(&order), reference, "order {order:?}");
1180
        }
1181
    }
1182
}