# HG changeset patch # User Raphaël Gomès # Date 2022-04-05 09:09:03 # Node ID 12adf8c695edafdb345b8eb1a9626ee0d6a9fc04 # Parent 20c6c9e43397d0b099ca8adde3e7ae702c3e4248 # Parent 9dcfd1d05e6ea0a0c3d74c331282d93c48c12042 merge: stable into default diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py --- a/mercurial/dispatch.py +++ b/mercurial/dispatch.py @@ -269,7 +269,7 @@ def _rundispatch(req): ferr.write(inst.format()) return -1 - msg = _formatargs(req.args) + formattedargs = _formatargs(req.args) starttime = util.timer() ret = 1 # default of Python exit code on unhandled exception try: @@ -308,7 +308,7 @@ def _rundispatch(req): req.ui.log( b"commandfinish", b"%s exited %d after %0.2f seconds\n", - msg, + formattedargs, return_code, duration, return_code=return_code, diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3,6 +3,12 @@ version = 3 [[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] name = "adler" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -24,6 +30,12 @@ dependencies = [ ] [[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -210,12 +222,22 @@ dependencies = [ [[package]] name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-channel" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils", + "crossbeam-utils 0.8.1", ] [[package]] @@ -226,7 +248,7 @@ checksum = "94af6efb46fef72616855b036a62 dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils", + "crossbeam-utils 0.8.1", ] [[package]] @@ -237,7 +259,7 @@ checksum = "a1aaa739f95311c2c7887a76863f dependencies = [ "cfg-if 1.0.0", "const_fn", - "crossbeam-utils", + "crossbeam-utils 0.8.1", "lazy_static", "memoffset", "scopeguard", @@ -245,6 +267,17 @@ dependencies = [ [[package]] name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" @@ -443,7 +476,7 @@ dependencies = [ "byteorder", "bytes-cast", "clap", - "crossbeam-channel", + "crossbeam-channel 0.4.4", "derive_more", "flate2", "format-bytes", @@ -455,7 +488,8 @@ dependencies = [ "libc", "log", "memmap2", - "micro-timer", + "micro-timer 0.3.1", + "ouroboros", "pretty_assertions", "rand 0.8.5", "rand_distr", @@ -464,7 +498,6 @@ dependencies = [ "regex", "same-file", "sha-1 0.10.0", - "stable_deref_trait", "tempfile", "twox-hash", "zstd", @@ -475,7 +508,7 @@ name = "hg-cpython" version = "0.1.0" dependencies = [ "cpython", - "crossbeam-channel", + "crossbeam-channel 0.5.2", "env_logger", "hg-core", "libc", @@ -588,6 +621,12 @@ dependencies = [ ] [[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -595,9 +634,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2 [[package]] name = "memmap2" -version = "0.5.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "de5d3112c080d58ce560081baeaab7e1e864ca21795ddbf533d5b1842bb1ecf8" dependencies = [ "libc", "stable_deref_trait", @@ -614,16 +653,38 @@ dependencies = [ [[package]] name = "micro-timer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c" +dependencies = [ + "micro-timer-macros 0.3.1", + "scopeguard", +] + +[[package]] +name = "micro-timer" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405" dependencies = [ - "micro-timer-macros", + "micro-timer-macros 0.4.0", "scopeguard", ] [[package]] name = "micro-timer-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d" +dependencies = [ + "proc-macro2", + "quote", + "scopeguard", + "syn", +] + +[[package]] +name = "micro-timer-macros" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cee948b94700125b52dfb68dd17c19f6326696c1df57f92c05ee857463c93ba1" @@ -681,6 +742,30 @@ source = "registry+https://github.com/ru checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] +name = "ouroboros" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf" +dependencies = [ + "aliasable", + "ouroboros_macro", + "stable_deref_trait", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "output_vt100" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -720,6 +805,30 @@ dependencies = [ ] [[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -864,9 +973,9 @@ version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ - "crossbeam-channel", + "crossbeam-channel 0.5.2", "crossbeam-deque", - "crossbeam-utils", + "crossbeam-utils 0.8.1", "lazy_static", "num_cpus", ] @@ -920,7 +1029,7 @@ dependencies = [ "home", "lazy_static", "log", - "micro-timer", + "micro-timer 0.4.0", "regex", "users", ] diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml +++ b/rust/hg-core/Cargo.toml @@ -18,8 +18,9 @@ home = "0.5.3" im-rc = "15.0.0" itertools = "0.10.3" lazy_static = "1.4.0" -libc = "0.2.119" -rand = "0.8.5" +libc = "0.2" +ouroboros = "0.15.0" +rand = "0.8.4" rand_pcg = "0.3.1" rand_distr = "0.4.3" rayon = "1.5.1" @@ -27,12 +28,11 @@ regex = "1.5.5" sha-1 = "0.10.0" twox-hash = "1.6.2" same-file = "1.0.6" -stable_deref_trait = "1.2.0" -tempfile = "3.3.0" -crossbeam-channel = "0.5.2" -micro-timer = "0.4.0" -log = "0.4.14" -memmap2 = { version = "0.5.3", features = ["stable_deref_trait"] } +tempfile = "3.1.0" +crossbeam-channel = "0.4" +micro-timer = "0.3.0" +log = "0.4.8" +memmap2 = {version = "0.4", features = ["stable_deref_trait"]} zstd = "0.5.3" format-bytes = "0.3.0" diff --git a/rust/hg-core/src/dirstate/status.rs b/rust/hg-core/src/dirstate/status.rs --- a/rust/hg-core/src/dirstate/status.rs +++ b/rust/hg-core/src/dirstate/status.rs @@ -136,8 +136,6 @@ pub enum StatusError { DirstateV2ParseError(DirstateV2ParseError), } -pub type StatusResult = Result; - impl fmt::Display for StatusError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -723,10 +723,11 @@ where impl OwningDirstateMap { pub fn clear(&mut self) { - let map = self.get_map_mut(); - map.root = Default::default(); - map.nodes_with_entry_count = 0; - map.nodes_with_copy_source_count = 0; + self.with_dmap_mut(|map| { + map.root = Default::default(); + map.nodes_with_entry_count = 0; + map.nodes_with_copy_source_count = 0; + }); } pub fn set_entry( @@ -734,9 +735,10 @@ impl OwningDirstateMap { filename: &HgPath, entry: DirstateEntry, ) -> Result<(), DirstateV2ParseError> { - let map = self.get_map_mut(); - map.get_or_insert(&filename)?.data = NodeData::Entry(entry); - Ok(()) + self.with_dmap_mut(|map| { + map.get_or_insert(&filename)?.data = NodeData::Entry(entry); + Ok(()) + }) } pub fn add_file( @@ -745,8 +747,9 @@ impl OwningDirstateMap { entry: DirstateEntry, ) -> Result<(), DirstateError> { let old_state = self.get(filename)?.map(|e| e.state()); - let map = self.get_map_mut(); - Ok(map.add_or_remove_file(filename, old_state, entry)?) + self.with_dmap_mut(|map| { + Ok(map.add_or_remove_file(filename, old_state, entry)?) + }) } pub fn remove_file( @@ -777,9 +780,10 @@ impl OwningDirstateMap { if size == 0 { self.copy_map_remove(filename)?; } - let map = self.get_map_mut(); - let entry = DirstateEntry::new_removed(size); - Ok(map.add_or_remove_file(filename, old_state, entry)?) + self.with_dmap_mut(|map| { + let entry = DirstateEntry::new_removed(size); + Ok(map.add_or_remove_file(filename, old_state, entry)?) + }) } pub fn drop_entry_and_copy_source( @@ -789,7 +793,6 @@ impl OwningDirstateMap { let was_tracked = self .get(filename)? .map_or(false, |e| e.state().is_tracked()); - let map = self.get_map_mut(); struct Dropped { was_tracked: bool, had_entry: bool, @@ -826,10 +829,20 @@ impl OwningDirstateMap { )? { dropped = d; if dropped.had_entry { - node.descendants_with_entry_count -= 1; + node.descendants_with_entry_count = node + .descendants_with_entry_count + .checked_sub(1) + .expect( + "descendants_with_entry_count should be >= 0", + ); } if dropped.was_tracked { - node.tracked_descendants_count -= 1; + node.tracked_descendants_count = node + .tracked_descendants_count + .checked_sub(1) + .expect( + "tracked_descendants_count should be >= 0", + ); } // Directory caches must be invalidated when removing a @@ -843,21 +856,22 @@ impl OwningDirstateMap { return Ok(None); } } else { - let had_entry = node.data.has_entry(); + let entry = node.data.as_entry(); + let was_tracked = entry.map_or(false, |entry| entry.tracked()); + let had_entry = entry.is_some(); if had_entry { node.data = NodeData::None } + let mut had_copy_source = false; if let Some(source) = &node.copy_source { DirstateMap::count_dropped_path(unreachable_bytes, source); + had_copy_source = true; node.copy_source = None } dropped = Dropped { - was_tracked: node - .data - .as_entry() - .map_or(false, |entry| entry.state().is_tracked()), + was_tracked, had_entry, - had_copy_source: node.copy_source.take().is_some(), + had_copy_source, }; } // After recursion, for both leaf (rest_of_path is None) nodes and @@ -876,52 +890,62 @@ impl OwningDirstateMap { Ok(Some((dropped, remove))) } - if let Some((dropped, _removed)) = recur( - map.on_disk, - &mut map.unreachable_bytes, - &mut map.root, - filename, - )? { - if dropped.had_entry { - map.nodes_with_entry_count -= 1 + self.with_dmap_mut(|map| { + if let Some((dropped, _removed)) = recur( + map.on_disk, + &mut map.unreachable_bytes, + &mut map.root, + filename, + )? { + if dropped.had_entry { + map.nodes_with_entry_count = map + .nodes_with_entry_count + .checked_sub(1) + .expect("nodes_with_entry_count should be >= 0"); + } + if dropped.had_copy_source { + map.nodes_with_copy_source_count = map + .nodes_with_copy_source_count + .checked_sub(1) + .expect("nodes_with_copy_source_count should be >= 0"); + } + } else { + debug_assert!(!was_tracked); } - if dropped.had_copy_source { - map.nodes_with_copy_source_count -= 1 - } - } else { - debug_assert!(!was_tracked); - } - Ok(()) + Ok(()) + }) } pub fn has_tracked_dir( &mut self, directory: &HgPath, ) -> Result { - let map = self.get_map_mut(); - if let Some(node) = map.get_node(directory)? { - // A node without a `DirstateEntry` was created to hold child - // nodes, and is therefore a directory. - let state = node.state()?; - Ok(state.is_none() && node.tracked_descendants_count() > 0) - } else { - Ok(false) - } + self.with_dmap_mut(|map| { + if let Some(node) = map.get_node(directory)? { + // A node without a `DirstateEntry` was created to hold child + // nodes, and is therefore a directory. + let state = node.state()?; + Ok(state.is_none() && node.tracked_descendants_count() > 0) + } else { + Ok(false) + } + }) } pub fn has_dir( &mut self, directory: &HgPath, ) -> Result { - let map = self.get_map_mut(); - if let Some(node) = map.get_node(directory)? { - // A node without a `DirstateEntry` was created to hold child - // nodes, and is therefore a directory. - let state = node.state()?; - Ok(state.is_none() && node.descendants_with_entry_count() > 0) - } else { - Ok(false) - } + self.with_dmap_mut(|map| { + if let Some(node) = map.get_node(directory)? { + // A node without a `DirstateEntry` was created to hold child + // nodes, and is therefore a directory. + let state = node.state()?; + Ok(state.is_none() && node.descendants_with_entry_count() > 0) + } else { + Ok(false) + } + }) } #[timed] @@ -973,16 +997,29 @@ impl OwningDirstateMap { on_disk::write(map, can_append) } - pub fn status<'a>( - &'a mut self, - matcher: &'a (dyn Matcher + Sync), + /// `callback` allows the caller to process and do something with the + /// results of the status. This is needed to do so efficiently (i.e. + /// without cloning the `DirstateStatus` object with its paths) because + /// we need to borrow from `Self`. + pub fn with_status( + &mut self, + matcher: &(dyn Matcher + Sync), root_dir: PathBuf, ignore_files: Vec, options: StatusOptions, - ) -> Result<(DirstateStatus<'a>, Vec), StatusError> - { - let map = self.get_map_mut(); - super::status::status(map, matcher, root_dir, ignore_files, options) + callback: impl for<'r> FnOnce( + Result<(DirstateStatus<'r>, Vec), StatusError>, + ) -> R, + ) -> R { + self.with_dmap_mut(|map| { + callback(super::status::status( + map, + matcher, + root_dir, + ignore_files, + options, + )) + }) } pub fn copy_map_len(&self) -> usize { @@ -1030,22 +1067,23 @@ impl OwningDirstateMap { &mut self, key: &HgPath, ) -> Result, DirstateV2ParseError> { - let map = self.get_map_mut(); - let count = &mut map.nodes_with_copy_source_count; - let unreachable_bytes = &mut map.unreachable_bytes; - Ok(DirstateMap::get_node_mut( - map.on_disk, - unreachable_bytes, - &mut map.root, - key, - )? - .and_then(|node| { - if let Some(source) = &node.copy_source { - *count -= 1; - DirstateMap::count_dropped_path(unreachable_bytes, source); - } - node.copy_source.take().map(Cow::into_owned) - })) + self.with_dmap_mut(|map| { + let count = &mut map.nodes_with_copy_source_count; + let unreachable_bytes = &mut map.unreachable_bytes; + Ok(DirstateMap::get_node_mut( + map.on_disk, + unreachable_bytes, + &mut map.root, + key, + )? + .and_then(|node| { + if let Some(source) = &node.copy_source { + *count -= 1; + DirstateMap::count_dropped_path(unreachable_bytes, source); + } + node.copy_source.take().map(Cow::into_owned) + })) + }) } pub fn copy_map_insert( @@ -1053,19 +1091,20 @@ impl OwningDirstateMap { key: HgPathBuf, value: HgPathBuf, ) -> Result, DirstateV2ParseError> { - let map = self.get_map_mut(); - let node = DirstateMap::get_or_insert_node( - map.on_disk, - &mut map.unreachable_bytes, - &mut map.root, - &key, - WithBasename::to_cow_owned, - |_ancestor| {}, - )?; - if node.copy_source.is_none() { - map.nodes_with_copy_source_count += 1 - } - Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) + self.with_dmap_mut(|map| { + let node = DirstateMap::get_or_insert_node( + map.on_disk, + &mut map.unreachable_bytes, + &mut map.root, + &key, + WithBasename::to_cow_owned, + |_ancestor| {}, + )?; + if node.copy_source.is_none() { + map.nodes_with_copy_source_count += 1 + } + Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) + }) } pub fn len(&self) -> usize { @@ -1113,7 +1152,7 @@ impl OwningDirstateMap { >, DirstateError, > { - let map = self.get_map_mut(); + let map = self.get_map(); let on_disk = map.on_disk; Ok(Box::new(filter_map_results( map.iter_nodes(), diff --git a/rust/hg-core/src/dirstate_tree/owning.rs b/rust/hg-core/src/dirstate_tree/owning.rs --- a/rust/hg-core/src/dirstate_tree/owning.rs +++ b/rust/hg-core/src/dirstate_tree/owning.rs @@ -1,105 +1,89 @@ +use crate::{DirstateError, DirstateParents}; + use super::dirstate_map::DirstateMap; -use stable_deref_trait::StableDeref; use std::ops::Deref; +use ouroboros::self_referencing; + /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it /// borrows. -/// -/// This is similar to [`OwningRef`] which is more limited because it -/// represents exactly one `&T` reference next to the value it borrows, as -/// opposed to a struct that may contain an arbitrary number of references in -/// arbitrarily-nested data structures. -/// -/// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html +#[self_referencing] pub struct OwningDirstateMap { - /// Owned handle to a bytes buffer with a stable address. - /// - /// See . on_disk: Box + Send>, - - /// Pointer for `Box>`, typed-erased because the - /// language cannot represent a lifetime referencing a sibling field. - /// This is not quite a self-referencial struct (moving this struct is not - /// a problem as it doesn’t change the address of the bytes buffer owned - /// by `on_disk`) but touches similar borrow-checker limitations. - ptr: *mut (), + #[borrows(on_disk)] + #[covariant] + map: DirstateMap<'this>, } impl OwningDirstateMap { pub fn new_empty(on_disk: OnDisk) -> Self where - OnDisk: Deref + StableDeref + Send + 'static, + OnDisk: Deref + Send + 'static, { let on_disk = Box::new(on_disk); - let bytes: &'_ [u8] = &on_disk; - let map = DirstateMap::empty(bytes); - // Like in `bytes` above, this `'_` lifetime parameter borrows from - // the bytes buffer owned by `on_disk`. - let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map)); - - // Erase the pointed type entirely in order to erase the lifetime. - let ptr: *mut () = ptr.cast(); - - Self { on_disk, ptr } + OwningDirstateMapBuilder { + on_disk, + map_builder: |bytes| DirstateMap::empty(&bytes), + } + .build() } - pub fn get_pair_mut<'a>( - &'a mut self, - ) -> (&'a [u8], &'a mut DirstateMap<'a>) { - // SAFETY: We cast the type-erased pointer back to the same type it had - // in `new`, except with a different lifetime parameter. This time we - // connect the lifetime to that of `self`. This cast is valid because - // `self` owns the same `on_disk` whose buffer `DirstateMap` - // references. That buffer has a stable memory address because our - // `Self::new_empty` counstructor requires `StableDeref`. - let ptr: *mut DirstateMap<'a> = self.ptr.cast(); - // SAFETY: we dereference that pointer, connecting the lifetime of the - // new `&mut` to that of `self`. This is valid because the - // raw pointer is to a boxed value, and `self` owns that box. - (&self.on_disk, unsafe { &mut *ptr }) - } + pub fn new_v1( + on_disk: OnDisk, + ) -> Result<(Self, DirstateParents), DirstateError> + where + OnDisk: Deref + Send + 'static, + { + let on_disk = Box::new(on_disk); + let mut parents = DirstateParents::NULL; - pub fn get_map_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> { - self.get_pair_mut().1 + Ok(( + OwningDirstateMapTryBuilder { + on_disk, + map_builder: |bytes| { + DirstateMap::new_v1(&bytes).map(|(dmap, p)| { + parents = p.unwrap_or(DirstateParents::NULL); + dmap + }) + }, + } + .try_build()?, + parents, + )) } - pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> { - // SAFETY: same reasoning as in `get_pair_mut` above. - let ptr: *mut DirstateMap<'a> = self.ptr.cast(); - unsafe { &*ptr } + pub fn new_v2( + on_disk: OnDisk, + data_size: usize, + metadata: &[u8], + ) -> Result + where + OnDisk: Deref + Send + 'static, + { + let on_disk = Box::new(on_disk); + + OwningDirstateMapTryBuilder { + on_disk, + map_builder: |bytes| { + DirstateMap::new_v2(&bytes, data_size, metadata) + }, + } + .try_build() } - pub fn on_disk<'a>(&'a self) -> &'a [u8] { - &self.on_disk + pub fn with_dmap_mut( + &mut self, + f: impl FnOnce(&mut DirstateMap) -> R, + ) -> R { + self.with_map_mut(f) + } + + pub fn get_map(&self) -> &DirstateMap { + self.borrow_map() + } + + pub fn on_disk(&self) -> &[u8] { + self.borrow_on_disk() } } - -impl Drop for OwningDirstateMap { - fn drop(&mut self) { - // Silence a "field is never read" warning, and demonstrate that this - // value is still alive. - let _: &Box + Send> = &self.on_disk; - // SAFETY: this cast is the same as in `get_mut`, and is valid for the - // same reason. `self.on_disk` still exists at this point, drop glue - // will drop it implicitly after this `drop` method returns. - let ptr: *mut DirstateMap<'_> = self.ptr.cast(); - // SAFETY: `Box::from_raw` takes ownership of the box away from `self`. - // This is fine because drop glue does nothing for `*mut ()` and we’re - // in `drop`, so `get` and `get_mut` cannot be called again. - unsafe { drop(Box::from_raw(ptr)) } - } -} - -fn _static_assert_is_send() {} - -fn _static_assert_fields_are_send() { - _static_assert_is_send::>>(); -} - -// SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because -// thread-safety of raw pointers is unknown in the general case. However this -// particular raw pointer represents a `Box>` that we -// own. Since that `Box` is `Send` as shown in above, it is sound to mark -// this struct as `Send` too. -unsafe impl Send for OwningDirstateMap {} diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -40,13 +40,14 @@ use std::time::SystemTime; /// exists in one of the two trees, depending on information requested by /// `options` we may need to traverse the remaining subtree. #[timed] -pub fn status<'tree, 'on_disk: 'tree>( - dmap: &'tree mut DirstateMap<'on_disk>, +pub fn status<'dirstate>( + dmap: &'dirstate mut DirstateMap, matcher: &(dyn Matcher + Sync), root_dir: PathBuf, ignore_files: Vec, options: StatusOptions, -) -> Result<(DirstateStatus<'on_disk>, Vec), StatusError> { +) -> Result<(DirstateStatus<'dirstate>, Vec), StatusError> +{ // Force the global rayon threadpool to not exceed 16 concurrent threads. // This is a stop-gap measure until we figure out why using more than 16 // threads makes `status` slower for each additional thread. diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -1,7 +1,6 @@ use crate::changelog::Changelog; use crate::config::{Config, ConfigError, ConfigParseError}; use crate::dirstate::DirstateParents; -use crate::dirstate_tree::dirstate_map::DirstateMap; use crate::dirstate_tree::on_disk::Docket as DirstateDocket; use crate::dirstate_tree::owning::OwningDirstateMap; use crate::errors::HgResultExt; @@ -316,25 +315,19 @@ impl Repo { .set(Some(docket.uuid.to_owned())); let data_size = docket.data_size(); let metadata = docket.tree_metadata(); - let mut map = if let Some(data_mmap) = self + if let Some(data_mmap) = self .hg_vfs() .mmap_open(docket.data_filename()) .io_not_found_as_none()? { - OwningDirstateMap::new_empty(data_mmap) + OwningDirstateMap::new_v2(data_mmap, data_size, metadata) } else { - OwningDirstateMap::new_empty(Vec::new()) - }; - let (on_disk, placeholder) = map.get_pair_mut(); - *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?; - Ok(map) + OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) + } } else { - let mut map = OwningDirstateMap::new_empty(dirstate_file_contents); - let (on_disk, placeholder) = map.get_pair_mut(); - let (inner, parents) = DirstateMap::new_v1(on_disk)?; - self.dirstate_parents - .set(parents.unwrap_or(DirstateParents::NULL)); - *placeholder = inner; + let (map, parents) = + OwningDirstateMap::new_v1(dirstate_file_contents)?; + self.dirstate_parents.set(parents); Ok(map) } } diff --git a/rust/hg-core/src/utils/hg_path.rs b/rust/hg-core/src/utils/hg_path.rs --- a/rust/hg-core/src/utils/hg_path.rs +++ b/rust/hg-core/src/utils/hg_path.rs @@ -144,15 +144,8 @@ impl From for std::io::Erro /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source /// character encoding will be determined on a per-repository basis. -// -// FIXME: (adapted from a comment in the stdlib) -// `HgPath::new()` current implementation relies on `Slice` being -// layout-compatible with `[u8]`. -// When attribute privacy is implemented, `Slice` should be annotated as -// `#[repr(transparent)]`. -// Anyway, `Slice` representation and layout are considered implementation -// detail, are not documented and must not be relied upon. #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)] +#[repr(transparent)] pub struct HgPath { inner: [u8], } diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -23,7 +23,6 @@ use crate::{ }; use hg::{ dirstate::StateMapIter, - dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, dirstate_tree::on_disk::DirstateV2ParseError, dirstate_tree::owning::OwningDirstateMap, revlog::Node, @@ -53,18 +52,12 @@ py_class!(pub class DirstateMap |py| { on_disk: PyBytes, ) -> PyResult { let on_disk = PyBytesDeref::new(py, on_disk); - let mut map = OwningDirstateMap::new_empty(on_disk); - let (on_disk, map_placeholder) = map.get_pair_mut(); - - let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk) + let (map, parents) = OwningDirstateMap::new_v1(on_disk) .map_err(|e| dirstate_error(py, e))?; - *map_placeholder = actual_map; let map = Self::create_instance(py, map)?; - let parents = parents.map(|p| { - let p1 = PyBytes::new(py, p.p1.as_bytes()); - let p2 = PyBytes::new(py, p.p2.as_bytes()); - (p1, p2) - }); + let p1 = PyBytes::new(py, parents.p1.as_bytes()); + let p2 = PyBytes::new(py, parents.p2.as_bytes()); + let parents = (p1, p2); Ok((map, parents).to_py_object(py).into_object()) } @@ -79,9 +72,7 @@ py_class!(pub class DirstateMap |py| { PyErr::new::(py, format!("Dirstate error: {:?}", e)) }; let on_disk = PyBytesDeref::new(py, on_disk); - let mut map = OwningDirstateMap::new_empty(on_disk); - let (on_disk, map_placeholder) = map.get_pair_mut(); - *map_placeholder = TreeDirstateMap::new_v2( + let map = OwningDirstateMap::new_v2( on_disk, data_size, tree_metadata.data(py), ).map_err(dirstate_error)?; let map = Self::create_instance(py, map)?; diff --git a/rust/hg-cpython/src/dirstate/status.rs b/rust/hg-cpython/src/dirstate/status.rs --- a/rust/hg-cpython/src/dirstate/status.rs +++ b/rust/hg-cpython/src/dirstate/status.rs @@ -127,25 +127,29 @@ pub fn status_wrapper( // The caller may call `copymap.items()` separately let list_copies = false; + let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| { + let (status_res, warnings) = + res.map_err(|e| handle_fallback(py, e))?; + build_response(py, status_res, warnings) + }; + match matcher.get_type(py).name(py).borrow() { "alwaysmatcher" => { let matcher = AlwaysMatcher; - let (status_res, warnings) = dmap - .status( - &matcher, - root_dir.to_path_buf(), - ignore_files, - StatusOptions { - check_exec, - list_clean, - list_ignored, - list_unknown, - list_copies, - collect_traversed_dirs, - }, - ) - .map_err(|e| handle_fallback(py, e))?; - build_response(py, status_res, warnings) + dmap.with_status( + &matcher, + root_dir.to_path_buf(), + ignore_files, + StatusOptions { + check_exec, + list_clean, + list_ignored, + list_unknown, + list_copies, + collect_traversed_dirs, + }, + after_status, + ) } "exactmatcher" => { let files = matcher.call_method( @@ -167,22 +171,20 @@ pub fn status_wrapper( let files = files?; let matcher = FileMatcher::new(files.as_ref()) .map_err(|e| PyErr::new::(py, e.to_string()))?; - let (status_res, warnings) = dmap - .status( - &matcher, - root_dir.to_path_buf(), - ignore_files, - StatusOptions { - check_exec, - list_clean, - list_ignored, - list_unknown, - list_copies, - collect_traversed_dirs, - }, - ) - .map_err(|e| handle_fallback(py, e))?; - build_response(py, status_res, warnings) + dmap.with_status( + &matcher, + root_dir.to_path_buf(), + ignore_files, + StatusOptions { + check_exec, + list_clean, + list_ignored, + list_unknown, + list_copies, + collect_traversed_dirs, + }, + after_status, + ) } "includematcher" => { // Get the patterns from Python even though most of them are @@ -219,23 +221,20 @@ pub fn status_wrapper( let matcher = IncludeMatcher::new(ignore_patterns) .map_err(|e| handle_fallback(py, e.into()))?; - let (status_res, warnings) = dmap - .status( - &matcher, - root_dir.to_path_buf(), - ignore_files, - StatusOptions { - check_exec, - list_clean, - list_ignored, - list_unknown, - list_copies, - collect_traversed_dirs, - }, - ) - .map_err(|e| handle_fallback(py, e))?; - - build_response(py, status_res, warnings) + dmap.with_status( + &matcher, + root_dir.to_path_buf(), + ignore_files, + StatusOptions { + check_exec, + list_clean, + list_ignored, + list_unknown, + list_copies, + collect_traversed_dirs, + }, + after_status, + ) } e => Err(PyErr::new::( py, diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -25,6 +25,9 @@ use hg::utils::files::get_bytes_from_os_ use hg::utils::files::get_bytes_from_path; use hg::utils::files::get_path_from_bytes; use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; +use hg::DirstateStatus; +use hg::PatternFileWarning; +use hg::StatusError; use hg::StatusOptions; use log::info; use std::io; @@ -230,117 +233,132 @@ pub fn run(invocation: &crate::CliInvoca list_copies, collect_traversed_dirs: false, }; - let (mut ds_status, pattern_warnings) = dmap.status( - &AlwaysMatcher, - repo.working_directory_path().to_owned(), - ignore_files(repo, config), - options, - )?; - for warning in pattern_warnings { - match warning { - hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui - .write_stderr(&format_bytes!( - b"{}: ignoring invalid syntax '{}'\n", - get_bytes_from_path(path), - &*syntax - ))?, - hg::PatternFileWarning::NoSuchFile(path) => { - let path = if let Ok(relative) = - path.strip_prefix(repo.working_directory_path()) - { - relative - } else { - &*path - }; - ui.write_stderr(&format_bytes!( - b"skipping unreadable pattern file '{}': \ - No such file or directory\n", - get_bytes_from_path(path), - ))? + + type StatusResult<'a> = + Result<(DirstateStatus<'a>, Vec), StatusError>; + + let after_status = |res: StatusResult| -> Result<_, CommandError> { + let (mut ds_status, pattern_warnings) = res?; + for warning in pattern_warnings { + match warning { + hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui + .write_stderr(&format_bytes!( + b"{}: ignoring invalid syntax '{}'\n", + get_bytes_from_path(path), + &*syntax + ))?, + hg::PatternFileWarning::NoSuchFile(path) => { + let path = if let Ok(relative) = + path.strip_prefix(repo.working_directory_path()) + { + relative + } else { + &*path + }; + ui.write_stderr(&format_bytes!( + b"skipping unreadable pattern file '{}': \ + No such file or directory\n", + get_bytes_from_path(path), + ))? + } } } - } - for (path, error) in ds_status.bad { - let error = match error { - hg::BadMatch::OsError(code) => { - std::io::Error::from_raw_os_error(code).to_string() - } - hg::BadMatch::BadType(ty) => { - format!("unsupported file type (type is {})", ty) - } - }; - ui.write_stderr(&format_bytes!( - b"{}: {}\n", - path.as_bytes(), - error.as_bytes() - ))? - } - if !ds_status.unsure.is_empty() { - info!( - "Files to be rechecked by retrieval from filelog: {:?}", - ds_status.unsure.iter().map(|s| &s.path).collect::>() - ); - } - let mut fixup = Vec::new(); - if !ds_status.unsure.is_empty() - && (display_states.modified || display_states.clean) - { - let p1 = repo.dirstate_parents()?.p1; - let manifest = repo.manifest_for_node(p1).map_err(|e| { - CommandError::from((e, &*format!("{:x}", p1.short()))) - })?; - for to_check in ds_status.unsure { - if unsure_is_modified(repo, &manifest, &to_check.path)? { - if display_states.modified { - ds_status.modified.push(to_check); + for (path, error) in ds_status.bad { + let error = match error { + hg::BadMatch::OsError(code) => { + std::io::Error::from_raw_os_error(code).to_string() + } + hg::BadMatch::BadType(ty) => { + format!("unsupported file type (type is {})", ty) } - } else { - if display_states.clean { - ds_status.clean.push(to_check.clone()); + }; + ui.write_stderr(&format_bytes!( + b"{}: {}\n", + path.as_bytes(), + error.as_bytes() + ))? + } + if !ds_status.unsure.is_empty() { + info!( + "Files to be rechecked by retrieval from filelog: {:?}", + ds_status.unsure.iter().map(|s| &s.path).collect::>() + ); + } + let mut fixup = Vec::new(); + if !ds_status.unsure.is_empty() + && (display_states.modified || display_states.clean) + { + let p1 = repo.dirstate_parents()?.p1; + let manifest = repo.manifest_for_node(p1).map_err(|e| { + CommandError::from((e, &*format!("{:x}", p1.short()))) + })?; + for to_check in ds_status.unsure { + if unsure_is_modified(repo, &manifest, &to_check.path)? { + if display_states.modified { + ds_status.modified.push(to_check); + } + } else { + if display_states.clean { + ds_status.clean.push(to_check.clone()); + } + fixup.push(to_check.path.into_owned()) } - fixup.push(to_check.path.into_owned()) } } - } - let relative_paths = (!ui.plain(None)) - && config - .get_option(b"commands", b"status.relative")? - .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); - let output = DisplayStatusPaths { - ui, - no_status, - relativize: if relative_paths { - Some(RelativizePaths::new(repo)?) - } else { - None - }, + let relative_paths = (!ui.plain(None)) + && config + .get_option(b"commands", b"status.relative")? + .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); + let output = DisplayStatusPaths { + ui, + no_status, + relativize: if relative_paths { + Some(RelativizePaths::new(repo)?) + } else { + None + }, + }; + if display_states.modified { + output.display(b"M ", "status.modified", ds_status.modified)?; + } + if display_states.added { + output.display(b"A ", "status.added", ds_status.added)?; + } + if display_states.removed { + output.display(b"R ", "status.removed", ds_status.removed)?; + } + if display_states.deleted { + output.display(b"! ", "status.deleted", ds_status.deleted)?; + } + if display_states.unknown { + output.display(b"? ", "status.unknown", ds_status.unknown)?; + } + if display_states.ignored { + output.display(b"I ", "status.ignored", ds_status.ignored)?; + } + if display_states.clean { + output.display(b"C ", "status.clean", ds_status.clean)?; + } + + let dirstate_write_needed = ds_status.dirty; + let filesystem_time_at_status_start = + ds_status.filesystem_time_at_status_start; + + Ok(( + fixup, + dirstate_write_needed, + filesystem_time_at_status_start, + )) }; - if display_states.modified { - output.display(b"M ", "status.modified", ds_status.modified)?; - } - if display_states.added { - output.display(b"A ", "status.added", ds_status.added)?; - } - if display_states.removed { - output.display(b"R ", "status.removed", ds_status.removed)?; - } - if display_states.deleted { - output.display(b"! ", "status.deleted", ds_status.deleted)?; - } - if display_states.unknown { - output.display(b"? ", "status.unknown", ds_status.unknown)?; - } - if display_states.ignored { - output.display(b"I ", "status.ignored", ds_status.ignored)?; - } - if display_states.clean { - output.display(b"C ", "status.clean", ds_status.clean)?; - } - - let mut dirstate_write_needed = ds_status.dirty; - let filesystem_time_at_status_start = - ds_status.filesystem_time_at_status_start; + let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) = + dmap.with_status( + &AlwaysMatcher, + repo.working_directory_path().to_owned(), + ignore_files(repo, config), + options, + after_status, + )?; if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) && !dirstate_write_needed