diff --git a/rust/hg-core/src/dirstate.rs b/rust/hg-core/src/dirstate.rs --- a/rust/hg-core/src/dirstate.rs +++ b/rust/hg-core/src/dirstate.rs @@ -5,6 +5,7 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. +use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::errors::HgError; use crate::revlog::Node; use crate::utils::hg_path::{HgPath, HgPathBuf}; @@ -76,12 +77,19 @@ const MTIME_UNSET: i32 = -1; pub const SIZE_FROM_OTHER_PARENT: i32 = -2; pub type StateMap = FastHashMap; -pub type StateMapIter<'a> = - Box + Send + 'a>; +pub type StateMapIter<'a> = Box< + dyn Iterator< + Item = Result<(&'a HgPath, DirstateEntry), DirstateV2ParseError>, + > + Send + + 'a, +>; pub type CopyMap = FastHashMap; -pub type CopyMapIter<'a> = - Box + Send + 'a>; +pub type CopyMapIter<'a> = Box< + dyn Iterator> + + Send + + 'a, +>; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum EntryState { diff --git a/rust/hg-core/src/dirstate/dirs_multiset.rs b/rust/hg-core/src/dirstate/dirs_multiset.rs --- a/rust/hg-core/src/dirstate/dirs_multiset.rs +++ b/rust/hg-core/src/dirstate/dirs_multiset.rs @@ -8,13 +8,14 @@ //! A multiset of directory names. //! //! Used to counts the references to directories in a manifest or dirstate. +use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::{ dirstate::EntryState, utils::{ files, hg_path::{HgPath, HgPathBuf, HgPathError}, }, - DirstateEntry, DirstateMapError, FastHashMap, + DirstateEntry, DirstateError, DirstateMapError, FastHashMap, }; use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet}; @@ -33,15 +34,18 @@ impl DirsMultiset { pub fn from_dirstate( dirstate: I, skip_state: Option, - ) -> Result + ) -> Result where - I: IntoIterator, + I: IntoIterator< + Item = Result<(P, DirstateEntry), DirstateV2ParseError>, + >, P: AsRef, { let mut multiset = DirsMultiset { inner: FastHashMap::default(), }; - for (filename, entry) in dirstate { + for item in dirstate { + let (filename, entry) = item?; let filename = filename.as_ref(); // This `if` is optimized out of the loop if let Some(skip) = skip_state { @@ -337,8 +341,11 @@ mod tests { }; assert_eq!(expected, new); - let new = - DirsMultiset::from_dirstate(StateMap::default(), None).unwrap(); + let new = DirsMultiset::from_dirstate( + StateMap::default().into_iter().map(Ok), + None, + ) + .unwrap(); let expected = DirsMultiset { inner: FastHashMap::default(), }; @@ -362,20 +369,17 @@ mod tests { }; assert_eq!(expected, new); - let input_map: HashMap<_, _> = ["b/x", "a/c", "a/d/x"] - .iter() - .map(|f| { - ( - HgPathBuf::from_bytes(f.as_bytes()), - DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0, - }, - ) - }) - .collect(); + let input_map = ["b/x", "a/c", "a/d/x"].iter().map(|f| { + Ok(( + HgPathBuf::from_bytes(f.as_bytes()), + DirstateEntry { + state: EntryState::Normal, + mode: 0, + mtime: 0, + size: 0, + }, + )) + }); let expected_inner = [("", 2), ("a", 2), ("b", 1), ("a/d", 1)] .iter() .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) @@ -390,7 +394,7 @@ mod tests { #[test] fn test_dirsmultiset_new_skip() { - let input_map: HashMap<_, _> = [ + let input_map = [ ("a/", EntryState::Normal), ("a/b", EntryState::Normal), ("a/c", EntryState::Removed), @@ -398,7 +402,7 @@ mod tests { ] .iter() .map(|(f, state)| { - ( + Ok(( HgPathBuf::from_bytes(f.as_bytes()), DirstateEntry { state: *state, @@ -406,9 +410,8 @@ mod tests { mtime: 0, size: 0, }, - ) - }) - .collect(); + )) + }); // "a" incremented with "a/c" and "a/d/" let expected_inner = [("", 1), ("a", 2)] diff --git a/rust/hg-core/src/dirstate/dirstate_map.rs b/rust/hg-core/src/dirstate/dirstate_map.rs --- a/rust/hg-core/src/dirstate/dirstate_map.rs +++ b/rust/hg-core/src/dirstate/dirstate_map.rs @@ -10,8 +10,8 @@ use crate::{ dirstate::EntryState, pack_dirstate, parse_dirstate, utils::hg_path::{HgPath, HgPathBuf}, - CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, - DirstateParents, StateMap, + CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateParents, + StateMap, }; use micro_timer::timed; use std::collections::HashSet; @@ -66,7 +66,7 @@ impl DirstateMap { filename: &HgPath, old_state: EntryState, entry: DirstateEntry, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { if old_state == EntryState::Unknown || old_state == EntryState::Removed { if let Some(ref mut dirs) = self.dirs { @@ -104,7 +104,7 @@ impl DirstateMap { filename: &HgPath, old_state: EntryState, size: i32, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { if old_state != EntryState::Unknown && old_state != EntryState::Removed { if let Some(ref mut dirs) = self.dirs { @@ -138,7 +138,7 @@ impl DirstateMap { &mut self, filename: &HgPath, old_state: EntryState, - ) -> Result { + ) -> Result { let exists = self.state_map.remove(filename).is_some(); if exists { @@ -246,20 +246,20 @@ impl DirstateMap { /// emulate a Python lazy property, but it is ugly and unidiomatic. /// TODO One day, rewriting this struct using the typestate might be a /// good idea. - pub fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { + pub fn set_all_dirs(&mut self) -> Result<(), DirstateError> { if self.all_dirs.is_none() { self.all_dirs = Some(DirsMultiset::from_dirstate( - self.state_map.iter().map(|(k, v)| (k, *v)), + self.state_map.iter().map(|(k, v)| Ok((k, *v))), None, )?); } Ok(()) } - pub fn set_dirs(&mut self) -> Result<(), DirstateMapError> { + pub fn set_dirs(&mut self) -> Result<(), DirstateError> { if self.dirs.is_none() { self.dirs = Some(DirsMultiset::from_dirstate( - self.state_map.iter().map(|(k, v)| (k, *v)), + self.state_map.iter().map(|(k, v)| Ok((k, *v))), Some(EntryState::Removed), )?); } @@ -269,7 +269,7 @@ impl DirstateMap { pub fn has_tracked_dir( &mut self, directory: &HgPath, - ) -> Result { + ) -> Result { self.set_dirs()?; Ok(self.dirs.as_ref().unwrap().contains(directory)) } @@ -277,7 +277,7 @@ impl DirstateMap { pub fn has_dir( &mut self, directory: &HgPath, - ) -> Result { + ) -> Result { self.set_all_dirs()?; Ok(self.all_dirs.as_ref().unwrap().contains(directory)) } diff --git a/rust/hg-core/src/dirstate/parsers.rs b/rust/hg-core/src/dirstate/parsers.rs --- a/rust/hg-core/src/dirstate/parsers.rs +++ b/rust/hg-core/src/dirstate/parsers.rs @@ -43,13 +43,18 @@ pub fn parse_dirstate(contents: &[u8]) - copies.push((path, source)); } entries.push((path, *entry)); + Ok(()) })?; Ok((parents, entries, copies)) } pub fn parse_dirstate_entries<'a>( mut contents: &'a [u8], - mut each_entry: impl FnMut(&'a HgPath, &DirstateEntry, Option<&'a HgPath>), + mut each_entry: impl FnMut( + &'a HgPath, + &DirstateEntry, + Option<&'a HgPath>, + ) -> Result<(), HgError>, ) -> Result<&'a DirstateParents, HgError> { let (parents, rest) = DirstateParents::from_bytes(contents) .map_err(|_| HgError::corrupted("Too little data for dirstate."))?; @@ -75,7 +80,7 @@ pub fn parse_dirstate_entries<'a>( iter.next().expect("splitn always yields at least one item"), ); let copy_source = iter.next().map(HgPath::new); - each_entry(path, &entry, copy_source); + each_entry(path, &entry, copy_source)?; contents = rest; } 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 @@ -9,6 +9,7 @@ //! It is currently missing a lot of functionality compared to the Python one //! and will only be triggered in narrow cases. +use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::utils::path_auditor::PathAuditor; use crate::{ dirstate::SIZE_FROM_OTHER_PARENT, @@ -302,6 +303,8 @@ pub enum StatusError { Path(HgPathError), /// An invalid "ignore" pattern was found Pattern(PatternError), + /// Corrupted dirstate + DirstateV2ParseError(DirstateV2ParseError), } pub type StatusResult = Result; @@ -312,6 +315,9 @@ impl fmt::Display for StatusError { StatusError::IO(error) => error.fmt(f), StatusError::Path(error) => error.fmt(f), StatusError::Pattern(error) => error.fmt(f), + StatusError::DirstateV2ParseError(_) => { + f.write_str("dirstate-v2 parse error") + } } } } 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 @@ -5,6 +5,7 @@ use std::convert::TryInto; use std::path::PathBuf; use super::on_disk; +use super::on_disk::DirstateV2ParseError; use super::path_with_basename::WithBasename; use crate::dirstate::parsers::pack_entry; use crate::dirstate::parsers::packed_entry_size; @@ -15,7 +16,6 @@ use crate::utils::hg_path::{HgPath, HgPa use crate::CopyMapIter; use crate::DirstateEntry; use crate::DirstateError; -use crate::DirstateMapError; use crate::DirstateParents; use crate::DirstateStatus; use crate::EntryState; @@ -81,9 +81,12 @@ impl<'on_disk> ChildNodes<'on_disk> { pub(super) fn make_mut( &mut self, - ) -> &mut FastHashMap, Node<'on_disk>> { + ) -> Result< + &mut FastHashMap, Node<'on_disk>>, + DirstateV2ParseError, + > { match self { - ChildNodes::InMemory(nodes) => nodes, + ChildNodes::InMemory(nodes) => Ok(nodes), } } } @@ -92,11 +95,11 @@ impl<'tree, 'on_disk> ChildNodesRef<'tre pub(super) fn get( &self, base_name: &HgPath, - ) -> Option> { + ) -> Result>, DirstateV2ParseError> { match self { - ChildNodesRef::InMemory(nodes) => nodes + ChildNodesRef::InMemory(nodes) => Ok(nodes .get_key_value(base_name) - .map(|(k, v)| NodeRef::InMemory(k, v)), + .map(|(k, v)| NodeRef::InMemory(k, v))), } } @@ -131,9 +134,14 @@ impl<'tree, 'on_disk> ChildNodesRef<'tre .iter() .map(|(k, v)| NodeRef::InMemory(k, v)) .collect(); + fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath { + match node { + NodeRef::InMemory(path, _node) => path.base_name(), + } + } // `sort_unstable_by_key` doesn’t allow keys borrowing from the // value: https://github.com/rust-lang/rust/issues/34162 - vec.sort_unstable_by(|a, b| a.base_name().cmp(b.base_name())); + vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b))); vec } } @@ -141,35 +149,51 @@ impl<'tree, 'on_disk> ChildNodesRef<'tre } impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> { - pub(super) fn full_path(&self) -> &'tree HgPath { + pub(super) fn full_path( + &self, + ) -> Result<&'tree HgPath, DirstateV2ParseError> { match self { - NodeRef::InMemory(path, _node) => path.full_path(), + NodeRef::InMemory(path, _node) => Ok(path.full_path()), } } /// Returns a `Cow` that can borrow 'on_disk but is detached from 'tree - pub(super) fn full_path_cow(&self) -> Cow<'on_disk, HgPath> { + pub(super) fn full_path_cow( + &self, + ) -> Result, DirstateV2ParseError> { match self { - NodeRef::InMemory(path, _node) => path.full_path().clone(), + NodeRef::InMemory(path, _node) => Ok(path.full_path().clone()), + } + } + + pub(super) fn base_name( + &self, + ) -> Result<&'tree HgPath, DirstateV2ParseError> { + match self { + NodeRef::InMemory(path, _node) => Ok(path.base_name()), } } - pub(super) fn base_name(&self) -> &'tree HgPath { + pub(super) fn children( + &self, + ) -> Result, DirstateV2ParseError> { match self { - NodeRef::InMemory(path, _node) => path.base_name(), + NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()), } } - pub(super) fn children(&self) -> ChildNodesRef<'tree, 'on_disk> { + pub(super) fn has_copy_source(&self) -> bool { match self { - NodeRef::InMemory(_path, node) => node.children.as_ref(), + NodeRef::InMemory(_path, node) => node.copy_source.is_some(), } } - pub(super) fn copy_source(&self) -> Option<&'tree HgPath> { + pub(super) fn copy_source( + &self, + ) -> Result, DirstateV2ParseError> { match self { NodeRef::InMemory(_path, node) => { - node.copy_source.as_ref().map(|s| &**s) + Ok(node.copy_source.as_ref().map(|s| &**s)) } } } @@ -180,15 +204,20 @@ impl<'tree, 'on_disk> NodeRef<'tree, 'on } } - pub(super) fn entry(&self) -> Option { + pub(super) fn entry( + &self, + ) -> Result, DirstateV2ParseError> { match self { - NodeRef::InMemory(_path, node) => node.entry, + NodeRef::InMemory(_path, node) => Ok(node.entry), } } - pub(super) fn state(&self) -> Option { + + pub(super) fn state( + &self, + ) -> Result, DirstateV2ParseError> { match self { NodeRef::InMemory(_path, node) => { - node.entry.as_ref().map(|entry| entry.state) + Ok(node.entry.as_ref().map(|entry| entry.state)) } } } @@ -253,7 +282,7 @@ impl<'on_disk> DirstateMap<'on_disk> { ancestor.tracked_descendants_count += 1 } }, - ); + )?; assert!( node.entry.is_none(), "duplicate dirstate entry in read" @@ -268,6 +297,7 @@ impl<'on_disk> DirstateMap<'on_disk> { if copy_source.is_some() { map.nodes_with_copy_source_count += 1 } + Ok(()) }, )?; let parents = Some(parents.clone()); @@ -278,18 +308,21 @@ impl<'on_disk> DirstateMap<'on_disk> { fn get_node<'tree>( &'tree self, path: &HgPath, - ) -> Option> { + ) -> Result>, DirstateV2ParseError> { let mut children = self.root.as_ref(); let mut components = path.components(); let mut component = components.next().expect("expected at least one components"); loop { - let child = children.get(component)?; - if let Some(next_component) = components.next() { - component = next_component; - children = child.children(); + if let Some(child) = children.get(component)? { + if let Some(next_component) = components.next() { + component = next_component; + children = child.children()?; + } else { + return Ok(Some(child)); + } } else { - return Some(child); + return Ok(None); } } } @@ -301,18 +334,21 @@ impl<'on_disk> DirstateMap<'on_disk> { fn get_node_mut<'tree>( root: &'tree mut ChildNodes<'on_disk>, path: &HgPath, - ) -> Option<&'tree mut Node<'on_disk>> { + ) -> Result>, DirstateV2ParseError> { let mut children = root; let mut components = path.components(); let mut component = components.next().expect("expected at least one components"); loop { - let child = children.make_mut().get_mut(component)?; - if let Some(next_component) = components.next() { - component = next_component; - children = &mut child.children; + if let Some(child) = children.make_mut()?.get_mut(component) { + if let Some(next_component) = components.next() { + component = next_component; + children = &mut child.children; + } else { + return Ok(Some(child)); + } } else { - return Some(child); + return Ok(None); } } } @@ -324,7 +360,7 @@ impl<'on_disk> DirstateMap<'on_disk> { WithBasename<&'path HgPath>, ) -> WithBasename>, mut each_ancestor: impl FnMut(&mut Node), - ) -> &'tree mut Node<'on_disk> { + ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> { let mut child_nodes = root; let mut inclusive_ancestor_paths = WithBasename::inclusive_ancestors_of(path); @@ -336,7 +372,7 @@ impl<'on_disk> DirstateMap<'on_disk> { // map already contains that key, without introducing double // lookup? let child_node = child_nodes - .make_mut() + .make_mut()? .entry(to_cow(ancestor_path)) .or_default(); if let Some(next) = inclusive_ancestor_paths.next() { @@ -344,7 +380,7 @@ impl<'on_disk> DirstateMap<'on_disk> { ancestor_path = next; child_nodes = &mut child_node.children; } else { - return child_node; + return Ok(child_node); } } } @@ -354,7 +390,7 @@ impl<'on_disk> DirstateMap<'on_disk> { path: &HgPath, old_state: EntryState, new_entry: DirstateEntry, - ) { + ) -> Result<(), DirstateV2ParseError> { let tracked_count_increment = match (old_state.is_tracked(), new_entry.state.is_tracked()) { (false, true) => 1, @@ -376,16 +412,19 @@ impl<'on_disk> DirstateMap<'on_disk> { _ => {} } }, - ); + )?; if node.entry.is_none() { self.nodes_with_entry_count += 1 } - node.entry = Some(new_entry) + node.entry = Some(new_entry); + Ok(()) } fn iter_nodes<'tree>( &'tree self, - ) -> impl Iterator> + 'tree { + ) -> impl Iterator< + Item = Result, DirstateV2ParseError>, + > + 'tree { // Depth first tree traversal. // // If we could afford internal iteration and recursion, @@ -409,8 +448,12 @@ impl<'on_disk> DirstateMap<'on_disk> { let mut iter = self.root.as_ref().iter(); std::iter::from_fn(move || { while let Some(child_node) = iter.next() { + let children = match child_node.children() { + Ok(children) => children, + Err(error) => return Some(Err(error)), + }; // Pseudo-recursion - let new_iter = child_node.children().iter(); + let new_iter = children.iter(); let old_iter = std::mem::replace(&mut iter, new_iter); stack.push((child_node, old_iter)); } @@ -420,7 +463,7 @@ impl<'on_disk> DirstateMap<'on_disk> { // explicit stack iter = next_iter; - Some(child_node) + Some(Ok(child_node)) } else { // Reached the bottom of the stack, we’re done None @@ -428,17 +471,62 @@ impl<'on_disk> DirstateMap<'on_disk> { }) } - fn clear_known_ambiguous_mtimes(&mut self, paths: &[impl AsRef]) { + fn clear_known_ambiguous_mtimes( + &mut self, + paths: &[impl AsRef], + ) -> Result<(), DirstateV2ParseError> { for path in paths { if let Some(node) = - Self::get_node_mut(&mut self.root, path.as_ref()) + Self::get_node_mut(&mut self.root, path.as_ref())? { if let Some(entry) = node.entry.as_mut() { entry.clear_mtime(); } } } + Ok(()) } + + /// Return a faillilble iterator of full paths of nodes that have an + /// `entry` for which the given `predicate` returns true. + /// + /// Fallibility means that each iterator item is a `Result`, which may + /// indicate a parse error of the on-disk dirstate-v2 format. Such errors + /// should only happen if Mercurial is buggy or a repository is corrupted. + fn filter_full_paths<'tree>( + &'tree self, + predicate: impl Fn(&DirstateEntry) -> bool + 'tree, + ) -> impl Iterator> + 'tree + { + filter_map_results(self.iter_nodes(), move |node| { + if let Some(entry) = node.entry()? { + if predicate(&entry) { + return Ok(Some(node.full_path()?)); + } + } + Ok(None) + }) + } +} + +/// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s. +/// +/// The callback is only called for incoming `Ok` values. Errors are passed +/// through as-is. In order to let it use the `?` operator the callback is +/// expected to return a `Result` of `Option`, instead of an `Option` of +/// `Result`. +fn filter_map_results<'a, I, F, A, B, E>( + iter: I, + f: F, +) -> impl Iterator> + 'a +where + I: Iterator> + 'a, + F: Fn(A) -> Result, E> + 'a, +{ + iter.filter_map(move |result| match result { + Ok(node) => f(node).transpose(), + Err(e) => Some(Err(e)), + }) } impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> { @@ -453,9 +541,8 @@ impl<'on_disk> super::dispatch::Dirstate filename: &HgPath, old_state: EntryState, entry: DirstateEntry, - ) -> Result<(), DirstateMapError> { - self.add_or_remove_file(filename, old_state, entry); - Ok(()) + ) -> Result<(), DirstateError> { + Ok(self.add_or_remove_file(filename, old_state, entry)?) } fn remove_file( @@ -463,36 +550,48 @@ impl<'on_disk> super::dispatch::Dirstate filename: &HgPath, old_state: EntryState, size: i32, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { let entry = DirstateEntry { state: EntryState::Removed, mode: 0, size, mtime: 0, }; - self.add_or_remove_file(filename, old_state, entry); - Ok(()) + Ok(self.add_or_remove_file(filename, old_state, entry)?) } fn drop_file( &mut self, filename: &HgPath, old_state: EntryState, - ) -> Result { + ) -> Result { struct Dropped { was_tracked: bool, had_entry: bool, had_copy_source: bool, } - fn recur(nodes: &mut ChildNodes, path: &HgPath) -> Option { + fn recur( + nodes: &mut ChildNodes, + path: &HgPath, + ) -> Result, DirstateV2ParseError> { let (first_path_component, rest_of_path) = path.split_first_component(); - let node = nodes.make_mut().get_mut(first_path_component)?; + let node = if let Some(node) = + nodes.make_mut()?.get_mut(first_path_component) + { + node + } else { + return Ok(None); + }; let dropped; if let Some(rest) = rest_of_path { - dropped = recur(&mut node.children, rest)?; - if dropped.was_tracked { - node.tracked_descendants_count -= 1; + if let Some(d) = recur(&mut node.children, rest)? { + dropped = d; + if dropped.was_tracked { + node.tracked_descendants_count -= 1; + } + } else { + return Ok(None); } } else { dropped = Dropped { @@ -510,12 +609,12 @@ impl<'on_disk> super::dispatch::Dirstate && node.copy_source.is_none() && node.children.is_empty() { - nodes.make_mut().remove(first_path_component); + nodes.make_mut()?.remove(first_path_component); } - Some(dropped) + Ok(Some(dropped)) } - if let Some(dropped) = recur(&mut self.root, filename) { + if let Some(dropped) = recur(&mut self.root, filename)? { if dropped.had_entry { self.nodes_with_entry_count -= 1 } @@ -529,20 +628,31 @@ impl<'on_disk> super::dispatch::Dirstate } } - fn clear_ambiguous_times(&mut self, filenames: Vec, now: i32) { + fn clear_ambiguous_times( + &mut self, + filenames: Vec, + now: i32, + ) -> Result<(), DirstateV2ParseError> { for filename in filenames { - if let Some(node) = Self::get_node_mut(&mut self.root, &filename) { + if let Some(node) = Self::get_node_mut(&mut self.root, &filename)? + { if let Some(entry) = node.entry.as_mut() { entry.clear_ambiguous_mtime(now); } } } + Ok(()) } - fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool { - self.get_node(key) - .and_then(|node| node.entry()) - .map_or(false, |entry| entry.is_non_normal()) + fn non_normal_entries_contains( + &mut self, + key: &HgPath, + ) -> Result { + Ok(if let Some(node) = self.get_node(key)? { + node.entry()?.map_or(false, |entry| entry.is_non_normal()) + } else { + false + }) } fn non_normal_entries_remove(&mut self, _key: &HgPath) { @@ -552,13 +662,10 @@ impl<'on_disk> super::dispatch::Dirstate fn non_normal_or_other_parent_paths( &mut self, - ) -> Box + '_> { - Box::new(self.iter_nodes().filter_map(|node| { - node.entry() - .filter(|entry| { - entry.is_non_normal() || entry.is_from_other_parent() - }) - .map(|_| node.full_path()) + ) -> Box> + '_> + { + Box::new(self.filter_full_paths(|entry| { + entry.is_non_normal() || entry.is_from_other_parent() })) } @@ -569,35 +676,33 @@ impl<'on_disk> super::dispatch::Dirstate fn iter_non_normal_paths( &mut self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { self.iter_non_normal_paths_panic() } fn iter_non_normal_paths_panic( &self, - ) -> Box + Send + '_> { - Box::new(self.iter_nodes().filter_map(|node| { - node.entry() - .filter(|entry| entry.is_non_normal()) - .map(|_| node.full_path()) - })) + ) -> Box< + dyn Iterator> + Send + '_, + > { + Box::new(self.filter_full_paths(|entry| entry.is_non_normal())) } fn iter_other_parent_paths( &mut self, - ) -> Box + Send + '_> { - Box::new(self.iter_nodes().filter_map(|node| { - node.entry() - .filter(|entry| entry.is_from_other_parent()) - .map(|_| node.full_path()) - })) + ) -> Box< + dyn Iterator> + Send + '_, + > { + Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent())) } fn has_tracked_dir( &mut self, directory: &HgPath, - ) -> Result { - if let Some(node) = self.get_node(directory) { + ) -> Result { + if let Some(node) = self.get_node(directory)? { // A node without a `DirstateEntry` was created to hold child // nodes, and is therefore a directory. Ok(!node.has_entry() && node.tracked_descendants_count() > 0) @@ -606,11 +711,8 @@ impl<'on_disk> super::dispatch::Dirstate } } - fn has_dir( - &mut self, - directory: &HgPath, - ) -> Result { - if let Some(node) = self.get_node(directory) { + fn has_dir(&mut self, directory: &HgPath) -> Result { + if let Some(node) = self.get_node(directory)? { // A node without a `DirstateEntry` was created to hold child // nodes, and is therefore a directory. Ok(!node.has_entry()) @@ -631,25 +733,27 @@ impl<'on_disk> super::dispatch::Dirstate // reallocations let mut size = parents.as_bytes().len(); for node in self.iter_nodes() { - if let Some(entry) = node.entry() { + let node = node?; + if let Some(entry) = node.entry()? { size += - packed_entry_size(node.full_path(), node.copy_source()); + packed_entry_size(node.full_path()?, node.copy_source()?); if entry.mtime_is_ambiguous(now) { - ambiguous_mtimes.push(node.full_path_cow()) + ambiguous_mtimes.push(node.full_path_cow()?) } } } - self.clear_known_ambiguous_mtimes(&ambiguous_mtimes); + self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?; let mut packed = Vec::with_capacity(size); packed.extend(parents.as_bytes()); for node in self.iter_nodes() { - if let Some(entry) = node.entry() { + let node = node?; + if let Some(entry) = node.entry()? { pack_entry( - node.full_path(), + node.full_path()?, &entry, - node.copy_source(), + node.copy_source()?, &mut packed, ); } @@ -667,26 +771,27 @@ impl<'on_disk> super::dispatch::Dirstate let now: i32 = now.0.try_into().expect("time overflow"); let mut paths = Vec::new(); for node in self.iter_nodes() { - if let Some(entry) = node.entry() { + let node = node?; + if let Some(entry) = node.entry()? { if entry.mtime_is_ambiguous(now) { - paths.push(node.full_path_cow()) + paths.push(node.full_path_cow()?) } } } // Borrow of `self` ends here since we collect cloned paths - self.clear_known_ambiguous_mtimes(&paths); + self.clear_known_ambiguous_mtimes(&paths)?; on_disk::write(self, parents) } - fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { + fn set_all_dirs(&mut self) -> Result<(), DirstateError> { // Do nothing, this `DirstateMap` does not a separate `all_dirs` that // needs to be recomputed Ok(()) } - fn set_dirs(&mut self) -> Result<(), DirstateMapError> { + fn set_dirs(&mut self) -> Result<(), DirstateError> { // Do nothing, this `DirstateMap` does not a separate `dirs` that needs // to be recomputed Ok(()) @@ -708,66 +813,97 @@ impl<'on_disk> super::dispatch::Dirstate } fn copy_map_iter(&self) -> CopyMapIter<'_> { - Box::new(self.iter_nodes().filter_map(|node| { - node.copy_source() - .map(|copy_source| (node.full_path(), copy_source)) + Box::new(filter_map_results(self.iter_nodes(), |node| { + Ok(if let Some(source) = node.copy_source()? { + Some((node.full_path()?, source)) + } else { + None + }) })) } - fn copy_map_contains_key(&self, key: &HgPath) -> bool { - if let Some(node) = self.get_node(key) { - node.copy_source().is_some() + fn copy_map_contains_key( + &self, + key: &HgPath, + ) -> Result { + Ok(if let Some(node) = self.get_node(key)? { + node.has_copy_source() } else { false - } + }) } - fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath> { - self.get_node(key)?.copy_source() + fn copy_map_get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { + if let Some(node) = self.get_node(key)? { + if let Some(source) = node.copy_source()? { + return Ok(Some(source)); + } + } + Ok(None) } - fn copy_map_remove(&mut self, key: &HgPath) -> Option { + fn copy_map_remove( + &mut self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { let count = &mut self.nodes_with_copy_source_count; - Self::get_node_mut(&mut self.root, key).and_then(|node| { + Ok(Self::get_node_mut(&mut self.root, key)?.and_then(|node| { if node.copy_source.is_some() { *count -= 1 } node.copy_source.take().map(Cow::into_owned) - }) + })) } fn copy_map_insert( &mut self, key: HgPathBuf, value: HgPathBuf, - ) -> Option { + ) -> Result, DirstateV2ParseError> { let node = Self::get_or_insert_node( &mut self.root, &key, WithBasename::to_cow_owned, |_ancestor| {}, - ); + )?; if node.copy_source.is_none() { self.nodes_with_copy_source_count += 1 } - node.copy_source.replace(value.into()).map(Cow::into_owned) + Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) } fn len(&self) -> usize { self.nodes_with_entry_count as usize } - fn contains_key(&self, key: &HgPath) -> bool { - self.get(key).is_some() + fn contains_key( + &self, + key: &HgPath, + ) -> Result { + Ok(self.get(key)?.is_some()) } - fn get(&self, key: &HgPath) -> Option { - self.get_node(key)?.entry() + fn get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { + Ok(if let Some(node) = self.get_node(key)? { + node.entry()? + } else { + None + }) } fn iter(&self) -> StateMapIter<'_> { - Box::new(self.iter_nodes().filter_map(|node| { - node.entry().map(|entry| (node.full_path(), entry)) + Box::new(filter_map_results(self.iter_nodes(), |node| { + Ok(if let Some(entry) = node.entry()? { + Some((node.full_path()?, entry)) + } else { + None + }) })) } } diff --git a/rust/hg-core/src/dirstate_tree/dispatch.rs b/rust/hg-core/src/dirstate_tree/dispatch.rs --- a/rust/hg-core/src/dirstate_tree/dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/dispatch.rs @@ -1,13 +1,13 @@ use std::path::PathBuf; use crate::dirstate::parsers::Timestamp; +use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::matchers::Matcher; use crate::utils::hg_path::{HgPath, HgPathBuf}; use crate::CopyMapIter; use crate::DirstateEntry; use crate::DirstateError; use crate::DirstateMap; -use crate::DirstateMapError; use crate::DirstateParents; use crate::DirstateStatus; use crate::EntryState; @@ -24,54 +24,64 @@ pub trait DirstateMapMethods { filename: &HgPath, old_state: EntryState, entry: DirstateEntry, - ) -> Result<(), DirstateMapError>; + ) -> Result<(), DirstateError>; fn remove_file( &mut self, filename: &HgPath, old_state: EntryState, size: i32, - ) -> Result<(), DirstateMapError>; + ) -> Result<(), DirstateError>; fn drop_file( &mut self, filename: &HgPath, old_state: EntryState, - ) -> Result; + ) -> Result; - fn clear_ambiguous_times(&mut self, filenames: Vec, now: i32); + fn clear_ambiguous_times( + &mut self, + filenames: Vec, + now: i32, + ) -> Result<(), DirstateV2ParseError>; - fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool; + fn non_normal_entries_contains( + &mut self, + key: &HgPath, + ) -> Result; fn non_normal_entries_remove(&mut self, key: &HgPath); fn non_normal_or_other_parent_paths( &mut self, - ) -> Box + '_>; + ) -> Box> + '_>; fn set_non_normal_other_parent_entries(&mut self, force: bool); fn iter_non_normal_paths( &mut self, - ) -> Box + Send + '_>; + ) -> Box< + dyn Iterator> + Send + '_, + >; fn iter_non_normal_paths_panic( &self, - ) -> Box + Send + '_>; + ) -> Box< + dyn Iterator> + Send + '_, + >; fn iter_other_parent_paths( &mut self, - ) -> Box + Send + '_>; + ) -> Box< + dyn Iterator> + Send + '_, + >; fn has_tracked_dir( &mut self, directory: &HgPath, - ) -> Result; + ) -> Result; - fn has_dir( - &mut self, - directory: &HgPath, - ) -> Result; + fn has_dir(&mut self, directory: &HgPath) -> Result; fn pack_v1( &mut self, @@ -85,9 +95,9 @@ pub trait DirstateMapMethods { now: Timestamp, ) -> Result, DirstateError>; - fn set_all_dirs(&mut self) -> Result<(), DirstateMapError>; + fn set_all_dirs(&mut self) -> Result<(), DirstateError>; - fn set_dirs(&mut self) -> Result<(), DirstateMapError>; + fn set_dirs(&mut self) -> Result<(), DirstateError>; fn status<'a>( &'a mut self, @@ -101,23 +111,36 @@ pub trait DirstateMapMethods { fn copy_map_iter(&self) -> CopyMapIter<'_>; - fn copy_map_contains_key(&self, key: &HgPath) -> bool; + fn copy_map_contains_key( + &self, + key: &HgPath, + ) -> Result; - fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath>; + fn copy_map_get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError>; - fn copy_map_remove(&mut self, key: &HgPath) -> Option; + fn copy_map_remove( + &mut self, + key: &HgPath, + ) -> Result, DirstateV2ParseError>; fn copy_map_insert( &mut self, key: HgPathBuf, value: HgPathBuf, - ) -> Option; + ) -> Result, DirstateV2ParseError>; fn len(&self) -> usize; - fn contains_key(&self, key: &HgPath) -> bool; + fn contains_key(&self, key: &HgPath) + -> Result; - fn get(&self, key: &HgPath) -> Option; + fn get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError>; fn iter(&self) -> StateMapIter<'_>; } @@ -132,7 +155,7 @@ impl DirstateMapMethods for DirstateMap filename: &HgPath, old_state: EntryState, entry: DirstateEntry, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { self.add_file(filename, old_state, entry) } @@ -141,7 +164,7 @@ impl DirstateMapMethods for DirstateMap filename: &HgPath, old_state: EntryState, size: i32, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { self.remove_file(filename, old_state, size) } @@ -149,18 +172,25 @@ impl DirstateMapMethods for DirstateMap &mut self, filename: &HgPath, old_state: EntryState, - ) -> Result { + ) -> Result { self.drop_file(filename, old_state) } - fn clear_ambiguous_times(&mut self, filenames: Vec, now: i32) { - self.clear_ambiguous_times(filenames, now) + fn clear_ambiguous_times( + &mut self, + filenames: Vec, + now: i32, + ) -> Result<(), DirstateV2ParseError> { + Ok(self.clear_ambiguous_times(filenames, now)) } - fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool { + fn non_normal_entries_contains( + &mut self, + key: &HgPath, + ) -> Result { let (non_normal, _other_parent) = self.get_non_normal_other_parent_entries(); - non_normal.contains(key) + Ok(non_normal.contains(key)) } fn non_normal_entries_remove(&mut self, key: &HgPath) { @@ -169,10 +199,11 @@ impl DirstateMapMethods for DirstateMap fn non_normal_or_other_parent_paths( &mut self, - ) -> Box + '_> { + ) -> Box> + '_> + { let (non_normal, other_parent) = self.get_non_normal_other_parent_entries(); - Box::new(non_normal.union(other_parent).map(|p| &**p)) + Box::new(non_normal.union(other_parent).map(|p| Ok(&**p))) } fn set_non_normal_other_parent_entries(&mut self, force: bool) { @@ -181,39 +212,42 @@ impl DirstateMapMethods for DirstateMap fn iter_non_normal_paths( &mut self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { let (non_normal, _other_parent) = self.get_non_normal_other_parent_entries(); - Box::new(non_normal.iter().map(|p| &**p)) + Box::new(non_normal.iter().map(|p| Ok(&**p))) } fn iter_non_normal_paths_panic( &self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { let (non_normal, _other_parent) = self.get_non_normal_other_parent_entries_panic(); - Box::new(non_normal.iter().map(|p| &**p)) + Box::new(non_normal.iter().map(|p| Ok(&**p))) } fn iter_other_parent_paths( &mut self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { let (_non_normal, other_parent) = self.get_non_normal_other_parent_entries(); - Box::new(other_parent.iter().map(|p| &**p)) + Box::new(other_parent.iter().map(|p| Ok(&**p))) } fn has_tracked_dir( &mut self, directory: &HgPath, - ) -> Result { + ) -> Result { self.has_tracked_dir(directory) } - fn has_dir( - &mut self, - directory: &HgPath, - ) -> Result { + fn has_dir(&mut self, directory: &HgPath) -> Result { self.has_dir(directory) } @@ -235,11 +269,11 @@ impl DirstateMapMethods for DirstateMap ) } - fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { + fn set_all_dirs(&mut self) -> Result<(), DirstateError> { self.set_all_dirs() } - fn set_dirs(&mut self) -> Result<(), DirstateMapError> { + fn set_dirs(&mut self) -> Result<(), DirstateError> { self.set_dirs() } @@ -259,42 +293,61 @@ impl DirstateMapMethods for DirstateMap } fn copy_map_iter(&self) -> CopyMapIter<'_> { - Box::new(self.copy_map.iter().map(|(key, value)| (&**key, &**value))) - } - - fn copy_map_contains_key(&self, key: &HgPath) -> bool { - self.copy_map.contains_key(key) + Box::new( + self.copy_map + .iter() + .map(|(key, value)| Ok((&**key, &**value))), + ) } - fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath> { - self.copy_map.get(key).map(|p| &**p) + fn copy_map_contains_key( + &self, + key: &HgPath, + ) -> Result { + Ok(self.copy_map.contains_key(key)) } - fn copy_map_remove(&mut self, key: &HgPath) -> Option { - self.copy_map.remove(key) + fn copy_map_get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { + Ok(self.copy_map.get(key).map(|p| &**p)) + } + + fn copy_map_remove( + &mut self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { + Ok(self.copy_map.remove(key)) } fn copy_map_insert( &mut self, key: HgPathBuf, value: HgPathBuf, - ) -> Option { - self.copy_map.insert(key, value) + ) -> Result, DirstateV2ParseError> { + Ok(self.copy_map.insert(key, value)) } fn len(&self) -> usize { (&**self).len() } - fn contains_key(&self, key: &HgPath) -> bool { - (&**self).contains_key(key) + fn contains_key( + &self, + key: &HgPath, + ) -> Result { + Ok((&**self).contains_key(key)) } - fn get(&self, key: &HgPath) -> Option { - (&**self).get(key).cloned() + fn get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { + Ok((&**self).get(key).cloned()) } fn iter(&self) -> StateMapIter<'_> { - Box::new((&**self).iter().map(|(key, value)| (&**key, *value))) + Box::new((&**self).iter().map(|(key, value)| Ok((&**key, *value)))) } } diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -109,7 +109,10 @@ fn _static_assert_size_of() { } /// Unexpected file format found in `.hg/dirstate` with the "v2" format. -pub(crate) struct DirstateV2ParseError; +/// +/// This should only happen if Mercurial is buggy or a repository is corrupted. +#[derive(Debug)] +pub struct DirstateV2ParseError; impl From for HgError { fn from(_: DirstateV2ParseError) -> Self { @@ -295,9 +298,9 @@ fn write_nodes( // First accumulate serialized nodes in a `Vec` let mut on_disk_nodes = Vec::with_capacity(nodes.len()); for node in nodes { - let children = write_nodes(node.children(), out)?; - let full_path = write_slice::(node.full_path().as_bytes(), out); - let copy_source = if let Some(source) = node.copy_source() { + let children = write_nodes(node.children()?, out)?; + let full_path = write_slice::(node.full_path()?.as_bytes(), out); + let copy_source = if let Some(source) = node.copy_source()? { write_slice::(source.as_bytes(), out) } else { Slice { 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 @@ -2,6 +2,7 @@ use crate::dirstate::status::IgnoreFnTyp use crate::dirstate_tree::dirstate_map::ChildNodesRef; use crate::dirstate_tree::dirstate_map::DirstateMap; use crate::dirstate_tree::dirstate_map::NodeRef; +use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::matchers::get_ignore_function; use crate::matchers::Matcher; use crate::utils::files::get_bytes_from_os_string; @@ -60,7 +61,7 @@ pub fn status<'tree>( hg_path, &root_dir, is_at_repo_root, - ); + )?; Ok((common.outcome.into_inner().unwrap(), warnings)) } @@ -97,7 +98,7 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> directory_hg_path: &'tree HgPath, directory_fs_path: &Path, is_at_repo_root: bool, - ) { + ) -> Result<(), DirstateV2ParseError> { let mut fs_entries = if let Ok(entries) = self.read_dir( directory_hg_path, directory_fs_path, @@ -105,7 +106,7 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> ) { entries } else { - return; + return Ok(()); }; // `merge_join_by` requires both its input iterators to be sorted: @@ -115,34 +116,41 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> // https://github.com/rust-lang/rust/issues/34162 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name)); + // Propagate here any error that would happen inside the comparison + // callback below + for dirstate_node in &dirstate_nodes { + dirstate_node.base_name()?; + } itertools::merge_join_by( dirstate_nodes, &fs_entries, |dirstate_node, fs_entry| { - dirstate_node.base_name().cmp(&fs_entry.base_name) + // This `unwrap` never panics because we already propagated + // those errors above + dirstate_node.base_name().unwrap().cmp(&fs_entry.base_name) }, ) .par_bridge() - .for_each(|pair| { + .map(|pair| { use itertools::EitherOrBoth::*; match pair { - Both(dirstate_node, fs_entry) => { - self.traverse_fs_and_dirstate( + Both(dirstate_node, fs_entry) => self + .traverse_fs_and_dirstate( fs_entry, dirstate_node, has_ignored_ancestor, - ); - } + ), Left(dirstate_node) => { self.traverse_dirstate_only(dirstate_node) } - Right(fs_entry) => self.traverse_fs_only( + Right(fs_entry) => Ok(self.traverse_fs_only( has_ignored_ancestor, directory_hg_path, fs_entry, - ), + )), } }) + .collect() } fn traverse_fs_and_dirstate( @@ -150,8 +158,8 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> fs_entry: &DirEntry, dirstate_node: NodeRef<'tree, '_>, has_ignored_ancestor: bool, - ) { - let hg_path = dirstate_node.full_path(); + ) -> Result<(), DirstateV2ParseError> { + let hg_path = dirstate_node.full_path()?; let file_type = fs_entry.metadata.file_type(); let file_or_symlink = file_type.is_file() || file_type.is_symlink(); if !file_or_symlink { @@ -159,8 +167,8 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> // `hg rm` or similar) or deleted before it could be // replaced by a directory or something else. self.mark_removed_or_deleted_if_file( - dirstate_node.full_path(), - dirstate_node.state(), + hg_path, + dirstate_node.state()?, ); } if file_type.is_dir() { @@ -171,15 +179,15 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> let is_at_repo_root = false; self.traverse_fs_directory_and_dirstate( is_ignored, - dirstate_node.children(), + dirstate_node.children()?, hg_path, &fs_entry.full_path, is_at_repo_root, - ); + )? } else { if file_or_symlink && self.matcher.matches(hg_path) { let full_path = Cow::from(hg_path); - if let Some(state) = dirstate_node.state() { + if let Some(state) = dirstate_node.state()? { match state { EntryState::Added => { self.outcome.lock().unwrap().added.push(full_path) @@ -197,7 +205,7 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> .modified .push(full_path), EntryState::Normal => { - self.handle_normal_file(&dirstate_node, fs_entry); + self.handle_normal_file(&dirstate_node, fs_entry)? } // This variant is not used in DirstateMap // nodes @@ -213,10 +221,11 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> } } - for child_node in dirstate_node.children().iter() { - self.traverse_dirstate_only(child_node) + for child_node in dirstate_node.children()?.iter() { + self.traverse_dirstate_only(child_node)? } } + Ok(()) } /// A file with `EntryState::Normal` in the dirstate was found in the @@ -225,7 +234,7 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> &self, dirstate_node: &NodeRef<'tree, '_>, fs_entry: &DirEntry, - ) { + ) -> Result<(), DirstateV2ParseError> { // Keep the low 31 bits fn truncate_u64(value: u64) -> i32 { (value & 0x7FFF_FFFF) as i32 @@ -235,9 +244,9 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> } let entry = dirstate_node - .entry() + .entry()? .expect("handle_normal_file called with entry-less node"); - let full_path = Cow::from(dirstate_node.full_path()); + let full_path = Cow::from(dirstate_node.full_path()?); let mode_changed = || { self.options.check_exec && entry.mode_changed(&fs_entry.metadata) }; @@ -249,7 +258,7 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> // issue6456: Size returned may be longer due to encryption // on EXT-4 fscrypt. TODO maybe only do it on EXT4? self.outcome.lock().unwrap().unsure.push(full_path) - } else if dirstate_node.copy_source().is_some() + } else if dirstate_node.has_copy_source() || entry.is_from_other_parent() || (entry.size >= 0 && (size_changed || mode_changed())) { @@ -264,18 +273,23 @@ impl<'tree, 'a> StatusCommon<'tree, 'a> self.outcome.lock().unwrap().clean.push(full_path) } } + Ok(()) } /// A node in the dirstate tree has no corresponding filesystem entry - fn traverse_dirstate_only(&self, dirstate_node: NodeRef<'tree, '_>) { + fn traverse_dirstate_only( + &self, + dirstate_node: NodeRef<'tree, '_>, + ) -> Result<(), DirstateV2ParseError> { self.mark_removed_or_deleted_if_file( - dirstate_node.full_path(), - dirstate_node.state(), + dirstate_node.full_path()?, + dirstate_node.state()?, ); dirstate_node - .children() + .children()? .par_iter() - .for_each(|child_node| self.traverse_dirstate_only(child_node)) + .map(|child_node| self.traverse_dirstate_only(child_node)) + .collect() } /// A node in the dirstate tree has no corresponding *file* on the diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -83,6 +83,15 @@ pub enum DirstateError { Common(errors::HgError), } +impl fmt::Display for DirstateError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DirstateError::Map(error) => error.fmt(f), + DirstateError::Common(error) => error.fmt(f), + } + } +} + #[derive(Debug, derive_more::From)] pub enum PatternError { #[from] diff --git a/rust/hg-cpython/src/dirstate/copymap.rs b/rust/hg-cpython/src/dirstate/copymap.rs --- a/rust/hg-cpython/src/dirstate/copymap.rs +++ b/rust/hg-cpython/src/dirstate/copymap.rs @@ -13,7 +13,9 @@ use cpython::{ }; use std::cell::RefCell; +use crate::dirstate::dirstate_map::v2_error; use crate::dirstate::dirstate_map::DirstateMap; +use hg::dirstate_tree::on_disk::DirstateV2ParseError; use hg::utils::hg_path::HgPath; use hg::CopyMapIter; @@ -88,15 +90,16 @@ impl CopyMap { } fn translate_key( py: Python, - res: (&HgPath, &HgPath), + res: Result<(&HgPath, &HgPath), DirstateV2ParseError>, ) -> PyResult> { - Ok(Some(PyBytes::new(py, res.0.as_bytes()))) + let (k, _v) = res.map_err(|e| v2_error(py, e))?; + Ok(Some(PyBytes::new(py, k.as_bytes()))) } fn translate_key_value( py: Python, - res: (&HgPath, &HgPath), + res: Result<(&HgPath, &HgPath), DirstateV2ParseError>, ) -> PyResult> { - let (k, v) = res; + let (k, v) = res.map_err(|e| v2_error(py, e))?; Ok(Some(( PyBytes::new(py, k.as_bytes()), PyBytes::new(py, v.as_bytes()), diff --git a/rust/hg-cpython/src/dirstate/dirs_multiset.rs b/rust/hg-cpython/src/dirstate/dirs_multiset.rs --- a/rust/hg-cpython/src/dirstate/dirs_multiset.rs +++ b/rust/hg-cpython/src/dirstate/dirs_multiset.rs @@ -20,7 +20,8 @@ use crate::dirstate::extract_dirstate; use hg::{ errors::HgError, utils::hg_path::{HgPath, HgPathBuf}, - DirsMultiset, DirsMultisetIter, DirstateMapError, EntryState, + DirsMultiset, DirsMultisetIter, DirstateError, DirstateMapError, + EntryState, }; py_class!(pub class Dirs |py| { @@ -45,9 +46,9 @@ py_class!(pub class Dirs |py| { } let inner = if let Ok(map) = map.cast_as::(py) { let dirstate = extract_dirstate(py, &map)?; - let dirstate = dirstate.iter().map(|(k, v)| (k, *v)); + let dirstate = dirstate.iter().map(|(k, v)| Ok((k, *v))); DirsMultiset::from_dirstate(dirstate, skip_state) - .map_err(|e: DirstateMapError| { + .map_err(|e: DirstateError| { PyErr::new::(py, e.to_string()) })? } else { 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 @@ -29,13 +29,13 @@ use crate::{ use hg::{ dirstate::parsers::Timestamp, dirstate_tree::dispatch::DirstateMapMethods, + dirstate_tree::on_disk::DirstateV2ParseError, errors::HgError, revlog::Node, utils::files::normalize_case, utils::hg_path::{HgPath, HgPathBuf}, DirsMultiset, DirstateEntry, DirstateError, - DirstateMap as RustDirstateMap, DirstateMapError, DirstateParents, - EntryState, StateMapIter, + DirstateMap as RustDirstateMap, DirstateParents, EntryState, StateMapIter, }; // TODO @@ -90,7 +90,12 @@ py_class!(pub class DirstateMap |py| { default: Option = None ) -> PyResult> { let key = key.extract::(py)?; - match self.inner(py).borrow().get(HgPath::new(key.data(py))) { + match self + .inner(py) + .borrow() + .get(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e))? + { Some(entry) => { Ok(Some(make_dirstate_tuple(py, &entry)?)) }, @@ -124,7 +129,7 @@ py_class!(pub class DirstateMap |py| { size: size.extract(py)?, mtime: mtime.extract(py)?, }, - ).and(Ok(py.None())).or_else(|e: DirstateMapError| { + ).and(Ok(py.None())).or_else(|e: DirstateError| { Err(PyErr::new::(py, e.to_string())) }) } @@ -190,8 +195,10 @@ py_class!(pub class DirstateMap |py| { )) }) .collect(); - self.inner(py).borrow_mut() - .clear_ambiguous_times(files?, now.extract(py)?); + self.inner(py) + .borrow_mut() + .clear_ambiguous_times(files?, now.extract(py)?) + .map_err(|e| v2_error(py, e))?; Ok(py.None()) } @@ -199,6 +206,7 @@ py_class!(pub class DirstateMap |py| { let mut inner_shared = self.inner(py).borrow_mut(); let set = PySet::empty(py)?; for path in inner_shared.iter_other_parent_paths() { + let path = path.map_err(|e| v2_error(py, e))?; set.add(py, PyBytes::new(py, path.as_bytes()))?; } Ok(set.into_object()) @@ -210,28 +218,20 @@ py_class!(pub class DirstateMap |py| { def non_normal_entries_contains(&self, key: PyObject) -> PyResult { let key = key.extract::(py)?; - Ok(self - .inner(py) + self.inner(py) .borrow_mut() - .non_normal_entries_contains(HgPath::new(key.data(py)))) + .non_normal_entries_contains(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e)) } def non_normal_entries_display(&self) -> PyResult { - Ok( - PyString::new( - py, - &format!( - "NonNormalEntries: {}", - hg::utils::join_display( - self - .inner(py) - .borrow_mut() - .iter_non_normal_paths(), - ", " - ) - ) - ) - ) + let mut inner = self.inner(py).borrow_mut(); + let paths = inner + .iter_non_normal_paths() + .collect::, _>>() + .map_err(|e| v2_error(py, e))?; + let formatted = format!("NonNormalEntries: {}", hg::utils::join_display(paths, ", ")); + Ok(PyString::new(py, &formatted)) } def non_normal_entries_remove(&self, key: PyObject) -> PyResult { @@ -248,6 +248,7 @@ py_class!(pub class DirstateMap |py| { let ret = PyList::new(py, &[]); for filename in inner.non_normal_or_other_parent_paths() { + let filename = filename.map_err(|e| v2_error(py, e))?; let as_pystring = PyBytes::new(py, filename.as_bytes()); ret.append(py, as_pystring.into_object()); } @@ -320,7 +321,8 @@ py_class!(pub class DirstateMap |py| { def filefoldmapasdict(&self) -> PyResult { let dict = PyDict::new(py); - for (path, entry) in self.inner(py).borrow_mut().iter() { + for item in self.inner(py).borrow_mut().iter() { + let (path, entry) = item.map_err(|e| v2_error(py, e))?; if entry.state != EntryState::Removed { let key = normalize_case(path); let value = path; @@ -340,13 +342,21 @@ py_class!(pub class DirstateMap |py| { def __contains__(&self, key: PyObject) -> PyResult { let key = key.extract::(py)?; - Ok(self.inner(py).borrow().contains_key(HgPath::new(key.data(py)))) + self.inner(py) + .borrow() + .contains_key(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e)) } def __getitem__(&self, key: PyObject) -> PyResult { let key = key.extract::(py)?; let key = HgPath::new(key.data(py)); - match self.inner(py).borrow().get(key) { + match self + .inner(py) + .borrow() + .get(key) + .map_err(|e| v2_error(py, e))? + { Some(entry) => { Ok(make_dirstate_tuple(py, &entry)?) }, @@ -418,7 +428,8 @@ py_class!(pub class DirstateMap |py| { // TODO all copymap* methods, see docstring above def copymapcopy(&self) -> PyResult { let dict = PyDict::new(py); - for (key, value) in self.inner(py).borrow().copy_map_iter() { + for item in self.inner(py).borrow().copy_map_iter() { + let (key, value) = item.map_err(|e| v2_error(py, e))?; dict.set_item( py, PyBytes::new(py, key.as_bytes()), @@ -430,7 +441,12 @@ py_class!(pub class DirstateMap |py| { def copymapgetitem(&self, key: PyObject) -> PyResult { let key = key.extract::(py)?; - match self.inner(py).borrow().copy_map_get(HgPath::new(key.data(py))) { + match self + .inner(py) + .borrow() + .copy_map_get(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e))? + { Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())), None => Err(PyErr::new::( py, @@ -447,10 +463,10 @@ py_class!(pub class DirstateMap |py| { } def copymapcontains(&self, key: PyObject) -> PyResult { let key = key.extract::(py)?; - Ok(self - .inner(py) + self.inner(py) .borrow() - .copy_map_contains_key(HgPath::new(key.data(py)))) + .copy_map_contains_key(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e)) } def copymapget( &self, @@ -462,6 +478,7 @@ py_class!(pub class DirstateMap |py| { .inner(py) .borrow() .copy_map_get(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e))? { Some(copy) => Ok(Some( PyBytes::new(py, copy.as_bytes()).into_object(), @@ -476,10 +493,13 @@ py_class!(pub class DirstateMap |py| { ) -> PyResult { let key = key.extract::(py)?; let value = value.extract::(py)?; - self.inner(py).borrow_mut().copy_map_insert( - HgPathBuf::from_bytes(key.data(py)), - HgPathBuf::from_bytes(value.data(py)), - ); + self.inner(py) + .borrow_mut() + .copy_map_insert( + HgPathBuf::from_bytes(key.data(py)), + HgPathBuf::from_bytes(value.data(py)), + ) + .map_err(|e| v2_error(py, e))?; Ok(py.None()) } def copymappop( @@ -492,6 +512,7 @@ py_class!(pub class DirstateMap |py| { .inner(py) .borrow_mut() .copy_map_remove(HgPath::new(key.data(py))) + .map_err(|e| v2_error(py, e))? { Some(_) => Ok(None), None => Ok(default), @@ -525,15 +546,16 @@ impl DirstateMap { } fn translate_key( py: Python, - res: (&HgPath, DirstateEntry), + res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>, ) -> PyResult> { - Ok(Some(PyBytes::new(py, res.0.as_bytes()))) + let (f, _entry) = res.map_err(|e| v2_error(py, e))?; + Ok(Some(PyBytes::new(py, f.as_bytes()))) } fn translate_key_value( py: Python, - res: (&HgPath, DirstateEntry), + res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>, ) -> PyResult> { - let (f, entry) = res; + let (f, entry) = res.map_err(|e| v2_error(py, e))?; Ok(Some(( PyBytes::new(py, f.as_bytes()), make_dirstate_tuple(py, &entry)?, @@ -562,3 +584,7 @@ fn extract_node_id(py: Python, obj: &PyO Err(e) => Err(PyErr::new::(py, e.to_string())), } } + +pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr { + PyErr::new::(py, "corrupted dirstate-v2") +} diff --git a/rust/hg-cpython/src/dirstate/dispatch.rs b/rust/hg-cpython/src/dirstate/dispatch.rs --- a/rust/hg-cpython/src/dirstate/dispatch.rs +++ b/rust/hg-cpython/src/dirstate/dispatch.rs @@ -1,12 +1,12 @@ use crate::dirstate::owning::OwningDirstateMap; use hg::dirstate::parsers::Timestamp; use hg::dirstate_tree::dispatch::DirstateMapMethods; +use hg::dirstate_tree::on_disk::DirstateV2ParseError; use hg::matchers::Matcher; use hg::utils::hg_path::{HgPath, HgPathBuf}; use hg::CopyMapIter; use hg::DirstateEntry; use hg::DirstateError; -use hg::DirstateMapError; use hg::DirstateParents; use hg::DirstateStatus; use hg::EntryState; @@ -26,7 +26,7 @@ impl DirstateMapMethods for OwningDirsta filename: &HgPath, old_state: EntryState, entry: DirstateEntry, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { self.get_mut().add_file(filename, old_state, entry) } @@ -35,7 +35,7 @@ impl DirstateMapMethods for OwningDirsta filename: &HgPath, old_state: EntryState, size: i32, - ) -> Result<(), DirstateMapError> { + ) -> Result<(), DirstateError> { self.get_mut().remove_file(filename, old_state, size) } @@ -43,15 +43,22 @@ impl DirstateMapMethods for OwningDirsta &mut self, filename: &HgPath, old_state: EntryState, - ) -> Result { + ) -> Result { self.get_mut().drop_file(filename, old_state) } - fn clear_ambiguous_times(&mut self, filenames: Vec, now: i32) { + fn clear_ambiguous_times( + &mut self, + filenames: Vec, + now: i32, + ) -> Result<(), DirstateV2ParseError> { self.get_mut().clear_ambiguous_times(filenames, now) } - fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool { + fn non_normal_entries_contains( + &mut self, + key: &HgPath, + ) -> Result { self.get_mut().non_normal_entries_contains(key) } @@ -61,7 +68,8 @@ impl DirstateMapMethods for OwningDirsta fn non_normal_or_other_parent_paths( &mut self, - ) -> Box + '_> { + ) -> Box> + '_> + { self.get_mut().non_normal_or_other_parent_paths() } @@ -71,33 +79,36 @@ impl DirstateMapMethods for OwningDirsta fn iter_non_normal_paths( &mut self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { self.get_mut().iter_non_normal_paths() } fn iter_non_normal_paths_panic( &self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { self.get().iter_non_normal_paths_panic() } fn iter_other_parent_paths( &mut self, - ) -> Box + Send + '_> { + ) -> Box< + dyn Iterator> + Send + '_, + > { self.get_mut().iter_other_parent_paths() } fn has_tracked_dir( &mut self, directory: &HgPath, - ) -> Result { + ) -> Result { self.get_mut().has_tracked_dir(directory) } - fn has_dir( - &mut self, - directory: &HgPath, - ) -> Result { + fn has_dir(&mut self, directory: &HgPath) -> Result { self.get_mut().has_dir(directory) } @@ -117,11 +128,11 @@ impl DirstateMapMethods for OwningDirsta self.get_mut().pack_v2(parents, now) } - fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { + fn set_all_dirs(&mut self) -> Result<(), DirstateError> { self.get_mut().set_all_dirs() } - fn set_dirs(&mut self) -> Result<(), DirstateMapError> { + fn set_dirs(&mut self) -> Result<(), DirstateError> { self.get_mut().set_dirs() } @@ -145,15 +156,24 @@ impl DirstateMapMethods for OwningDirsta self.get().copy_map_iter() } - fn copy_map_contains_key(&self, key: &HgPath) -> bool { + fn copy_map_contains_key( + &self, + key: &HgPath, + ) -> Result { self.get().copy_map_contains_key(key) } - fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath> { + fn copy_map_get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { self.get().copy_map_get(key) } - fn copy_map_remove(&mut self, key: &HgPath) -> Option { + fn copy_map_remove( + &mut self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { self.get_mut().copy_map_remove(key) } @@ -161,7 +181,7 @@ impl DirstateMapMethods for OwningDirsta &mut self, key: HgPathBuf, value: HgPathBuf, - ) -> Option { + ) -> Result, DirstateV2ParseError> { self.get_mut().copy_map_insert(key, value) } @@ -169,11 +189,17 @@ impl DirstateMapMethods for OwningDirsta self.get().len() } - fn contains_key(&self, key: &HgPath) -> bool { + fn contains_key( + &self, + key: &HgPath, + ) -> Result { self.get().contains_key(key) } - fn get(&self, key: &HgPath) -> Option { + fn get( + &self, + key: &HgPath, + ) -> Result, DirstateV2ParseError> { self.get().get(key) } diff --git a/rust/hg-cpython/src/dirstate/non_normal_entries.rs b/rust/hg-cpython/src/dirstate/non_normal_entries.rs --- a/rust/hg-cpython/src/dirstate/non_normal_entries.rs +++ b/rust/hg-cpython/src/dirstate/non_normal_entries.rs @@ -11,7 +11,9 @@ use cpython::{ UnsafePyLeaked, }; +use crate::dirstate::dirstate_map::v2_error; use crate::dirstate::DirstateMap; +use hg::dirstate_tree::on_disk::DirstateV2ParseError; use hg::utils::hg_path::HgPath; use std::cell::RefCell; @@ -54,13 +56,18 @@ impl NonNormalEntries { Ok(true) } - fn translate_key(py: Python, key: &HgPath) -> PyResult> { + fn translate_key( + py: Python, + key: Result<&HgPath, DirstateV2ParseError>, + ) -> PyResult> { + let key = key.map_err(|e| v2_error(py, e))?; Ok(Some(PyBytes::new(py, key.as_bytes()))) } } -type NonNormalEntriesIter<'a> = - Box + Send + 'a>; +type NonNormalEntriesIter<'a> = Box< + dyn Iterator> + Send + 'a, +>; py_shared_iterator!( NonNormalEntriesIterator,