dirstate_map.rs
1158 lines
| 37.4 KiB
| application/rls-services+xml
|
RustLexer
|
r47872 | use bytes_cast::BytesCast; | ||
|
r47888 | use micro_timer::timed; | ||
|
r47895 | use std::borrow::Cow; | ||
|
r47865 | use std::path::PathBuf; | ||
|
r48058 | use super::on_disk; | ||
|
r48126 | use super::on_disk::DirstateV2ParseError; | ||
|
r48883 | use super::owning::OwningDirstateMap; | ||
|
r47867 | use super::path_with_basename::WithBasename; | ||
|
r47872 | use crate::dirstate::parsers::pack_entry; | ||
use crate::dirstate::parsers::packed_entry_size; | ||||
|
r47868 | use crate::dirstate::parsers::parse_dirstate_entries; | ||
|
r48882 | use crate::dirstate::CopyMapIter; | ||
use crate::dirstate::StateMapIter; | ||||
|
r49007 | use crate::dirstate::TruncatedTimestamp; | ||
r48300 | use crate::dirstate::SIZE_FROM_OTHER_PARENT; | |||
use crate::dirstate::SIZE_NON_NORMAL; | ||||
|
r47865 | use crate::matchers::Matcher; | ||
use crate::utils::hg_path::{HgPath, HgPathBuf}; | ||||
use crate::DirstateEntry; | ||||
use crate::DirstateError; | ||||
use crate::DirstateParents; | ||||
use crate::DirstateStatus; | ||||
use crate::EntryState; | ||||
|
r47889 | use crate::FastHashMap; | ||
|
r47865 | use crate::PatternFileWarning; | ||
use crate::StatusError; | ||||
use crate::StatusOptions; | ||||
|
r48481 | /// Append to an existing data file if the amount of unreachable data (not used | ||
/// anymore) is less than this fraction of the total amount of existing data. | ||||
const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5; | ||||
|
r47893 | pub struct DirstateMap<'on_disk> { | ||
/// Contents of the `.hg/dirstate` file | ||||
|
r48058 | pub(super) on_disk: &'on_disk [u8], | ||
|
r47893 | |||
|
r47895 | pub(super) root: ChildNodes<'on_disk>, | ||
|
r47873 | |||
/// Number of nodes anywhere in the tree that have `.entry.is_some()`. | ||||
|
r48058 | pub(super) nodes_with_entry_count: u32, | ||
|
r47873 | |||
/// Number of nodes anywhere in the tree that have | ||||
/// `.copy_source.is_some()`. | ||||
|
r48058 | pub(super) nodes_with_copy_source_count: u32, | ||
|
r48202 | |||
/// See on_disk::Header | ||||
pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash, | ||||
|
r48481 | |||
/// How many bytes of `on_disk` are not used anymore | ||||
pub(super) unreachable_bytes: u32, | ||||
|
r47867 | } | ||
/// Using a plain `HgPathBuf` of the full path from the repository root as a | ||||
/// map key would also work: all paths in a given map have the same parent | ||||
/// path, so comparing full paths gives the same result as comparing base | ||||
|
r48057 | /// names. However `HashMap` would waste time always re-hashing the same | ||
|
r47867 | /// string prefix. | ||
|
r48057 | pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>; | ||
|
r48124 | |||
|
r48136 | /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned | ||
/// for on-disk nodes that don’t actually have a `Cow` to borrow. | ||||
pub(super) enum BorrowedPath<'tree, 'on_disk> { | ||||
InMemory(&'tree HgPathBuf), | ||||
OnDisk(&'on_disk HgPath), | ||||
} | ||||
|
r48124 | pub(super) enum ChildNodes<'on_disk> { | ||
InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>), | ||||
|
r48128 | OnDisk(&'on_disk [on_disk::Node]), | ||
|
r48124 | } | ||
pub(super) enum ChildNodesRef<'tree, 'on_disk> { | ||||
InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>), | ||||
|
r48128 | OnDisk(&'on_disk [on_disk::Node]), | ||
|
r48124 | } | ||
pub(super) enum NodeRef<'tree, 'on_disk> { | ||||
InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>), | ||||
|
r48128 | OnDisk(&'on_disk on_disk::Node), | ||
|
r48124 | } | ||
|
r48136 | impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> { | ||
pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> { | ||||
match *self { | ||||
BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()), | ||||
BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk), | ||||
} | ||||
} | ||||
} | ||||
impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> { | ||||
type Target = HgPath; | ||||
fn deref(&self) -> &HgPath { | ||||
match *self { | ||||
BorrowedPath::InMemory(in_memory) => in_memory, | ||||
BorrowedPath::OnDisk(on_disk) => on_disk, | ||||
} | ||||
} | ||||
} | ||||
|
r48124 | impl Default for ChildNodes<'_> { | ||
fn default() -> Self { | ||||
ChildNodes::InMemory(Default::default()) | ||||
} | ||||
} | ||||
impl<'on_disk> ChildNodes<'on_disk> { | ||||
pub(super) fn as_ref<'tree>( | ||||
&'tree self, | ||||
) -> ChildNodesRef<'tree, 'on_disk> { | ||||
match self { | ||||
ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes), | ||||
|
r48128 | ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes), | ||
|
r48124 | } | ||
} | ||||
pub(super) fn is_empty(&self) -> bool { | ||||
match self { | ||||
ChildNodes::InMemory(nodes) => nodes.is_empty(), | ||||
|
r48128 | ChildNodes::OnDisk(nodes) => nodes.is_empty(), | ||
|
r48124 | } | ||
} | ||||
|
r48481 | fn make_mut( | ||
|
r48124 | &mut self, | ||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48481 | unreachable_bytes: &mut u32, | ||
|
r48126 | ) -> Result< | ||
&mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>, | ||||
DirstateV2ParseError, | ||||
> { | ||||
|
r48124 | match self { | ||
|
r48126 | ChildNodes::InMemory(nodes) => Ok(nodes), | ||
|
r48128 | ChildNodes::OnDisk(nodes) => { | ||
|
r48481 | *unreachable_bytes += | ||
std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32; | ||||
|
r48128 | let nodes = nodes | ||
.iter() | ||||
.map(|node| { | ||||
Ok(( | ||||
node.path(on_disk)?, | ||||
node.to_in_memory_node(on_disk)?, | ||||
)) | ||||
}) | ||||
.collect::<Result<_, _>>()?; | ||||
*self = ChildNodes::InMemory(nodes); | ||||
match self { | ||||
ChildNodes::InMemory(nodes) => Ok(nodes), | ||||
ChildNodes::OnDisk(_) => unreachable!(), | ||||
} | ||||
} | ||||
|
r48124 | } | ||
} | ||||
} | ||||
impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> { | ||||
pub(super) fn get( | ||||
&self, | ||||
base_name: &HgPath, | ||||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48126 | ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> { | ||
|
r48124 | match self { | ||
|
r48126 | ChildNodesRef::InMemory(nodes) => Ok(nodes | ||
|
r48124 | .get_key_value(base_name) | ||
|
r48126 | .map(|(k, v)| NodeRef::InMemory(k, v))), | ||
|
r48128 | ChildNodesRef::OnDisk(nodes) => { | ||
let mut parse_result = Ok(()); | ||||
let search_result = nodes.binary_search_by(|node| { | ||||
match node.base_name(on_disk) { | ||||
Ok(node_base_name) => node_base_name.cmp(base_name), | ||||
Err(e) => { | ||||
parse_result = Err(e); | ||||
// Dummy comparison result, `search_result` won’t | ||||
// be used since `parse_result` is an error | ||||
std::cmp::Ordering::Equal | ||||
} | ||||
} | ||||
}); | ||||
parse_result.map(|()| { | ||||
search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i])) | ||||
}) | ||||
} | ||||
|
r48124 | } | ||
} | ||||
/// Iterate in undefined order | ||||
pub(super) fn iter( | ||||
&self, | ||||
) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> { | ||||
match self { | ||||
|
r48128 | ChildNodesRef::InMemory(nodes) => itertools::Either::Left( | ||
nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)), | ||||
), | ||||
ChildNodesRef::OnDisk(nodes) => { | ||||
itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk)) | ||||
|
r48124 | } | ||
} | ||||
} | ||||
/// Iterate in parallel in undefined order | ||||
pub(super) fn par_iter( | ||||
&self, | ||||
) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>> | ||||
{ | ||||
use rayon::prelude::*; | ||||
match self { | ||||
|
r48128 | ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left( | ||
nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)), | ||||
), | ||||
ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right( | ||||
nodes.par_iter().map(NodeRef::OnDisk), | ||||
), | ||||
|
r48124 | } | ||
} | ||||
pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> { | ||||
match self { | ||||
ChildNodesRef::InMemory(nodes) => { | ||||
let mut vec: Vec<_> = nodes | ||||
.iter() | ||||
.map(|(k, v)| NodeRef::InMemory(k, v)) | ||||
.collect(); | ||||
|
r48126 | fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath { | ||
match node { | ||||
NodeRef::InMemory(path, _node) => path.base_name(), | ||||
|
r48128 | NodeRef::OnDisk(_) => unreachable!(), | ||
|
r48126 | } | ||
} | ||||
|
r48124 | // `sort_unstable_by_key` doesn’t allow keys borrowing from the | ||
// value: https://github.com/rust-lang/rust/issues/34162 | ||||
|
r48126 | vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b))); | ||
|
r48124 | vec | ||
} | ||||
|
r48128 | ChildNodesRef::OnDisk(nodes) => { | ||
// Nodes on disk are already sorted | ||||
nodes.iter().map(NodeRef::OnDisk).collect() | ||||
} | ||||
|
r48124 | } | ||
} | ||||
} | ||||
impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> { | ||||
|
r48126 | pub(super) fn full_path( | ||
&self, | ||||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48126 | ) -> Result<&'tree HgPath, DirstateV2ParseError> { | ||
|
r48124 | match self { | ||
|
r48126 | NodeRef::InMemory(path, _node) => Ok(path.full_path()), | ||
|
r48128 | NodeRef::OnDisk(node) => node.full_path(on_disk), | ||
|
r48124 | } | ||
} | ||||
|
r48136 | /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk, | ||
/// HgPath>` detached from `'tree` | ||||
pub(super) fn full_path_borrowed( | ||||
|
r48126 | &self, | ||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48136 | ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> { | ||
|
r48124 | match self { | ||
|
r48136 | NodeRef::InMemory(path, _node) => match path.full_path() { | ||
Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)), | ||||
Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)), | ||||
}, | ||||
|
r48128 | NodeRef::OnDisk(node) => { | ||
|
r48136 | Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?)) | ||
|
r48128 | } | ||
|
r48126 | } | ||
} | ||||
pub(super) fn base_name( | ||||
&self, | ||||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48126 | ) -> Result<&'tree HgPath, DirstateV2ParseError> { | ||
match self { | ||||
NodeRef::InMemory(path, _node) => Ok(path.base_name()), | ||||
|
r48128 | NodeRef::OnDisk(node) => node.base_name(on_disk), | ||
|
r48124 | } | ||
} | ||||
|
r48126 | pub(super) fn children( | ||
&self, | ||||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48126 | ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> { | ||
|
r48124 | match self { | ||
|
r48126 | NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()), | ||
|
r48128 | NodeRef::OnDisk(node) => { | ||
Ok(ChildNodesRef::OnDisk(node.children(on_disk)?)) | ||||
} | ||||
|
r48124 | } | ||
} | ||||
|
r48126 | pub(super) fn has_copy_source(&self) -> bool { | ||
|
r48124 | match self { | ||
|
r48126 | NodeRef::InMemory(_path, node) => node.copy_source.is_some(), | ||
|
r48128 | NodeRef::OnDisk(node) => node.has_copy_source(), | ||
|
r48124 | } | ||
} | ||||
|
r48126 | pub(super) fn copy_source( | ||
&self, | ||||
|
r48128 | on_disk: &'on_disk [u8], | ||
|
r48126 | ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> { | ||
|
r48124 | match self { | ||
NodeRef::InMemory(_path, node) => { | ||||
|
r48126 | Ok(node.copy_source.as_ref().map(|s| &**s)) | ||
|
r48124 | } | ||
|
r48128 | NodeRef::OnDisk(node) => node.copy_source(on_disk), | ||
|
r48124 | } | ||
} | ||||
|
r49285 | /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk, | ||
/// HgPath>` detached from `'tree` | ||||
pub(super) fn copy_source_borrowed( | ||||
&self, | ||||
on_disk: &'on_disk [u8], | ||||
) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError> | ||||
{ | ||||
Ok(match self { | ||||
NodeRef::InMemory(_path, node) => { | ||||
node.copy_source.as_ref().map(|source| match source { | ||||
Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk), | ||||
Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory), | ||||
}) | ||||
} | ||||
NodeRef::OnDisk(node) => node | ||||
.copy_source(on_disk)? | ||||
.map(|source| BorrowedPath::OnDisk(source)), | ||||
}) | ||||
} | ||||
|
r48124 | |||
|
r48126 | pub(super) fn entry( | ||
&self, | ||||
) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ||||
|
r48124 | match self { | ||
|
r48137 | NodeRef::InMemory(_path, node) => { | ||
Ok(node.data.as_entry().copied()) | ||||
} | ||||
|
r48128 | NodeRef::OnDisk(node) => node.entry(), | ||
|
r48124 | } | ||
} | ||||
|
r48126 | |||
pub(super) fn state( | ||||
&self, | ||||
) -> Result<Option<EntryState>, DirstateV2ParseError> { | ||||
|
r48951 | Ok(self.entry()?.map(|e| e.state())) | ||
|
r48124 | } | ||
|
r48138 | pub(super) fn cached_directory_mtime( | ||
&self, | ||||
|
r49007 | ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> { | ||
|
r48138 | match self { | ||
|
r49007 | NodeRef::InMemory(_path, node) => Ok(match node.data { | ||
|
r48138 | NodeData::CachedDirectory { mtime } => Some(mtime), | ||
_ => None, | ||||
|
r49007 | }), | ||
|
r48138 | NodeRef::OnDisk(node) => node.cached_directory_mtime(), | ||
} | ||||
} | ||||
|
r48272 | pub(super) fn descendants_with_entry_count(&self) -> u32 { | ||
match self { | ||||
NodeRef::InMemory(_path, node) => { | ||||
node.descendants_with_entry_count | ||||
} | ||||
NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(), | ||||
} | ||||
} | ||||
|
r48124 | pub(super) fn tracked_descendants_count(&self) -> u32 { | ||
match self { | ||||
NodeRef::InMemory(_path, node) => node.tracked_descendants_count, | ||||
|
r48128 | NodeRef::OnDisk(node) => node.tracked_descendants_count.get(), | ||
|
r48124 | } | ||
} | ||||
} | ||||
|
r47867 | |||
|
r47876 | /// Represents a file or a directory | ||
|
r47867 | #[derive(Default)] | ||
|
r47895 | pub(super) struct Node<'on_disk> { | ||
|
r48137 | pub(super) data: NodeData, | ||
|
r47876 | |||
|
r47895 | pub(super) copy_source: Option<Cow<'on_disk, HgPath>>, | ||
|
r47876 | |||
|
r47895 | pub(super) children: ChildNodes<'on_disk>, | ||
|
r47876 | |||
|
r48272 | /// How many (non-inclusive) descendants of this node have an entry. | ||
pub(super) descendants_with_entry_count: u32, | ||||
/// How many (non-inclusive) descendants of this node have an entry whose | ||||
/// state is "tracked". | ||||
|
r48058 | pub(super) tracked_descendants_count: u32, | ||
|
r47876 | } | ||
|
r48137 | pub(super) enum NodeData { | ||
Entry(DirstateEntry), | ||||
|
r49007 | CachedDirectory { mtime: TruncatedTimestamp }, | ||
|
r48137 | None, | ||
} | ||||
impl Default for NodeData { | ||||
fn default() -> Self { | ||||
NodeData::None | ||||
} | ||||
} | ||||
impl NodeData { | ||||
fn has_entry(&self) -> bool { | ||||
match self { | ||||
NodeData::Entry(_) => true, | ||||
_ => false, | ||||
} | ||||
} | ||||
fn as_entry(&self) -> Option<&DirstateEntry> { | ||||
match self { | ||||
NodeData::Entry(entry) => Some(entry), | ||||
_ => None, | ||||
} | ||||
} | ||||
} | ||||
|
r47893 | impl<'on_disk> DirstateMap<'on_disk> { | ||
|
r48058 | pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self { | ||
Self { | ||||
on_disk, | ||||
root: ChildNodes::default(), | ||||
nodes_with_entry_count: 0, | ||||
nodes_with_copy_source_count: 0, | ||||
|
r48202 | ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN], | ||
|
r48481 | unreachable_bytes: 0, | ||
|
r48058 | } | ||
} | ||||
|
r48055 | #[timed] | ||
|
r48475 | pub fn new_v2( | ||
on_disk: &'on_disk [u8], | ||||
data_size: usize, | ||||
|
r48482 | metadata: &[u8], | ||
|
r48475 | ) -> Result<Self, DirstateError> { | ||
if let Some(data) = on_disk.get(..data_size) { | ||||
|
r48482 | Ok(on_disk::read(data, metadata)?) | ||
|
r48475 | } else { | ||
Err(DirstateV2ParseError.into()) | ||||
} | ||||
|
r48055 | } | ||
#[timed] | ||||
pub fn new_v1( | ||||
|
r47893 | on_disk: &'on_disk [u8], | ||
) -> Result<(Self, Option<DirstateParents>), DirstateError> { | ||||
|
r48058 | let mut map = Self::empty(on_disk); | ||
|
r48055 | if map.on_disk.is_empty() { | ||
return Ok((map, None)); | ||||
|
r47867 | } | ||
|
r47893 | |||
let parents = parse_dirstate_entries( | ||||
|
r48055 | map.on_disk, | ||
|
r47893 | |path, entry, copy_source| { | ||
|
r48834 | let tracked = entry.state().is_tracked(); | ||
|
r47896 | let node = Self::get_or_insert_node( | ||
|
r48127 | map.on_disk, | ||
|
r48481 | &mut map.unreachable_bytes, | ||
|
r48055 | &mut map.root, | ||
|
r47893 | path, | ||
|
r47896 | WithBasename::to_cow_borrowed, | ||
|
r47893 | |ancestor| { | ||
if tracked { | ||||
ancestor.tracked_descendants_count += 1 | ||||
} | ||||
|
r48272 | ancestor.descendants_with_entry_count += 1 | ||
|
r47893 | }, | ||
|
r48126 | )?; | ||
|
r47893 | assert!( | ||
|
r48137 | !node.data.has_entry(), | ||
|
r47893 | "duplicate dirstate entry in read" | ||
); | ||||
assert!( | ||||
node.copy_source.is_none(), | ||||
"duplicate dirstate entry in read" | ||||
); | ||||
|
r48137 | node.data = NodeData::Entry(*entry); | ||
|
r47895 | node.copy_source = copy_source.map(Cow::Borrowed); | ||
|
r48055 | map.nodes_with_entry_count += 1; | ||
|
r47893 | if copy_source.is_some() { | ||
|
r48055 | map.nodes_with_copy_source_count += 1 | ||
|
r47893 | } | ||
|
r48126 | Ok(()) | ||
|
r47893 | }, | ||
)?; | ||||
|
r48055 | let parents = Some(parents.clone()); | ||
|
r47893 | |||
|
r48055 | Ok((map, parents)) | ||
|
r47867 | } | ||
|
r48478 | /// Assuming dirstate-v2 format, returns whether the next write should | ||
/// append to the existing data file that contains `self.on_disk` (true), | ||||
/// or create a new data file from scratch (false). | ||||
pub(super) fn write_should_append(&self) -> bool { | ||||
|
r48481 | let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32; | ||
ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO | ||||
|
r48478 | } | ||
|
r48124 | fn get_node<'tree>( | ||
&'tree self, | ||||
path: &HgPath, | ||||
|
r48126 | ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> { | ||
|
r48124 | let mut children = self.root.as_ref(); | ||
|
r47869 | let mut components = path.components(); | ||
let mut component = | ||||
components.next().expect("expected at least one components"); | ||||
loop { | ||||
|
r48127 | if let Some(child) = children.get(component, self.on_disk)? { | ||
|
r48126 | if let Some(next_component) = components.next() { | ||
component = next_component; | ||||
|
r48127 | children = child.children(self.on_disk)?; | ||
|
r48126 | } else { | ||
return Ok(Some(child)); | ||||
} | ||||
|
r47869 | } else { | ||
|
r48126 | return Ok(None); | ||
|
r47869 | } | ||
} | ||||
} | ||||
|
r47876 | /// Returns a mutable reference to the node at `path` if it exists | ||
/// | ||||
|
r47873 | /// This takes `root` instead of `&mut self` so that callers can mutate | ||
/// other fields while the returned borrow is still valid | ||||
|
r47874 | fn get_node_mut<'tree>( | ||
|
r48127 | on_disk: &'on_disk [u8], | ||
|
r48481 | unreachable_bytes: &mut u32, | ||
|
r47895 | root: &'tree mut ChildNodes<'on_disk>, | ||
|
r47874 | path: &HgPath, | ||
|
r48126 | ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> { | ||
|
r47874 | let mut children = root; | ||
let mut components = path.components(); | ||||
let mut component = | ||||
components.next().expect("expected at least one components"); | ||||
loop { | ||||
|
r48481 | if let Some(child) = children | ||
.make_mut(on_disk, unreachable_bytes)? | ||||
.get_mut(component) | ||||
|
r48127 | { | ||
|
r48126 | if let Some(next_component) = components.next() { | ||
component = next_component; | ||||
children = &mut child.children; | ||||
} else { | ||||
return Ok(Some(child)); | ||||
} | ||||
|
r47874 | } else { | ||
|
r48126 | return Ok(None); | ||
|
r47874 | } | ||
} | ||||
} | ||||
|
r48268 | pub(super) fn get_or_insert<'tree, 'path>( | ||
&'tree mut self, | ||||
path: &HgPath, | ||||
) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> { | ||||
Self::get_or_insert_node( | ||||
self.on_disk, | ||||
|
r48481 | &mut self.unreachable_bytes, | ||
|
r48268 | &mut self.root, | ||
path, | ||||
WithBasename::to_cow_owned, | ||||
|_| {}, | ||||
) | ||||
} | ||||
|
r48481 | fn get_or_insert_node<'tree, 'path>( | ||
|
r48127 | on_disk: &'on_disk [u8], | ||
|
r48481 | unreachable_bytes: &mut u32, | ||
|
r47895 | root: &'tree mut ChildNodes<'on_disk>, | ||
|
r47896 | path: &'path HgPath, | ||
to_cow: impl Fn( | ||||
WithBasename<&'path HgPath>, | ||||
) -> WithBasename<Cow<'on_disk, HgPath>>, | ||||
|
r47890 | mut each_ancestor: impl FnMut(&mut Node), | ||
|
r48126 | ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> { | ||
|
r47873 | let mut child_nodes = root; | ||
|
r47867 | let mut inclusive_ancestor_paths = | ||
WithBasename::inclusive_ancestors_of(path); | ||||
let mut ancestor_path = inclusive_ancestor_paths | ||||
.next() | ||||
.expect("expected at least one inclusive ancestor"); | ||||
loop { | ||||
|
r47886 | // TODO: can we avoid allocating an owned key in cases where the | ||
// map already contains that key, without introducing double | ||||
// lookup? | ||||
|
r48124 | let child_node = child_nodes | ||
|
r48481 | .make_mut(on_disk, unreachable_bytes)? | ||
|
r48124 | .entry(to_cow(ancestor_path)) | ||
.or_default(); | ||||
|
r47867 | if let Some(next) = inclusive_ancestor_paths.next() { | ||
|
r47890 | each_ancestor(child_node); | ||
|
r47867 | ancestor_path = next; | ||
child_nodes = &mut child_node.children; | ||||
} else { | ||||
|
r48126 | return Ok(child_node); | ||
|
r47867 | } | ||
} | ||||
|
r47865 | } | ||
|
r47870 | |||
|
r47890 | fn add_or_remove_file( | ||
|
r47873 | &mut self, | ||
path: &HgPath, | ||||
|
r48838 | old_state: Option<EntryState>, | ||
|
r47873 | new_entry: DirstateEntry, | ||
|
r48126 | ) -> Result<(), DirstateV2ParseError> { | ||
|
r48838 | let had_entry = old_state.is_some(); | ||
let was_tracked = old_state.map_or(false, |s| s.is_tracked()); | ||||
|
r47876 | let tracked_count_increment = | ||
|
r48838 | match (was_tracked, new_entry.state().is_tracked()) { | ||
|
r47876 | (false, true) => 1, | ||
(true, false) => -1, | ||||
_ => 0, | ||||
}; | ||||
|
r47896 | let node = Self::get_or_insert_node( | ||
|
r48127 | self.on_disk, | ||
|
r48481 | &mut self.unreachable_bytes, | ||
|
r47890 | &mut self.root, | ||
path, | ||||
|
r47896 | WithBasename::to_cow_owned, | ||
|
r47890 | |ancestor| { | ||
|
r48272 | if !had_entry { | ||
ancestor.descendants_with_entry_count += 1; | ||||
} | ||||
|
r47890 | // We can’t use `+= increment` because the counter is unsigned, | ||
// and we want debug builds to detect accidental underflow | ||||
// through zero | ||||
match tracked_count_increment { | ||||
1 => ancestor.tracked_descendants_count += 1, | ||||
-1 => ancestor.tracked_descendants_count -= 1, | ||||
_ => {} | ||||
} | ||||
}, | ||||
|
r48126 | )?; | ||
|
r48272 | if !had_entry { | ||
|
r47890 | self.nodes_with_entry_count += 1 | ||
|
r47873 | } | ||
|
r48137 | node.data = NodeData::Entry(new_entry); | ||
|
r48126 | Ok(()) | ||
|
r47873 | } | ||
|
r48124 | fn iter_nodes<'tree>( | ||
&'tree self, | ||||
|
r48126 | ) -> impl Iterator< | ||
Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>, | ||||
> + 'tree { | ||||
|
r47870 | // Depth first tree traversal. | ||
// | ||||
// If we could afford internal iteration and recursion, | ||||
// this would look like: | ||||
// | ||||
// ``` | ||||
// fn traverse_children( | ||||
// children: &ChildNodes, | ||||
// each: &mut impl FnMut(&Node), | ||||
// ) { | ||||
// for child in children.values() { | ||||
// traverse_children(&child.children, each); | ||||
// each(child); | ||||
// } | ||||
// } | ||||
// ``` | ||||
// | ||||
// However we want an external iterator and therefore can’t use the | ||||
// call stack. Use an explicit stack instead: | ||||
let mut stack = Vec::new(); | ||||
|
r48124 | let mut iter = self.root.as_ref().iter(); | ||
|
r47870 | std::iter::from_fn(move || { | ||
|
r48124 | while let Some(child_node) = iter.next() { | ||
|
r48127 | let children = match child_node.children(self.on_disk) { | ||
|
r48126 | Ok(children) => children, | ||
Err(error) => return Some(Err(error)), | ||||
}; | ||||
|
r47870 | // Pseudo-recursion | ||
|
r48126 | let new_iter = children.iter(); | ||
|
r47870 | let old_iter = std::mem::replace(&mut iter, new_iter); | ||
|
r48124 | stack.push((child_node, old_iter)); | ||
|
r47870 | } | ||
// Found the end of a `children.iter()` iterator. | ||||
|
r48124 | if let Some((child_node, next_iter)) = stack.pop() { | ||
|
r47870 | // "Return" from pseudo-recursion by restoring state from the | ||
// explicit stack | ||||
iter = next_iter; | ||||
|
r48126 | Some(Ok(child_node)) | ||
|
r47870 | } else { | ||
// Reached the bottom of the stack, we’re done | ||||
None | ||||
} | ||||
}) | ||||
} | ||||
|
r48481 | fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) { | ||
if let Cow::Borrowed(path) = path { | ||||
*unreachable_bytes += path.len() as u32 | ||||
} | ||||
} | ||||
|
r48126 | } | ||
/// 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<Item = Result<B, E>> + 'a | ||||
where | ||||
I: Iterator<Item = Result<A, E>> + 'a, | ||||
F: Fn(A) -> Result<Option<B>, E> + 'a, | ||||
{ | ||||
iter.filter_map(move |result| match result { | ||||
Ok(node) => f(node).transpose(), | ||||
Err(e) => Some(Err(e)), | ||||
}) | ||||
|
r47865 | } | ||
|
r48883 | 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; | ||||
|
r47865 | } | ||
|
r48883 | pub fn set_entry( | ||
|
r48861 | &mut self, | ||
filename: &HgPath, | ||||
entry: DirstateEntry, | ||||
) -> Result<(), DirstateV2ParseError> { | ||||
|
r48883 | let map = self.get_map_mut(); | ||
map.get_or_insert(&filename)?.data = NodeData::Entry(entry); | ||||
|
r48861 | Ok(()) | ||
r48492 | } | |||
|
r48883 | pub fn add_file( | ||
|
r47865 | &mut self, | ||
|
r47877 | filename: &HgPath, | ||
entry: DirstateEntry, | ||||
|
r48126 | ) -> Result<(), DirstateError> { | ||
|
r48838 | let old_state = self.get(filename)?.map(|e| e.state()); | ||
|
r48883 | let map = self.get_map_mut(); | ||
Ok(map.add_or_remove_file(filename, old_state, entry)?) | ||||
|
r47865 | } | ||
|
r48883 | pub fn remove_file( | ||
|
r47865 | &mut self, | ||
|
r47877 | filename: &HgPath, | ||
r48300 | in_merge: bool, | |||
|
r48126 | ) -> Result<(), DirstateError> { | ||
r48300 | let old_entry_opt = self.get(filename)?; | |||
|
r48838 | let old_state = old_entry_opt.map(|e| e.state()); | ||
r48300 | let mut size = 0; | |||
if in_merge { | ||||
// XXX we should not be able to have 'm' state and 'FROM_P2' if not | ||||
// during a merge. So I (marmoute) am not sure we need the | ||||
// conditionnal at all. Adding double checking this with assert | ||||
// would be nice. | ||||
if let Some(old_entry) = old_entry_opt { | ||||
// backup the previous state | ||||
|
r48834 | if old_entry.state() == EntryState::Merged { | ||
r48300 | size = SIZE_NON_NORMAL; | |||
|
r48834 | } else if old_entry.state() == EntryState::Normal | ||
&& old_entry.size() == SIZE_FROM_OTHER_PARENT | ||||
r48300 | { | |||
// other parent | ||||
size = SIZE_FROM_OTHER_PARENT; | ||||
} | ||||
} | ||||
} | ||||
if size == 0 { | ||||
self.copy_map_remove(filename)?; | ||||
} | ||||
|
r48883 | let map = self.get_map_mut(); | ||
|
r48834 | let entry = DirstateEntry::new_removed(size); | ||
|
r48883 | Ok(map.add_or_remove_file(filename, old_state, entry)?) | ||
|
r47865 | } | ||
|
r48883 | pub fn drop_entry_and_copy_source( | ||
|
r48864 | &mut self, | ||
filename: &HgPath, | ||||
) -> Result<(), DirstateError> { | ||||
|
r48838 | let was_tracked = self | ||
.get(filename)? | ||||
.map_or(false, |e| e.state().is_tracked()); | ||||
|
r48883 | let map = self.get_map_mut(); | ||
|
r47963 | struct Dropped { | ||
was_tracked: bool, | ||||
had_entry: bool, | ||||
had_copy_source: bool, | ||||
} | ||||
|
r48141 | |||
/// If this returns `Ok(Some((dropped, removed)))`, then | ||||
/// | ||||
/// * `dropped` is about the leaf node that was at `filename` | ||||
/// * `removed` is whether this particular level of recursion just | ||||
/// removed a node in `nodes`. | ||||
|
r48127 | fn recur<'on_disk>( | ||
on_disk: &'on_disk [u8], | ||||
|
r48481 | unreachable_bytes: &mut u32, | ||
|
r48127 | nodes: &mut ChildNodes<'on_disk>, | ||
|
r48126 | path: &HgPath, | ||
|
r48141 | ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> { | ||
|
r47963 | let (first_path_component, rest_of_path) = | ||
path.split_first_component(); | ||||
|
r48481 | let nodes = nodes.make_mut(on_disk, unreachable_bytes)?; | ||
let node = if let Some(node) = nodes.get_mut(first_path_component) | ||||
|
r48126 | { | ||
node | ||||
} else { | ||||
return Ok(None); | ||||
}; | ||||
|
r47963 | let dropped; | ||
if let Some(rest) = rest_of_path { | ||||
|
r48481 | if let Some((d, removed)) = recur( | ||
on_disk, | ||||
unreachable_bytes, | ||||
&mut node.children, | ||||
rest, | ||||
)? { | ||||
|
r48126 | dropped = d; | ||
|
r48272 | if dropped.had_entry { | ||
node.descendants_with_entry_count -= 1; | ||||
} | ||||
|
r48126 | if dropped.was_tracked { | ||
node.tracked_descendants_count -= 1; | ||||
} | ||||
|
r48141 | |||
// Directory caches must be invalidated when removing a | ||||
// child node | ||||
if removed { | ||||
if let NodeData::CachedDirectory { .. } = &node.data { | ||||
node.data = NodeData::None | ||||
} | ||||
} | ||||
|
r48126 | } else { | ||
return Ok(None); | ||||
|
r47890 | } | ||
|
r47963 | } else { | ||
|
r48137 | let had_entry = node.data.has_entry(); | ||
if had_entry { | ||||
node.data = NodeData::None | ||||
} | ||||
|
r48481 | if let Some(source) = &node.copy_source { | ||
|
r48864 | DirstateMap::count_dropped_path(unreachable_bytes, source); | ||
node.copy_source = None | ||||
|
r48481 | } | ||
|
r47963 | dropped = Dropped { | ||
was_tracked: node | ||||
|
r48137 | .data | ||
.as_entry() | ||||
|
r48834 | .map_or(false, |entry| entry.state().is_tracked()), | ||
|
r48137 | had_entry, | ||
|
r47963 | had_copy_source: node.copy_source.take().is_some(), | ||
}; | ||||
|
r47964 | } | ||
// After recursion, for both leaf (rest_of_path is None) nodes and | ||||
// parent nodes, remove a node if it just became empty. | ||||
|
r48141 | let remove = !node.data.has_entry() | ||
|
r47964 | && node.copy_source.is_none() | ||
|
r48141 | && node.children.is_empty(); | ||
if remove { | ||||
|
r48481 | let (key, _) = | ||
nodes.remove_entry(first_path_component).unwrap(); | ||||
DirstateMap::count_dropped_path( | ||||
unreachable_bytes, | ||||
key.full_path(), | ||||
) | ||||
|
r47963 | } | ||
|
r48141 | Ok(Some((dropped, remove))) | ||
|
r47963 | } | ||
|
r47877 | |||
|
r48481 | if let Some((dropped, _removed)) = recur( | ||
|
r48883 | map.on_disk, | ||
&mut map.unreachable_bytes, | ||||
&mut map.root, | ||||
|
r48481 | filename, | ||
)? { | ||||
|
r47963 | if dropped.had_entry { | ||
|
r48883 | map.nodes_with_entry_count -= 1 | ||
|
r47877 | } | ||
|
r47963 | if dropped.had_copy_source { | ||
|
r48883 | map.nodes_with_copy_source_count -= 1 | ||
|
r47877 | } | ||
} else { | ||||
|
r48838 | debug_assert!(!was_tracked); | ||
|
r47877 | } | ||
|
r48862 | Ok(()) | ||
|
r47865 | } | ||
|
r48883 | pub fn has_tracked_dir( | ||
|
r47865 | &mut self, | ||
|
r47876 | directory: &HgPath, | ||
|
r48126 | ) -> Result<bool, DirstateError> { | ||
|
r48883 | let map = self.get_map_mut(); | ||
if let Some(node) = map.get_node(directory)? { | ||||
|
r47876 | // A node without a `DirstateEntry` was created to hold child | ||
// nodes, and is therefore a directory. | ||||
|
r48137 | let state = node.state()?; | ||
Ok(state.is_none() && node.tracked_descendants_count() > 0) | ||||
|
r47876 | } else { | ||
Ok(false) | ||||
} | ||||
|
r47865 | } | ||
|
r48883 | 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)? { | ||||
|
r47876 | // A node without a `DirstateEntry` was created to hold child | ||
// nodes, and is therefore a directory. | ||||
|
r48272 | let state = node.state()?; | ||
Ok(state.is_none() && node.descendants_with_entry_count() > 0) | ||||
|
r47876 | } else { | ||
Ok(false) | ||||
} | ||||
|
r47865 | } | ||
|
r48055 | #[timed] | ||
|
r48883 | pub fn pack_v1( | ||
|
r49244 | &self, | ||
|
r47872 | parents: DirstateParents, | ||
|
r47865 | ) -> Result<Vec<u8>, DirstateError> { | ||
|
r49244 | let map = self.get_map(); | ||
|
r47872 | // Optizimation (to be measured?): pre-compute size to avoid `Vec` | ||
// reallocations | ||||
let mut size = parents.as_bytes().len(); | ||||
|
r48883 | for node in map.iter_nodes() { | ||
|
r48126 | let node = node?; | ||
r49221 | if node.entry()?.is_some() { | |||
|
r48127 | size += packed_entry_size( | ||
|
r48883 | node.full_path(map.on_disk)?, | ||
node.copy_source(map.on_disk)?, | ||||
|
r48127 | ); | ||
|
r47872 | } | ||
} | ||||
let mut packed = Vec::with_capacity(size); | ||||
packed.extend(parents.as_bytes()); | ||||
|
r48883 | for node in map.iter_nodes() { | ||
|
r48126 | let node = node?; | ||
if let Some(entry) = node.entry()? { | ||||
|
r47872 | pack_entry( | ||
|
r48883 | node.full_path(map.on_disk)?, | ||
|
r48124 | &entry, | ||
|
r48883 | node.copy_source(map.on_disk)?, | ||
|
r47872 | &mut packed, | ||
); | ||||
} | ||||
} | ||||
Ok(packed) | ||||
|
r47865 | } | ||
|
r48482 | /// Returns new data and metadata together with whether that data should be | ||
/// appended to the existing data file whose content is at | ||||
|
r48883 | /// `map.on_disk` (true), instead of written to a new data file | ||
|
r48482 | /// (false). | ||
|
r48055 | #[timed] | ||
|
r48883 | pub fn pack_v2( | ||
|
r49244 | &self, | ||
|
r48478 | can_append: bool, | ||
|
r49249 | ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> { | ||
|
r49244 | let map = self.get_map(); | ||
|
r48883 | on_disk::write(map, can_append) | ||
|
r48055 | } | ||
|
r48883 | pub fn status<'a>( | ||
|
r47882 | &'a mut self, | ||
matcher: &'a (dyn Matcher + Sync), | ||||
root_dir: PathBuf, | ||||
ignore_files: Vec<PathBuf>, | ||||
options: StatusOptions, | ||||
|
r47880 | ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError> | ||
{ | ||||
|
r48883 | let map = self.get_map_mut(); | ||
super::status::status(map, matcher, root_dir, ignore_files, options) | ||||
|
r47865 | } | ||
|
r48883 | pub fn copy_map_len(&self) -> usize { | ||
let map = self.get_map(); | ||||
map.nodes_with_copy_source_count as usize | ||||
|
r47865 | } | ||
|
r48883 | pub fn copy_map_iter(&self) -> CopyMapIter<'_> { | ||
let map = self.get_map(); | ||||
Box::new(filter_map_results(map.iter_nodes(), move |node| { | ||||
Ok(if let Some(source) = node.copy_source(map.on_disk)? { | ||||
Some((node.full_path(map.on_disk)?, source)) | ||||
|
r48126 | } else { | ||
None | ||||
}) | ||||
|
r47870 | })) | ||
|
r47865 | } | ||
|
r48883 | pub fn copy_map_contains_key( | ||
|
r48126 | &self, | ||
key: &HgPath, | ||||
) -> Result<bool, DirstateV2ParseError> { | ||||
|
r48883 | let map = self.get_map(); | ||
Ok(if let Some(node) = map.get_node(key)? { | ||||
|
r48126 | node.has_copy_source() | ||
|
r47869 | } else { | ||
false | ||||
|
r48126 | }) | ||
|
r47865 | } | ||
|
r48883 | pub fn copy_map_get( | ||
|
r48126 | &self, | ||
key: &HgPath, | ||||
) -> Result<Option<&HgPath>, DirstateV2ParseError> { | ||||
|
r48883 | let map = self.get_map(); | ||
if let Some(node) = map.get_node(key)? { | ||||
if let Some(source) = node.copy_source(map.on_disk)? { | ||||
|
r48126 | return Ok(Some(source)); | ||
} | ||||
} | ||||
Ok(None) | ||||
|
r47865 | } | ||
|
r48883 | pub fn copy_map_remove( | ||
|
r48126 | &mut self, | ||
key: &HgPath, | ||||
) -> Result<Option<HgPathBuf>, DirstateV2ParseError> { | ||||
|
r48883 | 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, | ||||
|
r48481 | unreachable_bytes, | ||
|
r48883 | &mut map.root, | ||
|
r48481 | key, | ||
)? | ||||
.and_then(|node| { | ||||
if let Some(source) = &node.copy_source { | ||||
*count -= 1; | ||||
|
r48883 | DirstateMap::count_dropped_path(unreachable_bytes, source); | ||
|
r48481 | } | ||
node.copy_source.take().map(Cow::into_owned) | ||||
})) | ||||
|
r47865 | } | ||
|
r48883 | pub fn copy_map_insert( | ||
|
r47865 | &mut self, | ||
|
r47874 | key: HgPathBuf, | ||
value: HgPathBuf, | ||||
|
r48126 | ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> { | ||
|
r48883 | let map = self.get_map_mut(); | ||
let node = DirstateMap::get_or_insert_node( | ||||
map.on_disk, | ||||
&mut map.unreachable_bytes, | ||||
&mut map.root, | ||||
|
r47896 | &key, | ||
WithBasename::to_cow_owned, | ||||
|_ancestor| {}, | ||||
|
r48126 | )?; | ||
|
r47874 | if node.copy_source.is_none() { | ||
|
r48883 | map.nodes_with_copy_source_count += 1 | ||
|
r47874 | } | ||
|
r48126 | Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) | ||
|
r47865 | } | ||
|
r48883 | pub fn len(&self) -> usize { | ||
let map = self.get_map(); | ||||
map.nodes_with_entry_count as usize | ||||
|
r47865 | } | ||
|
r48883 | pub fn contains_key( | ||
|
r48126 | &self, | ||
key: &HgPath, | ||||
) -> Result<bool, DirstateV2ParseError> { | ||||
Ok(self.get(key)?.is_some()) | ||||
|
r47865 | } | ||
|
r48883 | pub fn get( | ||
|
r48126 | &self, | ||
key: &HgPath, | ||||
) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ||||
|
r48883 | let map = self.get_map(); | ||
Ok(if let Some(node) = map.get_node(key)? { | ||||
|
r48126 | node.entry()? | ||
} else { | ||||
None | ||||
}) | ||||
|
r47865 | } | ||
|
r48883 | pub fn iter(&self) -> StateMapIter<'_> { | ||
let map = self.get_map(); | ||||
Box::new(filter_map_results(map.iter_nodes(), move |node| { | ||||
|
r48126 | Ok(if let Some(entry) = node.entry()? { | ||
|
r48883 | Some((node.full_path(map.on_disk)?, entry)) | ||
|
r48126 | } else { | ||
None | ||||
}) | ||||
|
r47870 | })) | ||
|
r47865 | } | ||
|
r48140 | |||
|
r48883 | pub fn iter_tracked_dirs( | ||
|
r48483 | &mut self, | ||
) -> Result< | ||||
Box< | ||||
dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> | ||||
+ Send | ||||
+ '_, | ||||
>, | ||||
DirstateError, | ||||
> { | ||||
|
r48883 | let map = self.get_map_mut(); | ||
let on_disk = map.on_disk; | ||||
|
r48483 | Ok(Box::new(filter_map_results( | ||
|
r48883 | map.iter_nodes(), | ||
|
r48483 | move |node| { | ||
Ok(if node.tracked_descendants_count() > 0 { | ||||
Some(node.full_path(on_disk)?) | ||||
} else { | ||||
None | ||||
}) | ||||
}, | ||||
))) | ||||
} | ||||
|
r48883 | pub fn debug_iter( | ||
|
r48140 | &self, | ||
|
r48835 | all: bool, | ||
|
r48140 | ) -> Box< | ||
dyn Iterator< | ||||
Item = Result< | ||||
|
r48483 | (&HgPath, (u8, i32, i32, i32)), | ||
|
r48140 | DirstateV2ParseError, | ||
>, | ||||
> + Send | ||||
+ '_, | ||||
> { | ||||
|
r48883 | let map = self.get_map(); | ||
Box::new(filter_map_results(map.iter_nodes(), move |node| { | ||||
|
r48483 | let debug_tuple = if let Some(entry) = node.entry()? { | ||
entry.debug_tuple() | ||||
|
r48835 | } else if !all { | ||
return Ok(None); | ||||
|
r49007 | } else if let Some(mtime) = node.cached_directory_mtime()? { | ||
(b' ', 0, -1, mtime.truncated_seconds() as i32) | ||||
|
r48140 | } else { | ||
|
r48483 | (b' ', 0, -1, -1) | ||
}; | ||||
|
r48883 | Ok(Some((node.full_path(map.on_disk)?, debug_tuple))) | ||
|
r48140 | })) | ||
} | ||||
|
r47865 | } | ||