OXYGENHUB
# c2e1d09047db oxygen / crates / oxy-core / src / tree.rs
58.4 KiB · Rust · last changed The same file added on both sides, identically, is one file 17 days ago source blame history raw
1
//! The tree graph: nodes, placements, and modes as atoms (spec §7.1, §7.2),
2
//! with tree conflict detection and path assignment (spec §9.2).
3
4
use crate::hash::ChangeHash;
5
use crate::ids::NodeId;
6
use crate::objects::{Mode, NodeKind, OpRef, TreeOp};
7
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
8
9
/// `(change, index into that change's tree_ops)`.
10
pub type OpKey = (ChangeHash, u32);
11
12
/// The graph's own maps: keyed by hashes and random ids, which need no
13
/// SipHash (see [`crate::fasthash`]).
14
type Map<K, V> = crate::fasthash::FastMap<K, V>;
15
16
/// A node's placement or mode atoms, sorted: nearly always one, held
17
/// without an allocation.
18
#[derive(Clone, Debug, Default, PartialEq, Eq)]
19
enum KeySet {
20
    #[default]
21
    Empty,
22
    One(OpKey),
23
    Many(Vec<OpKey>),
24
}
25
26
impl KeySet {
27
    fn insert(&mut self, k: OpKey) {
28
        match self {
29
            KeySet::Empty => *self = KeySet::One(k),
30
            KeySet::One(a) if *a == k => {}
31
            KeySet::One(a) => {
32
                let a = *a;
33
                *self = KeySet::Many(if a < k { vec![a, k] } else { vec![k, a] });
34
            }
35
            KeySet::Many(v) => {
36
                if let Err(i) = v.binary_search(&k) {
37
                    v.insert(i, k);
38
                }
39
            }
40
        }
41
    }
42
    fn remove(&mut self, k: &OpKey) {
43
        match self {
44
            KeySet::One(a) if a == k => *self = KeySet::Empty,
45
            KeySet::Many(v) => {
46
                if let Ok(i) = v.binary_search(k) {
47
                    v.remove(i);
48
                }
49
                if v.len() == 1 {
50
                    *self = KeySet::One(v[0]);
51
                } else if v.is_empty() {
52
                    *self = KeySet::Empty;
53
                }
54
            }
55
            _ => {}
56
        }
57
    }
58
    fn as_slice(&self) -> &[OpKey] {
59
        match self {
60
            KeySet::Empty => &[],
61
            KeySet::One(a) => std::slice::from_ref(a),
62
            KeySet::Many(v) => v,
63
        }
64
    }
65
    fn iter(&self) -> std::slice::Iter<'_, OpKey> {
66
        self.as_slice().iter()
67
    }
68
    fn len(&self) -> usize {
69
        self.as_slice().len()
70
    }
71
    fn is_empty(&self) -> bool {
72
        matches!(self, KeySet::Empty)
73
    }
74
}
75
76
impl<'a> IntoIterator for &'a KeySet {
77
    type Item = &'a OpKey;
78
    type IntoIter = std::slice::Iter<'a, OpKey>;
79
    fn into_iter(self) -> Self::IntoIter {
80
        self.iter()
81
    }
82
}
83
84
#[derive(Clone, Debug, PartialEq, Eq)]
85
pub struct Placement {
86
    pub node: NodeId,
87
    pub parent: NodeId,
88
    pub name: String,
89
}
90
91
#[derive(Clone, Debug, Default)]
92
struct TreeMaps {
93
    kinds: Map<NodeId, NodeKind>,
94
    introduced: Map<NodeId, ChangeHash>,
95
    placements: Map<OpKey, Placement>,
96
    modes: Map<OpKey, (NodeId, Mode)>,
97
    /// Placement atoms retired by `RemoveNode`/`Move`, and mode atoms retired
98
    /// by `SetMode`. Kept apart because `AddNode` introduces one of each
99
    /// under the same key.
100
    deleted_placements: Map<OpKey, BTreeSet<ChangeHash>>,
101
    deleted_modes: Map<OpKey, BTreeSet<ChangeHash>>,
102
    placements_of: Map<NodeId, KeySet>,
103
    modes_of: Map<NodeId, KeySet>,
104
    /// The live subsets of `placements_of` and `modes_of`, kept as changes
105
    /// apply, so the view walks what is live rather than all history.
106
    live_placements_of: Map<NodeId, KeySet>,
107
    live_modes_of: Map<NodeId, KeySet>,
108
}
109
110
#[derive(Clone, Debug, Default)]
111
pub struct TreeGraph {
112
    /// The graph itself; see [`TreeGraph::m`].
113
    maps: std::sync::OnceLock<TreeMaps>,
114
    /// A state index's encoding of `maps`, decoded when first needed: a
115
    /// command that only reads the view (a status) never needs them.
116
    raw: Option<std::sync::Arc<Vec<u8>>>,
117
    /// The view, once computed, with the path of each directory in it;
118
    /// kept up to date as ops apply when that is simple (see
119
    /// `update_view`), cleared otherwise.
120
    view_cache: std::sync::OnceLock<CachedView>,
121
}
122
123
#[derive(Clone, Debug, PartialEq, Eq)]
124
pub enum TreeConflict {
125
    /// Two live nodes share `(parent, name)`.
126
    Name {
127
        parent: NodeId,
128
        name: String,
129
        nodes: Vec<NodeId>,
130
    },
131
    /// One node has several live placements.
132
    MultiPlacement {
133
        node: NodeId,
134
        placements: Vec<OpKey>,
135
    },
136
    /// A live node's parent is not live.
137
    Orphan { node: NodeId, parent: NodeId },
138
    /// A directory is placed inside its own descendant.
139
    Cycle { node: NodeId },
140
    /// Several live mode atoms.
141
    Mode { node: NodeId, modes: Vec<Mode> },
142
}
143
144
/// A node as it appears in the working copy.
145
#[derive(Clone, Debug, PartialEq, Eq)]
146
pub struct TreeEntry {
147
    pub path: String,
148
    pub node: NodeId,
149
    pub kind: NodeKind,
150
    pub mode: Mode,
151
    /// The placement this entry materializes (for building `RemoveNode`/`Move`).
152
    pub placement: OpKey,
153
    /// The live mode atom (for building `SetMode`).
154
    pub mode_atom: Option<OpKey>,
155
}
156
157
#[derive(Clone, Debug, Default)]
158
struct CachedView {
159
    view: TreeView,
160
    dirs: HashMap<NodeId, String>,
161
}
162
163
impl CachedView {
164
    fn new(view: TreeView) -> CachedView {
165
        let mut dirs: HashMap<NodeId, String> = view
166
            .entries
167
            .values()
168
            .filter(|e| e.kind == NodeKind::Dir)
169
            .map(|e| (e.node, e.path.clone()))
170
            .collect();
171
        dirs.insert(NodeId::ROOT, String::new());
172
        CachedView { view, dirs }
173
    }
174
}
175
176
#[derive(Clone, Debug, Default, PartialEq, Eq)]
177
pub struct TreeView {
178
    /// Path → entry, paths use `/` and have no leading slash; conflicted
179
    /// entries carry a `.oxy-conflict-<n>` suffix. Shared, so a view is
180
    /// cheap to clone.
181
    pub entries: std::sync::Arc<BTreeMap<String, TreeEntry>>,
182
    pub conflicts: Vec<TreeConflict>,
183
    /// Nodes added at a place another node holds with identical content
184
    /// (spec §9.2), each with the node the view shows there in its stead:
185
    /// one file to every reader, and retired by the first change that
186
    /// touches the node shown ([`TreeGraph::unify`]).
187
    pub unified: BTreeMap<NodeId, NodeId>,
188
}
189
190
impl TreeView {
191
    pub fn is_conflicted(&self) -> bool {
192
        !self.conflicts.is_empty()
193
    }
194
    pub fn by_node(&self) -> HashMap<NodeId, &TreeEntry> {
195
        self.entries.values().map(|e| (e.node, e)).collect()
196
    }
197
}
198
199
impl TreeGraph {
200
    pub fn new() -> Self {
201
        Self::default()
202
    }
203
204
    /// The graph's maps, decoded from a state index on first use.
205
    fn m(&self) -> &TreeMaps {
206
        self.maps.get_or_init(|| match &self.raw {
207
            Some(raw) => {
208
                TreeMaps::decode(raw).expect("a state index is checked against its hash when read")
209
            }
210
            None => TreeMaps::default(),
211
        })
212
    }
213
214
    fn m_mut(&mut self) -> &mut TreeMaps {
215
        self.m();
216
        self.maps.get_mut().expect("decoded just now")
217
    }
218
219
    /// Apply tree op `index` of `change`; `OpRef` self references must already be resolved.
220
    pub fn apply(&mut self, change: ChangeHash, index: u32, op: &TreeOp) {
221
        let cached = self.view_cache.take();
222
        // Whether the placement or mode atom this op retires was live.
223
        let was_live = match op {
224
            TreeOp::RemoveNode { placement, .. } => self.placement_live(&op_key(*placement)),
225
            TreeOp::Move { old, .. } => self.placement_live(&op_key(*old)),
226
            TreeOp::SetMode { old, .. } => self.mode_live(&op_key(*old)),
227
            TreeOp::AddNode { .. } => false,
228
        };
229
        self.apply_to_graph(change, index, op);
230
        if let Some(mut c) = cached {
231
            if self.update_view(&mut c, (change, index), op, was_live) {
232
                #[cfg(test)]
233
                assert_eq!(
234
                    c.view,
235
                    self.compute_view(),
236
                    "view kept up to date by {op:?}"
237
                );
238
                self.view_cache = std::sync::OnceLock::from(c);
239
            }
240
        }
241
    }
242
243
    fn apply_to_graph(&mut self, change: ChangeHash, index: u32, op: &TreeOp) {
244
        let key = (change, index);
245
        match op {
246
            TreeOp::AddNode {
247
                node,
248
                kind,
249
                mode,
250
                parent,
251
                name,
252
            } => {
253
                self.m_mut().kinds.insert(*node, *kind);
254
                self.m_mut().introduced.insert(*node, change);
255
                self.m_mut().placements.insert(
256
                    key,
257
                    Placement {
258
                        node: *node,
259
                        parent: *parent,
260
                        name: name.clone(),
261
                    },
262
                );
263
                self.m_mut()
264
                    .placements_of
265
                    .entry(*node)
266
                    .or_default()
267
                    .insert(key);
268
                self.index_placement(key);
269
                if *kind != NodeKind::Dir {
270
                    self.m_mut().modes.insert(key, (*node, *mode));
271
                    self.m_mut().modes_of.entry(*node).or_default().insert(key);
272
                    self.index_mode(key);
273
                }
274
            }
275
            TreeOp::RemoveNode { placement, .. } => {
276
                self.m_mut()
277
                    .deleted_placements
278
                    .entry(op_key(*placement))
279
                    .or_default()
280
                    .insert(change);
281
                self.index_placement(op_key(*placement));
282
            }
283
            TreeOp::Move {
284
                node,
285
                old,
286
                parent,
287
                name,
288
            } => {
289
                self.m_mut()
290
                    .deleted_placements
291
                    .entry(op_key(*old))
292
                    .or_default()
293
                    .insert(change);
294
                self.index_placement(op_key(*old));
295
                self.m_mut().placements.insert(
296
                    key,
297
                    Placement {
298
                        node: *node,
299
                        parent: *parent,
300
                        name: name.clone(),
301
                    },
302
                );
303
                self.m_mut()
304
                    .placements_of
305
                    .entry(*node)
306
                    .or_default()
307
                    .insert(key);
308
                self.index_placement(key);
309
            }
310
            TreeOp::SetMode { node, old, mode } => {
311
                self.m_mut()
312
                    .deleted_modes
313
                    .entry(op_key(*old))
314
                    .or_default()
315
                    .insert(change);
316
                self.index_mode(op_key(*old));
317
                self.m_mut().modes.insert(key, (*node, *mode));
318
                self.m_mut().modes_of.entry(*node).or_default().insert(key);
319
                self.index_mode(key);
320
            }
321
        }
322
    }
323
324
    /// Bring the cached view `c` up to date with `op` (already applied as
325
    /// `key`) where that is simple and the view has no conflicts; false
326
    /// when it must be computed again. `was_live`: whether the placement or
327
    /// mode atom `op` retires was live before it.
328
    fn update_view(&self, c: &mut CachedView, key: OpKey, op: &TreeOp, was_live: bool) -> bool {
329
        if !c.view.conflicts.is_empty() {
330
            return false;
331
        }
332
        match op {
333
            TreeOp::AddNode { node, .. } => self.view_add(c, *node, key),
334
            TreeOp::RemoveNode { node, placement } => {
335
                !was_live || self.view_remove(c, *node, op_key(*placement))
336
            }
337
            TreeOp::Move { node, old, .. } => {
338
                (!was_live || self.view_remove(c, *node, op_key(*old)))
339
                    && self.view_add(c, *node, key)
340
            }
341
            TreeOp::SetMode { node, .. } => {
342
                let live = self.live_placements(node);
343
                match live.as_slice() {
344
                    [] => true,
345
                    [(k, p)] => {
346
                        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
347
                            return false;
348
                        };
349
                        let Some(mode) = self.entry_mode(node) else {
350
                            return false;
351
                        };
352
                        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
353
                        match entries.get_mut(&path) {
354
                            Some(e) if e.node == *node && e.placement == *k => {
355
                                (e.mode_atom, e.mode) = mode;
356
                                true
357
                            }
358
                            _ => false,
359
                        }
360
                    }
361
                    _ => false,
362
                }
363
            }
364
        }
365
    }
366
367
    /// The view entry for `node`'s placement `key`, just made live.
368
    fn view_add(&self, c: &mut CachedView, node: NodeId, key: OpKey) -> bool {
369
        if !self.placement_live(&key) {
370
            return true;
371
        }
372
        if self
373
            .m()
374
            .live_placements_of
375
            .get(&node)
376
            .is_none_or(|s| s.len() != 1)
377
        {
378
            return false;
379
        }
380
        let Some(p) = self.m().placements.get(&key) else {
381
            return false;
382
        };
383
        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
384
            return false;
385
        };
386
        let Some((mode_atom, mode)) = self.entry_mode(&node) else {
387
            return false;
388
        };
389
        let kind = self.kind(&node).unwrap_or(NodeKind::File);
390
        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
391
        if entries.contains_key(&path) || node == NodeId::ROOT {
392
            return false;
393
        }
394
        // Nothing can be placed under a directory that was not in the view
395
        // yet: it would have been an orphan, and the view had no conflicts.
396
        if kind == NodeKind::Dir {
397
            c.dirs.insert(node, path.clone());
398
        }
399
        entries.insert(
400
            path.clone(),
401
            TreeEntry {
402
                path,
403
                node,
404
                kind,
405
                mode,
406
                placement: key,
407
                mode_atom,
408
            },
409
        );
410
        true
411
    }
412
413
    /// Drop the view entry of `node`'s placement `key`, just retired.
414
    fn view_remove(&self, c: &mut CachedView, node: NodeId, key: OpKey) -> bool {
415
        if self.m().live_placements_of.contains_key(&node) {
416
            return false;
417
        }
418
        let Some(p) = self.m().placements.get(&key) else {
419
            return false;
420
        };
421
        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
422
            return false;
423
        };
424
        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
425
        match entries.get(&path) {
426
            Some(e) if e.node == node && e.placement == key => {}
427
            _ => return false,
428
        }
429
        if entries.get(&path).is_some_and(|e| e.kind == NodeKind::Dir) {
430
            let prefix = format!("{path}/");
431
            if entries
432
                .range(prefix.clone()..)
433
                .next()
434
                .is_some_and(|(k, _)| k.starts_with(&prefix))
435
            {
436
                return false;
437
            }
438
            c.dirs.remove(&node);
439
        }
440
        entries.remove(&path);
441
        true
442
    }
443
444
    /// The mode atom and mode of `node`'s entry, as `compute_view` finds
445
    /// them; `None` when its live modes conflict.
446
    fn entry_mode(&self, node: &NodeId) -> Option<(Option<OpKey>, Mode)> {
447
        let lm = self.live_modes(node);
448
        let distinct: BTreeSet<Mode> = lm.iter().map(|(_, m)| *m).collect();
449
        if distinct.len() > 1 {
450
            return None;
451
        }
452
        let mode_atom = lm.iter().map(|(k, _)| *k).max();
453
        let mode = mode_atom
454
            .and_then(|k| self.m().modes.get(&k).map(|(_, m)| *m))
455
            .unwrap_or_default();
456
        Some((mode_atom, mode))
457
    }
458
459
    /// Bring placement `k` in the live index up to date.
460
    fn index_placement(&mut self, k: OpKey) {
461
        let Some(node) = self.m_mut().placements.get(&k).map(|p| p.node) else {
462
            return;
463
        };
464
        if self.placement_live(&k) {
465
            self.m_mut()
466
                .live_placements_of
467
                .entry(node)
468
                .or_default()
469
                .insert(k);
470
        } else if let Some(set) = self.m_mut().live_placements_of.get_mut(&node) {
471
            set.remove(&k);
472
            if set.is_empty() {
473
                self.m_mut().live_placements_of.remove(&node);
474
            }
475
        }
476
    }
477
478
    /// Bring mode atom `k` in the live index up to date.
479
    fn index_mode(&mut self, k: OpKey) {
480
        let Some(node) = self.m_mut().modes.get(&k).map(|(n, _)| *n) else {
481
            return;
482
        };
483
        if self.mode_live(&k) {
484
            self.m_mut()
485
                .live_modes_of
486
                .entry(node)
487
                .or_default()
488
                .insert(k);
489
        } else if let Some(set) = self.m_mut().live_modes_of.get_mut(&node) {
490
            set.remove(&k);
491
            if set.is_empty() {
492
                self.m_mut().live_modes_of.remove(&node);
493
            }
494
        }
495
    }
496
497
    /// Directories no longer placed anywhere, by the (parent, name) they
498
    /// were last placed at, with that placement: what an import can bring
499
    /// back rather than make a second node for one Git path.
500
    pub fn dead_dirs(&self) -> HashMap<(NodeId, String), (NodeId, OpKey)> {
501
        let mut out: HashMap<(NodeId, String), (NodeId, OpKey)> = HashMap::new();
502
        for (k, p) in &self.m().placements {
503
            if self.kind(&p.node) != Some(NodeKind::Dir)
504
                || self.placement_live(k)
505
                || !self.live_placements(&p.node).is_empty()
506
            {
507
                continue;
508
            }
509
            let slot = out
510
                .entry((p.parent, p.name.clone()))
511
                .or_insert((p.node, *k));
512
            // The newest placement, by key: the same answer in any order.
513
            if *k > slot.1 {
514
                *slot = (p.node, *k);
515
            }
516
        }
517
        out
518
    }
519
520
    /// Every placement `node` ever had, live or not, with the changes that
521
    /// retired it (none while live).
522
    pub fn placement_history(&self, node: &NodeId) -> Vec<(OpKey, &Placement, Vec<ChangeHash>)> {
523
        self.m()
524
            .placements_of
525
            .get(node)
526
            .into_iter()
527
            .flatten()
528
            .filter_map(|k| {
529
                let p = self.m().placements.get(k)?;
530
                let by = self
531
                    .m()
532
                    .deleted_placements
533
                    .get(k)
534
                    .map(|d| d.iter().copied().collect())
535
                    .unwrap_or_default();
536
                Some((*k, p, by))
537
            })
538
            .collect()
539
    }
540
541
    fn placement_live(&self, k: &OpKey) -> bool {
542
        self.m()
543
            .deleted_placements
544
            .get(k)
545
            .is_none_or(|d| d.is_empty())
546
    }
547
    fn mode_live(&self, k: &OpKey) -> bool {
548
        self.m().deleted_modes.get(k).is_none_or(|d| d.is_empty())
549
    }
550
551
    pub fn kind(&self, node: &NodeId) -> Option<NodeKind> {
552
        if node.is_root() {
553
            Some(NodeKind::Dir)
554
        } else {
555
            self.m().kinds.get(node).copied()
556
        }
557
    }
558
559
    /// The change whose `AddNode` introduced `node`.
560
    pub fn introducer(&self, node: &NodeId) -> Option<ChangeHash> {
561
        self.m().introduced.get(node).copied()
562
    }
563
564
    pub fn placement(&self, k: &OpKey) -> Option<&Placement> {
565
        self.m().placements.get(k)
566
    }
567
568
    pub fn live_placements(&self, node: &NodeId) -> Vec<(OpKey, &Placement)> {
569
        self.m()
570
            .live_placements_of
571
            .get(node)
572
            .into_iter()
573
            .flat_map(|ks| ks.iter())
574
            .filter_map(|k| self.m().placements.get(k).map(|p| (*k, p)))
575
            .collect()
576
    }
577
578
    pub fn live_modes(&self, node: &NodeId) -> Vec<(OpKey, Mode)> {
579
        self.m()
580
            .live_modes_of
581
            .get(node)
582
            .into_iter()
583
            .flat_map(|ks| ks.iter())
584
            .filter_map(|k| self.m().modes.get(k).map(|(_, m)| (*k, *m)))
585
            .collect()
586
    }
587
588
    /// Every node that has at least one live placement.
589
    pub fn placed_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
590
        self.m().live_placements_of.keys().copied()
591
    }
592
593
    /// Resolve the tree: assign paths, find conflicts.
594
    /// The materialized tree (spec §7.1): computed once per graph and
595
    /// cloned for each caller.
596
    pub fn view(&self) -> TreeView {
597
        self.view_cache
598
            .get_or_init(|| CachedView::new(self.compute_view()))
599
            .view
600
            .clone()
601
    }
602
603
    fn compute_view(&self) -> TreeView {
604
        let mut view = TreeView::default();
605
        let mut entries: BTreeMap<String, TreeEntry> = BTreeMap::new();
606
607
        // Live placements grouped by parent.
608
        let mut children: HashMap<NodeId, Vec<(OpKey, &Placement)>> = HashMap::new();
609
        for ks in self.m().live_placements_of.values() {
610
            for k in ks {
611
                if let Some(p) = self.m().placements.get(k) {
612
                    children.entry(p.parent).or_default().push((*k, p));
613
                }
614
            }
615
        }
616
        for c in children.values_mut() {
617
            c.sort_by(|a, b| a.1.name.cmp(&b.1.name).then(a.0.cmp(&b.0)));
618
        }
619
620
        // Multi-placement and mode conflicts.
621
        // Only placed nodes: one with no live placement is not in the tree.
622
        let mut nodes: Vec<NodeId> = self.m().live_placements_of.keys().copied().collect();
623
        nodes.sort();
624
        let mut multi: HashSet<NodeId> = HashSet::new();
625
        // Each node's mode and the atom it comes from, for the entries.
626
        let mut modes: HashMap<NodeId, (Option<OpKey>, Mode)> = HashMap::new();
627
        for n in &nodes {
628
            let lp = self.live_placements(n);
629
            // One placement (nearly every node) cannot be in two places.
630
            if lp.len() > 1 {
631
                let distinct_places: BTreeSet<(&NodeId, &str)> = lp
632
                    .iter()
633
                    .map(|(_, p)| (&p.parent, p.name.as_str()))
634
                    .collect();
635
                if distinct_places.len() > 1 {
636
                    multi.insert(*n);
637
                    view.conflicts.push(TreeConflict::MultiPlacement {
638
                        node: *n,
639
                        placements: lp.iter().map(|(k, _)| *k).collect(),
640
                    });
641
                }
642
            }
643
            let lm = self.live_modes(n);
644
            if lm.len() > 1 {
645
                let distinct_modes: BTreeSet<Mode> = lm.iter().map(|(_, m)| *m).collect();
646
                if distinct_modes.len() > 1 {
647
                    view.conflicts.push(TreeConflict::Mode {
648
                        node: *n,
649
                        modes: distinct_modes.into_iter().collect(),
650
                    });
651
                }
652
            }
653
            let mode_atom = lm.iter().map(|(k, _)| *k).max();
654
            let mode = mode_atom
655
                .and_then(|k| self.m().modes.get(&k).map(|(_, m)| *m))
656
                .unwrap_or_default();
657
            modes.insert(*n, (mode_atom, mode));
658
        }
659
660
        // BFS from the root, assigning paths.
661
        let mut reached: HashSet<NodeId> = HashSet::new();
662
        reached.insert(NodeId::ROOT);
663
        let mut queue: Vec<(NodeId, String)> = vec![(NodeId::ROOT, String::new())];
664
        let mut on_path: HashSet<NodeId> = HashSet::new();
665
        while let Some((dir, dir_path)) = queue.pop() {
666
            let Some(kids) = children.get(&dir) else {
667
                continue;
668
            };
669
            // Name conflicts within this directory: the children are sorted
670
            // by name, so each name's group is a run of them.
671
            let mut groups: Vec<(&str, &[(OpKey, &Placement)])> = Vec::new();
672
            let mut start = 0;
673
            for i in 1..=kids.len() {
674
                if i == kids.len() || kids[i].1.name != kids[start].1.name {
675
                    groups.push((kids[start].1.name.as_str(), &kids[start..i]));
676
                    start = i;
677
                }
678
            }
679
            for (name, group) in groups {
680
                let distinct: BTreeSet<NodeId> = group.iter().map(|(_, p)| p.node).collect();
681
                if distinct.len() > 1 {
682
                    view.conflicts.push(TreeConflict::Name {
683
                        parent: dir,
684
                        name: name.to_string(),
685
                        nodes: distinct.iter().copied().collect(),
686
                    });
687
                }
688
                let suffixed = distinct.len() > 1;
689
                for (i, (k, p)) in group.iter().enumerate() {
690
                    let node = p.node;
691
                    if node == dir || on_path.contains(&node) {
692
                        view.conflicts.push(TreeConflict::Cycle { node });
693
                        continue;
694
                    }
695
                    let needs_suffix = suffixed || multi.contains(&node);
696
                    let leaf = if needs_suffix {
697
                        format!("{name}.oxy-conflict-{}", i + 1)
698
                    } else {
699
                        name.to_string()
700
                    };
701
                    let path = if dir_path.is_empty() {
702
                        leaf
703
                    } else {
704
                        format!("{dir_path}/{leaf}")
705
                    };
706
                    let kind = self.kind(&node).unwrap_or(NodeKind::File);
707
                    let (mode_atom, mode) = modes.get(&node).copied().unwrap_or_default();
708
                    entries.insert(
709
                        path.clone(),
710
                        TreeEntry {
711
                            path: path.clone(),
712
                            node,
713
                            kind,
714
                            mode,
715
                            placement: *k,
716
                            mode_atom,
717
                        },
718
                    );
719
                    if kind == NodeKind::Dir && reached.insert(node) {
720
                        on_path.insert(dir);
721
                        queue.push((node, path));
722
                    } else {
723
                        reached.insert(node);
724
                    }
725
                }
726
            }
727
        }
728
729
        // Nodes with live placements that were never reached: orphans or cycles.
730
        for n in &nodes {
731
            if reached.contains(n) {
732
                continue;
733
            }
734
            let lp = self.live_placements(n);
735
            if lp.is_empty() {
736
                continue;
737
            }
738
            let mut seen = HashSet::new();
739
            let mut cur = *n;
740
            let conflict = loop {
741
                if !seen.insert(cur) {
742
                    break TreeConflict::Cycle { node: *n };
743
                }
744
                let lp = self.live_placements(&cur);
745
                let Some((_, p)) = lp.first() else {
746
                    break TreeConflict::Orphan {
747
                        node: *n,
748
                        parent: cur,
749
                    };
750
                };
751
                if reached.contains(&p.parent) {
752
                    // Parent reached but this node was not: the parent is not a
753
                    // directory, so this is a name/kind problem surfaced as orphan.
754
                    break TreeConflict::Orphan {
755
                        node: *n,
756
                        parent: p.parent,
757
                    };
758
                }
759
                cur = p.parent;
760
            };
761
            if !view.conflicts.contains(&conflict) {
762
                view.conflicts.push(conflict);
763
            }
764
        }
765
        view.entries = std::sync::Arc::new(entries);
766
        view
767
    }
768
}
769
770
impl TreeGraph {
771
    /// `view` (this graph's) with each name conflict between nodes added
772
    /// with identical content made one entry (spec §9.2): the node whose
773
    /// placement there has the least key is shown at the plain name, and
774
    /// the others are listed in [`TreeView::unified`]. Only a group of
775
    /// files, symlinks or subrepos of one kind and mode, none in another
776
    /// conflict, is offered to `same`, one entry per node, which says
777
    /// whether their contents are identical (`None`: it cannot tell yet).
778
    /// The key order is the same for every peer, so all show one node.
779
    /// Also returns whether `same` answered for every group.
780
    pub fn unify(
781
        &self,
782
        mut view: TreeView,
783
        same: impl Fn(&[&TreeEntry]) -> Option<bool>,
784
    ) -> (TreeView, bool) {
785
        let mut decided = true;
786
        if !view
787
            .conflicts
788
            .iter()
789
            .any(|c| matches!(c, TreeConflict::Name { .. }))
790
        {
791
            return (view, decided);
792
        }
793
        // Nodes in any other conflict stay as they are.
794
        let mut troubled: HashSet<NodeId> = HashSet::new();
795
        for c in &view.conflicts {
796
            match c {
797
                TreeConflict::Name { .. } => {}
798
                TreeConflict::MultiPlacement { node, .. }
799
                | TreeConflict::Orphan { node, .. }
800
                | TreeConflict::Cycle { node }
801
                | TreeConflict::Mode { node, .. } => {
802
                    troubled.insert(*node);
803
                }
804
            }
805
        }
806
        let mut of_node: HashMap<NodeId, Vec<&TreeEntry>> = HashMap::new();
807
        for e in view.entries.values() {
808
            of_node.entry(e.node).or_default().push(e);
809
        }
810
        // (paths to drop, entry to add, duplicates) per unified group.
811
        let mut merged: Vec<(Vec<String>, TreeEntry, Vec<NodeId>)> = Vec::new();
812
        let mut gone: HashSet<usize> = HashSet::new();
813
        for (i, c) in view.conflicts.iter().enumerate() {
814
            let TreeConflict::Name {
815
                parent,
816
                name,
817
                nodes,
818
            } = c
819
            else {
820
                continue;
821
            };
822
            if nodes.iter().any(|n| troubled.contains(n)) {
823
                continue;
824
            }
825
            // The group's entries: each node's placements at this place.
826
            let mut group: Vec<&TreeEntry> = Vec::new();
827
            for n in nodes {
828
                for e in of_node.get(n).into_iter().flatten() {
829
                    if self
830
                        .placement(&e.placement)
831
                        .is_some_and(|p| p.parent == *parent && p.name == *name)
832
                    {
833
                        group.push(e);
834
                    }
835
                }
836
            }
837
            let Some(first) = group.first() else {
838
                continue;
839
            };
840
            if first.kind == NodeKind::Dir
841
                || group
842
                    .iter()
843
                    .any(|e| e.kind != first.kind || e.mode != first.mode)
844
            {
845
                continue;
846
            }
847
            group.sort_by_key(|e| e.placement);
848
            let mut reps: Vec<&TreeEntry> = Vec::new();
849
            for e in &group {
850
                if !reps.iter().any(|r| r.node == e.node) {
851
                    reps.push(e);
852
                }
853
            }
854
            if reps.len() < 2 {
855
                continue;
856
            }
857
            match same(&reps) {
858
                Some(true) => {}
859
                Some(false) => continue,
860
                None => {
861
                    decided = false;
862
                    continue;
863
                }
864
            }
865
            let shown = reps[0];
866
            let path = match shown.path.rsplit_once('/') {
867
                Some((dir, _)) => format!("{dir}/{name}"),
868
                None => name.clone(),
869
            };
870
            merged.push((
871
                group.iter().map(|e| e.path.clone()).collect(),
872
                TreeEntry {
873
                    path,
874
                    ..shown.clone()
875
                },
876
                reps[1..].iter().map(|e| e.node).collect(),
877
            ));
878
            gone.insert(i);
879
        }
880
        drop(of_node);
881
        if merged.is_empty() {
882
            return (view, decided);
883
        }
884
        let entries = std::sync::Arc::make_mut(&mut view.entries);
885
        for (paths, shown, dups) in merged {
886
            for p in paths {
887
                entries.remove(&p);
888
            }
889
            for d in dups {
890
                view.unified.insert(d, shown.node);
891
            }
892
            entries.insert(shown.path.clone(), shown);
893
        }
894
        let mut i = 0;
895
        view.conflicts.retain(|_| {
896
            i += 1;
897
            !gone.contains(&(i - 1))
898
        });
899
        (view, decided)
900
    }
901
}
902
903
impl TreeGraph {
904
    /// Serialize for a checkpoint.
905
    pub fn to_value(&self) -> crate::cbor::Value {
906
        use crate::cbor::Value;
907
        let opkey = |k: &OpKey| vec![Value::Bytes(k.0 .0.to_vec()), Value::Uint(k.1 as u64)];
908
        let deleters = |m: &Map<OpKey, BTreeSet<ChangeHash>>| {
909
            let mut ks: Vec<&OpKey> = m.keys().collect();
910
            ks.sort();
911
            Value::Array(
912
                ks.into_iter()
913
                    .map(|k| {
914
                        let mut v = opkey(k);
915
                        v.push(Value::Array(
916
                            m[k].iter().map(|d| Value::Bytes(d.0.to_vec())).collect(),
917
                        ));
918
                        Value::Array(v)
919
                    })
920
                    .collect(),
921
            )
922
        };
923
        let mut nodes: Vec<&NodeId> = self.m().kinds.keys().collect();
924
        nodes.sort();
925
        let mut pks: Vec<&OpKey> = self.m().placements.keys().collect();
926
        pks.sort();
927
        let mut mks: Vec<&OpKey> = self.m().modes.keys().collect();
928
        mks.sort();
929
        Value::map([
930
            (
931
                0,
932
                Value::Array(
933
                    nodes
934
                        .into_iter()
935
                        .map(|n| {
936
                            Value::Array(vec![
937
                                Value::Bytes(n.0.to_vec()),
938
                                Value::Uint(self.m().kinds[n].tag()),
939
                                self.m()
940
                                    .introduced
941
                                    .get(n)
942
                                    .map(|h| Value::Bytes(h.0.to_vec()))
943
                                    .unwrap_or(Value::Null),
944
                            ])
945
                        })
946
                        .collect(),
947
                ),
948
            ),
949
            (
950
                1,
951
                Value::Array(
952
                    pks.into_iter()
953
                        .map(|k| {
954
                            let p = &self.m().placements[k];
955
                            let mut v = opkey(k);
956
                            v.extend([
957
                                Value::Bytes(p.node.0.to_vec()),
958
                                Value::Bytes(p.parent.0.to_vec()),
959
                                Value::Text(p.name.clone()),
960
                            ]);
961
                            Value::Array(v)
962
                        })
963
                        .collect(),
964
                ),
965
            ),
966
            (2, deleters(&self.m().deleted_placements)),
967
            (
968
                3,
969
                Value::Array(
970
                    mks.into_iter()
971
                        .map(|k| {
972
                            let (n, m) = &self.m().modes[k];
973
                            let mut v = opkey(k);
974
                            v.extend([Value::Bytes(n.0.to_vec()), Value::Uint(m.bits())]);
975
                            Value::Array(v)
976
                        })
977
                        .collect(),
978
                ),
979
            ),
980
            (4, deleters(&self.m().deleted_modes)),
981
        ])
982
    }
983
984
    pub fn from_value(v: &crate::cbor::Value) -> Result<TreeGraph, crate::objects::ObjectError> {
985
        use crate::objects::{array, bytes, field, map, oref, text, u32_, uint, ObjectError};
986
        let m = map(v, "tree graph")?;
987
        let node = |v: &crate::cbor::Value| {
988
            NodeId::from_slice(bytes(v, "node")?)
989
                .ok_or_else(|| ObjectError::Schema("node: expected 16 bytes".into()))
990
        };
991
        let opkey = |e: &[crate::cbor::Value]| -> Result<OpKey, ObjectError> {
992
            Ok((oref(&e[0], "op change")?, u32_(&e[1], "op index")?))
993
        };
994
        let mut g = TreeGraph::default();
995
        for n in array(field(m, 0, "nodes")?, "nodes")? {
996
            let e = array(n, "node entry")?;
997
            if e.len() != 3 {
998
                return Err(ObjectError::Schema("node entry: expected 3 fields".into()));
999
            }
1000
            let id = node(&e[0])?;
1001
            g.m_mut()
1002
                .kinds
1003
                .insert(id, NodeKind::from_tag(uint(&e[1], "kind")?)?);
1004
            if !e[2].is_null() {
1005
                g.m_mut().introduced.insert(id, oref(&e[2], "introducer")?);
1006
            }
1007
        }
1008
        for p in array(field(m, 1, "placements")?, "placements")? {
1009
            let e = array(p, "placement")?;
1010
            if e.len() != 5 {
1011
                return Err(ObjectError::Schema("placement: expected 5 fields".into()));
1012
            }
1013
            let k = opkey(e)?;
1014
            let pl = Placement {
1015
                node: node(&e[2])?,
1016
                parent: node(&e[3])?,
1017
                name: text(&e[4], "name")?.to_owned(),
1018
            };
1019
            g.m_mut()
1020
                .placements_of
1021
                .entry(pl.node)
1022
                .or_default()
1023
                .insert(k);
1024
            g.m_mut().placements.insert(k, pl);
1025
        }
1026
        let deleters = |v: &crate::cbor::Value,
1027
                        what: &str|
1028
         -> Result<Map<OpKey, BTreeSet<ChangeHash>>, ObjectError> {
1029
            let mut out = Map::default();
1030
            for d in array(v, what)? {
1031
                let e = array(d, what)?;
1032
                if e.len() != 3 {
1033
                    return Err(ObjectError::Schema(format!("{what}: expected 3 fields")));
1034
                }
1035
                out.insert(
1036
                    opkey(e)?,
1037
                    array(&e[2], "deleters")?
1038
                        .iter()
1039
                        .map(|x| oref(x, "deleter"))
1040
                        .collect::<Result<BTreeSet<_>, _>>()?,
1041
                );
1042
            }
1043
            Ok(out)
1044
        };
1045
        g.m_mut().deleted_placements =
1046
            deleters(field(m, 2, "deleted placements")?, "deleted placement")?;
1047
        for md in array(field(m, 3, "modes")?, "modes")? {
1048
            let e = array(md, "mode")?;
1049
            if e.len() != 4 {
1050
                return Err(ObjectError::Schema("mode: expected 4 fields".into()));
1051
            }
1052
            let k = opkey(e)?;
1053
            let n = node(&e[2])?;
1054
            g.m_mut()
1055
                .modes
1056
                .insert(k, (n, Mode::from_bits(uint(&e[3], "mode bits")?)?));
1057
            g.m_mut().modes_of.entry(n).or_default().insert(k);
1058
        }
1059
        g.m_mut().deleted_modes = deleters(field(m, 4, "deleted modes")?, "deleted mode")?;
1060
        let keys: Vec<OpKey> = g.m_mut().placements.keys().copied().collect();
1061
        for k in keys {
1062
            g.index_placement(k);
1063
        }
1064
        let keys: Vec<OpKey> = g.m_mut().modes.keys().copied().collect();
1065
        for k in keys {
1066
            g.index_mode(k);
1067
        }
1068
        Ok(g)
1069
    }
1070
}
1071
1072
/// The tree in the flat layout of a local index (oxygen#41), with its view
1073
/// when that has no conflicts, so a loaded tree need not work it out again.
1074
impl TreeGraph {
1075
    pub(crate) fn write_flat(&self, w: &mut crate::flat::Writer) {
1076
        let key = |w: &mut crate::flat::Writer, k: &OpKey| {
1077
            w.object(&k.0);
1078
            w.u32(k.1);
1079
        };
1080
        // The maps as a block of their own: a reader keeps it undecoded
1081
        // until something needs them.
1082
        let outer = std::mem::take(w);
1083
        let mut kinds: Vec<(&NodeId, &NodeKind)> = self.m().kinds.iter().collect();
1084
        kinds.sort_by_key(|(n, _)| **n);
1085
        w.count(kinds.len());
1086
        for (n, k) in kinds {
1087
            w.fixed(&n.0);
1088
            w.u8(k.tag() as u8);
1089
        }
1090
        let mut intro: Vec<(&NodeId, &ChangeHash)> = self.m().introduced.iter().collect();
1091
        intro.sort();
1092
        w.count(intro.len());
1093
        for (n, c) in intro {
1094
            w.fixed(&n.0);
1095
            w.object(c);
1096
        }
1097
        let mut pl: Vec<(&OpKey, &Placement)> = self.m().placements.iter().collect();
1098
        pl.sort_by_key(|(k, _)| **k);
1099
        w.count(pl.len());
1100
        for (k, p) in pl {
1101
            key(w, k);
1102
            w.fixed(&p.node.0);
1103
            w.fixed(&p.parent.0);
1104
            w.bytes(p.name.as_bytes());
1105
        }
1106
        let mut modes: Vec<(&OpKey, &(NodeId, Mode))> = self.m().modes.iter().collect();
1107
        modes.sort_by_key(|(k, _)| **k);
1108
        w.count(modes.len());
1109
        for (k, (n, m)) in modes {
1110
            key(w, k);
1111
            w.fixed(&n.0);
1112
            w.u8(m.bits() as u8);
1113
        }
1114
        for m in [&self.m().deleted_placements, &self.m().deleted_modes] {
1115
            let mut ks: Vec<(&OpKey, &BTreeSet<ChangeHash>)> = m.iter().collect();
1116
            ks.sort_by_key(|(k, _)| **k);
1117
            w.count(ks.len());
1118
            for (k, by) in ks {
1119
                key(w, k);
1120
                w.count(by.len());
1121
                for c in by {
1122
                    w.object(c);
1123
                }
1124
            }
1125
        }
1126
        let maps = std::mem::replace(w, outer);
1127
        w.bytes(&maps.0);
1128
        // The view as a block of its own.
1129
        let outer = std::mem::take(w);
1130
        let view = self.view();
1131
        if view.conflicts.is_empty() {
1132
            w.u8(1);
1133
            w.count(view.entries.len());
1134
            for (path, e) in view.entries.iter() {
1135
                w.bytes(path.as_bytes());
1136
                w.fixed(&e.node.0);
1137
                w.u8(e.kind.tag() as u8);
1138
                w.u8(e.mode.bits() as u8);
1139
                key(w, &e.placement);
1140
                match &e.mode_atom {
1141
                    None => w.u8(0),
1142
                    Some(k) => {
1143
                        w.u8(1);
1144
                        key(w, k);
1145
                    }
1146
                }
1147
            }
1148
        } else {
1149
            w.u8(0);
1150
        }
1151
        let view_bytes = std::mem::replace(w, outer);
1152
        w.bytes(&view_bytes.0);
1153
    }
1154
1155
    pub(crate) fn read_flat(
1156
        r: &mut crate::flat::Reader,
1157
    ) -> Result<TreeGraph, crate::objects::ObjectError> {
1158
        let maps = r.bytes()?;
1159
        let view = Self::read_view(r.bytes()?)?;
1160
        let mut g = TreeGraph {
1161
            raw: Some(std::sync::Arc::new(maps.to_vec())),
1162
            ..TreeGraph::default()
1163
        };
1164
        if let Some(view) = view {
1165
            #[cfg(test)]
1166
            assert_eq!(view, g.compute_view(), "an index's view is the tree's");
1167
            g.view_cache = std::sync::OnceLock::from(CachedView::new(view));
1168
        }
1169
        Ok(g)
1170
    }
1171
}
1172
1173
impl TreeMaps {
1174
    /// The maps block of a tree's flat form.
1175
    fn decode(bytes: &[u8]) -> Result<TreeMaps, crate::objects::ObjectError> {
1176
        use crate::objects::ObjectError;
1177
        let _t = crate::timing::phase("index: tree maps");
1178
        let node = |r: &mut crate::flat::Reader| -> Result<NodeId, ObjectError> {
1179
            Ok(NodeId(r.fixed::<16>()?))
1180
        };
1181
        let key = |r: &mut crate::flat::Reader| -> Result<OpKey, ObjectError> {
1182
            Ok((r.object()?, r.u32()?))
1183
        };
1184
        let r = &mut crate::flat::Reader::new(bytes);
1185
        let mut g = TreeGraph::new();
1186
        for _ in 0..r.count()? {
1187
            let n = node(r)?;
1188
            g.m_mut()
1189
                .kinds
1190
                .insert(n, NodeKind::from_tag(r.u8()? as u64)?);
1191
        }
1192
        for _ in 0..r.count()? {
1193
            let n = node(r)?;
1194
            g.m_mut().introduced.insert(n, r.object()?);
1195
        }
1196
        for _ in 0..r.count()? {
1197
            let k = key(r)?;
1198
            let p = Placement {
1199
                node: node(r)?,
1200
                parent: node(r)?,
1201
                name: r.text()?,
1202
            };
1203
            g.m_mut().placements_of.entry(p.node).or_default().insert(k);
1204
            g.m_mut().placements.insert(k, p);
1205
        }
1206
        for _ in 0..r.count()? {
1207
            let k = key(r)?;
1208
            let n = node(r)?;
1209
            g.m_mut()
1210
                .modes
1211
                .insert(k, (n, Mode::from_bits(r.u8()? as u64)?));
1212
            g.m_mut().modes_of.entry(n).or_default().insert(k);
1213
        }
1214
        for i in 0..2 {
1215
            let mut m: Map<OpKey, BTreeSet<ChangeHash>> = Map::default();
1216
            for _ in 0..r.count()? {
1217
                let k = key(r)?;
1218
                let mut by = BTreeSet::new();
1219
                for _ in 0..r.count()? {
1220
                    by.insert(r.object()?);
1221
                }
1222
                m.insert(k, by);
1223
            }
1224
            if i == 0 {
1225
                g.m_mut().deleted_placements = m;
1226
            } else {
1227
                g.m_mut().deleted_modes = m;
1228
            }
1229
        }
1230
        if !r.at_end() {
1231
            return Err(ObjectError::Schema("state index: tree maps".into()));
1232
        }
1233
        let keys: Vec<OpKey> = g.m().placements.keys().copied().collect();
1234
        for k in keys {
1235
            g.index_placement(k);
1236
        }
1237
        let keys: Vec<OpKey> = g.m().modes.keys().copied().collect();
1238
        for k in keys {
1239
            g.index_mode(k);
1240
        }
1241
        Ok(g.maps.into_inner().unwrap_or_default())
1242
    }
1243
}
1244
1245
impl TreeGraph {
1246
    /// The view block of a tree's flat form, when it holds one.
1247
    fn read_view(bytes: &[u8]) -> Result<Option<TreeView>, crate::objects::ObjectError> {
1248
        let _t = crate::timing::phase("index: view");
1249
        let mut r = crate::flat::Reader::new(bytes);
1250
        if r.u8()? != 1 {
1251
            return Ok(None);
1252
        }
1253
        let key = |r: &mut crate::flat::Reader| -> Result<OpKey, crate::objects::ObjectError> {
1254
            Ok((r.object()?, r.u32()?))
1255
        };
1256
        let n = r.count()?;
1257
        // Collected, then built in one step: the map is made from sorted
1258
        // input in a single pass rather than by searching for each insert.
1259
        let mut entries = Vec::with_capacity(n);
1260
        for _ in 0..n {
1261
            let path = r.text()?;
1262
            let e = TreeEntry {
1263
                path: path.clone(),
1264
                node: NodeId(r.fixed::<16>()?),
1265
                kind: NodeKind::from_tag(r.u8()? as u64)?,
1266
                mode: Mode::from_bits(r.u8()? as u64)?,
1267
                placement: key(&mut r)?,
1268
                mode_atom: match r.u8()? {
1269
                    0 => None,
1270
                    _ => Some(key(&mut r)?),
1271
                },
1272
            };
1273
            entries.push((path, e));
1274
        }
1275
        Ok(Some(TreeView {
1276
            entries: std::sync::Arc::new(entries.into_iter().collect()),
1277
            conflicts: Vec::new(),
1278
            unified: BTreeMap::new(),
1279
        }))
1280
    }
1281
}
1282
1283
fn join(dir: &str, name: &str) -> String {
1284
    if dir.is_empty() {
1285
        name.to_string()
1286
    } else {
1287
        format!("{dir}/{name}")
1288
    }
1289
}
1290
1291
pub fn op_key(r: OpRef) -> OpKey {
1292
    (r.change, r.index)
1293
}
1294
1295
#[cfg(test)]
1296
mod tests {
1297
    use super::*;
1298
    use crate::hash::ObjectRef;
1299
1300
    fn h(n: u8) -> ChangeHash {
1301
        ObjectRef([n; 32])
1302
    }
1303
    fn nid(n: u8) -> NodeId {
1304
        NodeId([n; 16])
1305
    }
1306
    fn add(g: &mut TreeGraph, c: u8, i: u32, node: u8, kind: NodeKind, parent: NodeId, name: &str) {
1307
        g.apply(
1308
            h(c),
1309
            i,
1310
            &TreeOp::AddNode {
1311
                node: nid(node),
1312
                kind,
1313
                mode: Mode::TEXT,
1314
                parent,
1315
                name: name.into(),
1316
            },
1317
        );
1318
    }
1319
    fn paths(v: &TreeView) -> Vec<String> {
1320
        v.entries.keys().cloned().collect()
1321
    }
1322
1323
    /// Each op applied to a graph whose view is cached keeps it up to date
1324
    /// or drops it: `apply` checks every kept view against a full one.
1325
    #[test]
1326
    fn cached_view_follows_ops() {
1327
        let at = |c: u8, i: u32| OpRef {
1328
            change: h(c),
1329
            index: i,
1330
        };
1331
        let mut g = TreeGraph::new();
1332
        let ops = [
1333
            TreeOp::AddNode {
1334
                node: nid(1),
1335
                kind: NodeKind::Dir,
1336
                mode: Mode::TEXT,
1337
                parent: NodeId::ROOT,
1338
                name: "src".into(),
1339
            },
1340
            TreeOp::AddNode {
1341
                node: nid(2),
1342
                kind: NodeKind::File,
1343
                mode: Mode::TEXT,
1344
                parent: nid(1),
1345
                name: "a.rs".into(),
1346
            },
1347
            TreeOp::SetMode {
1348
                node: nid(2),
1349
                old: at(1, 1),
1350
                mode: Mode {
1351
                    exec: true,
1352
                    opaque: false,
1353
                },
1354
            },
1355
            TreeOp::Move {
1356
                node: nid(2),
1357
                old: at(1, 1),
1358
                parent: NodeId::ROOT,
1359
                name: "b.rs".into(),
1360
            },
1361
            // Moving an empty directory, then removing it.
1362
            TreeOp::Move {
1363
                node: nid(1),
1364
                old: at(1, 0),
1365
                parent: NodeId::ROOT,
1366
                name: "lib".into(),
1367
            },
1368
            TreeOp::RemoveNode {
1369
                node: nid(1),
1370
                placement: at(1, 4),
1371
            },
1372
            // A name taken twice: a conflict, so the view is computed again.
1373
            TreeOp::AddNode {
1374
                node: nid(3),
1375
                kind: NodeKind::File,
1376
                mode: Mode::TEXT,
1377
                parent: NodeId::ROOT,
1378
                name: "b.rs".into(),
1379
            },
1380
        ];
1381
        for (i, op) in ops.iter().enumerate() {
1382
            let _ = g.view();
1383
            g.apply(h(1), i as u32, op);
1384
            assert_eq!(g.view(), g.compute_view());
1385
        }
1386
        let v = g.view();
1387
        assert_eq!(paths(&v), ["b.rs.oxy-conflict-1", "b.rs.oxy-conflict-2"]);
1388
        assert!(v.entries.values().any(|e| e.mode.exec));
1389
    }
1390
1391
    #[test]
1392
    fn nested_paths_and_rename() {
1393
        let mut g = TreeGraph::new();
1394
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "src");
1395
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "main.rs");
1396
        add(&mut g, 1, 2, 3, NodeKind::File, NodeId::ROOT, "README");
1397
        let v = g.view();
1398
        assert_eq!(paths(&v), ["README", "src", "src/main.rs"]);
1399
        assert!(!v.is_conflicted());
1400
1401
        // Rename src/main.rs → src/lib.rs, move src → lib (subtree follows).
1402
        g.apply(
1403
            h(2),
1404
            0,
1405
            &TreeOp::Move {
1406
                node: nid(2),
1407
                old: OpRef {
1408
                    change: h(1),
1409
                    index: 1,
1410
                },
1411
                parent: nid(1),
1412
                name: "lib.rs".into(),
1413
            },
1414
        );
1415
        g.apply(
1416
            h(2),
1417
            1,
1418
            &TreeOp::Move {
1419
                node: nid(1),
1420
                old: OpRef {
1421
                    change: h(1),
1422
                    index: 0,
1423
                },
1424
                parent: NodeId::ROOT,
1425
                name: "lib".into(),
1426
            },
1427
        );
1428
        let v = g.view();
1429
        assert_eq!(paths(&v), ["README", "lib", "lib/lib.rs"]);
1430
        assert_eq!(v.entries["lib/lib.rs"].placement, (h(2), 0));
1431
1432
        g.apply(
1433
            h(3),
1434
            0,
1435
            &TreeOp::RemoveNode {
1436
                node: nid(3),
1437
                placement: OpRef {
1438
                    change: h(1),
1439
                    index: 2,
1440
                },
1441
            },
1442
        );
1443
        assert_eq!(paths(&g.view()), ["lib", "lib/lib.rs"]);
1444
    }
1445
1446
    #[test]
1447
    fn name_conflict_gets_suffixes() {
1448
        let mut g = TreeGraph::new();
1449
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "a.txt");
1450
        add(&mut g, 2, 0, 2, NodeKind::File, NodeId::ROOT, "a.txt");
1451
        let v = g.view();
1452
        assert_eq!(paths(&v), ["a.txt.oxy-conflict-1", "a.txt.oxy-conflict-2"]);
1453
        assert_eq!(
1454
            v.conflicts,
1455
            vec![TreeConflict::Name {
1456
                parent: NodeId::ROOT,
1457
                name: "a.txt".into(),
1458
                nodes: vec![nid(1), nid(2)]
1459
            }]
1460
        );
1461
    }
1462
1463
    /// Identical additions at one place are one entry, the node with the
1464
    /// least placement key shown; anything else stays a name conflict.
1465
    #[test]
1466
    fn identical_additions_unify() {
1467
        let mut g = TreeGraph::new();
1468
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
1469
        add(&mut g, 3, 0, 3, NodeKind::File, nid(1), "a.txt");
1470
        add(&mut g, 2, 0, 2, NodeKind::File, nid(1), "a.txt");
1471
        let raw = g.view();
1472
        let (v, decided) = g.unify(raw.clone(), |es| {
1473
            assert_eq!(es.len(), 2, "one entry per node");
1474
            Some(true)
1475
        });
1476
        assert!(decided);
1477
        assert_eq!(paths(&v), ["d", "d/a.txt"]);
1478
        assert!(v.conflicts.is_empty());
1479
        assert_eq!(v.entries["d/a.txt"].node, nid(2), "the least placement key");
1480
        assert_eq!(v.entries["d/a.txt"].placement, (h(2), 0));
1481
        assert_eq!(v.unified, [(nid(3), nid(2))].into_iter().collect());
1482
        // Different contents, or not known yet: the conflict as it was.
1483
        let (v, decided) = g.unify(raw.clone(), |_| Some(false));
1484
        assert!(decided);
1485
        assert_eq!(v, raw);
1486
        let (v, decided) = g.unify(raw.clone(), |_| None);
1487
        assert!(!decided);
1488
        assert_eq!(v, raw);
1489
        // Directories, and kinds that differ, are never offered.
1490
        let mut g = TreeGraph::new();
1491
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
1492
        add(&mut g, 2, 0, 2, NodeKind::Dir, NodeId::ROOT, "d");
1493
        add(&mut g, 3, 0, 3, NodeKind::File, NodeId::ROOT, "f");
1494
        add(&mut g, 4, 0, 4, NodeKind::Symlink, NodeId::ROOT, "f");
1495
        let raw = g.view();
1496
        let (v, _) = g.unify(raw.clone(), |_| panic!("not offered"));
1497
        assert_eq!(v, raw);
1498
    }
1499
1500
    #[test]
1501
    fn concurrent_renames_are_multi_placement() {
1502
        let mut g = TreeGraph::new();
1503
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "a");
1504
        g.apply(
1505
            h(2),
1506
            0,
1507
            &TreeOp::Move {
1508
                node: nid(1),
1509
                old: OpRef {
1510
                    change: h(1),
1511
                    index: 0,
1512
                },
1513
                parent: NodeId::ROOT,
1514
                name: "b".into(),
1515
            },
1516
        );
1517
        g.apply(
1518
            h(3),
1519
            0,
1520
            &TreeOp::Move {
1521
                node: nid(1),
1522
                old: OpRef {
1523
                    change: h(1),
1524
                    index: 0,
1525
                },
1526
                parent: NodeId::ROOT,
1527
                name: "c".into(),
1528
            },
1529
        );
1530
        let v = g.view();
1531
        assert_eq!(paths(&v), ["b.oxy-conflict-1", "c.oxy-conflict-1"]);
1532
        assert!(
1533
            matches!(v.conflicts[0], TreeConflict::MultiPlacement { node, .. } if node == nid(1))
1534
        );
1535
    }
1536
1537
    #[test]
1538
    fn orphan_and_cycle() {
1539
        let mut g = TreeGraph::new();
1540
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
1541
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "f");
1542
        // Concurrently: 2 removes d, 3 adds d/g.
1543
        g.apply(
1544
            h(2),
1545
            0,
1546
            &TreeOp::RemoveNode {
1547
                node: nid(1),
1548
                placement: OpRef {
1549
                    change: h(1),
1550
                    index: 0,
1551
                },
1552
            },
1553
        );
1554
        add(&mut g, 3, 0, 3, NodeKind::File, nid(1), "g");
1555
        let v = g.view();
1556
        assert_eq!(paths(&v), Vec::<String>::new());
1557
        assert!(v.conflicts.contains(&TreeConflict::Orphan {
1558
            node: nid(2),
1559
            parent: nid(1)
1560
        }));
1561
        assert!(v.conflicts.contains(&TreeConflict::Orphan {
1562
            node: nid(3),
1563
            parent: nid(1)
1564
        }));
1565
1566
        // Cycle: a/b, then concurrently move a under b and b under a's parent... simplest: move a into b.
1567
        let mut g = TreeGraph::new();
1568
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "a");
1569
        add(&mut g, 1, 1, 2, NodeKind::Dir, nid(1), "b");
1570
        g.apply(
1571
            h(2),
1572
            0,
1573
            &TreeOp::Move {
1574
                node: nid(1),
1575
                old: OpRef {
1576
                    change: h(1),
1577
                    index: 0,
1578
                },
1579
                parent: nid(2),
1580
                name: "a".into(),
1581
            },
1582
        );
1583
        let v = g.view();
1584
        assert!(
1585
            v.conflicts
1586
                .iter()
1587
                .any(|c| matches!(c, TreeConflict::Cycle { .. })),
1588
            "{:?}",
1589
            v.conflicts
1590
        );
1591
    }
1592
1593
    #[test]
1594
    fn serialization_round_trip() {
1595
        let mut g = TreeGraph::new();
1596
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "src");
1597
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "main.rs");
1598
        g.apply(
1599
            h(2),
1600
            0,
1601
            &TreeOp::Move {
1602
                node: nid(2),
1603
                old: OpRef {
1604
                    change: h(1),
1605
                    index: 1,
1606
                },
1607
                parent: nid(1),
1608
                name: "lib.rs".into(),
1609
            },
1610
        );
1611
        g.apply(
1612
            h(3),
1613
            0,
1614
            &TreeOp::SetMode {
1615
                node: nid(2),
1616
                old: OpRef {
1617
                    change: h(1),
1618
                    index: 1,
1619
                },
1620
                mode: Mode {
1621
                    exec: true,
1622
                    opaque: false,
1623
                },
1624
            },
1625
        );
1626
        let bytes = crate::cbor::encode(&g.to_value());
1627
        let back = TreeGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap();
1628
        assert_eq!(back.view(), g.view());
1629
        assert_eq!(back.introducer(&nid(2)), Some(h(1)));
1630
        assert_eq!(crate::cbor::encode(&back.to_value()), bytes);
1631
    }
1632
1633
    #[test]
1634
    fn mode_conflict_and_resolution() {
1635
        let mut g = TreeGraph::new();
1636
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "run.sh");
1637
        g.apply(
1638
            h(2),
1639
            0,
1640
            &TreeOp::SetMode {
1641
                node: nid(1),
1642
                old: OpRef {
1643
                    change: h(1),
1644
                    index: 0,
1645
                },
1646
                mode: Mode {
1647
                    exec: true,
1648
                    opaque: false,
1649
                },
1650
            },
1651
        );
1652
        g.apply(
1653
            h(3),
1654
            0,
1655
            &TreeOp::SetMode {
1656
                node: nid(1),
1657
                old: OpRef {
1658
                    change: h(1),
1659
                    index: 0,
1660
                },
1661
                mode: Mode {
1662
                    exec: false,
1663
                    opaque: true,
1664
                },
1665
            },
1666
        );
1667
        let v = g.view();
1668
        assert!(matches!(v.conflicts[0], TreeConflict::Mode { .. }));
1669
        // Materialized with the greatest mode atom.
1670
        assert_eq!(
1671
            v.entries["run.sh"].mode,
1672
            Mode {
1673
                exec: false,
1674
                opaque: true
1675
            }
1676
        );
1677
        // The placement introduced by the same AddNode is unaffected.
1678
        assert_eq!(v.entries["run.sh"].placement, (h(1), 0));
1679
        // Resolution: retire both live mode atoms; identical live values do not conflict.
1680
        g.apply(
1681
            h(4),
1682
            0,
1683
            &TreeOp::SetMode {
1684
                node: nid(1),
1685
                old: OpRef {
1686
                    change: h(2),
1687
                    index: 0,
1688
                },
1689
                mode: Mode {
1690
                    exec: true,
1691
                    opaque: false,
1692
                },
1693
            },
1694
        );
1695
        g.apply(
1696
            h(4),
1697
            1,
1698
            &TreeOp::SetMode {
1699
                node: nid(1),
1700
                old: OpRef {
1701
                    change: h(3),
1702
                    index: 0,
1703
                },
1704
                mode: Mode {
1705
                    exec: true,
1706
                    opaque: false,
1707
                },
1708
            },
1709
        );
1710
        let v = g.view();
1711
        assert!(!v.is_conflicted(), "{:?}", v.conflicts);
1712
        assert_eq!(
1713
            v.entries["run.sh"].mode,
1714
            Mode {
1715
                exec: true,
1716
                opaque: false
1717
            }
1718
        );
1719
    }
1720
}