OXYGENHUB

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

1281 of 1372 lines run, 206 of 234 functions · run #401 on subrepo-pin-state · 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 {
27289580×
    fn insert(&mut self, k: OpKey) {
28167×
        match self {
29289367×
            KeySet::Empty => *self = KeySet::One(k),
30167×
            KeySet::One(a) if *a == k => {}
31167×
            KeySet::One(a) => {
32167×
                let a = *a;
33167×
                *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
        }
41289580×
    }
42839×
    fn remove(&mut self, k: &OpKey) {
43831×
        match self {
44831×
            KeySet::One(a) if a == k => *self = KeySet::Empty,
458×
            KeySet::Many(v) => {
468×
                if let Ok(i) = v.binary_search(k) {
478×
                    v.remove(i);
488×
                }
498×
                if v.len() == 1 {
508×
                    *self = KeySet::One(v[0]);
518×
                } else if v.is_empty() {
520×
                    *self = KeySet::Empty;
530×
                }
54
            }
5590×
            _ => {}
56
        }
57839×
    }
5820311499×
    fn as_slice(&self) -> &[OpKey] {
5920311499×
        match self {
600×
            KeySet::Empty => &[],
6120311457×
            KeySet::One(a) => std::slice::from_ref(a),
6242×
            KeySet::Many(v) => v,
63
        }
6420311499×
    }
6520298593×
    fn iter(&self) -> std::slice::Iter<'_, OpKey> {
6620298593×
        self.as_slice().iter()
6720298593×
    }
6812906×
    fn len(&self) -> usize {
6912906×
        self.as_slice().len()
7012906×
    }
71839×
    fn is_empty(&self) -> bool {
72839×
        matches!(self, KeySet::Empty)
73839×
    }
74
}
75
76
impl<'a> IntoIterator for &'a KeySet {
77
    type Item = &'a OpKey;
78
    type IntoIter = std::slice::Iter<'a, OpKey>;
796765129×
    fn into_iter(self) -> Self::IntoIter {
806765129×
        self.iter()
816765129×
    }
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 {
1684963×
    fn new(view: TreeView) -> CachedView {
1694963×
        let mut dirs: HashMap<NodeId, String> = view
1704963×
            .entries
1714963×
            .values()
17240112×
            .filter(|e| e.kind == NodeKind::Dir)
1734963×
            .map(|e| (e.node, e.path.clone()))
1744963×
            .collect();
1754963×
        dirs.insert(NodeId::ROOT, String::new());
1764963×
        CachedView { view, dirs }
1774963×
    }
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
    /// 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>,
192
}
193
194
impl TreeView {
1953054×
    pub fn is_conflicted(&self) -> bool {
1963054×
        !self.conflicts.is_empty()
1973054×
    }
1981683×
    pub fn by_node(&self) -> HashMap<NodeId, &TreeEntry> {
19910267×
        self.entries.values().map(|e| (e.node, e)).collect()
2001683×
    }
201
}
202
203
impl TreeGraph {
2043698×
    pub fn new() -> Self {
2053698×
        Self::default()
2063698×
    }
207
208
    /// The graph's maps, decoded from a state index on first use.
20948383036×
    fn m(&self) -> &TreeMaps {
21048383036×
        self.maps.get_or_init(|| match &self.raw {
2113688×
            Some(raw) => {
2123688×
                TreeMaps::decode(raw).expect("a state index is checked against its hash when read")
213
            }
2146524×
            None => TreeMaps::default(),
21510212×
        })
21648383036×
    }
217
218738751×
    fn m_mut(&mut self) -> &mut TreeMaps {
219738751×
        self.m();
220738751×
        self.maps.get_mut().expect("decoded just now")
221738751×
    }
222
223
    /// Apply tree op `index` of `change`; `OpRef` self references must already be resolved.
22440813×
    pub fn apply(&mut self, change: ChangeHash, index: u32, op: &TreeOp) {
22540813×
        let cached = self.view_cache.take();
226
        // Whether the placement or mode atom this op retires was live.
22740813×
        let was_live = match op {
2281004×
            TreeOp::RemoveNode { placement, .. } => self.placement_live(&op_key(*placement)),
229164×
            TreeOp::Move { old, .. } => self.placement_live(&op_key(*old)),
23028×
            TreeOp::SetMode { old, .. } => self.mode_live(&op_key(*old)),
23139617×
            TreeOp::AddNode { .. } => false,
232
        };
23340813×
        self.apply_to_graph(change, index, op);
23440813×
        if let Some(mut c) = cached {
23513160×
            if self.update_view(&mut c, (change, index), op, was_live) {
2363055×
                #[cfg(test)]
2379790×
                assert_eq!(
2383055×
                    c.view,
2399790×
                    self.compute_view(),
2403055×
                    "view kept up to date by {op:?}"
2413055×
                );
24212845×
                self.view_cache = std::sync::OnceLock::from(c);
2433055×
            }
24427653×
        }
24540813×
    }
246
24740813×
    fn apply_to_graph(&mut self, change: ChangeHash, index: u32, op: &TreeOp) {
24840813×
        let key = (change, index);
24940813×
        match op {
250
            TreeOp::AddNode {
25139617×
                node,
25239617×
                kind,
25339617×
                mode,
25439617×
                parent,
25539617×
                name,
256
            } => {
25739617×
                self.m_mut().kinds.insert(*node, *kind);
25839617×
                self.m_mut().introduced.insert(*node, change);
25939617×
                self.m_mut().placements.insert(
26039617×
                    key,
26139617×
                    Placement {
26239617×
                        node: *node,
26339617×
                        parent: *parent,
26439617×
                        name: name.clone(),
26539617×
                    },
266
                );
26739617×
                self.m_mut()
26839617×
                    .placements_of
26939617×
                    .entry(*node)
27039617×
                    .or_default()
27139617×
                    .insert(key);
27239617×
                self.index_placement(key);
27339617×
                if *kind != NodeKind::Dir {
27437611×
                    self.m_mut().modes.insert(key, (*node, *mode));
27537611×
                    self.m_mut().modes_of.entry(*node).or_default().insert(key);
27637611×
                    self.index_mode(key);
27737611×
                }
278
            }
2791004×
            TreeOp::RemoveNode { placement, .. } => {
2801004×
                self.m_mut()
2811004×
                    .deleted_placements
2821004×
                    .entry(op_key(*placement))
2831004×
                    .or_default()
2841004×
                    .insert(change);
2851004×
                self.index_placement(op_key(*placement));
2861004×
            }
287
            TreeOp::Move {
288164×
                node,
289164×
                old,
290164×
                parent,
291164×
                name,
292164×
            } => {
293164×
                self.m_mut()
294164×
                    .deleted_placements
295164×
                    .entry(op_key(*old))
296164×
                    .or_default()
297164×
                    .insert(change);
298164×
                self.index_placement(op_key(*old));
299164×
                self.m_mut().placements.insert(
300164×
                    key,
301164×
                    Placement {
302164×
                        node: *node,
303164×
                        parent: *parent,
304164×
                        name: name.clone(),
305164×
                    },
306164×
                );
307164×
                self.m_mut()
308164×
                    .placements_of
309164×
                    .entry(*node)
310164×
                    .or_default()
311164×
                    .insert(key);
312164×
                self.index_placement(key);
313164×
            }
31428×
            TreeOp::SetMode { node, old, mode } => {
31528×
                self.m_mut()
31628×
                    .deleted_modes
31728×
                    .entry(op_key(*old))
31828×
                    .or_default()
31928×
                    .insert(change);
32028×
                self.index_mode(op_key(*old));
32128×
                self.m_mut().modes.insert(key, (*node, *mode));
32228×
                self.m_mut().modes_of.entry(*node).or_default().insert(key);
32328×
                self.index_mode(key);
32428×
            }
325
        }
32640813×
    }
327
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.
33213160×
    fn update_view(&self, c: &mut CachedView, key: OpKey, op: &TreeOp, was_live: bool) -> bool {
33313160×
        if !c.view.conflicts.is_empty() {
33414×
            return false;
33513146×
        }
33613146×
        match op {
33712895×
            TreeOp::AddNode { node, .. } => self.view_add(c, *node, key),
338181×
            TreeOp::RemoveNode { node, placement } => {
339181×
                !was_live || self.view_remove(c, *node, op_key(*placement))
340
            }
34161×
            TreeOp::Move { node, old, .. } => {
34261×
                (!was_live || self.view_remove(c, *node, op_key(*old)))
34311×
                    && self.view_add(c, *node, key)
344
            }
3459×
            TreeOp::SetMode { node, .. } => {
3469×
                let live = self.live_placements(node);
3479×
                match live.as_slice() {
3489×
                    [] => true,
3495×
                    [(k, p)] => {
3505×
                        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
3510×
                            return false;
352
                        };
3535×
                        let Some(mode) = self.entry_mode(node) else {
3540×
                            return false;
355
                        };
3565×
                        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
3575×
                        match entries.get_mut(&path) {
3585×
                            Some(e) if e.node == *node && e.placement == *k => {
3595×
                                (e.mode_atom, e.mode) = mode;
3605×
                                true
361
                            }
3620×
                            _ => false,
363
                        }
364
                    }
3650×
                    _ => false,
366
                }
367
            }
368
        }
36913160×
    }
370
371
    /// The view entry for `node`'s placement `key`, just made live.
37212906×
    fn view_add(&self, c: &mut CachedView, node: NodeId, key: OpKey) -> bool {
37312906×
        if !self.placement_live(&key) {
3740×
            return true;
37512906×
        }
37612906×
        if self
37712906×
            .m()
37812906×
            .live_placements_of
37912906×
            .get(&node)
38012906×
            .is_none_or(|s| s.len() != 1)
381
        {
3827×
            return false;
38312899×
        }
38412899×
        let Some(p) = self.m().placements.get(&key) else {
3850×
            return false;
386
        };
38712899×
        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
3882×
            return false;
389
        };
39012897×
        let Some((mode_atom, mode)) = self.entry_mode(&node) else {
3910×
            return false;
392
        };
39312897×
        let kind = self.kind(&node).unwrap_or(NodeKind::File);
39412897×
        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
39512897×
        if entries.contains_key(&path) || node == NodeId::ROOT {
396119×
            return false;
39712778×
        }
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.
40012778×
        if kind == NodeKind::Dir {
401886×
            c.dirs.insert(node, path.clone());
40211892×
        }
40312778×
        entries.insert(
40412778×
            path.clone(),
40512778×
            TreeEntry {
40612778×
                path,
40712778×
                node,
40812778×
                kind,
40912778×
                mode,
41012778×
                placement: key,
41112778×
                mode_atom,
41212778×
            },
413
        );
41412778×
        true
41512906×
    }
416
417
    /// Drop the view entry of `node`'s placement `key`, just retired.
418231×
    fn view_remove(&self, c: &mut CachedView, node: NodeId, key: OpKey) -> bool {
419231×
        if self.m().live_placements_of.contains_key(&node) {
42050×
            return false;
421181×
        }
422181×
        let Some(p) = self.m().placements.get(&key) else {
42379×
            return false;
424
        };
425102×
        let Some(path) = c.dirs.get(&p.parent).map(|d| join(d, &p.name)) else {
4260×
            return false;
427
        };
428102×
        let entries = std::sync::Arc::make_mut(&mut c.view.entries);
429102×
        match entries.get(&path) {
430102×
            Some(e) if e.node == node && e.placement == key => {}
4310×
            _ => return false,
432
        }
433102×
        if entries.get(&path).is_some_and(|e| e.kind == NodeKind::Dir) {
43445×
            let prefix = format!("{path}/");
43545×
            if entries
43645×
                .range(prefix.clone()..)
43745×
                .next()
43845×
                .is_some_and(|(k, _)| k.starts_with(&prefix))
439
            {
44044×
                return false;
4411×
            }
4421×
            c.dirs.remove(&node);
44357×
        }
44458×
        entries.remove(&path);
44558×
        true
446231×
    }
447
448
    /// The mode atom and mode of `node`'s entry, as `compute_view` finds
449
    /// them; `None` when its live modes conflict.
45012902×
    fn entry_mode(&self, node: &NodeId) -> Option<(Option<OpKey>, Mode)> {
45112902×
        let lm = self.live_modes(node);
45212902×
        let distinct: BTreeSet<Mode> = lm.iter().map(|(_, m)| *m).collect();
45312902×
        if distinct.len() > 1 {
4540×
            return None;
45512902×
        }
45612902×
        let mode_atom = lm.iter().map(|(k, _)| *k).max();
45712902×
        let mode = mode_atom
45812902×
            .and_then(|k| self.m().modes.get(&k).map(|(_, m)| *m))
45912902×
            .unwrap_or_default();
46012902×
        Some((mode_atom, mode))
46112902×
    }
462
463
    /// Bring placement `k` in the live index up to date.
46474801×
    fn index_placement(&mut self, k: OpKey) {
46574801×
        let Some(node) = self.m_mut().placements.get(&k).map(|p| p.node) else {
466379×
            return;
467
        };
46874422×
        if self.placement_live(&k) {
46973213×
            self.m_mut()
47073213×
                .live_placements_of
47173213×
                .entry(node)
47273213×
                .or_default()
47373213×
                .insert(k);
47473213×
        } else if let Some(set) = self.m_mut().live_placements_of.get_mut(&node) {
475811×
            set.remove(&k);
476811×
            if set.is_empty() {
477731×
                self.m_mut().live_placements_of.remove(&node);
478731×
            }
479398×
        }
48074801×
    }
481
482
    /// Bring mode atom `k` in the live index up to date.
48371403×
    fn index_mode(&mut self, k: OpKey) {
48471403×
        let Some(node) = self.m_mut().modes.get(&k).map(|(n, _)| *n) else {
48515×
            return;
486
        };
48771388×
        if self.mode_live(&k) {
48871359×
            self.m_mut()
48971359×
                .live_modes_of
49071359×
                .entry(node)
49171359×
                .or_default()
49271359×
                .insert(k);
49371359×
        } else if let Some(set) = self.m_mut().live_modes_of.get_mut(&node) {
49428×
            set.remove(&k);
49528×
            if set.is_empty() {
49610×
                self.m_mut().live_modes_of.remove(&node);
49718×
            }
4981×
        }
49971403×
    }
500
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.
50414×
    pub fn dead_dirs(&self) -> HashMap<(NodeId, String), (NodeId, OpKey)> {
50514×
        let mut out: HashMap<(NodeId, String), (NodeId, OpKey)> = HashMap::new();
50615×
        for (k, p) in &self.m().placements {
50715×
            if self.kind(&p.node) != Some(NodeKind::Dir)
5085×
                || self.placement_live(k)
5092×
                || !self.live_placements(&p.node).is_empty()
510
            {
51113×
                continue;
5122×
            }
5132×
            let slot = out
5142×
                .entry((p.parent, p.name.clone()))
5152×
                .or_insert((p.node, *k));
516
            // The newest placement, by key: the same answer in any order.
5172×
            if *k > slot.1 {
5180×
                *slot = (p.node, *k);
5192×
            }
520
        }
52114×
        out
52214×
    }
523
524
    /// Every placement `node` ever had, live or not, with the changes that
525
    /// retired it (none while live).
5260×
    pub fn placement_history(&self, node: &NodeId) -> Vec<(OpKey, &Placement, Vec<ChangeHash>)> {
5270×
        self.m()
5280×
            .placements_of
5290×
            .get(node)
5300×
            .into_iter()
5310×
            .flatten()
5320×
            .filter_map(|k| {
5330×
                let p = self.m().placements.get(k)?;
5340×
                let by = self
5350×
                    .m()
5360×
                    .deleted_placements
5370×
                    .get(k)
5380×
                    .map(|d| d.iter().copied().collect())
5390×
                    .unwrap_or_default();
5400×
                Some((*k, p, by))
5410×
            })
5420×
            .collect()
5430×
    }
544
54588501×
    fn placement_live(&self, k: &OpKey) -> bool {
54688501×
        self.m()
54788501×
            .deleted_placements
54888501×
            .get(k)
54988501×
            .is_none_or(|d| d.is_empty())
55088501×
    }
55171416×
    fn mode_live(&self, k: &OpKey) -> bool {
55271416×
        self.m().deleted_modes.get(k).is_none_or(|d| d.is_empty())
55371416×
    }
554
5556778050×
    pub fn kind(&self, node: &NodeId) -> Option<NodeKind> {
5566778050×
        if node.is_root() {
5570×
            Some(NodeKind::Dir)
558
        } else {
5596778050×
            self.m().kinds.get(node).copied()
560
        }
5616778050×
    }
562
563
    /// The change whose `AddNode` introduced `node`.
56415436×
    pub fn introducer(&self, node: &NodeId) -> Option<ChangeHash> {
56515436×
        if let (None, Some(raw)) = (self.maps.get(), &self.raw_introduced) {
566562×
            let only = self.introduced_only.get_or_init(|| {
56763×
                read_introduced(raw).expect("a state index is checked against its hash when read")
56863×
            });
569
            #[cfg(test)]
5700×
            assert_eq!(
5710×
                only.get(node),
5720×
                TreeMaps::decode(self.raw.as_ref().unwrap())
5730×
                    .unwrap()
5740×
                    .introduced
5750×
                    .get(node),
576
                "the introducers block is the maps'"
577
            );
578562×
            return only.get(node).copied();
57914874×
        }
58014874×
        self.m().introduced.get(node).copied()
58115436×
    }
582
5831121×
    pub fn placement(&self, k: &OpKey) -> Option<&Placement> {
5841121×
        self.m().placements.get(k)
5851121×
    }
586
5876765356×
    pub fn live_placements(&self, node: &NodeId) -> Vec<(OpKey, &Placement)> {
5886765356×
        self.m()
5896765356×
            .live_placements_of
5906765356×
            .get(node)
5916765356×
            .into_iter()
5926765356×
            .flat_map(|ks| ks.iter())
5936765363×
            .filter_map(|k| self.m().placements.get(k).map(|p| (*k, p)))
5946765356×
            .collect()
5956765356×
    }
596
5976778031×
    pub fn live_modes(&self, node: &NodeId) -> Vec<(OpKey, Mode)> {
5986778031×
        self.m()
5996778031×
            .live_modes_of
6006778031×
            .get(node)
6016778031×
            .into_iter()
6026778031×
            .flat_map(|ks| ks.iter())
6036778031×
            .filter_map(|k| self.m().modes.get(k).map(|(_, m)| (*k, *m)))
6046778031×
            .collect()
6056778031×
    }
606
607
    /// Every node that has at least one live placement.
6080×
    pub fn placed_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
6090×
        self.m().live_placements_of.keys().copied()
6100×
    }
611
612
    /// Resolve the tree: assign paths, find conflicts.
613
    /// The materialized tree (spec §7.1): computed once per graph and
614
    /// cloned for each caller.
61511511×
    pub fn view(&self) -> TreeView {
61611511×
        self.view_cache
61711511×
            .get_or_init(|| CachedView::new(self.compute_view()))
618
            .view
61911511×
            .clone()
62011511×
    }
621
62213867×
    fn compute_view(&self) -> TreeView {
62313867×
        let mut view = TreeView::default();
62413867×
        let mut entries: BTreeMap<String, TreeEntry> = BTreeMap::new();
625
626
        // Live placements grouped by parent.
62713867×
        let mut children: HashMap<NodeId, Vec<(OpKey, &Placement)>> = HashMap::new();
6286765129×
        for ks in self.m().live_placements_of.values() {
6296765144×
            for k in ks {
6306765144×
                if let Some(p) = self.m().placements.get(k) {
6316765144×
                    children.entry(p.parent).or_default().push((*k, p));
6326765144×
                }
633
            }
634
        }
63520590×
        for c in children.values_mut() {
63675029080×
            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.
640
        // Only placed nodes: one with no live placement is not in the tree.
64113867×
        let mut nodes: Vec<NodeId> = self.m().live_placements_of.keys().copied().collect();
64213867×
        nodes.sort();
64313867×
        let mut multi: HashSet<NodeId> = HashSet::new();
644
        // Each node's mode and the atom it comes from, for the entries.
64513867×
        let mut modes: HashMap<NodeId, (Option<OpKey>, Mode)> = HashMap::new();
6466765129×
        for n in &nodes {
6476765129×
            let lp = self.live_placements(n);
648
            // One placement (nearly every node) cannot be in two places.
6496765129×
            if lp.len() > 1 {
65015×
                let distinct_places: BTreeSet<(&NodeId, &str)> = lp
65115×
                    .iter()
65230×
                    .map(|(_, p)| (&p.parent, p.name.as_str()))
65315×
                    .collect();
65415×
                if distinct_places.len() > 1 {
6552×
                    multi.insert(*n);
6562×
                    view.conflicts.push(TreeConflict::MultiPlacement {
6572×
                        node: *n,
6582×
                        placements: lp.iter().map(|(k, _)| *k).collect(),
659
                    });
66013×
                }
6616765114×
            }
6626765129×
            let lm = self.live_modes(n);
6636765129×
            if lm.len() > 1 {
6642×
                let distinct_modes: BTreeSet<Mode> = lm.iter().map(|(_, m)| *m).collect();
6652×
                if distinct_modes.len() > 1 {
6661×
                    view.conflicts.push(TreeConflict::Mode {
6671×
                        node: *n,
6681×
                        modes: distinct_modes.into_iter().collect(),
6691×
                    });
6701×
                }
6716765127×
            }
6726765129×
            let mode_atom = lm.iter().map(|(k, _)| *k).max();
6736765129×
            let mode = mode_atom
6746765129×
                .and_then(|k| self.m().modes.get(&k).map(|(_, m)| *m))
6756765129×
                .unwrap_or_default();
6766765129×
            modes.insert(*n, (mode_atom, mode));
677
        }
678
679
        // BFS from the root, assigning paths.
68013867×
        let mut reached: HashSet<NodeId> = HashSet::new();
68113867×
        reached.insert(NodeId::ROOT);
68213867×
        let mut queue: Vec<(NodeId, String)> = vec![(NodeId::ROOT, String::new())];
68313867×
        let mut on_path: HashSet<NodeId> = HashSet::new();
68436751×
        while let Some((dir, dir_path)) = queue.pop() {
68522884×
            let Some(kids) = children.get(&dir) else {
6862299×
                continue;
687
            };
688
            // Name conflicts within this directory: the children are sorted
689
            // by name, so each name's group is a run of them.
69020585×
            let mut groups: Vec<(&str, &[(OpKey, &Placement)])> = Vec::new();
69120585×
            let mut start = 0;
6926765138×
            for i in 1..=kids.len() {
6936765138×
                if i == kids.len() || kids[i].1.name != kids[start].1.name {
6946764603×
                    groups.push((kids[start].1.name.as_str(), &kids[start..i]));
6956764603×
                    start = i;
6966764603×
                }
697
            }
6986764603×
            for (name, group) in groups {
6996764603×
                let distinct: BTreeSet<NodeId> = group.iter().map(|(_, p)| p.node).collect();
7006764603×
                if distinct.len() > 1 {
701447×
                    view.conflicts.push(TreeConflict::Name {
702447×
                        parent: dir,
703447×
                        name: name.to_string(),
704447×
                        nodes: distinct.iter().copied().collect(),
705447×
                    });
7066764156×
                }
7076764603×
                let suffixed = distinct.len() > 1;
7086765138×
                for (i, (k, p)) in group.iter().enumerate() {
7096765138×
                    let node = p.node;
7106765138×
                    if node == dir || on_path.contains(&node) {
7110×
                        view.conflicts.push(TreeConflict::Cycle { node });
7120×
                        continue;
7136765138×
                    }
7146765138×
                    let needs_suffix = suffixed || multi.contains(&node);
7156765138×
                    let leaf = if needs_suffix {
716972×
                        format!("{name}.oxy-conflict-{}", i + 1)
717
                    } else {
7186764166×
                        name.to_string()
719
                    };
7206765138×
                    let path = if dir_path.is_empty() {
72119902×
                        leaf
722
                    } else {
7236745236×
                        format!("{dir_path}/{leaf}")
724
                    };
7256765138×
                    let kind = self.kind(&node).unwrap_or(NodeKind::File);
7266765138×
                    let (mode_atom, mode) = modes.get(&node).copied().unwrap_or_default();
7276765138×
                    entries.insert(
7286765138×
                        path.clone(),
7296765138×
                        TreeEntry {
7306765138×
                            path: path.clone(),
7316765138×
                            node,
7326765138×
                            kind,
7336765138×
                            mode,
7346765138×
                            placement: *k,
7356765138×
                            mode_atom,
7366765138×
                        },
737
                    );
7386765138×
                    if kind == NodeKind::Dir && reached.insert(node) {
7399017×
                        on_path.insert(dir);
7409017×
                        queue.push((node, path));
7416756121×
                    } else {
7426756121×
                        reached.insert(node);
7436756121×
                    }
744
                }
745
            }
746
        }
747
748
        // Nodes with live placements that were never reached: orphans or cycles.
7496765129×
        for n in &nodes {
7506765129×
            if reached.contains(n) {
7516765124×
                continue;
7525×
            }
7535×
            let lp = self.live_placements(n);
7545×
            if lp.is_empty() {
7550×
                continue;
7565×
            }
7575×
            let mut seen = HashSet::new();
7585×
            let mut cur = *n;
7595×
            let conflict = loop {
76012×
                if !seen.insert(cur) {
7612×
                    break TreeConflict::Cycle { node: *n };
76210×
                }
76310×
                let lp = self.live_placements(&cur);
76410×
                let Some((_, p)) = lp.first() else {
7653×
                    break TreeConflict::Orphan {
7663×
                        node: *n,
7673×
                        parent: cur,
7683×
                    };
769
                };
7707×
                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.
7730×
                    break TreeConflict::Orphan {
7740×
                        node: *n,
7750×
                        parent: p.parent,
7760×
                    };
7777×
                }
7787×
                cur = p.parent;
779
            };
7805×
            if !view.conflicts.contains(&conflict) {
7815×
                view.conflicts.push(conflict);
7825×
            }
783
        }
78413867×
        view.entries = std::sync::Arc::new(entries);
78513867×
        view
78613867×
    }
787
}
788
789
impl TreeGraph {
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.
799433×
    pub fn unify(
800433×
        &self,
801433×
        mut view: TreeView,
802433×
        same: impl Fn(&[&TreeEntry]) -> Option<bool>,
803433×
    ) -> (TreeView, bool) {
804433×
        let mut decided = true;
805433×
        if !view
806433×
            .conflicts
807433×
            .iter()
808433×
            .any(|c| matches!(c, TreeConflict::Name { .. }))
809
        {
8100×
            return (view, decided);
811433×
        }
812
        // Nodes in any other conflict stay as they are.
813433×
        let mut troubled: HashSet<NodeId> = HashSet::new();
814441×
        for c in &view.conflicts {
815441×
            match c {
816441×
                TreeConflict::Name { .. } => {}
8170×
                TreeConflict::MultiPlacement { node, .. }
8180×
                | TreeConflict::Orphan { node, .. }
8190×
                | TreeConflict::Cycle { node }
8200×
                | TreeConflict::Mode { node, .. } => {
8210×
                    troubled.insert(*node);
8220×
                }
823
            }
824
        }
825433×
        let mut of_node: HashMap<NodeId, Vec<&TreeEntry>> = HashMap::new();
8262062×
        for e in view.entries.values() {
8272062×
            of_node.entry(e.node).or_default().push(e);
8282062×
        }
829
        // (paths to drop, entry to add, duplicates) per unified group.
830433×
        let mut merged: Vec<(Vec<String>, TreeEntry, Vec<NodeId>)> = Vec::new();
831433×
        let mut gone: HashSet<usize> = HashSet::new();
832441×
        for (i, c) in view.conflicts.iter().enumerate() {
833
            let TreeConflict::Name {
834441×
                parent,
835441×
                name,
836441×
                nodes,
837441×
            } = c
838
            else {
8390×
                continue;
840
            };
841957×
            if nodes.iter().any(|n| troubled.contains(n)) {
8420×
                continue;
843441×
            }
844
            // The group's entries: each node's placements at this place.
845441×
            let mut group: Vec<&TreeEntry> = Vec::new();
846957×
            for n in nodes {
847957×
                for e in of_node.get(n).into_iter().flatten() {
848957×
                    if self
849957×
                        .placement(&e.placement)
850957×
                        .is_some_and(|p| p.parent == *parent && p.name == *name)
851957×
                    {
852957×
                        group.push(e);
853957×
                    }
854
                }
855
            }
856441×
            let Some(first) = group.first() else {
8570×
                continue;
858
            };
859441×
            if first.kind == NodeKind::Dir
860440×
                || group
861440×
                    .iter()
862955×
                    .any(|e| e.kind != first.kind || e.mode != first.mode)
863
            {
8649×
                continue;
865432×
            }
866432×
            group.sort_by_key(|e| e.placement);
867432×
            let mut reps: Vec<&TreeEntry> = Vec::new();
868939×
            for e in &group {
869939×
                if !reps.iter().any(|r| r.node == e.node) {
870939×
                    reps.push(e);
871939×
                }
872
            }
873432×
            if reps.len() < 2 {
8740×
                continue;
875432×
            }
876432×
            match same(&reps) {
877193×
                Some(true) => {}
878238×
                Some(false) => continue,
879
                None => {
8801×
                    decided = false;
8811×
                    continue;
882
                }
883
            }
884193×
            let shown = reps[0];
885193×
            let path = match shown.path.rsplit_once('/') {
8861×
                Some((dir, _)) => format!("{dir}/{name}"),
887192×
                None => name.clone(),
888
            };
889193×
            merged.push((
890386×
                group.iter().map(|e| e.path.clone()).collect(),
891193×
                TreeEntry {
892193×
                    path,
893193×
                    ..shown.clone()
894193×
                },
895193×
                reps[1..].iter().map(|e| e.node).collect(),
896
            ));
897193×
            gone.insert(i);
898
        }
899433×
        drop(of_node);
900433×
        if merged.is_empty() {
901247×
            return (view, decided);
902186×
        }
903186×
        let entries = std::sync::Arc::make_mut(&mut view.entries);
904193×
        for (paths, shown, dups) in merged {
905386×
            for p in paths {
906386×
                entries.remove(&p);
907386×
            }
908193×
            for d in dups {
909193×
                view.unified.insert(d, shown.node);
910193×
            }
911193×
            entries.insert(shown.path.clone(), shown);
912
        }
913186×
        let mut i = 0;
914193×
        view.conflicts.retain(|_| {
915193×
            i += 1;
916193×
            !gone.contains(&(i - 1))
917193×
        });
918186×
        (view, decided)
919433×
    }
920
}
921
922
impl TreeGraph {
923
    /// Serialize for a checkpoint.
924401×
    pub fn to_value(&self) -> crate::cbor::Value {
925
        use crate::cbor::Value;
9264752×
        let opkey = |k: &OpKey| vec![Value::Bytes(k.0 .0.to_vec()), Value::Uint(k.1 as u64)];
927802×
        let deleters = |m: &Map<OpKey, BTreeSet<ChangeHash>>| {
928802×
            let mut ks: Vec<&OpKey> = m.keys().collect();
929802×
            ks.sort();
930
            Value::Array(
931802×
                ks.into_iter()
932802×
                    .map(|k| {
933444×
                        let mut v = opkey(k);
934444×
                        v.push(Value::Array(
935444×
                            m[k].iter().map(|d| Value::Bytes(d.0.to_vec())).collect(),
936
                        ));
937444×
                        Value::Array(v)
938444×
                    })
939802×
                    .collect(),
940
            )
941802×
        };
942401×
        let mut nodes: Vec<&NodeId> = self.m().kinds.keys().collect();
943401×
        nodes.sort();
944401×
        let mut pks: Vec<&OpKey> = self.m().placements.keys().collect();
945401×
        pks.sort();
946401×
        let mut mks: Vec<&OpKey> = self.m().modes.keys().collect();
947401×
        mks.sort();
948401×
        Value::map([
949
            (
950
                0,
951
                Value::Array(
952401×
                    nodes
953401×
                        .into_iter()
9542351×
                        .map(|n| {
9552351×
                            Value::Array(vec![
9562351×
                                Value::Bytes(n.0.to_vec()),
9572351×
                                Value::Uint(self.m().kinds[n].tag()),
9582351×
                                self.m()
9592351×
                                    .introduced
9602351×
                                    .get(n)
9612351×
                                    .map(|h| Value::Bytes(h.0.to_vec()))
9622351×
                                    .unwrap_or(Value::Null),
963
                            ])
9642351×
                        })
965401×
                        .collect(),
966
                ),
967
            ),
968
            (
969
                1,
970
                Value::Array(
971401×
                    pks.into_iter()
9722353×
                        .map(|k| {
9732353×
                            let p = &self.m().placements[k];
9742353×
                            let mut v = opkey(k);
9752353×
                            v.extend([
9762353×
                                Value::Bytes(p.node.0.to_vec()),
9772353×
                                Value::Bytes(p.parent.0.to_vec()),
9782353×
                                Value::Text(p.name.clone()),
9792353×
                            ]);
9802353×
                            Value::Array(v)
9812353×
                        })
982401×
                        .collect(),
983
                ),
984
            ),
985401×
            (2, deleters(&self.m().deleted_placements)),
986
            (
987
                3,
988
                Value::Array(
989401×
                    mks.into_iter()
9901955×
                        .map(|k| {
9911955×
                            let (n, m) = &self.m().modes[k];
9921955×
                            let mut v = opkey(k);
9931955×
                            v.extend([Value::Bytes(n.0.to_vec()), Value::Uint(m.bits())]);
9941955×
                            Value::Array(v)
9951955×
                        })
996401×
                        .collect(),
997
                ),
998
            ),
999401×
            (4, deleters(&self.m().deleted_modes)),
1000
        ])
1001401×
    }
1002
1003118×
    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};
1005118×
        let m = map(v, "tree graph")?;
10061934×
        let node = |v: &crate::cbor::Value| {
10071934×
            NodeId::from_slice(bytes(v, "node")?)
10081934×
                .ok_or_else(|| ObjectError::Schema("node: expected 16 bytes".into()))
10091934×
        };
1010991×
        let opkey = |e: &[crate::cbor::Value]| -> Result<OpKey, ObjectError> {
1011991×
            Ok((oref(&e[0], "op change")?, u32_(&e[1], "op index")?))
1012991×
        };
1013118×
        let mut g = TreeGraph::default();
1014492×
        for n in array(field(m, 0, "nodes")?, "nodes")? {
1015492×
            let e = array(n, "node entry")?;
1016492×
            if e.len() != 3 {
10170×
                return Err(ObjectError::Schema("node entry: expected 3 fields".into()));
1018492×
            }
1019492×
            let id = node(&e[0])?;
1020492×
            g.m_mut()
1021492×
                .kinds
1022492×
                .insert(id, NodeKind::from_tag(uint(&e[1], "kind")?)?);
1023492×
            if !e[2].is_null() {
1024492×
                g.m_mut().introduced.insert(id, oref(&e[2], "introducer")?);
10250×
            }
1026
        }
1027493×
        for p in array(field(m, 1, "placements")?, "placements")? {
1028493×
            let e = array(p, "placement")?;
1029493×
            if e.len() != 5 {
10300×
                return Err(ObjectError::Schema("placement: expected 5 fields".into()));
1031493×
            }
1032493×
            let k = opkey(e)?;
1033493×
            let pl = Placement {
1034493×
                node: node(&e[2])?,
1035493×
                parent: node(&e[3])?,
1036493×
                name: text(&e[4], "name")?.to_owned(),
1037
            };
1038493×
            g.m_mut()
1039493×
                .placements_of
1040493×
                .entry(pl.node)
1041493×
                .or_default()
1042493×
                .insert(k);
1043493×
            g.m_mut().placements.insert(k, pl);
1044
        }
1045118×
        let deleters = |v: &crate::cbor::Value,
1046
                        what: &str|
1047236×
         -> Result<Map<OpKey, BTreeSet<ChangeHash>>, ObjectError> {
1048236×
            let mut out = Map::default();
1049236×
            for d in array(v, what)? {
105042×
                let e = array(d, what)?;
105142×
                if e.len() != 3 {
10520×
                    return Err(ObjectError::Schema(format!("{what}: expected 3 fields")));
105342×
                }
105442×
                out.insert(
105542×
                    opkey(e)?,
105642×
                    array(&e[2], "deleters")?
105742×
                        .iter()
105842×
                        .map(|x| oref(x, "deleter"))
105942×
                        .collect::<Result<BTreeSet<_>, _>>()?,
1060
                );
1061
            }
1062236×
            Ok(out)
1063236×
        };
1064118×
        g.m_mut().deleted_placements =
1065118×
            deleters(field(m, 2, "deleted placements")?, "deleted placement")?;
1066456×
        for md in array(field(m, 3, "modes")?, "modes")? {
1067456×
            let e = array(md, "mode")?;
1068456×
            if e.len() != 4 {
10690×
                return Err(ObjectError::Schema("mode: expected 4 fields".into()));
1070456×
            }
1071456×
            let k = opkey(e)?;
1072456×
            let n = node(&e[2])?;
1073456×
            g.m_mut()
1074456×
                .modes
1075456×
                .insert(k, (n, Mode::from_bits(uint(&e[3], "mode bits")?)?));
1076456×
            g.m_mut().modes_of.entry(n).or_default().insert(k);
1077
        }
1078118×
        g.m_mut().deleted_modes = deleters(field(m, 4, "deleted modes")?, "deleted mode")?;
1079118×
        let keys: Vec<OpKey> = g.m_mut().placements.keys().copied().collect();
1080493×
        for k in keys {
1081493×
            g.index_placement(k);
1082493×
        }
1083118×
        let keys: Vec<OpKey> = g.m_mut().modes.keys().copied().collect();
1084456×
        for k in keys {
1085456×
            g.index_mode(k);
1086456×
        }
1087118×
        Ok(g)
1088118×
    }
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 {
10941119×
    pub(crate) fn write_flat(&self, w: &mut crate::flat::Writer) {
1095131624×
        let key = |w: &mut crate::flat::Writer, k: &OpKey| {
1096131624×
            w.object(&k.0);
1097131624×
            w.u32(k.1);
1098131624×
        };
1099
        // The maps as a block of their own: a reader keeps it undecoded
1100
        // until something needs them.
11011119×
        let outer = std::mem::take(w);
11021119×
        let mut kinds: Vec<(&NodeId, &NodeKind)> = self.m().kinds.iter().collect();
11031119×
        kinds.sort_by_key(|(n, _)| **n);
11041119×
        w.count(kinds.len());
110532938×
        for (n, k) in kinds {
110632938×
            w.fixed(&n.0);
110732938×
            w.u8(k.tag() as u8);
110832938×
        }
11091119×
        let mut intro: Vec<(&NodeId, &ChangeHash)> = self.m().introduced.iter().collect();
11101119×
        intro.sort();
11111119×
        w.count(intro.len());
111232938×
        for (n, c) in intro {
111332938×
            w.fixed(&n.0);
111432938×
            w.object(c);
111532938×
        }
11161119×
        let mut pl: Vec<(&OpKey, &Placement)> = self.m().placements.iter().collect();
11171119×
        pl.sort_by_key(|(k, _)| **k);
11181119×
        w.count(pl.len());
111932938×
        for (k, p) in pl {
112032938×
            key(w, k);
112132938×
            w.fixed(&p.node.0);
112232938×
            w.fixed(&p.parent.0);
112332938×
            w.bytes(p.name.as_bytes());
112432938×
        }
11251119×
        let mut modes: Vec<(&OpKey, &(NodeId, Mode))> = self.m().modes.iter().collect();
11261119×
        modes.sort_by_key(|(k, _)| **k);
11271119×
        w.count(modes.len());
112832874×
        for (k, (n, m)) in modes {
112932874×
            key(w, k);
113032874×
            w.fixed(&n.0);
113132874×
            w.u8(m.bits() as u8);
113232874×
        }
11332238×
        for m in [&self.m().deleted_placements, &self.m().deleted_modes] {
11342238×
            let mut ks: Vec<(&OpKey, &BTreeSet<ChangeHash>)> = m.iter().collect();
11352238×
            ks.sort_by_key(|(k, _)| **k);
11362238×
            w.count(ks.len());
11372238×
            for (k, by) in ks {
11380×
                key(w, k);
11390×
                w.count(by.len());
11400×
                for c in by {
11410×
                    w.object(c);
11420×
                }
1143
            }
1144
        }
11451119×
        let maps = std::mem::replace(w, outer);
11461119×
        w.bytes(&maps.0);
1147
        // The introducers again, alone, for a reader that needs no more.
11481119×
        let mut iw = crate::flat::Writer::default();
11491119×
        let mut intro: Vec<(&NodeId, &ChangeHash)> = self.m().introduced.iter().collect();
11501119×
        intro.sort();
11511119×
        iw.count(intro.len());
115232938×
        for (n, c) in intro {
115332938×
            iw.fixed(&n.0);
115432938×
            iw.object(c);
115532938×
        }
11561119×
        w.bytes(&iw.0);
1157
        // The view as a block of its own.
11581119×
        let outer = std::mem::take(w);
11591119×
        let view = self.view();
11601119×
        if view.conflicts.is_empty() {
11611119×
            w.u8(1);
11621119×
            w.count(view.entries.len());
116332938×
            for (path, e) in view.entries.iter() {
116432938×
                w.bytes(path.as_bytes());
116532938×
                w.fixed(&e.node.0);
116632938×
                w.u8(e.kind.tag() as u8);
116732938×
                w.u8(e.mode.bits() as u8);
116832938×
                key(w, &e.placement);
116932938×
                match &e.mode_atom {
117064×
                    None => w.u8(0),
117132874×
                    Some(k) => {
117232874×
                        w.u8(1);
117332874×
                        key(w, k);
117432874×
                    }
1175
                }
1176
            }
11770×
        } else {
11780×
            w.u8(0);
11790×
        }
11801119×
        let view_bytes = std::mem::replace(w, outer);
11811119×
        w.bytes(&view_bytes.0);
11821119×
    }
1183
11842325×
    pub(crate) fn read_flat(
11852325×
        r: &mut crate::flat::Reader,
11862325×
    ) -> Result<TreeGraph, crate::objects::ObjectError> {
11872325×
        let maps = r.bytes()?;
11882325×
        let introduced = r.bytes()?;
11892325×
        let view = Self::read_view(r.bytes()?)?;
11902325×
        let mut g = TreeGraph {
11912325×
            raw: Some(std::sync::Arc::new(maps.to_vec())),
11922325×
            raw_introduced: Some(std::sync::Arc::new(introduced.to_vec())),
11932325×
            ..TreeGraph::default()
11942325×
        };
11952325×
        if let Some(view) = view {
1196893×
            // Checked on a copy, so the graph itself stays undecoded.
1197893×
            #[cfg(test)]
11981432×
            assert_eq!(
1199893×
                view,
12001432×
                TreeGraph {
12011432×
                    raw: g.raw.clone(),
12021432×
                    ..TreeGraph::default()
12031432×
                }
12041432×
                .compute_view(),
1205893×
                "an index's view is the tree's"
1206893×
            );
12072325×
            g.view_cache = std::sync::OnceLock::from(CachedView::new(view));
1208893×
        }
12092325×
        Ok(g)
12102325×
    }
1211
}
1212
1213
impl TreeMaps {
1214
    /// The maps block of a tree's flat form.
12153688×
    fn decode(bytes: &[u8]) -> Result<TreeMaps, crate::objects::ObjectError> {
1216
        use crate::objects::ObjectError;
12173688×
        let _t = crate::timing::phase("index: tree maps");
1218166716×
        let node = |r: &mut crate::flat::Reader| -> Result<NodeId, ObjectError> {
1219166716×
            Ok(NodeId(r.fixed::<16>()?))
1220166716×
        };
122166639×
        let key = |r: &mut crate::flat::Reader| -> Result<OpKey, ObjectError> {
122266639×
            Ok((r.object()?, r.u32()?))
122366639×
        };
12243688×
        let r = &mut crate::flat::Reader::new(bytes);
12253688×
        let mut g = TreeGraph::new();
12263688×
        for _ in 0..r.count()? {
122733359×
            let n = node(r)?;
122833359×
            g.m_mut()
122933359×
                .kinds
123033359×
                .insert(n, NodeKind::from_tag(r.u8()? as u64)?);
1231
        }
12323688×
        for _ in 0..r.count()? {
123333359×
            let n = node(r)?;
123433359×
            g.m_mut().introduced.insert(n, r.object()?);
1235
        }
12363688×
        for _ in 0..r.count()? {
123733359×
            let k = key(r)?;
123833359×
            let p = Placement {
123933359×
                node: node(r)?,
124033359×
                parent: node(r)?,
124133359×
                name: r.text()?,
1242
            };
124333359×
            g.m_mut().placements_of.entry(p.node).or_default().insert(k);
124433359×
            g.m_mut().placements.insert(k, p);
1245
        }
12463688×
        for _ in 0..r.count()? {
124733280×
            let k = key(r)?;
124833280×
            let n = node(r)?;
124933280×
            g.m_mut()
125033280×
                .modes
125133280×
                .insert(k, (n, Mode::from_bits(r.u8()? as u64)?));
125233280×
            g.m_mut().modes_of.entry(n).or_default().insert(k);
1253
        }
12547376×
        for i in 0..2 {
12557376×
            let mut m: Map<OpKey, BTreeSet<ChangeHash>> = Map::default();
12567376×
            for _ in 0..r.count()? {
12570×
                let k = key(r)?;
12580×
                let mut by = BTreeSet::new();
12590×
                for _ in 0..r.count()? {
12600×
                    by.insert(r.object()?);
1261
                }
12620×
                m.insert(k, by);
1263
            }
12647376×
            if i == 0 {
12653688×
                g.m_mut().deleted_placements = m;
12663688×
            } else {
12673688×
                g.m_mut().deleted_modes = m;
12683688×
            }
1269
        }
12703688×
        if !r.at_end() {
12710×
            return Err(ObjectError::Schema("state index: tree maps".into()));
12723688×
        }
12733688×
        let keys: Vec<OpKey> = g.m().placements.keys().copied().collect();
127433359×
        for k in keys {
127533359×
            g.index_placement(k);
127633359×
        }
12773688×
        let keys: Vec<OpKey> = g.m().modes.keys().copied().collect();
127833280×
        for k in keys {
127933280×
            g.index_mode(k);
128033280×
        }
12813688×
        Ok(g.maps.into_inner().unwrap_or_default())
12823688×
    }
1283
}
1284
1285
impl TreeGraph {
1286
    /// The view block of a tree's flat form, when it holds one.
12872325×
    fn read_view(bytes: &[u8]) -> Result<Option<TreeView>, crate::objects::ObjectError> {
12882325×
        let _t = crate::timing::phase("index: view");
12892325×
        let mut r = crate::flat::Reader::new(bytes);
12902325×
        if r.u8()? != 1 {
12910×
            return Ok(None);
12922325×
        }
129333626×
        let key = |r: &mut crate::flat::Reader| -> Result<OpKey, crate::objects::ObjectError> {
129433626×
            Ok((r.object()?, r.u32()?))
129533626×
        };
12962325×
        let n = r.count()?;
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.
12992325×
        let mut entries = Vec::with_capacity(n);
13002325×
        for _ in 0..n {
130116833×
            let path = r.text()?;
130216833×
            let e = TreeEntry {
130316833×
                path: path.clone(),
130416833×
                node: NodeId(r.fixed::<16>()?),
130516833×
                kind: NodeKind::from_tag(r.u8()? as u64)?,
130616833×
                mode: Mode::from_bits(r.u8()? as u64)?,
130716833×
                placement: key(&mut r)?,
130816833×
                mode_atom: match r.u8()? {
130940×
                    0 => None,
131016793×
                    _ => Some(key(&mut r)?),
1311
                },
1312
            };
131316833×
            entries.push((path, e));
1314
        }
13152325×
        Ok(Some(TreeView {
13162325×
            entries: std::sync::Arc::new(entries.into_iter().collect()),
13172325×
            conflicts: Vec::new(),
13182325×
            unified: BTreeMap::new(),
13192325×
        }))
13202325×
    }
1321
}
1322
1323
/// The introducers block of a tree's flat form.
132463×
fn read_introduced(bytes: &[u8]) -> Result<Map<NodeId, ChangeHash>, crate::objects::ObjectError> {
132563×
    let r = &mut crate::flat::Reader::new(bytes);
132663×
    let n = r.count()?;
132763×
    let mut m = Map::default();
132863×
    m.reserve(n);
132963×
    for _ in 0..n {
13300×
        let node = NodeId(r.fixed::<16>()?);
13310×
        m.insert(node, r.object()?);
1332
    }
133363×
    Ok(m)
133463×
}
1335
133613004×
fn join(dir: &str, name: &str) -> String {
133713004×
    if dir.is_empty() {
13385027×
        name.to_string()
1339
    } else {
13407977×
        format!("{dir}/{name}")
1341
    }
134213004×
}
1343
13443819×
pub fn op_key(r: OpRef) -> OpKey {
13453819×
    (r.change, r.index)
13463819×
}
1347
1348
#[cfg(test)]
1349
mod tests {
1350
    use super::*;
1351
    use crate::hash::ObjectRef;
1352
135362×
    fn h(n: u8) -> ChangeHash {
135462×
        ObjectRef([n; 32])
135562×
    }
135663×
    fn nid(n: u8) -> NodeId {
135763×
        NodeId([n; 16])
135863×
    }
135921×
    fn add(g: &mut TreeGraph, c: u8, i: u32, node: u8, kind: NodeKind, parent: NodeId, name: &str) {
136021×
        g.apply(
136121×
            h(c),
136221×
            i,
136321×
            &TreeOp::AddNode {
136421×
                node: nid(node),
136521×
                kind,
136621×
                mode: Mode::TEXT,
136721×
                parent,
136821×
                name: name.into(),
136921×
            },
1370
        );
137121×
    }
13728×
    fn paths(v: &TreeView) -> Vec<String> {
13738×
        v.entries.keys().cloned().collect()
13748×
    }
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]
13791×
    fn cached_view_follows_ops() {
13801×
        let at = |c: u8, i: u32| OpRef {
13814×
            change: h(c),
13824×
            index: i,
13834×
        };
13841×
        let mut g = TreeGraph::new();
13851×
        let ops = [
13861×
            TreeOp::AddNode {
13871×
                node: nid(1),
13881×
                kind: NodeKind::Dir,
13891×
                mode: Mode::TEXT,
13901×
                parent: NodeId::ROOT,
13911×
                name: "src".into(),
13921×
            },
13931×
            TreeOp::AddNode {
13941×
                node: nid(2),
13951×
                kind: NodeKind::File,
13961×
                mode: Mode::TEXT,
13971×
                parent: nid(1),
13981×
                name: "a.rs".into(),
13991×
            },
14001×
            TreeOp::SetMode {
14011×
                node: nid(2),
14021×
                old: at(1, 1),
14031×
                mode: Mode {
14041×
                    exec: true,
14051×
                    opaque: false,
14061×
                },
14071×
            },
14081×
            TreeOp::Move {
14091×
                node: nid(2),
14101×
                old: at(1, 1),
14111×
                parent: NodeId::ROOT,
14121×
                name: "b.rs".into(),
14131×
            },
14141×
            // Moving an empty directory, then removing it.
14151×
            TreeOp::Move {
14161×
                node: nid(1),
14171×
                old: at(1, 0),
14181×
                parent: NodeId::ROOT,
14191×
                name: "lib".into(),
14201×
            },
14211×
            TreeOp::RemoveNode {
14221×
                node: nid(1),
14231×
                placement: at(1, 4),
14241×
            },
14251×
            // A name taken twice: a conflict, so the view is computed again.
14261×
            TreeOp::AddNode {
14271×
                node: nid(3),
14281×
                kind: NodeKind::File,
14291×
                mode: Mode::TEXT,
14301×
                parent: NodeId::ROOT,
14311×
                name: "b.rs".into(),
14321×
            },
14331×
        ];
14347×
        for (i, op) in ops.iter().enumerate() {
14357×
            let _ = g.view();
14367×
            g.apply(h(1), i as u32, op);
14377×
            assert_eq!(g.view(), g.compute_view());
1438
        }
14391×
        let v = g.view();
14401×
        assert_eq!(paths(&v), ["b.rs.oxy-conflict-1", "b.rs.oxy-conflict-2"]);
14411×
        assert!(v.entries.values().any(|e| e.mode.exec));
14421×
    }
1443
1444
    #[test]
14451×
    fn nested_paths_and_rename() {
14461×
        let mut g = TreeGraph::new();
14471×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "src");
14481×
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "main.rs");
14491×
        add(&mut g, 1, 2, 3, NodeKind::File, NodeId::ROOT, "README");
14501×
        let v = g.view();
14511×
        assert_eq!(paths(&v), ["README", "src", "src/main.rs"]);
14521×
        assert!(!v.is_conflicted());
1453
1454
        // Rename src/main.rs → src/lib.rs, move src → lib (subtree follows).
14551×
        g.apply(
14561×
            h(2),
1457
            0,
14581×
            &TreeOp::Move {
14591×
                node: nid(2),
14601×
                old: OpRef {
14611×
                    change: h(1),
14621×
                    index: 1,
14631×
                },
14641×
                parent: nid(1),
14651×
                name: "lib.rs".into(),
14661×
            },
1467
        );
14681×
        g.apply(
14691×
            h(2),
1470
            1,
14711×
            &TreeOp::Move {
14721×
                node: nid(1),
14731×
                old: OpRef {
14741×
                    change: h(1),
14751×
                    index: 0,
14761×
                },
14771×
                parent: NodeId::ROOT,
14781×
                name: "lib".into(),
14791×
            },
1480
        );
14811×
        let v = g.view();
14821×
        assert_eq!(paths(&v), ["README", "lib", "lib/lib.rs"]);
14831×
        assert_eq!(v.entries["lib/lib.rs"].placement, (h(2), 0));
1484
14851×
        g.apply(
14861×
            h(3),
1487
            0,
14881×
            &TreeOp::RemoveNode {
14891×
                node: nid(3),
14901×
                placement: OpRef {
14911×
                    change: h(1),
14921×
                    index: 2,
14931×
                },
14941×
            },
1495
        );
14961×
        assert_eq!(paths(&g.view()), ["lib", "lib/lib.rs"]);
14971×
    }
1498
1499
    #[test]
15001×
    fn name_conflict_gets_suffixes() {
15011×
        let mut g = TreeGraph::new();
15021×
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "a.txt");
15031×
        add(&mut g, 2, 0, 2, NodeKind::File, NodeId::ROOT, "a.txt");
15041×
        let v = g.view();
15051×
        assert_eq!(paths(&v), ["a.txt.oxy-conflict-1", "a.txt.oxy-conflict-2"]);
15061×
        assert_eq!(
1507
            v.conflicts,
15081×
            vec![TreeConflict::Name {
15091×
                parent: NodeId::ROOT,
15101×
                name: "a.txt".into(),
15111×
                nodes: vec![nid(1), nid(2)]
15121×
            }]
1513
        );
15141×
    }
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]
15191×
    fn identical_additions_unify() {
15201×
        let mut g = TreeGraph::new();
15211×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
15221×
        add(&mut g, 3, 0, 3, NodeKind::File, nid(1), "a.txt");
15231×
        add(&mut g, 2, 0, 2, NodeKind::File, nid(1), "a.txt");
15241×
        let raw = g.view();
15251×
        let (v, decided) = g.unify(raw.clone(), |es| {
15261×
            assert_eq!(es.len(), 2, "one entry per node");
15271×
            Some(true)
15281×
        });
15291×
        assert!(decided);
15301×
        assert_eq!(paths(&v), ["d", "d/a.txt"]);
15311×
        assert!(v.conflicts.is_empty());
15321×
        assert_eq!(v.entries["d/a.txt"].node, nid(2), "the least placement key");
15331×
        assert_eq!(v.entries["d/a.txt"].placement, (h(2), 0));
15341×
        assert_eq!(v.unified, [(nid(3), nid(2))].into_iter().collect());
1535
        // Different contents, or not known yet: the conflict as it was.
15361×
        let (v, decided) = g.unify(raw.clone(), |_| Some(false));
15371×
        assert!(decided);
15381×
        assert_eq!(v, raw);
15391×
        let (v, decided) = g.unify(raw.clone(), |_| None);
15401×
        assert!(!decided);
15411×
        assert_eq!(v, raw);
1542
        // Directories, and kinds that differ, are never offered.
15431×
        let mut g = TreeGraph::new();
15441×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
15451×
        add(&mut g, 2, 0, 2, NodeKind::Dir, NodeId::ROOT, "d");
15461×
        add(&mut g, 3, 0, 3, NodeKind::File, NodeId::ROOT, "f");
15471×
        add(&mut g, 4, 0, 4, NodeKind::Symlink, NodeId::ROOT, "f");
15481×
        let raw = g.view();
15491×
        let (v, _) = g.unify(raw.clone(), |_| panic!("not offered"));
15501×
        assert_eq!(v, raw);
15511×
    }
1552
1553
    #[test]
15541×
    fn concurrent_renames_are_multi_placement() {
15551×
        let mut g = TreeGraph::new();
15561×
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "a");
15571×
        g.apply(
15581×
            h(2),
1559
            0,
15601×
            &TreeOp::Move {
15611×
                node: nid(1),
15621×
                old: OpRef {
15631×
                    change: h(1),
15641×
                    index: 0,
15651×
                },
15661×
                parent: NodeId::ROOT,
15671×
                name: "b".into(),
15681×
            },
1569
        );
15701×
        g.apply(
15711×
            h(3),
1572
            0,
15731×
            &TreeOp::Move {
15741×
                node: nid(1),
15751×
                old: OpRef {
15761×
                    change: h(1),
15771×
                    index: 0,
15781×
                },
15791×
                parent: NodeId::ROOT,
15801×
                name: "c".into(),
15811×
            },
1582
        );
15831×
        let v = g.view();
15841×
        assert_eq!(paths(&v), ["b.oxy-conflict-1", "c.oxy-conflict-1"]);
15851×
        assert!(
15861×
            matches!(v.conflicts[0], TreeConflict::MultiPlacement { node, .. } if node == nid(1))
1587
        );
15881×
    }
1589
1590
    #[test]
15911×
    fn orphan_and_cycle() {
15921×
        let mut g = TreeGraph::new();
15931×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "d");
15941×
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "f");
1595
        // Concurrently: 2 removes d, 3 adds d/g.
15961×
        g.apply(
15971×
            h(2),
1598
            0,
15991×
            &TreeOp::RemoveNode {
16001×
                node: nid(1),
16011×
                placement: OpRef {
16021×
                    change: h(1),
16031×
                    index: 0,
16041×
                },
16051×
            },
1606
        );
16071×
        add(&mut g, 3, 0, 3, NodeKind::File, nid(1), "g");
16081×
        let v = g.view();
16091×
        assert_eq!(paths(&v), Vec::<String>::new());
16101×
        assert!(v.conflicts.contains(&TreeConflict::Orphan {
16111×
            node: nid(2),
16121×
            parent: nid(1)
16131×
        }));
16141×
        assert!(v.conflicts.contains(&TreeConflict::Orphan {
16151×
            node: nid(3),
16161×
            parent: nid(1)
16171×
        }));
1618
1619
        // Cycle: a/b, then concurrently move a under b and b under a's parent... simplest: move a into b.
16201×
        let mut g = TreeGraph::new();
16211×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "a");
16221×
        add(&mut g, 1, 1, 2, NodeKind::Dir, nid(1), "b");
16231×
        g.apply(
16241×
            h(2),
1625
            0,
16261×
            &TreeOp::Move {
16271×
                node: nid(1),
16281×
                old: OpRef {
16291×
                    change: h(1),
16301×
                    index: 0,
16311×
                },
16321×
                parent: nid(2),
16331×
                name: "a".into(),
16341×
            },
1635
        );
16361×
        let v = g.view();
16371×
        assert!(
16381×
            v.conflicts
16391×
                .iter()
16401×
                .any(|c| matches!(c, TreeConflict::Cycle { .. })),
1641
            "{:?}",
1642
            v.conflicts
1643
        );
16441×
    }
1645
1646
    #[test]
16471×
    fn serialization_round_trip() {
16481×
        let mut g = TreeGraph::new();
16491×
        add(&mut g, 1, 0, 1, NodeKind::Dir, NodeId::ROOT, "src");
16501×
        add(&mut g, 1, 1, 2, NodeKind::File, nid(1), "main.rs");
16511×
        g.apply(
16521×
            h(2),
1653
            0,
16541×
            &TreeOp::Move {
16551×
                node: nid(2),
16561×
                old: OpRef {
16571×
                    change: h(1),
16581×
                    index: 1,
16591×
                },
16601×
                parent: nid(1),
16611×
                name: "lib.rs".into(),
16621×
            },
1663
        );
16641×
        g.apply(
16651×
            h(3),
1666
            0,
16671×
            &TreeOp::SetMode {
16681×
                node: nid(2),
16691×
                old: OpRef {
16701×
                    change: h(1),
16711×
                    index: 1,
16721×
                },
16731×
                mode: Mode {
16741×
                    exec: true,
16751×
                    opaque: false,
16761×
                },
16771×
            },
1678
        );
16791×
        let bytes = crate::cbor::encode(&g.to_value());
16801×
        let back = TreeGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap();
16811×
        assert_eq!(back.view(), g.view());
16821×
        assert_eq!(back.introducer(&nid(2)), Some(h(1)));
16831×
        assert_eq!(crate::cbor::encode(&back.to_value()), bytes);
16841×
    }
1685
1686
    #[test]
16871×
    fn mode_conflict_and_resolution() {
16881×
        let mut g = TreeGraph::new();
16891×
        add(&mut g, 1, 0, 1, NodeKind::File, NodeId::ROOT, "run.sh");
16901×
        g.apply(
16911×
            h(2),
1692
            0,
16931×
            &TreeOp::SetMode {
16941×
                node: nid(1),
16951×
                old: OpRef {
16961×
                    change: h(1),
16971×
                    index: 0,
16981×
                },
16991×
                mode: Mode {
17001×
                    exec: true,
17011×
                    opaque: false,
17021×
                },
17031×
            },
1704
        );
17051×
        g.apply(
17061×
            h(3),
1707
            0,
17081×
            &TreeOp::SetMode {
17091×
                node: nid(1),
17101×
                old: OpRef {
17111×
                    change: h(1),
17121×
                    index: 0,
17131×
                },
17141×
                mode: Mode {
17151×
                    exec: false,
17161×
                    opaque: true,
17171×
                },
17181×
            },
1719
        );
17201×
        let v = g.view();
17211×
        assert!(matches!(v.conflicts[0], TreeConflict::Mode { .. }));
1722
        // Materialized with the greatest mode atom.
17231×
        assert_eq!(
17241×
            v.entries["run.sh"].mode,
1725
            Mode {
1726
                exec: false,
1727
                opaque: true
1728
            }
1729
        );
1730
        // The placement introduced by the same AddNode is unaffected.
17311×
        assert_eq!(v.entries["run.sh"].placement, (h(1), 0));
1732
        // Resolution: retire both live mode atoms; identical live values do not conflict.
17331×
        g.apply(
17341×
            h(4),
1735
            0,
17361×
            &TreeOp::SetMode {
17371×
                node: nid(1),
17381×
                old: OpRef {
17391×
                    change: h(2),
17401×
                    index: 0,
17411×
                },
17421×
                mode: Mode {
17431×
                    exec: true,
17441×
                    opaque: false,
17451×
                },
17461×
            },
1747
        );
17481×
        g.apply(
17491×
            h(4),
1750
            1,
17511×
            &TreeOp::SetMode {
17521×
                node: nid(1),
17531×
                old: OpRef {
17541×
                    change: h(3),
17551×
                    index: 0,
17561×
                },
17571×
                mode: Mode {
17581×
                    exec: true,
17591×
                    opaque: false,
17601×
                },
17611×
            },
1762
        );
17631×
        let v = g.view();
17641×
        assert!(!v.is_conflicted(), "{:?}", v.conflicts);
17651×
        assert_eq!(
17661×
            v.entries["run.sh"].mode,
1767
            Mode {
1768
                exec: true,
1769
                opaque: false
1770
            }
1771
        );
17721×
    }
1773
}