@@ -25,6 +25,9 @@
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 @@
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 StatusReturn<'a> =
+ Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
+
+ let after_status = |res: StatusReturn| -> 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::<Vec<_>>()
- );
- }
- 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::<Vec<_>>()
+ );
+ }
+ 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.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
@@ -127,25 +127,29 @@
// 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.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 @@
let files = files?;
let matcher = FileMatcher::new(files.as_ref())
.map_err(|e| PyErr::new::<ValueError, _>(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.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 @@
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.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::<ValueError, _>(
py,
@@ -23,7 +23,6 @@
};
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 @@
on_disk: PyBytes,
) -> PyResult<PyObject> {
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 @@
PyErr::new::<exc::OSError, _>(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)?;
@@ -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;
@@ -340,25 +339,19 @@
.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)
}
}
@@ -40,13 +40,13 @@
/// 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<'a>(
+ dmap: &'a mut DirstateMap,
matcher: &(dyn Matcher + Sync),
root_dir: PathBuf,
ignore_files: Vec<PathBuf>,
options: StatusOptions,
-) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
+) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), 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.
@@ -1,142 +1,89 @@
+use crate::{DirstateError, DirstateParents};
+
use super::dirstate_map::DirstateMap;
-use stable_deref_trait::StableDeref;
use std::ops::Deref;
-/*
-// /!\ This is unsound and can cause use after free. It will be fixed in the
-// next patch
-
-// If we change `value` from its current use of `HgPathBuf` to `&HgPath`,
-// nothing here tells that `value` will outlive `OwningDirstateMap`
-pub fn copy_map_insert<'a,'owned>(
- &'owned mut self,
- key: &HgPath,
- value: &'a HgPath,
-) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
- // `'local` is smaller than `'a` here
- let map: &'local mut DirstateMap<'local> = self.get_map_mut();
- let node: &'local mut Node<'local> = 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))
- // and right here ----------^^^^^^^^^^^^
- // we are storing `&'a HgPath` in `Node<'local>` which is possible
- // because to the compiler, `'a` is longer than ``local`.
- // It is wrong because nothing proves that `&'a HgPath` will outlive `self`.
-}
-
-// All of this is caused by the wrong cast of the DirstateMap pointer that
-// fakes the lifetime of `DirstateMap` and ensures the compiler that it lives
-// as long as `on_disk`, which is only true for its immutable data.
-// This will be fixed in the next commit.
-*/
+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 <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
on_disk: Box<dyn Deref<Target = [u8]> + Send>,
-
- /// Pointer for `Box<DirstateMap<'on_disk>>`, 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<OnDisk>(on_disk: OnDisk) -> Self
where
- OnDisk: Deref<Target = [u8]> + StableDeref + Send + 'static,
+ OnDisk: Deref<Target = [u8]> + 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<OnDisk>(
+ on_disk: OnDisk,
+ ) -> Result<(Self, DirstateParents), DirstateError>
+ where
+ OnDisk: Deref<Target = [u8]> + Send + 'static,
+ {
+ let on_disk = Box::new(on_disk);
+ let mut parents = DirstateParents::NULL;
+
+ 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_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
- self.get_pair_mut().1
+ pub fn new_v2<OnDisk>(
+ on_disk: OnDisk,
+ data_size: usize,
+ metadata: &[u8],
+ ) -> Result<Self, DirstateError>
+ where
+ OnDisk: Deref<Target = [u8]> + 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 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 get_map_mut<'a, R>(
+ &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<'a>(&'a self) -> &'a [u8] {
- &self.on_disk
+ 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<dyn Deref<Target = [u8]> + 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<T: Send>() {}
-
-fn _static_assert_fields_are_send() {
- _static_assert_is_send::<Box<DirstateMap<'_>>>();
-}
-
-// 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<DirstateMap<'on_disk>>` 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 {}
@@ -725,10 +725,11 @@
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.get_map_mut(|map| {
+ map.root = Default::default();
+ map.nodes_with_entry_count = 0;
+ map.nodes_with_copy_source_count = 0;
+ });
}
pub fn set_entry(
@@ -736,9 +737,10 @@
filename: &HgPath,
entry: DirstateEntry,
) -> Result<(), DirstateV2ParseError> {
- let map = self.get_map_mut();
- map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
- Ok(())
+ self.get_map_mut(|map| {
+ map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
+ Ok(())
+ })
}
pub fn add_file(
@@ -747,8 +749,9 @@
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.get_map_mut(|map| {
+ Ok(map.add_or_remove_file(filename, old_state, entry)?)
+ })
}
pub fn remove_file(
@@ -779,9 +782,10 @@
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.get_map_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(
@@ -791,7 +795,6 @@
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,
@@ -878,52 +881,56 @@
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.get_map_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 -= 1
+ }
+ if dropped.had_copy_source {
+ map.nodes_with_copy_source_count -= 1
+ }
+ } 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<bool, DirstateError> {
- 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.get_map_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<bool, DirstateError> {
- 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.get_map_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]
@@ -975,16 +982,29 @@
on_disk::write(map, can_append)
}
- pub fn status<'a>(
+ /// `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 status<'a, R>(
&'a mut self,
matcher: &'a (dyn Matcher + Sync),
root_dir: PathBuf,
ignore_files: Vec<PathBuf>,
options: StatusOptions,
- ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), 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<PatternFileWarning>), StatusError>,
+ ) -> R,
+ ) -> R {
+ self.get_map_mut(|map| {
+ callback(super::status::status(
+ map,
+ matcher,
+ root_dir,
+ ignore_files,
+ options,
+ ))
+ })
}
pub fn copy_map_len(&self) -> usize {
@@ -1032,22 +1052,23 @@
&mut self,
key: &HgPath,
) -> Result<Option<HgPathBuf>, 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.get_map_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(
@@ -1055,19 +1076,20 @@
key: HgPathBuf,
value: HgPathBuf,
) -> Result<Option<HgPathBuf>, 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.get_map_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 {
@@ -1115,7 +1137,7 @@
>,
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(),
@@ -18,6 +18,7 @@
itertools = "0.9"
lazy_static = "1.4.0"
libc = "0.2"
+ouroboros = "0.15.0"
rand = "0.8.4"
rand_pcg = "0.3.1"
rand_distr = "0.4.2"
@@ -26,7 +27,6 @@
sha-1 = "0.9.6"
twox-hash = "1.5.0"
same-file = "1.0.6"
-stable_deref_trait = "1.2.0"
tempfile = "3.1.0"
crossbeam-channel = "0.4"
micro-timer = "0.3.0"
@@ -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"
@@ -18,6 +24,12 @@
]
[[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.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -407,6 +419,7 @@
"log",
"memmap2",
"micro-timer",
+ "ouroboros",
"pretty_assertions",
"rand 0.8.4",
"rand_distr",
@@ -415,7 +428,6 @@
"regex",
"same-file",
"sha-1",
- "stable_deref_trait",
"tempfile",
"twox-hash",
"zstd",
@@ -623,6 +635,30 @@
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"
@@ -662,6 +698,30 @@
]
[[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"