OXYGENHUB

Introducers from a state index without the tree's maps 930e5f3f1682

A state index also holds the tree's introducers as a block of their
own: a snapshot, which asks for the introducers of the nodes a change
touches, reads that block and leaves the rest of the maps encoded. Test
builds check it against the full maps.

refs #41
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-30 11:05 UTC
Signature
✔ signed by Andy McCall
Hash
930e5f3f16823c4b878fd19597948548f9a8f9b6c75080b3524a08b900e01892
Depends on
05059d434aca e83354cdac22 ff0af1ee299d
Depended on by
nothing yet
In bookmarks
agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
In tags
v0.1.0 v0.10.0 v0.11.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
Edits
9 atoms, 0 tree operations, 1 file

Browse the tree at this change

modified crates/oxy-core/src/tree.rs +54 −1
…
113 unchanged lines
114 114
    /// A state index's encoding of `maps`, decoded when first needed: a
115 115
    /// command that only reads the view (a status) never needs them.
116 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>>,
117 121
    /// The view, once computed, with the path of each directory in it;
118 122
    /// kept up to date as ops apply when that is simple (see
119 123
    /// `update_view`), cleared otherwise.
…
433 unchanged lines
553 557
554 558
    /// The change whose `AddNode` introduced `node`.
555 559
    pub fn introducer(&self, node: &NodeId) -> Option<ChangeHash> {
560 +
        if let (None, Some(raw)) = (self.maps.get(), &self.raw_introduced) {
561 +
            let only = self.introduced_only.get_or_init(|| {
562 +
                read_introduced(raw).expect("a state index is checked against its hash when read")
563 +
            });
564 +
            #[cfg(test)]
565 +
            assert_eq!(
566 +
                only.get(node),
567 +
                TreeMaps::decode(self.raw.as_ref().unwrap())
568 +
                    .unwrap()
569 +
                    .introduced
570 +
                    .get(node),
571 +
                "the introducers block is the maps'"
572 +
            );
573 +
            return only.get(node).copied();
574 +
        }
556 575
        self.m().introduced.get(node).copied()
557 576
    }
558 577
…
428 unchanged lines
987 1006
        }
988 1007
        let maps = std::mem::replace(w, outer);
989 1008
        w.bytes(&maps.0);
1009 +
        // The introducers again, alone, for a reader that needs no more.
1010 +
        let mut iw = crate::flat::Writer::default();
1011 +
        let mut intro: Vec<(&NodeId, &ChangeHash)> = self.m().introduced.iter().collect();
1012 +
        intro.sort();
1013 +
        iw.count(intro.len());
1014 +
        for (n, c) in intro {
1015 +
            iw.fixed(&n.0);
1016 +
            iw.object(c);
1017 +
        }
1018 +
        w.bytes(&iw.0);
990 1019
        // The view as a block of its own.
991 1020
        let outer = std::mem::take(w);
992 1021
        let view = self.view();
…
25 unchanged lines
1018 1047
        r: &mut crate::flat::Reader,
1019 1048
    ) -> Result<TreeGraph, crate::objects::ObjectError> {
1020 1049
        let maps = r.bytes()?;
1050 +
        let introduced = r.bytes()?;
1021 1051
        let view = Self::read_view(r.bytes()?)?;
1022 1052
        let mut g = TreeGraph {
1023 1053
            raw: Some(std::sync::Arc::new(maps.to_vec())),
1054 +
            raw_introduced: Some(std::sync::Arc::new(introduced.to_vec())),
1024 1055
            ..TreeGraph::default()
1025 1056
        };
1026 1057
        if let Some(view) = view {
1058 +
            // Checked on a copy, so the graph itself stays undecoded.
1027 1059
            #[cfg(test)]
1028 -
            assert_eq!(view, g.compute_view(), "an index's view is the tree's");
1060 +
            assert_eq!(
1061 +
                view,
1062 +
                TreeGraph {
1063 +
                    raw: g.raw.clone(),
1064 +
                    ..TreeGraph::default()
1065 +
                }
1066 +
                .compute_view(),
1067 +
                "an index's view is the tree's"
1068 +
            );
1029 1069
            g.view_cache = std::sync::OnceLock::from(CachedView::new(view));
1030 1070
        }
1031 1071
        Ok(g)
…
105 unchanged lines
1137 1177
            conflicts: Vec::new(),
1138 1178
        }))
1139 1179
    }
1180 +
}
1181 +
1182 +
/// The introducers block of a tree's flat form.
1183 +
fn read_introduced(bytes: &[u8]) -> Result<Map<NodeId, ChangeHash>, crate::objects::ObjectError> {
1184 +
    let r = &mut crate::flat::Reader::new(bytes);
1185 +
    let n = r.count()?;
1186 +
    let mut m = Map::default();
1187 +
    m.reserve(n);
1188 +
    for _ in 0..n {
1189 +
        let node = NodeId(r.fixed::<16>()?);
1190 +
        m.insert(node, r.object()?);
1191 +
    }
1192 +
    Ok(m)
1140 1193
}
1141 1194
1142 1195
fn join(dir: &str, name: &str) -> String {
…
400 unchanged lines