changeset 53237:1951f36f0327

branching: merge with stable
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 30 Apr 2025 02:15:23 +0200
parents 5392f4fd6764 (current diff) 4ac82bcae209 (diff)
children b09ae3ab76be
files rust/hg-core/src/revlog/mod.rs tests/test-persistent-nodemap.t
diffstat 4 files changed, 95 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-core/src/revlog/mod.rs	Tue Apr 01 10:42:42 2025 +0200
+++ b/rust/hg-core/src/revlog/mod.rs	Wed Apr 30 02:15:23 2025 +0200
@@ -14,6 +14,7 @@
 use inner_revlog::RevisionBuffer;
 use memmap2::MmapOptions;
 pub use node::{FromHexError, Node, NodePrefix, NULL_NODE, NULL_NODE_ID};
+use nodemap::read_persistent_nodemap;
 use options::RevlogOpenOptions;
 pub mod changelog;
 pub mod compression;
@@ -346,14 +347,7 @@
         let nodemap = if index.is_inline() || !options.use_nodemap {
             None
         } else {
-            NodeMapDocket::read_from_file(store_vfs, index_path)?.map(
-                |(docket, data)| {
-                    nodemap::NodeTree::load_bytes(
-                        Box::new(data),
-                        docket.data_length,
-                    )
-                },
-            )
+            read_persistent_nodemap(store_vfs, index_path, &index)?
         };
 
         let nodemap = nodemap_for_test.or(nodemap);
--- a/rust/hg-core/src/revlog/nodemap.rs	Tue Apr 01 10:42:42 2025 +0200
+++ b/rust/hg-core/src/revlog/nodemap.rs	Wed Apr 30 02:15:23 2025 +0200
@@ -12,8 +12,12 @@
 //! Following existing implicit conventions, the "nodemap" terminology
 //! is used in a more abstract context.
 
+use crate::errors::HgError;
+use crate::revlog::NodeMapDocket;
+use crate::vfs::VfsImpl;
 use crate::UncheckedRevision;
 
+use super::BaseRevision;
 use super::{
     node::NULL_NODE, Node, NodePrefix, Revision, RevlogIndex, NULL_REVISION,
 };
@@ -24,10 +28,11 @@
 use std::mem::{self, align_of, size_of};
 use std::ops::Deref;
 use std::ops::Index;
+use std::path::Path;
 
 type NodeTreeBuffer = Box<dyn Deref<Target = [u8]> + Send + Sync>;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, derive_more::Display)]
 pub enum NodeMapError {
     /// A `NodePrefix` matches several [`Revision`]s.
     ///
@@ -297,6 +302,42 @@
     }
 }
 
+/// Read the persistent nodemap corresponding to the `index` at `index_path`.
+/// `index` and `index_path` MUST reference the same index.
+pub(super) fn read_persistent_nodemap(
+    store_vfs: &VfsImpl,
+    index_path: &Path,
+    index: &impl RevlogIndex,
+) -> Result<Option<NodeTree>, HgError> {
+    if let Some((docket, data)) =
+        NodeMapDocket::read_from_file(store_vfs, index_path)?
+    {
+        let mut nodemap =
+            NodeTree::load_bytes(Box::new(data), docket.data_length);
+        if let Some(valid_tip_rev) = index.check_revision(docket.tip_rev) {
+            let valid_node =
+                index.node(valid_tip_rev) == Some(&docket.tip_node);
+            if valid_node && (valid_tip_rev.0 as usize) < index.len() {
+                // The index moved forward but wasn't rewritten
+                nodemap
+                    .catch_up_to_index(index, valid_tip_rev)
+                    .map_err(|e| HgError::abort_simple(e.to_string()))?;
+                return Ok(Some(nodemap));
+            }
+        }
+        if !index.is_empty() {
+            // The nodemap exists but is invalid somehow (strip, corruption...)
+            // so we rebuild it from scratch.
+            let mut nodemap = NodeTree::new(Box::<Vec<_>>::default());
+            nodemap
+                .catch_up_to_index(index, Revision(0))
+                .map_err(|e| HgError::abort_simple(e.to_string()))?;
+            return Ok(Some(nodemap));
+        }
+    }
+    Ok(None)
+}
+
 impl NodeTree {
     /// Initiate a NodeTree from an immutable slice-like of `Block`
     ///
@@ -529,6 +570,30 @@
         Ok(())
     }
 
+    /// Insert all [`Revision`] from `from` inclusive, up to
+    /// [`RevlogIndex::len`] exclusive.
+    ///
+    /// `from` must be a valid revision for `index`, most likely should be the
+    /// tip of the nodemap docket.
+    ///
+    /// Useful for updating the [`NodeTree`] when the index has moved forward.
+    pub fn catch_up_to_index(
+        &mut self,
+        index: &impl RevlogIndex,
+        from: Revision,
+    ) -> Result<(), NodeMapError> {
+        for r in (from.0)..index.len() as BaseRevision {
+            let rev = Revision(r);
+            // in this case node() won't ever return None
+            self.insert(
+                index,
+                index.node(rev).expect("node should exist"),
+                rev,
+            )?;
+        }
+        Ok(())
+    }
+
     /// Make the whole `NodeTree` logically empty, without touching the
     /// immutable part.
     pub fn invalidate_all(&mut self) {
--- a/rust/hg-core/src/revlog/nodemap_docket.rs	Tue Apr 01 10:42:42 2025 +0200
+++ b/rust/hg-core/src/revlog/nodemap_docket.rs	Wed Apr 30 02:15:23 2025 +0200
@@ -1,14 +1,23 @@
-use crate::errors::{HgError, HgResultExt};
+use crate::{
+    errors::{HgError, HgResultExt},
+    BaseRevision,
+};
 use bytes_cast::{unaligned, BytesCast};
 use memmap2::Mmap;
 use std::path::{Path, PathBuf};
 
 use crate::vfs::VfsImpl;
 
+use super::{Node, UncheckedRevision};
+
 const ONDISK_VERSION: u8 = 1;
 
 pub(super) struct NodeMapDocket {
     pub data_length: usize,
+    /// This is [`UncheckedRevision`] and not [`Revision`] because the nodemap
+    /// can be out-of-date (because of strip for example)
+    pub tip_rev: UncheckedRevision,
+    pub tip_node: Node,
     // TODO: keep here more of the data from `parse()` when we need it
 }
 
@@ -16,7 +25,7 @@
 #[repr(C)]
 struct DocketHeader {
     uid_size: u8,
-    _tip_rev: unaligned::U64Be,
+    tip_rev: unaligned::U64Be,
     data_length: unaligned::U64Be,
     _data_unused: unaligned::U64Be,
     tip_node_size: unaligned::U64Be,
@@ -66,10 +75,16 @@
         let tip_node_size = header.tip_node_size.get() as usize;
         let data_length = header.data_length.get() as usize;
         let (uid, rest) = parse(u8::slice_from_bytes(rest, uid_size))?;
-        let (_tip_node, _rest) =
+        let (tip_node, _rest) =
             parse(u8::slice_from_bytes(rest, tip_node_size))?;
         let uid = parse(std::str::from_utf8(uid))?;
-        let docket = NodeMapDocket { data_length };
+        let tip_node = parse(Node::from_bytes(tip_node))?.0.to_owned();
+        let revnum: BaseRevision = parse(header.tip_rev.get().try_into())?;
+        let docket = NodeMapDocket {
+            data_length,
+            tip_rev: revnum.into(),
+            tip_node,
+        };
 
         let data_path = rawdata_path(&docket_path, uid);
         // TODO: use `vfs.read()` here when the `persistent-nodemap.mmap`
--- a/tests/test-persistent-nodemap.t	Tue Apr 01 10:42:42 2025 +0200
+++ b/tests/test-persistent-nodemap.t	Wed Apr 30 02:15:23 2025 +0200
@@ -364,8 +364,8 @@
   data-length: 121088
   data-unused: 0
   data-unused: 0.000%
-  $ hg log -r "$NODE" -T '{rev}\n'
-  5003
+  $ env RHG_ON_UNSUPPORTED=abort hg cat -r $NODE bar
+  bar2
 
 changelog altered
 -----------------
@@ -410,8 +410,9 @@
   data-length: 121088
   data-unused: 0
   data-unused: 0.000%
-  $ hg log -r "$OTHERNODE" -T '{rev}\n'
-  5002
+  $ env RHG_ON_UNSUPPORTED=abort hg cat -r $OTHERNODE babar
+  bar
+
 
 missing data file
 -----------------
@@ -424,14 +425,9 @@
 
 mercurial don't crash
 
-  $ hg log -r .
-  changeset:   5002:b355ef8adce0
-  tag:         tip
-  parent:      4998:d918ad6d18d3
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     babar
-  
+  $ env RHG_ON_UNSUPPORTED=abort hg cat -r $OTHERNODE babar
+  bar
+
   $ hg debugnodemap --metadata
 
   $ hg debugupdatecache