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