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 |