OXYGENHUB

Coverage / crates/oxy-core/src/tree.rs 93.8%

1155 of 1231 lines run, 163 of 183 functions · run #261 on main · run missed

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 {
27278721×
    fn insert(&mut self, k: OpKey) {
28199×
        match self {
29278476×
            KeySet::Empty => *self = KeySet::One(k),
30199×
            KeySet::One(a) if *a == k => {}
31199×
            KeySet::One(a) => {
32199×
                let a = *a;
33199×
                *self = KeySet::Many(if a < k { vec![a, k] } else { vec![k, a] });
34
            }
3546×
            KeySet::Many(v) => {
3646×
                if let Err(i) = v.binary_search(&k) {
3746×
                    v.insert(i, k);
3846×
                }
39
            }
40
        }
41278721×
    }
42545×
    fn remove(&mut self, k: &OpKey) {
43534×
        match self {
44534×
            KeySet::One(a) if a == k => *self = KeySet::Empty,
4511×
            KeySet::Many(v) => {
4611×
                if let Ok(i) = v.binary_search(k) {
4711×
                    v.remove(i);
4811×
                }
4911×
                if v.len() == 1 {
5011×
                    *self = KeySet::One(v[0]);
5111×
                } else if v.is_empty() {
520×
                    *self = KeySet::Empty;
530×
                }
54
            }
5597×
            _ => {}
56
        }
57545×
    }
5820304872×
    fn as_slice(&self) -> &[OpKey] {
5920304872×
        match self {
600×
            KeySet::Empty => &[],
6120304830×
            KeySet::One(a) => std::slice::from_ref(a),
6242×
            KeySet::Many(v) => v,
63
        }
6420304872×
    }
6520293136×
    fn iter(&self) -> std::slice::Iter<'_, OpKey> {
6620293136×
        self.as_slice().iter()
6720293136×
    }
6811736×
    fn len(&self) -> usize {
6911736×
        self.as_slice().len()
7011736×
    }
71545×
    fn is_empty(&self) -> bool {
72545×
        matches!(self, KeySet::Empty)
73545×
    }
74
}
75
76
impl<'a> IntoIterator for &'a KeySet {
77
    type Item = &'a OpKey;
78
    type IntoIter = std::slice::Iter<'a, OpKey>;
796763658×
    fn into_iter(self) -> Self::IntoIter {
806763658×
        self.iter()
816763658×
    }
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 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>>,
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>,
125
}
126
127
#[derive(Clone, Debug, PartialEq, Eq)]
128
pub enum TreeConflict {
129
    /// Two live nodes share `(parent, name)`.
130
    Name {
131
        parent: NodeId,
132
        name: String,
133
        nodes: Vec<NodeId>,
134
    },
135
    /// One node has several live placements.
136
    MultiPlacement {
137
        node: NodeId,
138
        placements: Vec<OpKey>,
139
    },
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>,
159
}
160
161
#[derive(Clone, Debug, Default)]
162
struct CachedView {
163
    view: TreeView,
164
    dirs: HashMap<NodeId, String>,
165
}
166
167
impl CachedView {
1683708×
    fn new(view: TreeView) -> CachedView {
1693708×
        let mut dirs: HashMap<NodeId, String> = view
1703708×
            .entries
1713708×
            .values()
17238823×
            .filter(|e| e.kind == NodeKind::Dir)
1733708×
            .map(|e| (e.node, e.path.clone()))
1743708×
            .collect();
1753708×
        dirs.insert(NodeId::ROOT, String::new());
1763708×
        CachedView { view, dirs }
1773708×
    }
178
}
179
180
#[derive(Clone, Debug, Default, PartialEq, Eq)]
181
pub struct TreeView {
182
    /// Path → entry, paths use `/` and have no leading slash; conflicted
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>>,
186
    pub conflicts: Vec<TreeConflict>,
187
}
188
189
impl TreeView {
1902302×
    pub fn is_conflicted(&self) -> bool {
1912302×
        !self.conflicts.is_empty()
1922302×
    }
1931257×
    pub fn by_node(&self) -> HashMap<NodeId, &TreeEntry> {
1949474×
        self.entries.values().map(|e| (e.node, e)).collect()
1951257×
    }
196
}
197
198
impl TreeGraph {
1992649×
    pub fn new() -> Self {
2002649×
        Self::default()
2012649×
    }
202
203
    /// The graph's maps, decoded from a state index on first use.
20448317239×
    fn m(&self) -> &TreeMaps {
20548317239×
        self.maps.get_or_init(|| match &self.raw {
2062641×
            Some(raw) => {
2072641×
                TreeMaps::decode(raw).expect("a state index is checked against its hash when read")
208
            }
2094897×
            None => TreeMaps::default(),
2107538×
        })
21148317239×
    }
212
213707480×
    fn m_mut(&mut self) -> &mut TreeMaps {
214707480×
        self.m();
215707480×
        self.maps.get_mut().expect("decoded just now")
216707480×
    }
217
218
    /// Apply tree op `index` of `change`; `OpRef` self references must already be resolved.
21937762×
    pub fn apply(&mut self, change: ChangeHash, index: u32, op: &TreeOp) {
22037762×
        let cached = self.view_cache.take();
221
        // Whether the placement or mode atom this op retires was live.
22237762×
        let was_live = match op {
223585×
            TreeOp::RemoveNode { placement, .. } => self.placement_live(&op_key(*placement)),
224191×
            TreeOp::Move { old, .. } => self.placement_live(&op_key(*old)),
22528×
            TreeOp::SetMode { old, .. } => self.mode_live(&op_key(*old)),
22636958×
            TreeOp::AddNode { .. } => false,
227
        };
22837762×
        self.apply_to_graph(change, index, op);
22937762×
        if let Some(mut c) = cached {
23011919×
            if self.update_view(&mut c, (change, index), op, was_live) {
2312552×
                #[cfg(test)]
2329181×
                assert_eq!(
2332552×
                    c.view,
2349181×
                    self.compute_view(),
2352552×
                    "view kept up to date by {op:?}"
2362552×
                );
23711733×
                self.view_cache = std::sync::OnceLock::from(c);
2382552×
            }
23925843×
        }
24037762×
    }
241
24237762×
    fn apply_to_graph(&mut self, change: ChangeHash, index: u32, op: &TreeOp) {
24337762×
        let key = (change, index);
24437762×
        match op {
245
            TreeOp::AddNode {
24636958×
                node,
24736958×
                kind,
24836958×
                mode,
24936958×
                parent,
25036958×
                name,
251
            } => {
25236958×
                self.m_mut().kinds.insert(*node, *kind);
25336958×
                self.m_mut().introduced.insert(*node, change);
25436958×
                self.m_mut().placements.insert(
25536958×
                    key,
25636958×
                    Placement {
25736958×
                        node: *node,
25836958×
                        parent: *parent,
25936958×
                        name: name.clone(),
26036958×
                    },
261
                );
26236958×
                self.m_mut()
26336958×
                    .placements_of
26436958×
                    .entry(*node)
26536958×
                    .or_default()
26636958×
                    .insert(key);
26736958×
                self.index_placement(key);
26836958×
                if *kind != NodeKind::Dir {
26935139×
                    self.m_mut().modes.insert(key, (*node, *mode));
27035139×
                    self.m_mut().modes_of.entry(*node).or_default().insert(key);
27135139×
                    self.index_mode(key);
27235139×
                }
273
            }
274585×
            TreeOp::RemoveNode { placement, .. } => {
275585×
                self.m_mut()
276585×
                    .deleted_placements
277585×
                    .entry(op_key(*placement))
278585×
                    .or_default()
279585×
                    .insert(change);
280585×
                self.index_placement(op_key(*placement));
281585×
            }
282
            TreeOp::Move {
283191×
                node,
284191×
                old,
285191×
                parent,
286191×
                name,
287191×
            } => {
288191×
                self.m_mut()
289191×
                    .deleted_placements
290191×
                    .entry(op_key(*old))
291191×
                    .or_default()
292191×
                    .insert(change);
293191×
                self.index_placement(op_key(*old));
294191×
                self.m_mut().placements.insert(
295191×
                    key,
296191×
                    Placement {
297191×
                        node: *node,
298191×
                        parent: *parent,
299191×
                        name: name.clone(),
300191×
                    },
301191×
                );
302191×
                self.m_mut()
303191×
                    .placements_of
304191×
                    .entry(*node)
305191×
                    .or_default()
306191×
                    .insert(key);
307191×
                self.index_placement(key);
308191×
            }
30928×
            TreeOp::SetMode { node, old, mode } => {
31028×
                self.m_mut()
31128×
                    .deleted_modes
31228×
                    .entry(op_key(*old))
31328×
                    .or_default()
31428×
                    .insert(change);
31528×
                self.index_mode(op_key(*old));
31628×
                self.m_mut().modes.insert(key, (*node, *mode));
31728×
                self.m_mut().modes_of.entry(*node).or_default().insert(key);
31828×
                self.index_mode(key);
31928×
            }
320
        }
32137762×
    }
322
323
    /// Bring the cached view `c` up to date with `op` (already applied as
324
    /// `key`) where that is simple and the view has no conflicts; false
325
    /// when it must be computed again. `was_live`: whether the placement or
326
    /// mode atom `op` retires was live before it.
32711919×
    fn update_view(&self, c: &mut CachedView, key: OpKey, op: &TreeOp, was_live: bool) -> bool {
32811919×
        if !c.view.conflicts.is_empty() {
3299×
            return false;
33011910×
        }
33111910×
        match op {
33211726×
            TreeOp::AddNode { node, .. } => self.view_add(c, *node, key),
333105×
            TreeOp::RemoveNode { node, placement } => {
334105×
                !was_live || self.view_remove(c, *node, op_key(*placement))
335
            }
33670×
            TreeOp::Move { node, old, .. } => {
33770×
                (!was_live || self.view_remove(c, *node, op_key(*old)))
33810×
                    && self.view_add(c, *node, key)
339
            }
3409×
            TreeOp::SetMode { node, .. } => {
3419×
                let live = self.live_placements(node);
3429×
                match live.as_slice() {
3439×
                    [] => true,
3445×
                    [(k, p)] => {
3455×
                        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
3460×
                            return false;
347
                        };
3485×
                        let Some(mode) = self.entry_mode(node) else {
3490×
                            return false;
350
                        };
3515×
                        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
3525×
                        match entries.get_mut(&path) {
3535×
                            Some(e) if e.node == *node && e.placement == *k => {
3545×
                                (e.mode_atom, e.mode) = mode;
3555×
                                true
356
                            }
3570×
                            _ => false,
358
                        }
359
                    }
3600×
                    _ => false,
361
                }
362
            }
363
        }
36411919×
    }
365
366
    /// The view entry for `node`'s placement `key`, just made live.
36711736×
    fn view_add(&self, c: &mut CachedView, node: NodeId, key: OpKey) -> bool {
36811736×
        if !self.placement_live(&key) {
3690×
            return true;
37011736×
        }
37111736×
        if self
37211736×
            .m()
37311736×
            .live_placements_of
37411736×
            .get(&node)
37511736×
            .is_none_or(|s| s.len() != 1)
376
        {
3777×
            return false;
37811729×
        }
37911729×
        let Some(p) = self.m().placements.get(&key) else {
3800×
            return false;
381
        };
38211729×
        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
3832×
            return false;
384
        };
38511727×
        let Some((mode_atom, mode)) = self.entry_mode(&node) else {
3860×
            return false;
387
        };
38811727×
        let kind = self.kind(&node).unwrap_or(NodeKind::File);
38911727×
        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
39011727×
        if entries.contains_key(&path) || node == NodeId::ROOT {
39141×
            return false;
39211686×
        }
393
        // Nothing can be placed under a directory that was not in the view
394
        // yet: it would have been an orphan, and the view had no conflicts.
39511686×
        if kind == NodeKind::Dir {
396785×
            c.dirs.insert(node, path.clone());
39710901×
        }
39811686×
        entries.insert(
39911686×
            path.clone(),
40011686×
            TreeEntry {
40111686×
                path,
40211686×
                node,
40311686×
                kind,
40411686×
                mode,
40511686×
                placement: key,
40611686×
                mode_atom,
40711686×
            },
408
        );
40911686×
        true
41011736×
    }
411
412
    /// Drop the view entry of `node`'s placement `key`, just retired.
413165×
    fn view_remove(&self, c: &mut CachedView, node: NodeId, key: OpKey) -> bool {
414165×
        if self.m().live_placements_of.contains_key(&node) {
41560×
            return false;
416105×
        }
417105×
        let Some(p) = self.m().placements.get(&key) else {
41845×
            return false;
419
        };
42060×
        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
4210×
            return false;
422
        };
42360×
        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
42460×
        match entries.get(&path) {
42560×
            Some(e) if e.node == node && e.placement == key => {}
4260×
            _ => return false,
427
        }
42860×
        if entries.get(&path).is_some_and(|e| e.kind == NodeKind::Dir) {
42923×
            let prefix = format!("{path}/");
43023×
            if entries
43123×
                .range(prefix.clone()..)
43223×
                .next()
43323×
                .is_some_and(|(k, _)| k.starts_with(&prefix))
434
            {
43522×
                return false;
4361×
            }
4371×
            c.dirs.remove(&node);
43837×
        }
43938×
        entries.remove(&path);
44038×
        true
441165×
    }
442
443
    /// The mode atom and mode of `node`'s entry, as `compute_view` finds
444
    /// them; `None` when its live modes conflict.
44511732×
    fn entry_mode(&self, node: &NodeId) -> Option<(Option<OpKey>, Mode)> {
44611732×
        let lm = self.live_modes(node);
44711732×
        let distinct: BTreeSet<Mode> = lm.iter().map(|(_, m)| *m).collect();
44811732×
        if distinct.len() > 1 {
4490×
            return None;
45011732×
        }
45111732×
        let mode_atom = lm.iter().map(|(k, _)| *k).max();
45211732×
        let mode = mode_atom
45311732×
            .and_then(|k| self.m().modes.get(&k).map(|(_, m)| *m))
45411732×
            .unwrap_or_default();
45511732×
        Some((mode_atom, mode))
45611732×
    }
457
458
    /// Bring placement `k` in the live index up to date.
45971563×
    fn index_placement(&mut self, k: OpKey) {
46071563×
        let Some(node) = self.m_mut().placements.get(&k).map(|p| p.node) else {
461262×
            return;
462
        };
46371301×
        if self.placement_live(&k) {
46470504×
            self.m_mut()
46570504×
                .live_placements_of
46670504×
                .entry(node)
46770504×
                .or_default()
46870504×
                .insert(k);
46970504×
        } else if let Some(set) = self.m_mut().live_placements_of.get_mut(&node) {
470516×
            set.remove(&k);
471516×
            if set.is_empty() {
472427×
                self.m_mut().live_placements_of.remove(&node);
473427×
            }
474281×
        }
47571563×
    }
476
477
    /// Bring mode atom `k` in the live index up to date.
47868751×
    fn index_mode(&mut self, k: OpKey) {
47968751×
        let Some(node) = self.m_mut().modes.get(&k).map(|(n, _)| *n) else {
48015×
            return;
481
        };
48268736×
        if self.mode_live(&k) {
48368707×
            self.m_mut()
48468707×
                .live_modes_of
48568707×
                .entry(node)
48668707×
                .or_default()
48768707×
                .insert(k);
48868707×
        } else if let Some(set) = self.m_mut().live_modes_of.get_mut(&node) {
48929×
            set.remove(&k);
49029×
            if set.is_empty() {
49110×
                self.m_mut().live_modes_of.remove(&node);
49219×
            }
4930×
        }
49468751×
    }
495
496
    /// Directories no longer placed anywhere, by the (parent, name) they
497
    /// were last placed at, with that placement: what an import can bring
498
    /// back rather than make a second node for one Git path.
49914×
    pub fn dead_dirs(&self) -> HashMap<(NodeId, String), (NodeId, OpKey)> {
50014×
        let mut out: HashMap<(NodeId, String), (NodeId, OpKey)> = HashMap::new();
50115×
        for (k, p) in &self.m().placements {
50215×
            if self.kind(&p.node) != Some(NodeKind::Dir)
5035×
                || self.placement_live(k)
5042×
                || !self.live_placements(&p.node).is_empty()
505
            {
50613×
                continue;
5072×
            }
5082×
            let slot = out
5092×
                .entry((p.parent, p.name.clone()))
5102×
                .or_insert((p.node, *k));
511
            // The newest placement, by key: the same answer in any order.
5122×
            if *k > slot.1 {
5130×
                *slot = (p.node, *k);
5142×
            }
515
        }
51614×
        out
51714×
    }
518
519
    /// Every placement `node` ever had, live or not, with the changes that
520
    /// retired it (none while live).
5210×
    pub fn placement_history(&self, node: &NodeId) -> Vec<(OpKey, &Placement, Vec<ChangeHash>)> {
5220×
        self.m()
5230×
            .placements_of
5240×
            .get(node)
5250×
            .into_iter()
5260×
            .flatten()
5270×
            .filter_map(|k| {
5280×
                let p = self.m().placements.get(k)?;
5290×
                let by = self
5300×
                    .m()
5310×
                    .deleted_placements
5320×
                    .get(k)
5330×
                    .map(|d| d.iter().copied().collect())
5340×
                    .unwrap_or_default();
5350×
                Some((*k, p, by))
5360×
            })
5370×
            .collect()
5380×
    }
539
54083818×
    fn placement_live(&self, k: &OpKey) -> bool {
54183818×
        self.m()
54283818×
            .deleted_placements
54383818×
            .get(k)
54483818×
            .is_none_or(|d| d.is_empty())
54583818×
    }
54668764×
    fn mode_live(&self, k: &OpKey) -> bool {
54768764×
        self.m().deleted_modes.get(k).is_none_or(|d| d.is_empty())
54868764×
    }
549
5506775409×
    pub fn kind(&self, node: &NodeId) -> Option<NodeKind> {
5516775409×
        if node.is_root() {
5520×
            Some(NodeKind::Dir)
553
        } else {
5546775409×
            self.m().kinds.get(node).copied()
555
        }
5566775409×
    }
557
558
    /// The change whose `AddNode` introduced `node`.
55914500×
    pub fn introducer(&self, node: &NodeId) -> Option<ChangeHash> {
56014500×
        if let (None, Some(raw)) = (self.maps.get(), &self.raw_introduced) {
561546×
            let only = self.introduced_only.get_or_init(|| {
56258×
                read_introduced(raw).expect("a state index is checked against its hash when read")
56358×
            });
564
            #[cfg(test)]
5650×
            assert_eq!(
5660×
                only.get(node),
5670×
                TreeMaps::decode(self.raw.as_ref().unwrap())
5680×
                    .unwrap()
5690×
                    .introduced
5700×
                    .get(node),
571
                "the introducers block is the maps'"
572
            );
573546×
            return only.get(node).copied();
57413954×
        }
57513954×
        self.m().introduced.get(node).copied()
57614500×
    }
577
578148×
    pub fn placement(&self, k: &OpKey) -> Option<&Placement> {
579148×
        self.m().placements.get(k)
580148×
    }
581
5826763852×
    pub fn live_placements(&self, node: &NodeId) -> Vec<(OpKey, &Placement)> {
5836763852×
        self.m()
5846763852×
            .live_placements_of
5856763852×
            .get(node)
5866763852×
            .into_iter()
5876763852×
            .flat_map(|ks| ks.iter())
5886763859×
            .filter_map(|k| self.m().placements.get(k).map(|p| (*k, p)))
5896763852×
            .collect()
5906763852×
    }
591
5926775390×
    pub fn live_modes(&self, node: &NodeId) -> Vec<(OpKey, Mode)> {
5936775390×
        self.m()
5946775390×
            .live_modes_of
5956775390×
            .get(node)
5966775390×
            .into_iter()
5976775390×
            .flat_map(|ks| ks.iter())
5986775390×
            .filter_map(|k| self.m().modes.get(k).map(|(_, m)| (*k, *m)))
5996775390×
            .collect()
6006775390×
    }
601
602
    /// Every node that has at least one live placement.
6030×
    pub fn placed_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
6040×
        self.m().live_placements_of.keys().copied()
6050×
    }
606
607
    /// Resolve the tree: assign paths, find conflicts.
608
    /// The materialized tree (spec §7.1): computed once per graph and
609
    /// cloned for each caller.
6106650×
    pub fn view(&self) -> TreeView {
6116650×
        self.view_cache
6126650×
            .get_or_init(|| CachedView::new(self.compute_view()))
613
            .view
6146650×
            .clone()
6156650×
    }
616
61712161×
    fn compute_view(&self) -> TreeView {
61812161×
        let mut view = TreeView::default();
61912161×
        let mut entries: BTreeMap<String, TreeEntry> = BTreeMap::new();
620
621
        // Live placements grouped by parent.
62212161×
        let mut children: HashMap<NodeId, Vec<(OpKey, &Placement)>> = HashMap::new();
6236763658×
        for ks in self.m().live_placements_of.values() {
6246763673×
            for k in ks {
6256763673×
                if let Some(p) = self.m().placements.get(k) {
6266763673×
                    children.entry(p.parent).or_default().push((*k, p));
6276763673×
                }
628
            }
629
        }
63019455×
        for c in children.values_mut() {
63174583135×
            c.sort_by(|a, b| a.1.name.cmp(&b.1.name).then(a.0.cmp(&b.0)));
632
        }
633
634
        // Multi-placement and mode conflicts.
635
        // Only placed nodes: one with no live placement is not in the tree.
63612161×
        let mut nodes: Vec<NodeId> = self.m().live_placements_of.keys().copied().collect();
63712161×
        nodes.sort();
63812161×
        let mut multi: HashSet<NodeId> = HashSet::new();
639
        // Each node's mode and the atom it comes from, for the entries.
64012161×
        let mut modes: HashMap<NodeId, (Option<OpKey>, Mode)> = HashMap::new();
6416763658×
        for n in &nodes {
6426763658×
            let lp = self.live_placements(n);
643
            // One placement (nearly every node) cannot be in two places.
6446763658×
            if lp.len() > 1 {
64515×
                let distinct_places: BTreeSet<(&NodeId, &str)> = lp
64615×
                    .iter()
64730×
                    .map(|(_, p)| (&p.parent, p.name.as_str()))
64815×
                    .collect();
64915×
                if distinct_places.len() > 1 {
6502×
                    multi.insert(*n);
6512×
                    view.conflicts.push(TreeConflict::MultiPlacement {
6522×
                        node: *n,
6532×
                        placements: lp.iter().map(|(k, _)| *k).collect(),
654
                    });
65513×
                }
6566763643×
            }
6576763658×
            let lm = self.live_modes(n);
6586763658×
            if lm.len() > 1 {
6592×
                let distinct_modes: BTreeSet<Mode> = lm.iter().map(|(_, m)| *m).collect();
6602×
                if distinct_modes.len() > 1 {
6611×
                    view.conflicts.push(TreeConflict::Mode {
6621×
                        node: *n,
6631×
                        modes: distinct_modes.into_iter().collect(),
6641×
                    });
6651×
                }
6666763656×
            }
6676763658×
            let mode_atom = lm.iter().map(|(k, _)| *k).max();
6686763658×
            let mode = mode_atom
6696763658×
                .and_then(|k| self.m().modes.get(&k).map(|(_, m)| *m))
6706763658×
                .unwrap_or_default();
6716763658×
            modes.insert(*n, (mode_atom, mode));
672
        }
673
674
        // BFS from the root, assigning paths.
67512161×
        let mut reached: HashSet<NodeId> = HashSet::new();
67612161×
        reached.insert(NodeId::ROOT);
67712161×
        let mut queue: Vec<(NodeId, String)> = vec![(NodeId::ROOT, String::new())];
67812161×
        let mut on_path: HashSet<NodeId> = HashSet::new();
67933287×
        while let Some((dir, dir_path)) = queue.pop() {
68021126×
            let Some(kids) = children.get(&dir) else {
6811676×
                continue;
682
            };
683
            // Name conflicts within this directory: the children are sorted
684
            // by name, so each name's group is a run of them.
68519450×
            let mut groups: Vec<(&str, &[(OpKey, &Placement)])> = Vec::new();
68619450×
            let mut start = 0;
6876763667×
            for i in 1..=kids.len() {
6886763667×
                if i == kids.len() || kids[i].1.name != kids[start].1.name {
6896763323×
                    groups.push((kids[start].1.name.as_str(), &kids[start..i]));
6906763323×
                    start = i;
6916763323×
                }
692
            }
6936763323×
            for (name, group) in groups {
6946763323×
                let distinct: BTreeSet<NodeId> = group.iter().map(|(_, p)| p.node).collect();
6956763323×
                if distinct.len() > 1 {
696279×
                    view.conflicts.push(TreeConflict::Name {
697279×
                        parent: dir,
698279×
                        name: name.to_string(),
699279×
                        nodes: distinct.iter().copied().collect(),
700279×
                    });
7016763044×
                }
7026763323×
                let suffixed = distinct.len() > 1;
7036763667×
                for (i, (k, p)) in group.iter().enumerate() {
7046763667×
                    let node = p.node;
7056763667×
                    if node == dir || on_path.contains(&node) {
7060×
                        view.conflicts.push(TreeConflict::Cycle { node });
7070×
                        continue;
7086763667×
                    }
7096763667×
                    let needs_suffix = suffixed || multi.contains(&node);
7106763667×
                    let leaf = if needs_suffix {
711613×
                        format!("{name}.oxy-conflict-{}", i + 1)
712
                    } else {
7136763054×
                        name.to_string()
714
                    };
7156763667×
                    let path = if dir_path.is_empty() {
71617784×
                        leaf
717
                    } else {
7186745883×
                        format!("{dir_path}/{leaf}")
719
                    };
7206763667×
                    let kind = self.kind(&node).unwrap_or(NodeKind::File);
7216763667×
                    let (mode_atom, mode) = modes.get(&node).copied().unwrap_or_default();
7226763667×
                    entries.insert(
7236763667×
                        path.clone(),
7246763667×
                        TreeEntry {
7256763667×
                            path: path.clone(),
7266763667×
                            node,
7276763667×
                            kind,
7286763667×
                            mode,
7296763667×
                            placement: *k,
7306763667×
                            mode_atom,
7316763667×
                        },
732
                    );
7336763667×
                    if kind == NodeKind::Dir && reached.insert(node) {
7348965×
                        on_path.insert(dir);
7358965×
                        queue.push((node, path));
7366754702×
                    } else {
7376754702×
                        reached.insert(node);
7386754702×
                    }
739
                }
740
            }
741
        }
742
743
        // Nodes with live placements that were never reached: orphans or cycles.
7446763658×
        for n in &nodes {
7456763658×
            if reached.contains(n) {
7466763653×
                continue;
7475×
            }
7485×
            let lp = self.live_placements(n);
7495×
            if lp.is_empty() {
7500×
                continue;
7515×
            }
7525×
            let mut seen = HashSet::new();
7535×
            let mut cur = *n;
7545×
            let conflict = loop {
75512×
                if !seen.insert(cur) {
7562×
                    break TreeConflict::Cycle { node: *n };
75710×
                }
75810×
                let lp = self.live_placements(&cur);
75910×
                let Some((_, p)) = lp.first() else {
7603×
                    break TreeConflict::Orphan {
7613×
                        node: *n,
7623×
                        parent: cur,
7633×
                    };
764
                };
7657×
                if reached.contains(&p.parent) {
766
                    // Parent reached but this node was not: the parent is not a
767
                    // directory, so this is a name/kind problem surfaced as orphan.
7680×
                    break TreeConflict::Orphan {
7690×
                        node: *n,
7700×
                        parent: p.parent,
7710×
                    };
7727×
                }
7737×
                cur = p.parent;
774
            };
7755×
            if !view.conflicts.contains(&conflict) {
7765×
                view.conflicts.push(conflict);
7775×
            }
778
        }
77912161×
        view.entries = std::sync::Arc::new(entries);
78012161×
        view
78112161×
    }
782
}
783
784
impl TreeGraph {
785
    /// Serialize for a checkpoint.
786400×
    pub fn to_value(&self) -> crate::cbor::Value {
787
        use crate::cbor::Value;
7884220×
        let opkey = |k: &OpKey| vec![Value::Bytes(k.0 .0.to_vec()), Value::Uint(k.1 as u64)];
789800×
        let deleters = |m: &Map<OpKey, BTreeSet<ChangeHash>>| {
790800×
            let mut ks: Vec<&OpKey> = m.keys().collect();
791800×
            ks.sort();
792
            Value::Array(
793800×
                ks.into_iter()
794800×
                    .map(|k| {
795246×
                        let mut v = opkey(k);
796246×
                        v.push(Value::Array(
797268×
                            m[k].iter().map(|d| Value::Bytes(d.0.to_vec())).collect(),
798
                        ));
799246×
                        Value::Array(v)
800246×
                    })
801800×
                    .collect(),
802
            )
803800×
        };
804400×
        let mut nodes: Vec<&NodeId> = self.m().kinds.keys().collect();
805400×
        nodes.sort();
806400×
        let mut pks: Vec<&OpKey> = self.m().placements.keys().collect();
807400×
        pks.sort();
808400×
        let mut mks: Vec<&OpKey> = self.m().modes.keys().collect();
809400×
        mks.sort();
810400×
        Value::map([
811
            (
812
                0,
813
                Value::Array(
814400×
                    nodes
815400×
                        .into_iter()
8162173×
                        .map(|n| {
8172173×
                            Value::Array(vec![
8182173×
                                Value::Bytes(n.0.to_vec()),
8192173×
                                Value::Uint(self.m().kinds[n].tag()),
8202173×
                                self.m()
8212173×
                                    .introduced
8222173×
                                    .get(n)
8232173×
                                    .map(|h| Value::Bytes(h.0.to_vec()))
8242173×
                                    .unwrap_or(Value::Null),
825
                            ])
8262173×
                        })
827400×
                        .collect(),
828
                ),
829
            ),
830
            (
831
                1,
832
                Value::Array(
833400×
                    pks.into_iter()
8342197×
                        .map(|k| {
8352197×
                            let p = &self.m().placements[k];
8362197×
                            let mut v = opkey(k);
8372197×
                            v.extend([
8382197×
                                Value::Bytes(p.node.0.to_vec()),
8392197×
                                Value::Bytes(p.parent.0.to_vec()),
8402197×
                                Value::Text(p.name.clone()),
8412197×
                            ]);
8422197×
                            Value::Array(v)
8432197×
                        })
844400×
                        .collect(),
845
                ),
846
            ),
847400×
            (2, deleters(&self.m().deleted_placements)),
848
            (
849
                3,
850
                Value::Array(
851400×
                    mks.into_iter()
8521777×
                        .map(|k| {
8531777×
                            let (n, m) = &self.m().modes[k];
8541777×
                            let mut v = opkey(k);
8551777×
                            v.extend([Value::Bytes(n.0.to_vec()), Value::Uint(m.bits())]);
8561777×
                            Value::Array(v)
8571777×
                        })
858400×
                        .collect(),
859
                ),
860
            ),
861400×
            (4, deleters(&self.m().deleted_modes)),
862
        ])
863400×
    }
864
865100×
    pub fn from_value(v: &crate::cbor::Value) -> Result<TreeGraph, crate::objects::ObjectError> {
866
        use crate::objects::{array, bytes, field, map, oref, text, u32_, uint, ObjectError};
867100×
        let m = map(v, "tree graph")?;
8681734×
        let node = |v: &crate::cbor::Value| {
8691734×
            NodeId::from_slice(bytes(v, "node")?)
8701734×
                .ok_or_else(|| ObjectError::Schema("node: expected 16 bytes".into()))
8711734×
        };
872873×
        let opkey = |e: &[crate::cbor::Value]| -> Result<OpKey, ObjectError> {
873873×
            Ok((oref(&e[0], "op change")?, u32_(&e[1], "op index")?))
874873×
        };
875100×
        let mut g = TreeGraph::default();
876441×
        for n in array(field(m, 0, "nodes")?, "nodes")? {
877441×
            let e = array(n, "node entry")?;
878441×
            if e.len() != 3 {
8790×
                return Err(ObjectError::Schema("node entry: expected 3 fields".into()));
880441×
            }
881441×
            let id = node(&e[0])?;
882441×
            g.m_mut()
883441×
                .kinds
884441×
                .insert(id, NodeKind::from_tag(uint(&e[1], "kind")?)?);
885441×
            if !e[2].is_null() {
886441×
                g.m_mut().introduced.insert(id, oref(&e[2], "introducer")?);
8870×
            }
888
        }
889444×
        for p in array(field(m, 1, "placements")?, "placements")? {
890444×
            let e = array(p, "placement")?;
891444×
            if e.len() != 5 {
8920×
                return Err(ObjectError::Schema("placement: expected 5 fields".into()));
893444×
            }
894444×
            let k = opkey(e)?;
895444×
            let pl = Placement {
896444×
                node: node(&e[2])?,
897444×
                parent: node(&e[3])?,
898444×
                name: text(&e[4], "name")?.to_owned(),
899
            };
900444×
            g.m_mut()
901444×
                .placements_of
902444×
                .entry(pl.node)
903444×
                .or_default()
904444×
                .insert(k);
905444×
            g.m_mut().placements.insert(k, pl);
906
        }
907100×
        let deleters = |v: &crate::cbor::Value,
908
                        what: &str|
909200×
         -> Result<Map<OpKey, BTreeSet<ChangeHash>>, ObjectError> {
910200×
            let mut out = Map::default();
911200×
            for d in array(v, what)? {
91224×
                let e = array(d, what)?;
91324×
                if e.len() != 3 {
9140×
                    return Err(ObjectError::Schema(format!("{what}: expected 3 fields")));
91524×
                }
91624×
                out.insert(
91724×
                    opkey(e)?,
91824×
                    array(&e[2], "deleters")?
91924×
                        .iter()
92026×
                        .map(|x| oref(x, "deleter"))
92124×
                        .collect::<Result<BTreeSet<_>, _>>()?,
922
                );
923
            }
924200×
            Ok(out)
925200×
        };
926100×
        g.m_mut().deleted_placements =
927100×
            deleters(field(m, 2, "deleted placements")?, "deleted placement")?;
928405×
        for md in array(field(m, 3, "modes")?, "modes")? {
929405×
            let e = array(md, "mode")?;
930405×
            if e.len() != 4 {
9310×
                return Err(ObjectError::Schema("mode: expected 4 fields".into()));
932405×
            }
933405×
            let k = opkey(e)?;
934405×
            let n = node(&e[2])?;
935405×
            g.m_mut()
936405×
                .modes
937405×
                .insert(k, (n, Mode::from_bits(uint(&e[3], "mode bits")?)?));
938405×
            g.m_mut().modes_of.entry(n).or_default().insert(k);
939
        }
940100×
        g.m_mut().deleted_modes = deleters(field(m, 4, "deleted modes")?, "deleted mode")?;
941100×
        let keys: Vec<OpKey> = g.m_mut().placements.keys().copied().collect();
942444×
        for k in keys {
943444×
            g.index_placement(k);
944444×
        }
945100×
        let keys: Vec<OpKey> = g.m_mut().modes.keys().copied().collect();
946405×
        for k in keys {
947405×
            g.index_mode(k);
948405×
        }
949100×
        Ok(g)
950100×
    }
951
}
952
953
/// The tree in the flat layout of a local index (oxygen#41), with its view
954
/// when that has no conflicts, so a loaded tree need not work it out again.
955
impl TreeGraph {
956849×
    pub(crate) fn write_flat(&self, w: &mut crate::flat::Writer) {
957131324×
        let key = |w: &mut crate::flat::Writer, k: &OpKey| {
958131324×
            w.object(&k.0);
959131324×
            w.u32(k.1);
960131324×
        };
961
        // The maps as a block of their own: a reader keeps it undecoded
962
        // until something needs them.
963849×
        let outer = std::mem::take(w);
964849×
        let mut kinds: Vec<(&NodeId, &NodeKind)> = self.m().kinds.iter().collect();
965849×
        kinds.sort_by_key(|(n, _)| **n);
966849×
        w.count(kinds.len());
96732852×
        for (n, k) in kinds {
96832852×
            w.fixed(&n.0);
96932852×
            w.u8(k.tag() as u8);
97032852×
        }
971849×
        let mut intro: Vec<(&NodeId, &ChangeHash)> = self.m().introduced.iter().collect();
972849×
        intro.sort();
973849×
        w.count(intro.len());
97432852×
        for (n, c) in intro {
97532852×
            w.fixed(&n.0);
97632852×
            w.object(c);
97732852×
        }
978849×
        let mut pl: Vec<(&OpKey, &Placement)> = self.m().placements.iter().collect();
979849×
        pl.sort_by_key(|(k, _)| **k);
980849×
        w.count(pl.len());
98132852×
        for (k, p) in pl {
98232852×
            key(w, k);
98332852×
            w.fixed(&p.node.0);
98432852×
            w.fixed(&p.parent.0);
98532852×
            w.bytes(p.name.as_bytes());
98632852×
        }
987849×
        let mut modes: Vec<(&OpKey, &(NodeId, Mode))> = self.m().modes.iter().collect();
988849×
        modes.sort_by_key(|(k, _)| **k);
989849×
        w.count(modes.len());
99032810×
        for (k, (n, m)) in modes {
99132810×
            key(w, k);
99232810×
            w.fixed(&n.0);
99332810×
            w.u8(m.bits() as u8);
99432810×
        }
9951698×
        for m in [&self.m().deleted_placements, &self.m().deleted_modes] {
9961698×
            let mut ks: Vec<(&OpKey, &BTreeSet<ChangeHash>)> = m.iter().collect();
9971698×
            ks.sort_by_key(|(k, _)| **k);
9981698×
            w.count(ks.len());
9991698×
            for (k, by) in ks {
10000×
                key(w, k);
10010×
                w.count(by.len());
10020×
                for c in by {
10030×
                    w.object(c);
10040×
                }
1005
            }
1006
        }
1007849×
        let maps = std::mem::replace(w, outer);
1008849×
        w.bytes(&maps.0);
1009
        // The introducers again, alone, for a reader that needs no more.
1010849×
        let mut iw = crate::flat::Writer::default();
1011849×
        let mut intro: Vec<(&NodeId, &ChangeHash)> = self.m().introduced.iter().collect();
1012849×
        intro.sort();
1013849×
        iw.count(intro.len());
101432852×
        for (n, c) in intro {
101532852×
            iw.fixed(&n.0);
101632852×
            iw.object(c);
101732852×
        }
1018849×
        w.bytes(&iw.0);
1019
        // The view as a block of its own.
1020849×
        let outer = std::mem::take(w);
1021849×
        let view = self.view();
1022849×
        if view.conflicts.is_empty() {
1023849×
            w.u8(1);
1024849×
            w.count(view.entries.len());
102532852×
            for (path, e) in view.entries.iter() {
102632852×
                w.bytes(path.as_bytes());
102732852×
                w.fixed(&e.node.0);
102832852×
                w.u8(e.kind.tag() as u8);
102932852×
                w.u8(e.mode.bits() as u8);
103032852×
                key(w, &e.placement);
103132852×
                match &e.mode_atom {
103242×
                    None => w.u8(0),
103332810×
                    Some(k) => {
103432810×
                        w.u8(1);
103532810×
                        key(w, k);
103632810×
                    }
1037
                }
1038
            }
10390×
        } else {
10400×
            w.u8(0);
10410×
        }
1042849×
        let view_bytes = std::mem::replace(w, outer);
1043849×
        w.bytes(&view_bytes.0);
1044849×
    }
1045
10461717×
    pub(crate) fn read_flat(
10471717×
        r: &mut crate::flat::Reader,
10481717×
    ) -> Result<TreeGraph, crate::objects::ObjectError> {
10491717×
        let maps = r.bytes()?;
10501717×
        let introduced = r.bytes()?;
10511717×
        let view = Self::read_view(r.bytes()?)?;
10521717×
        let mut g = TreeGraph {
10531717×
            raw: Some(std::sync::Arc::new(maps.to_vec())),
10541717×
            raw_introduced: Some(std::sync::Arc::new(introduced.to_vec())),
10551717×
            ..TreeGraph::default()
10561717×
        };
10571717×
        if let Some(view) = view {
1058735×
            // Checked on a copy, so the graph itself stays undecoded.
1059735×
            #[cfg(test)]
1060982×
            assert_eq!(
1061735×
                view,
1062982×
                TreeGraph {
1063982×
                    raw: g.raw.clone(),
1064982×
                    ..TreeGraph::default()
1065982×
                }
1066982×
                .compute_view(),
1067735×
                "an index's view is the tree's"
1068735×
            );
10691717×
            g.view_cache = std::sync::OnceLock::from(CachedView::new(view));
1070735×
        }
10711717×
        Ok(g)
10721717×
    }
1073
}
1074
1075
impl TreeMaps {
1076
    /// The maps block of a tree's flat form.
10772641×
    fn decode(bytes: &[u8]) -> Result<TreeMaps, crate::objects::ObjectError> {
1078
        use crate::objects::ObjectError;
10792641×
        let _t = crate::timing::phase("index: tree maps");
1080165927×
        let node = |r: &mut crate::flat::Reader| -> Result<NodeId, ObjectError> {
1081165927×
            Ok(NodeId(r.fixed::<16>()?))
1082165927×
        };
108366345×
        let key = |r: &mut crate::flat::Reader| -> Result<OpKey, ObjectError> {
108466345×
            Ok((r.object()?, r.u32()?))
108566345×
        };
10862641×
        let r = &mut crate::flat::Reader::new(bytes);
10872641×
        let mut g = TreeGraph::new();
10882641×
        for _ in 0..r.count()? {
108933194×
            let n = node(r)?;
109033194×
            g.m_mut()
109133194×
                .kinds
109233194×
                .insert(n, NodeKind::from_tag(r.u8()? as u64)?);
1093
        }
10942641×
        for _ in 0..r.count()? {
109533194×
            let n = node(r)?;
109633194×
            g.m_mut().introduced.insert(n, r.object()?);
1097
        }
10982641×
        for _ in 0..r.count()? {
109933194×
            let k = key(r)?;
110033194×
            let p = Placement {
110133194×
                node: node(r)?,
110233194×
                parent: node(r)?,
110333194×
                name: r.text()?,
1104
            };
110533194×
            g.m_mut().placements_of.entry(p.node).or_default().insert(k);
110633194×
            g.m_mut().placements.insert(k, p);
1107
        }
11082641×
        for _ in 0..r.count()? {
110933151×
            let k = key(r)?;
111033151×
            let n = node(r)?;
111133151×
            g.m_mut()
111233151×
                .modes
111333151×
                .insert(k, (n, Mode::from_bits(r.u8()? as u64)?));
111433151×
            g.m_mut().modes_of.entry(n).or_default().insert(k);
1115
        }
11165282×
        for i in 0..2 {
11175282×
            let mut m: Map<OpKey, BTreeSet<ChangeHash>> = Map::default();
11185282×
            for _ in 0..r.count()? {
11190×
                let k = key(r)?;
11200×
                let mut by = BTreeSet::new();
11210×
                for _ in 0..r.count()? {
11220×
                    by.insert(r.object()?);
1123
                }
11240×
                m.insert(k, by);
1125
            }
11265282×
            if i == 0 {
11272641×
                g.m_mut().deleted_placements = m;
11282641×
            } else {
11292641×
                g.m_mut().deleted_modes = m;
11302641×
            }
1131
        }
11322641×
        if !r.at_end() {
11330×
            return Err(ObjectError::Schema("state index: tree maps".into()));
11342641×
        }
11352641×
        let keys: Vec<OpKey> = g.m().placements.keys().copied().collect();
113633194×
        for k in keys {
113733194×
            g.index_placement(k);
113833194×
        }
11392641×
        let keys: Vec<OpKey> = g.m().modes.keys().copied().collect();
114033151×
        for k in keys {
114133151×
            g.index_mode(k);
114233151×
        }
11432641×
        Ok(g.maps.into_inner().unwrap_or_default())
11442641×
    }
1145
}
1146
1147
impl TreeGraph {
1148
    /// The view block of a tree's flat form, when it holds one.
11491717×
    fn read_view(bytes: &[u8]) -> Result<Option<TreeView>, crate::objects::ObjectError> {
11501717×
        let _t = crate::timing::phase("index: view");
11511717×
        let mut r = crate::flat::Reader::new(bytes);
11521717×
        if r.u8()? != 1 {
11530×
            return Ok(None);
11541717×
        }
115533478×
        let key = |r: &mut crate::flat::Reader| -> Result<OpKey, crate::objects::ObjectError> {
115633478×
            Ok((r.object()?, r.u32()?))
115733478×
        };
11581717×
        let n = r.count()?;
1159
        // Collected, then built in one step: the map is made from sorted
1160
        // input in a single pass rather than by searching for each insert.
11611717×
        let mut entries = Vec::with_capacity(n);
11621717×
        for _ in 0..n {
116316750×
            let path = r.text()?;
116416750×
            let e = TreeEntry {
116516750×
                path: path.clone(),
116616750×
                node: NodeId(r.fixed::<16>()?),
116716750×
                kind: NodeKind::from_tag(r.u8()? as u64)?,
116816750×
                mode: Mode::from_bits(r.u8()? as u64)?,
116916750×
                placement: key(&mut r)?,
117016750×
                mode_atom: match r.u8()? {
117122×
                    0 => None,
117216728×
                    _ => Some(key(&mut r)?),
1173
                },
1174
            };
117516750×
            entries.push((path, e));
1176
        }
11771717×
        Ok(Some(TreeView {
11781717×
            entries: std::sync::Arc::new(entries.into_iter().collect()),
11791717×
            conflicts: Vec::new(),
11801717×
        }))
11811717×
    }
1182
}
1183
1184
/// The introducers block of a tree's flat form.
118558×
fn read_introduced(bytes: &[u8]) -> Result<Map<NodeId, ChangeHash>, crate::objects::ObjectError> {
118658×
    let r = &mut crate::flat::Reader::new(bytes);
118758×
    let n = r.count()?;
118858×
    let mut m = Map::default();
118958×
    m.reserve(n);
119058×
    for _ in 0..n {
11910×
        let node = NodeId(r.fixed::<16>()?);
11920×
        m.insert(node, r.object()?);
1193
    }
119458×
    Ok(m)
119558×
}
1196
119711792×
fn join(dir: &str, name: &str) -> String {
119811792×
    if dir.is_empty() {
11993881×
        name.to_string()
1200
    } else {
12017911×
        format!("{dir}/{name}")
1202
    }
120311792×
}
1204
12052577×
pub fn op_key(r: OpRef) -> OpKey {
12062577×
    (r.change, r.index)
12072577×
}
1208
1209
#[cfg(test)]
1210
mod tests {
1211
    use super::*;
1212
    use crate::hash::ObjectRef;
1213
121454×
    fn h(n: u8) -> ChangeHash {
121554×
        ObjectRef([n; 32])
121654×
    }
121751×
    fn nid(n: u8) -> NodeId {
121851×
        NodeId([n; 16])
121951×
    }
122014×
    fn add(g: &mut TreeGraph, c: u8, i: u32, node: u8, kind: NodeKind, parent: NodeId, name: &str) {
122114×
        g.apply(
122214×
            h(c),
122314×
            i,
122414×
            &TreeOp::AddNode {
122514×
                node: nid(node),
122614×
                kind,
122714×
                mode: Mode::TEXT,
122814×
                parent,
122914×
                name: name.into(),
123014×
            },
1231
        );
123214×
    }
12337×
    fn paths(v: &TreeView) -> Vec<String> {
12347×
        v.entries.keys().cloned().collect()
12357×
    }
1236
1237
    /// Each op applied to a graph whose view is cached keeps it up to date
1238
    /// or drops it: `apply` checks every kept view against a full one.
1239
    #[test]
12401×
    fn cached_view_follows_ops() {
12411×
        let at = |c: u8, i: u32| OpRef {
12424×
            change: h(c),
12434×
            index: i,
12444×
        };
12451×
        let mut g = TreeGraph::new();
12461×
        let ops = [
12471×
            TreeOp::AddNode {
12481×
                node: nid(1),
12491×
                kind: NodeKind::Dir,
12501×
                mode: Mode::TEXT,
12511×
                parent: NodeId::ROOT,
12521×
                name: "src".into(),
12531×
            },
12541×
            TreeOp::AddNode {
12551×
                node: nid(2),
12561×
                kind: NodeKind::File,
12571×
                mode: Mode::TEXT,
12581×
                parent: nid(1),
12591×
                name: "a.rs".into(),
12601×
            },
12611×
            TreeOp::SetMode {
12621×
                node: nid(2),
12631×
                old: at(1, 1),
12641×
                mode: Mode {
12651×
                    exec: true,
12661×
                    opaque: false,
12671×
                },
12681×
            },
12691×
            TreeOp::Move {
12701×
                node: nid(2),
12711×
                old: at(1, 1),
12721×
                parent: NodeId::ROOT,
12731×
                name: "b.rs".into(),
12741×
            },
12751×
            // Moving an empty directory, then removing it.
12761×
            TreeOp::Move {
12771×
                node: nid(1),
12781×
                old: at(1, 0),
12791×
                parent: NodeId::ROOT,
12801×
                name: "lib".into(),
12811×
            },
12821×
            TreeOp::RemoveNode {
12831×
                node: nid(1),
12841×
                placement: at(1, 4),
12851×
            },
12861×
            // A name taken twice: a conflict, so the view is computed again.
12871×
            TreeOp::AddNode {
12881×
                node: nid(3),
12891×
                kind: NodeKind::File,
12901×
                mode: Mode::TEXT,
12911×
                parent: NodeId::ROOT,
12921×
                name: "b.rs".into(),
12931×
            },
12941×
        ];
12957×
        for (i, op) in ops.iter().enumerate() {
12967×
            let _ = g.view();
12977×
            g.apply(h(1), i as u32, op);
12987×
            assert_eq!(g.view(), g.compute_view());
1299
        }
13001×
        let v = g.view();
13011×
        assert_eq!(paths(&v), ["b.rs.oxy-conflict-1", "b.rs.oxy-conflict-2"]);
13021×
        assert!(v.entries.values().any(|e| e.mode.exec));
13031×
    }
1304
1305
    #[test]
13061×
    fn nested_paths_and_rename() {
13071×
        let mut g = TreeGraph::new();
13081×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "src");
13091×
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "main.rs");
13101×
        add(&mut g, 1, 2, 3, NodeKind::File, NodeId::ROOT, "README");
13111×
        let v = g.view();
13121×
        assert_eq!(paths(&v), ["README", "src", "src/main.rs"]);
13131×
        assert!(!v.is_conflicted());
1314
1315
        // Rename src/main.rs → src/lib.rs, move src → lib (subtree follows).
13161×
        g.apply(
13171×
            h(2),
1318
            0,
13191×
            &TreeOp::Move {
13201×
                node: nid(2),
13211×
                old: OpRef {
13221×
                    change: h(1),
13231×
                    index: 1,
13241×
                },
13251×
                parent: nid(1),
13261×
                name: "lib.rs".into(),
13271×
            },
1328
        );
13291×
        g.apply(
13301×
            h(2),
1331
            1,
13321×
            &TreeOp::Move {
13331×
                node: nid(1),
13341×
                old: OpRef {
13351×
                    change: h(1),
13361×
                    index: 0,
13371×
                },
13381×
                parent: NodeId::ROOT,
13391×
                name: "lib".into(),
13401×
            },
1341
        );
13421×
        let v = g.view();
13431×
        assert_eq!(paths(&v), ["README", "lib", "lib/lib.rs"]);
13441×
        assert_eq!(v.entries["lib/lib.rs"].placement, (h(2), 0));
1345
13461×
        g.apply(
13471×
            h(3),
1348
            0,
13491×
            &TreeOp::RemoveNode {
13501×
                node: nid(3),
13511×
                placement: OpRef {
13521×
                    change: h(1),
13531×
                    index: 2,
13541×
                },
13551×
            },
1356
        );
13571×
        assert_eq!(paths(&g.view()), ["lib", "lib/lib.rs"]);
13581×
    }
1359
1360
    #[test]
13611×
    fn name_conflict_gets_suffixes() {
13621×
        let mut g = TreeGraph::new();
13631×
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "a.txt");
13641×
        add(&mut g, 2, 0, 2, NodeKind::File, NodeId::ROOT, "a.txt");
13651×
        let v = g.view();
13661×
        assert_eq!(paths(&v), ["a.txt.oxy-conflict-1", "a.txt.oxy-conflict-2"]);
13671×
        assert_eq!(
1368
            v.conflicts,
13691×
            vec![TreeConflict::Name {
13701×
                parent: NodeId::ROOT,
13711×
                name: "a.txt".into(),
13721×
                nodes: vec![nid(1), nid(2)]
13731×
            }]
1374
        );
13751×
    }
1376
1377
    #[test]
13781×
    fn concurrent_renames_are_multi_placement() {
13791×
        let mut g = TreeGraph::new();
13801×
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "a");
13811×
        g.apply(
13821×
            h(2),
1383
            0,
13841×
            &TreeOp::Move {
13851×
                node: nid(1),
13861×
                old: OpRef {
13871×
                    change: h(1),
13881×
                    index: 0,
13891×
                },
13901×
                parent: NodeId::ROOT,
13911×
                name: "b".into(),
13921×
            },
1393
        );
13941×
        g.apply(
13951×
            h(3),
1396
            0,
13971×
            &TreeOp::Move {
13981×
                node: nid(1),
13991×
                old: OpRef {
14001×
                    change: h(1),
14011×
                    index: 0,
14021×
                },
14031×
                parent: NodeId::ROOT,
14041×
                name: "c".into(),
14051×
            },
1406
        );
14071×
        let v = g.view();
14081×
        assert_eq!(paths(&v), ["b.oxy-conflict-1", "c.oxy-conflict-1"]);
14091×
        assert!(
14101×
            matches!(v.conflicts[0], TreeConflict::MultiPlacement { node, .. } if node == nid(1))
1411
        );
14121×
    }
1413
1414
    #[test]
14151×
    fn orphan_and_cycle() {
14161×
        let mut g = TreeGraph::new();
14171×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
14181×
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "f");
1419
        // Concurrently: 2 removes d, 3 adds d/g.
14201×
        g.apply(
14211×
            h(2),
1422
            0,
14231×
            &TreeOp::RemoveNode {
14241×
                node: nid(1),
14251×
                placement: OpRef {
14261×
                    change: h(1),
14271×
                    index: 0,
14281×
                },
14291×
            },
1430
        );
14311×
        add(&mut g, 3, 0, 3, NodeKind::File, nid(1), "g");
14321×
        let v = g.view();
14331×
        assert_eq!(paths(&v), Vec::<String>::new());
14341×
        assert!(v.conflicts.contains(&TreeConflict::Orphan {
14351×
            node: nid(2),
14361×
            parent: nid(1)
14371×
        }));
14381×
        assert!(v.conflicts.contains(&TreeConflict::Orphan {
14391×
            node: nid(3),
14401×
            parent: nid(1)
14411×
        }));
1442
1443
        // Cycle: a/b, then concurrently move a under b and b under a's parent... simplest: move a into b.
14441×
        let mut g = TreeGraph::new();
14451×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "a");
14461×
        add(&mut g, 1, 1, 2, NodeKind::Dir, nid(1), "b");
14471×
        g.apply(
14481×
            h(2),
1449
            0,
14501×
            &TreeOp::Move {
14511×
                node: nid(1),
14521×
                old: OpRef {
14531×
                    change: h(1),
14541×
                    index: 0,
14551×
                },
14561×
                parent: nid(2),
14571×
                name: "a".into(),
14581×
            },
1459
        );
14601×
        let v = g.view();
14611×
        assert!(
14621×
            v.conflicts
14631×
                .iter()
14641×
                .any(|c| matches!(c, TreeConflict::Cycle { .. })),
1465
            "{:?}",
1466
            v.conflicts
1467
        );
14681×
    }
1469
1470
    #[test]
14711×
    fn serialization_round_trip() {
14721×
        let mut g = TreeGraph::new();
14731×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "src");
14741×
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "main.rs");
14751×
        g.apply(
14761×
            h(2),
1477
            0,
14781×
            &TreeOp::Move {
14791×
                node: nid(2),
14801×
                old: OpRef {
14811×
                    change: h(1),
14821×
                    index: 1,
14831×
                },
14841×
                parent: nid(1),
14851×
                name: "lib.rs".into(),
14861×
            },
1487
        );
14881×
        g.apply(
14891×
            h(3),
1490
            0,
14911×
            &TreeOp::SetMode {
14921×
                node: nid(2),
14931×
                old: OpRef {
14941×
                    change: h(1),
14951×
                    index: 1,
14961×
                },
14971×
                mode: Mode {
14981×
                    exec: true,
14991×
                    opaque: false,
15001×
                },
15011×
            },
1502
        );
15031×
        let bytes = crate::cbor::encode(&g.to_value());
15041×
        let back = TreeGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap();
15051×
        assert_eq!(back.view(), g.view());
15061×
        assert_eq!(back.introducer(&nid(2)), Some(h(1)));
15071×
        assert_eq!(crate::cbor::encode(&back.to_value()), bytes);
15081×
    }
1509
1510
    #[test]
15111×
    fn mode_conflict_and_resolution() {
15121×
        let mut g = TreeGraph::new();
15131×
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "run.sh");
15141×
        g.apply(
15151×
            h(2),
1516
            0,
15171×
            &TreeOp::SetMode {
15181×
                node: nid(1),
15191×
                old: OpRef {
15201×
                    change: h(1),
15211×
                    index: 0,
15221×
                },
15231×
                mode: Mode {
15241×
                    exec: true,
15251×
                    opaque: false,
15261×
                },
15271×
            },
1528
        );
15291×
        g.apply(
15301×
            h(3),
1531
            0,
15321×
            &TreeOp::SetMode {
15331×
                node: nid(1),
15341×
                old: OpRef {
15351×
                    change: h(1),
15361×
                    index: 0,
15371×
                },
15381×
                mode: Mode {
15391×
                    exec: false,
15401×
                    opaque: true,
15411×
                },
15421×
            },
1543
        );
15441×
        let v = g.view();
15451×
        assert!(matches!(v.conflicts[0], TreeConflict::Mode { .. }));
1546
        // Materialized with the greatest mode atom.
15471×
        assert_eq!(
15481×
            v.entries["run.sh"].mode,
1549
            Mode {
1550
                exec: false,
1551
                opaque: true
1552
            }
1553
        );
1554
        // The placement introduced by the same AddNode is unaffected.
15551×
        assert_eq!(v.entries["run.sh"].placement, (h(1), 0));
1556
        // Resolution: retire both live mode atoms; identical live values do not conflict.
15571×
        g.apply(
15581×
            h(4),
1559
            0,
15601×
            &TreeOp::SetMode {
15611×
                node: nid(1),
15621×
                old: OpRef {
15631×
                    change: h(2),
15641×
                    index: 0,
15651×
                },
15661×
                mode: Mode {
15671×
                    exec: true,
15681×
                    opaque: false,
15691×
                },
15701×
            },
1571
        );
15721×
        g.apply(
15731×
            h(4),
1574
            1,
15751×
            &TreeOp::SetMode {
15761×
                node: nid(1),
15771×
                old: OpRef {
15781×
                    change: h(3),
15791×
                    index: 0,
15801×
                },
15811×
                mode: Mode {
15821×
                    exec: true,
15831×
                    opaque: false,
15841×
                },
15851×
            },
1586
        );
15871×
        let v = g.view();
15881×
        assert!(!v.is_conflicted(), "{:?}", v.conflicts);
15891×
        assert_eq!(
15901×
            v.entries["run.sh"].mode,
1591
            Mode {
1592
                exec: true,
1593
                opaque: false
1594
            }
1595
        );
15961×
    }
1597
}