dirstate-v2: Extend node flags to 16 bits
authorSimon Sapin <simon.sapin@octobus.net>
Thu, 14 Oct 2021 16:06:31 +0200
changeset 48231 0524c1359bfc
parent 48230 7ed0fc687220
child 48232 f7fd629ffb98
dirstate-v2: Extend node flags to 16 bits Only 7 out of 8 available bits are used right now. Reserve some more. Future versions of Mercurial may assign meaning to some of these bits, with the limitation that then-older versions will always reset those bits to unset when writing nodes. (A new node is written for any mutation in its subtree, leaving the bytes of the old node unreachable until the data file is rewritten entirely.) Differential Revision: https://phab.mercurial-scm.org/D11661
mercurial/dirstateutils/v2.py
mercurial/helptext/internals/dirstate-v2.txt
rust/hg-core/src/dirstate_tree/on_disk.rs
--- a/mercurial/dirstateutils/v2.py	Thu Oct 14 08:58:07 2021 -0700
+++ b/mercurial/dirstateutils/v2.py	Thu Oct 14 16:06:31 2021 +0200
@@ -18,7 +18,7 @@
 # Must match the constant of the same name in
 # `rust/hg-core/src/dirstate_tree/on_disk.rs`
 TREE_METADATA_SIZE = 44
-NODE_SIZE = 43
+NODE_SIZE = 44
 
 
 # Must match the `TreeMetadata` Rust struct in
@@ -50,7 +50,7 @@
 # * 4 bytes: expected size
 # * 4 bytes: mtime seconds
 # * 4 bytes: mtime nanoseconds
-NODE = struct.Struct('>LHHLHLLLLBlll')
+NODE = struct.Struct('>LHHLHLLLLHlll')
 
 
 assert TREE_METADATA_SIZE == TREE_METADATA.size
--- a/mercurial/helptext/internals/dirstate-v2.txt	Thu Oct 14 08:58:07 2021 -0700
+++ b/mercurial/helptext/internals/dirstate-v2.txt	Thu Oct 14 16:06:31 2021 +0200
@@ -372,7 +372,7 @@
   This counter is used to implement `has_tracked_dir`.
 
 * Offset 30:
-  A single `flags` byte that packs some boolean values as bits.
+  A `flags` fields  that packs some boolean values as bits of a 16-bit integer.
   Starting from least-significant, bit masks are::
 
     WDIR_TRACKED = 1 << 0
@@ -384,22 +384,29 @@
     MODE_IS_SYMLINK = 1 << 6
 
   The meaning of each bit is described below.
-  Other bits are unset.
 
-* Offset 31:
+  Other bits are unset.
+  They may be assigned meaning if the future,
+  with the limitation that Mercurial versions that pre-date such meaning
+  will always reset those bits to unset when writing nodes.
+  (A new node is written for any mutation in its subtree,
+  leaving the bytes of the old node unreachable
+  until the data file is rewritten entirely.)
+
+* Offset 32:
   A `size` field described below, as a 32-bit integer.
   Unlike in dirstate-v1, negative values are not used.
 
-* Offset 35:
+* Offset 36:
   The seconds component of an `mtime` field described below,
   as a 32-bit integer.
   Unlike in dirstate-v1, negative values are not used.
 
-* Offset 39:
+* Offset 40:
   The nanoseconds component of an `mtime` field described below,
   as a 32-bit integer.
 
-* (Offset 43: end of this node)
+* (Offset 44: end of this node)
 
 The meaning of the boolean values packed in `flags` is:
 
--- a/rust/hg-core/src/dirstate_tree/on_disk.rs	Thu Oct 14 08:58:07 2021 -0700
+++ b/rust/hg-core/src/dirstate_tree/on_disk.rs	Thu Oct 14 16:06:31 2021 +0200
@@ -33,7 +33,7 @@
 
 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
 const TREE_METADATA_SIZE: usize = 44;
-const NODE_SIZE: usize = 43;
+const NODE_SIZE: usize = 44;
 
 /// Make sure that size-affecting changes are made knowingly
 #[allow(unused)]
@@ -94,15 +94,14 @@
     children: ChildNodes,
     pub(super) descendants_with_entry_count: Size,
     pub(super) tracked_descendants_count: Size,
-    flags: Flags,
+    flags: U16Be,
     size: U32Be,
     mtime: PackedTruncatedTimestamp,
 }
 
 bitflags! {
-    #[derive(BytesCast)]
     #[repr(C)]
-    struct Flags: u8 {
+    struct Flags: u16 {
         const WDIR_TRACKED = 1 << 0;
         const P1_TRACKED = 1 << 1;
         const P2_INFO = 1 << 2;
@@ -296,8 +295,12 @@
         })
     }
 
+    fn flags(&self) -> Flags {
+        Flags::from_bits_truncate(self.flags.get())
+    }
+
     fn has_entry(&self) -> bool {
-        self.flags.intersects(
+        self.flags().intersects(
             Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
         )
     }
@@ -318,7 +321,7 @@
         &self,
     ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
         Ok(
-            if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
+            if self.flags().contains(Flags::HAS_MTIME) && !self.has_entry() {
                 Some(self.mtime.try_into()?)
             } else {
                 None
@@ -327,12 +330,12 @@
     }
 
     fn synthesize_unix_mode(&self) -> u32 {
-        let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) {
+        let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
             libc::S_IFLNK
         } else {
             libc::S_IFREG
         };
-        let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) {
+        let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
             0o755
         } else {
             0o644
@@ -342,15 +345,15 @@
 
     fn assume_entry(&self) -> DirstateEntry {
         // TODO: convert through raw bits instead?
-        let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
-        let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
-        let p2_info = self.flags.contains(Flags::P2_INFO);
-        let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
+        let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
+        let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
+        let p2_info = self.flags().contains(Flags::P2_INFO);
+        let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE) {
             Some((self.synthesize_unix_mode(), self.size.into()))
         } else {
             None
         };
-        let mtime = if self.flags.contains(Flags::HAS_MTIME) {
+        let mtime = if self.flags().contains(Flags::HAS_MTIME) {
             Some(self.mtime.truncated_seconds.into())
         } else {
             None
@@ -600,7 +603,7 @@
                         tracked_descendants_count: node
                             .tracked_descendants_count
                             .into(),
-                        flags,
+                        flags: flags.bits().into(),
                         size,
                         mtime,
                     }