##// END OF EJS Templates
rust-dirstatemap: add unit tests...
rust-dirstatemap: add unit tests These were missing and have already proven valuable since they have found two bugs (fixed in previous patches). There may be other behavior to test, but this gives us a decent coverage. Differential Revision: https://phab.mercurial-scm.org/D12524

File last commit:

r50017:e7b74bb6 default
r50017:e7b74bb6 default
Show More
dirstate_map.rs
1849 lines | 62.0 KiB | application/rls-services+xml | RustLexer
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 use bytes_cast::BytesCast;
Simon Sapin
dirstate-tree: Add #[timed] attribute to `status` and `DirstateMap::read`...
r47888 use micro_timer::timed;
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 use std::borrow::Cow;
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 use std::path::PathBuf;
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 use super::on_disk;
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 use super::on_disk::DirstateV2ParseError;
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 use super::owning::OwningDirstateMap;
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 use super::path_with_basename::WithBasename;
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 use crate::dirstate::parsers::pack_entry;
use crate::dirstate::parsers::packed_entry_size;
Simon Sapin
dirstate-tree: Add parsing only dirstate parents from disk...
r47868 use crate::dirstate::parsers::parse_dirstate_entries;
Simon Sapin
dirstate: Remove the flat Rust DirstateMap implementation...
r48882 use crate::dirstate::CopyMapIter;
Raphaël Gomès
rust-dirstatemap: add Rust implementation of `reset_state`...
r49992 use crate::dirstate::DirstateV2Data;
use crate::dirstate::ParentFileData;
Simon Sapin
dirstate: Remove the flat Rust DirstateMap implementation...
r48882 use crate::dirstate::StateMapIter;
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 use crate::dirstate::TruncatedTimestamp;
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 use crate::matchers::Matcher;
use crate::utils::hg_path::{HgPath, HgPathBuf};
use crate::DirstateEntry;
use crate::DirstateError;
Raphaël Gomès
rust-dirstatemap: add `set_clean` method...
r49995 use crate::DirstateMapError;
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 use crate::DirstateParents;
use crate::DirstateStatus;
use crate::EntryState;
Simon Sapin
dirstate-tree: optimize HashMap lookups with raw_entry_mut...
r49805 use crate::FastHashbrownMap as FastHashMap;
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 use crate::PatternFileWarning;
use crate::StatusError;
use crate::StatusOptions;
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
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;
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Debug)]
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 pub struct DirstateMap<'on_disk> {
/// Contents of the `.hg/dirstate` file
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 pub(super) on_disk: &'on_disk [u8],
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 pub(super) root: ChildNodes<'on_disk>,
Simon Sapin
dirstate-tree: Maintain a counter of DirstateEntry’s and copy sources...
r47873
/// Number of nodes anywhere in the tree that have `.entry.is_some()`.
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 pub(super) nodes_with_entry_count: u32,
Simon Sapin
dirstate-tree: Maintain a counter of DirstateEntry’s and copy sources...
r47873
/// Number of nodes anywhere in the tree that have
/// `.copy_source.is_some()`.
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 pub(super) nodes_with_copy_source_count: u32,
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202
/// See on_disk::Header
pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481
/// How many bytes of `on_disk` are not used anymore
pub(super) unreachable_bytes: u32,
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
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
Simon Sapin
dirstate-tree: Extract into a method sorting children of a given node...
r48057 /// names. However `HashMap` would waste time always re-hashing the same
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 /// string prefix.
Simon Sapin
dirstate-tree: Extract into a method sorting children of a given node...
r48057 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
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.
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Debug)]
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 pub(super) enum BorrowedPath<'tree, 'on_disk> {
InMemory(&'tree HgPathBuf),
OnDisk(&'on_disk HgPath),
}
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Debug)]
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 pub(super) enum ChildNodes<'on_disk> {
InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 OnDisk(&'on_disk [on_disk::Node]),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Debug)]
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 OnDisk(&'on_disk [on_disk::Node]),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Debug)]
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 pub(super) enum NodeRef<'tree, 'on_disk> {
InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 OnDisk(&'on_disk on_disk::Node),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
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,
}
}
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
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),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
pub(super) fn is_empty(&self) -> bool {
match self {
ChildNodes::InMemory(nodes) => nodes.is_empty(),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 fn make_mut(
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 &mut self,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 unreachable_bytes: &mut u32,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<
&mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
DirstateV2ParseError,
> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ChildNodes::InMemory(nodes) => Ok(nodes),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 ChildNodes::OnDisk(nodes) => {
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 *unreachable_bytes +=
std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
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!(),
}
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
}
impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
pub(super) fn get(
&self,
base_name: &HgPath,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ChildNodesRef::InMemory(nodes) => Ok(nodes
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 .get_key_value(base_name)
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 .map(|(k, v)| NodeRef::InMemory(k, v))),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
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]))
})
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
/// Iterate in undefined order
pub(super) fn iter(
&self,
) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
match self {
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
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))
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
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 {
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
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),
),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
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();
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
match node {
NodeRef::InMemory(path, _node) => path.base_name(),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(_) => unreachable!(),
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 }
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
// value: https://github.com/rust-lang/rust/issues/34162
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 vec
}
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 ChildNodesRef::OnDisk(nodes) => {
// Nodes on disk are already sorted
nodes.iter().map(NodeRef::OnDisk).collect()
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
}
impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 pub(super) fn full_path(
&self,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => node.full_path(on_disk),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
/// HgPath>` detached from `'tree`
pub(super) fn full_path_borrowed(
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 &self,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
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)),
},
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 }
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 }
}
pub(super) fn base_name(
&self,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
match self {
NodeRef::InMemory(path, _node) => Ok(path.base_name()),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => node.base_name(on_disk),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 pub(super) fn children(
&self,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => {
Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 pub(super) fn has_copy_source(&self) -> bool {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => node.has_copy_source(),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 pub(super) fn copy_source(
&self,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
NodeRef::InMemory(_path, node) => {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Ok(node.copy_source.as_ref().map(|s| &**s))
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => node.copy_source(on_disk),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
rhg: Add support for `rhg status --copies`...
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)),
})
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 pub(super) fn entry(
&self,
) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match self {
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 NodeRef::InMemory(_path, node) => {
Ok(node.data.as_entry().copied())
}
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => node.entry(),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126
pub(super) fn state(
&self,
) -> Result<Option<EntryState>, DirstateV2ParseError> {
Raphaël Gomès
rust-dirstate: don't return a state for untracked entries...
r49921 Ok(self.entry()?.and_then(|e| {
if e.any_tracked() {
Some(e.state())
} else {
None
}
}))
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 pub(super) fn cached_directory_mtime(
&self,
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 match self {
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 NodeRef::InMemory(_path, node) => Ok(match node.data {
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 NodeData::CachedDirectory { mtime } => Some(mtime),
_ => None,
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 }),
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
}
}
Simon Sapin
dirstate-tree: Keep a counter of descendant nodes that have an entry...
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(),
}
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 pub(super) fn tracked_descendants_count(&self) -> u32 {
match self {
NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
Simon Sapin
dirstate-v2: Parse the dirstate lazily, with copy-on-write nodes...
r48128 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
}
}
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876 /// Represents a file or a directory
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Default, Debug)]
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 pub(super) struct Node<'on_disk> {
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 pub(super) data: NodeData,
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 pub(super) children: ChildNodes<'on_disk>,
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876
Simon Sapin
dirstate-tree: Keep a counter of descendant nodes that have an entry...
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".
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 pub(super) tracked_descendants_count: u32,
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876 }
Raphaël Gomès
rust: add `Debug` trait to a bunch of structs...
r50016 #[derive(Debug)]
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 pub(super) enum NodeData {
Entry(DirstateEntry),
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 CachedDirectory { mtime: TruncatedTimestamp },
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
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,
}
}
Raphaël Gomès
rust-dirstatemap: add `set_possibly_dirty` method...
r49997
fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
match self {
NodeData::Entry(entry) => Some(entry),
_ => None,
}
}
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 }
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 impl<'on_disk> DirstateMap<'on_disk> {
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
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,
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 unreachable_bytes: 0,
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 }
}
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 #[timed]
Simon Sapin
dirstate-v2: Enforce data size read from the docket file...
r48475 pub fn new_v2(
on_disk: &'on_disk [u8],
data_size: usize,
Simon Sapin
dirstate-v2: Move fixed-size tree metadata into the docket file...
r48482 metadata: &[u8],
Simon Sapin
dirstate-v2: Enforce data size read from the docket file...
r48475 ) -> Result<Self, DirstateError> {
if let Some(data) = on_disk.get(..data_size) {
Simon Sapin
dirstate-v2: Move fixed-size tree metadata into the docket file...
r48482 Ok(on_disk::read(data, metadata)?)
Simon Sapin
dirstate-v2: Enforce data size read from the docket file...
r48475 } else {
Err(DirstateV2ParseError.into())
}
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 }
#[timed]
pub fn new_v1(
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 on_disk: &'on_disk [u8],
) -> Result<(Self, Option<DirstateParents>), DirstateError> {
Simon Sapin
dirstate-v2: Change the on-disk format to be tree-shaped...
r48058 let mut map = Self::empty(on_disk);
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 if map.on_disk.is_empty() {
return Ok((map, None));
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 }
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893
let parents = parse_dirstate_entries(
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 map.on_disk,
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 |path, entry, copy_source| {
Simon Sapin
rust: Make the fields of DirstateEntry private...
r48834 let tracked = entry.state().is_tracked();
Simon Sapin
dirstate-tree: Borrow paths from the "on disk" bytes...
r47896 let node = Self::get_or_insert_node(
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 map.on_disk,
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 &mut map.unreachable_bytes,
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 &mut map.root,
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 path,
Simon Sapin
dirstate-tree: Borrow paths from the "on disk" bytes...
r47896 WithBasename::to_cow_borrowed,
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 |ancestor| {
if tracked {
ancestor.tracked_descendants_count += 1
}
Simon Sapin
dirstate-tree: Keep a counter of descendant nodes that have an entry...
r48272 ancestor.descendants_with_entry_count += 1
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 },
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 )?;
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 assert!(
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 !node.data.has_entry(),
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 "duplicate dirstate entry in read"
);
assert!(
node.copy_source.is_none(),
"duplicate dirstate entry in read"
);
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 node.data = NodeData::Entry(*entry);
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 node.copy_source = copy_source.map(Cow::Borrowed);
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 map.nodes_with_entry_count += 1;
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 if copy_source.is_some() {
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 map.nodes_with_copy_source_count += 1
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 }
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Ok(())
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893 },
)?;
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 let parents = Some(parents.clone());
Simon Sapin
dirstate-tree: Make `DirstateMap` borrow from a bytes buffer...
r47893
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 Ok((map, parents))
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 }
Simon Sapin
dirstate-v2: Support appending to the same data file...
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 {
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
Simon Sapin
dirstate-v2: Support appending to the same data file...
r48478 }
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 fn get_node<'tree>(
&'tree self,
path: &HgPath,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 let mut children = self.root.as_ref();
Simon Sapin
dirstate-tree: Add map `get` and `contains_key` methods...
r47869 let mut components = path.components();
let mut component =
components.next().expect("expected at least one components");
loop {
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 if let Some(child) = children.get(component, self.on_disk)? {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 if let Some(next_component) = components.next() {
component = next_component;
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 children = child.children(self.on_disk)?;
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 } else {
return Ok(Some(child));
}
Simon Sapin
dirstate-tree: Add map `get` and `contains_key` methods...
r47869 } else {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 return Ok(None);
Simon Sapin
dirstate-tree: Add map `get` and `contains_key` methods...
r47869 }
}
}
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876 /// Returns a mutable reference to the node at `path` if it exists
///
Simon Sapin
dirstate-tree: Maintain a counter of DirstateEntry’s and copy sources...
r47873 /// This takes `root` instead of `&mut self` so that callers can mutate
/// other fields while the returned borrow is still valid
Simon Sapin
dirstate-tree: Add copy_map_insert and copy_map_remove...
r47874 fn get_node_mut<'tree>(
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 unreachable_bytes: &mut u32,
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 root: &'tree mut ChildNodes<'on_disk>,
Simon Sapin
dirstate-tree: Add copy_map_insert and copy_map_remove...
r47874 path: &HgPath,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add copy_map_insert and copy_map_remove...
r47874 let mut children = root;
let mut components = path.components();
let mut component =
components.next().expect("expected at least one components");
loop {
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 if let Some(child) = children
.make_mut(on_disk, unreachable_bytes)?
.get_mut(component)
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 if let Some(next_component) = components.next() {
component = next_component;
children = &mut child.children;
} else {
return Ok(Some(child));
}
Simon Sapin
dirstate-tree: Add copy_map_insert and copy_map_remove...
r47874 } else {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 return Ok(None);
Simon Sapin
dirstate-tree: Add copy_map_insert and copy_map_remove...
r47874 }
}
}
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
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,
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 &mut self.unreachable_bytes,
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 &mut self.root,
path,
WithBasename::to_cow_owned,
|_| {},
)
}
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 fn get_or_insert_node<'tree, 'path>(
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 unreachable_bytes: &mut u32,
Simon Sapin
dirstate-tree: Borrow copy source paths from the "on disk" bytes...
r47895 root: &'tree mut ChildNodes<'on_disk>,
Simon Sapin
dirstate-tree: Borrow paths from the "on disk" bytes...
r47896 path: &'path HgPath,
to_cow: impl Fn(
WithBasename<&'path HgPath>,
) -> WithBasename<Cow<'on_disk, HgPath>>,
Simon Sapin
dirstate-tree: Fold "tracked descendants" counter update in main walk...
r47890 mut each_ancestor: impl FnMut(&mut Node),
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Maintain a counter of DirstateEntry’s and copy sources...
r47873 let mut child_nodes = root;
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
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 {
Simon Sapin
dirstate-tree: optimize HashMap lookups with raw_entry_mut...
r49805 let (_, child_node) = child_nodes
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 .make_mut(on_disk, unreachable_bytes)?
Simon Sapin
dirstate-tree: optimize HashMap lookups with raw_entry_mut...
r49805 .raw_entry_mut()
.from_key(ancestor_path.base_name())
.or_insert_with(|| (to_cow(ancestor_path), Node::default()));
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 if let Some(next) = inclusive_ancestor_paths.next() {
Simon Sapin
dirstate-tree: Fold "tracked descendants" counter update in main walk...
r47890 each_ancestor(child_node);
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 ancestor_path = next;
child_nodes = &mut child_node.children;
} else {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 return Ok(child_node);
Simon Sapin
dirstate-tree: Implement DirstateMap::read...
r47867 }
}
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870
Raphaël Gomès
rust-dirstatemap: add Rust implementation of `reset_state`...
r49992 fn reset_state(
&mut self,
filename: &HgPath,
old_entry_opt: Option<DirstateEntry>,
wc_tracked: bool,
p1_tracked: bool,
p2_info: bool,
has_meaningful_mtime: bool,
parent_file_data_opt: Option<ParentFileData>,
) -> Result<(), DirstateError> {
let (had_entry, was_tracked) = match old_entry_opt {
Some(old_entry) => (true, old_entry.tracked()),
None => (false, false),
};
let node = Self::get_or_insert_node(
self.on_disk,
&mut self.unreachable_bytes,
&mut self.root,
filename,
WithBasename::to_cow_owned,
|ancestor| {
if !had_entry {
ancestor.descendants_with_entry_count += 1;
}
if was_tracked {
if !wc_tracked {
ancestor.tracked_descendants_count = ancestor
.tracked_descendants_count
.checked_sub(1)
.expect("tracked count to be >= 0");
}
} else {
if wc_tracked {
ancestor.tracked_descendants_count += 1;
}
}
},
)?;
let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
DirstateV2Data {
wc_tracked,
p1_tracked,
p2_info,
mode_size: parent_file_data.mode_size,
mtime: if has_meaningful_mtime {
parent_file_data.mtime
} else {
None
},
..Default::default()
}
} else {
DirstateV2Data {
wc_tracked,
p1_tracked,
p2_info,
..Default::default()
}
};
if !had_entry {
self.nodes_with_entry_count += 1;
}
node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
Ok(())
}
Raphaël Gomès
rust-dirstatemap: add `set_tracked` method...
r49988 fn set_tracked(
&mut self,
filename: &HgPath,
old_entry_opt: Option<DirstateEntry>,
) -> Result<bool, DirstateV2ParseError> {
let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
let had_entry = old_entry_opt.is_some();
let tracked_count_increment = if was_tracked { 0 } else { 1 };
let mut new = false;
let node = Self::get_or_insert_node(
self.on_disk,
&mut self.unreachable_bytes,
&mut self.root,
filename,
WithBasename::to_cow_owned,
|ancestor| {
if !had_entry {
ancestor.descendants_with_entry_count += 1;
}
ancestor.tracked_descendants_count += tracked_count_increment;
},
)?;
let new_entry = if let Some(old_entry) = old_entry_opt {
let mut e = old_entry.clone();
if e.tracked() {
// XXX
// This is probably overkill for more case, but we need this to
// fully replace the `normallookup` call with `set_tracked`
// one. Consider smoothing this in the future.
e.set_possibly_dirty();
} else {
new = true;
e.set_tracked();
}
e
} else {
self.nodes_with_entry_count += 1;
new = true;
DirstateEntry::new_tracked()
};
node.data = NodeData::Entry(new_entry);
Ok(new)
}
Raphaël Gomès
rust-dirstatemap: add `set_untracked` method...
r49999 /// It is the responsibility of the caller to know that there was an entry
/// there before. Does not handle the removal of copy source
fn set_untracked(
&mut self,
filename: &HgPath,
old_entry: DirstateEntry,
) -> Result<(), DirstateV2ParseError> {
let node = Self::get_or_insert_node(
self.on_disk,
&mut self.unreachable_bytes,
&mut self.root,
filename,
WithBasename::to_cow_owned,
|ancestor| {
ancestor.tracked_descendants_count = ancestor
.tracked_descendants_count
.checked_sub(1)
.expect("tracked_descendants_count should be >= 0");
},
)?;
let mut new_entry = old_entry.clone();
new_entry.set_untracked();
node.data = NodeData::Entry(new_entry);
Ok(())
}
Raphaël Gomès
rust-dirstatemap: add `set_clean` method...
r49995 fn set_clean(
&mut self,
filename: &HgPath,
old_entry: DirstateEntry,
mode: u32,
size: u32,
mtime: TruncatedTimestamp,
) -> Result<(), DirstateError> {
let node = Self::get_or_insert_node(
self.on_disk,
&mut self.unreachable_bytes,
&mut self.root,
filename,
WithBasename::to_cow_owned,
|ancestor| {
if !old_entry.tracked() {
ancestor.tracked_descendants_count += 1;
}
},
)?;
let mut new_entry = old_entry.clone();
new_entry.set_clean(mode, size, mtime);
node.data = NodeData::Entry(new_entry);
Ok(())
}
Raphaël Gomès
rust-dirstatemap: add `set_possibly_dirty` method...
r49997 fn set_possibly_dirty(
&mut self,
filename: &HgPath,
) -> Result<(), DirstateError> {
let node = Self::get_or_insert_node(
self.on_disk,
&mut self.unreachable_bytes,
&mut self.root,
filename,
WithBasename::to_cow_owned,
|_ancestor| {},
)?;
let entry = node.data.as_entry_mut().expect("entry should exist");
entry.set_possibly_dirty();
node.data = NodeData::Entry(*entry);
Ok(())
}
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 fn iter_nodes<'tree>(
&'tree self,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> impl Iterator<
Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
> + 'tree {
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
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();
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 let mut iter = self.root.as_ref().iter();
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 std::iter::from_fn(move || {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 while let Some(child_node) = iter.next() {
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 let children = match child_node.children(self.on_disk) {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Ok(children) => children,
Err(error) => return Some(Err(error)),
};
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 // Pseudo-recursion
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 let new_iter = children.iter();
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 let old_iter = std::mem::replace(&mut iter, new_iter);
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 stack.push((child_node, old_iter));
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 }
// Found the end of a `children.iter()` iterator.
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 if let Some((child_node, next_iter)) = stack.pop() {
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 // "Return" from pseudo-recursion by restoring state from the
// explicit stack
iter = next_iter;
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Some(Ok(child_node))
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 } else {
// Reached the bottom of the stack, we’re done
None
}
})
}
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
if let Cow::Borrowed(path) = path {
*unreachable_bytes += path.len() as u32
}
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
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)),
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 impl OwningDirstateMap {
pub fn clear(&mut self) {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 self.with_dmap_mut(|map| {
map.root = Default::default();
map.nodes_with_entry_count = 0;
map.nodes_with_copy_source_count = 0;
});
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Raphaël Gomès
rust-dirstatemap: add `set_tracked` method...
r49988 pub fn set_tracked(
&mut self,
filename: &HgPath,
) -> Result<bool, DirstateV2ParseError> {
let old_entry_opt = self.get(filename)?;
self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
}
Raphaël Gomès
rust-dirstatemap: add `set_untracked` method...
r49999 pub fn set_untracked(
&mut self,
filename: &HgPath,
) -> Result<bool, DirstateError> {
let old_entry_opt = self.get(filename)?;
match old_entry_opt {
None => Ok(false),
Some(old_entry) => {
if !old_entry.tracked() {
// `DirstateMap::set_untracked` is not a noop if
// already not tracked as it will decrement the
// tracked counters while going down.
return Ok(true);
}
if old_entry.added() {
// Untracking an "added" entry will just result in a
// worthless entry (and other parts of the code will
// complain about it), just drop it entirely.
self.drop_entry_and_copy_source(filename)?;
return Ok(true);
}
if !old_entry.p2_info() {
self.copy_map_remove(filename)?;
}
self.with_dmap_mut(|map| {
map.set_untracked(filename, old_entry)?;
Ok(true)
})
}
}
}
Raphaël Gomès
rust-dirstatemap: add `set_clean` method...
r49995 pub fn set_clean(
&mut self,
filename: &HgPath,
mode: u32,
size: u32,
mtime: TruncatedTimestamp,
) -> Result<(), DirstateError> {
let old_entry = match self.get(filename)? {
None => {
return Err(
DirstateMapError::PathNotFound(filename.into()).into()
)
}
Some(e) => e,
};
self.copy_map_remove(filename)?;
self.with_dmap_mut(|map| {
map.set_clean(filename, old_entry, mode, size, mtime)
})
}
Raphaël Gomès
rust-dirstatemap: add `set_possibly_dirty` method...
r49997 pub fn set_possibly_dirty(
&mut self,
filename: &HgPath,
) -> Result<(), DirstateError> {
if self.get(filename)?.is_none() {
return Err(DirstateMapError::PathNotFound(filename.into()).into());
}
self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
}
Raphaël Gomès
rust-dirstatemap: add Rust implementation of `reset_state`...
r49992 pub fn reset_state(
&mut self,
filename: &HgPath,
wc_tracked: bool,
p1_tracked: bool,
p2_info: bool,
has_meaningful_mtime: bool,
parent_file_data_opt: Option<ParentFileData>,
) -> Result<(), DirstateError> {
if !(p1_tracked || p2_info || wc_tracked) {
self.drop_entry_and_copy_source(filename)?;
return Ok(());
}
self.copy_map_remove(filename)?;
let old_entry_opt = self.get(filename)?;
self.with_dmap_mut(|map| {
map.reset_state(
filename,
old_entry_opt,
wc_tracked,
p1_tracked,
p2_info,
has_meaningful_mtime,
parent_file_data_opt,
)
})
}
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn drop_entry_and_copy_source(
Simon Sapin
dirstate: Replace dropfile with drop_item_and_copy_source...
r48864 &mut self,
filename: &HgPath,
) -> Result<(), DirstateError> {
Raphaël Gomès
rust-dirstatemap: use `DirstateEntry::tracked` directly...
r50014 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 struct Dropped {
was_tracked: bool,
had_entry: bool,
had_copy_source: bool,
}
Simon Sapin
dirstate-v2: Drop parent directory cache when removing a dirstate node...
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`.
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 fn recur<'on_disk>(
on_disk: &'on_disk [u8],
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 unreachable_bytes: &mut u32,
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 nodes: &mut ChildNodes<'on_disk>,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 path: &HgPath,
Simon Sapin
dirstate-v2: Drop parent directory cache when removing a dirstate node...
r48141 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 let (first_path_component, rest_of_path) =
path.split_first_component();
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
let node = if let Some(node) = nodes.get_mut(first_path_component)
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 {
node
} else {
return Ok(None);
};
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 let dropped;
if let Some(rest) = rest_of_path {
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 if let Some((d, removed)) = recur(
on_disk,
unreachable_bytes,
&mut node.children,
rest,
)? {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 dropped = d;
Simon Sapin
dirstate-tree: Keep a counter of descendant nodes that have an entry...
r48272 if dropped.had_entry {
Raphaël Gomès
rust-dirstate: panic if the DirstateMap counters go below 0...
r49865 node.descendants_with_entry_count = node
.descendants_with_entry_count
.checked_sub(1)
.expect(
"descendants_with_entry_count should be >= 0",
);
Simon Sapin
dirstate-tree: Keep a counter of descendant nodes that have an entry...
r48272 }
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 if dropped.was_tracked {
Raphaël Gomès
rust-dirstate: panic if the DirstateMap counters go below 0...
r49865 node.tracked_descendants_count = node
.tracked_descendants_count
.checked_sub(1)
.expect(
"tracked_descendants_count should be >= 0",
);
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 }
Simon Sapin
dirstate-v2: Drop parent directory cache when removing a dirstate node...
r48141
// Directory caches must be invalidated when removing a
// child node
if removed {
if let NodeData::CachedDirectory { .. } = &node.data {
node.data = NodeData::None
}
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 } else {
return Ok(None);
Simon Sapin
dirstate-tree: Fold "tracked descendants" counter update in main walk...
r47890 }
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 } else {
Raphaël Gomès
rust-dirstatemap: properly decrement counter for tracked descendants...
r49866 let entry = node.data.as_entry();
let was_tracked = entry.map_or(false, |entry| entry.tracked());
let had_entry = entry.is_some();
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 if had_entry {
node.data = NodeData::None
}
Raphaël Gomès
rust-dirstatemap: correctly decrement the copies counter...
r49867 let mut had_copy_source = false;
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 if let Some(source) = &node.copy_source {
Simon Sapin
dirstate: Replace dropfile with drop_item_and_copy_source...
r48864 DirstateMap::count_dropped_path(unreachable_bytes, source);
Raphaël Gomès
rust-dirstatemap: correctly decrement the copies counter...
r49867 had_copy_source = true;
Simon Sapin
dirstate: Replace dropfile with drop_item_and_copy_source...
r48864 node.copy_source = None
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 }
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 dropped = Dropped {
Raphaël Gomès
rust-dirstatemap: properly decrement counter for tracked descendants...
r49866 was_tracked,
Simon Sapin
dirstate-v2: Allow tree nodes without an entry to store a timestamp...
r48137 had_entry,
Raphaël Gomès
rust-dirstatemap: correctly decrement the copies counter...
r49867 had_copy_source,
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 };
Simon Sapin
dirstate-tree: Remove newly-empty nodes after removing a `DirstateEntry`...
r47964 }
// After recursion, for both leaf (rest_of_path is None) nodes and
// parent nodes, remove a node if it just became empty.
Simon Sapin
dirstate-v2: Drop parent directory cache when removing a dirstate node...
r48141 let remove = !node.data.has_entry()
Simon Sapin
dirstate-tree: Remove newly-empty nodes after removing a `DirstateEntry`...
r47964 && node.copy_source.is_none()
Simon Sapin
dirstate-v2: Drop parent directory cache when removing a dirstate node...
r48141 && node.children.is_empty();
if remove {
Simon Sapin
dirstate-v2: Add heuristic for when to create a new data file...
r48481 let (key, _) =
nodes.remove_entry(first_path_component).unwrap();
DirstateMap::count_dropped_path(
unreachable_bytes,
key.full_path(),
)
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 }
Simon Sapin
dirstate-v2: Drop parent directory cache when removing a dirstate node...
r48141 Ok(Some((dropped, remove)))
Simon Sapin
dirstate-tree: Refactor DirstateMap::drop_file to be recursive...
r47963 }
Simon Sapin
dirstate-tree: Add add_file, remove_file, and drop_file...
r47877
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 self.with_dmap_mut(|map| {
if let Some((dropped, _removed)) = recur(
map.on_disk,
&mut map.unreachable_bytes,
&mut map.root,
filename,
)? {
if dropped.had_entry {
Raphaël Gomès
rust-dirstate: panic if the DirstateMap counters go below 0...
r49865 map.nodes_with_entry_count = map
.nodes_with_entry_count
.checked_sub(1)
.expect("nodes_with_entry_count should be >= 0");
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 }
if dropped.had_copy_source {
Raphaël Gomès
rust-dirstate: panic if the DirstateMap counters go below 0...
r49865 map.nodes_with_copy_source_count = map
.nodes_with_copy_source_count
.checked_sub(1)
.expect("nodes_with_copy_source_count should be >= 0");
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 }
} else {
debug_assert!(!was_tracked);
Simon Sapin
dirstate-tree: Add add_file, remove_file, and drop_file...
r47877 }
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 Ok(())
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn has_tracked_dir(
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 &mut self,
Simon Sapin
dirstate-tree: Add has_dir and has_tracked_dir...
r47876 directory: &HgPath,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<bool, DirstateError> {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 self.with_dmap_mut(|map| {
if let Some(node) = map.get_node(directory)? {
// A node without a `DirstateEntry` was created to hold child
// nodes, and is therefore a directory.
let state = node.state()?;
Ok(state.is_none() && node.tracked_descendants_count() > 0)
} else {
Ok(false)
}
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn has_dir(
&mut self,
directory: &HgPath,
) -> Result<bool, DirstateError> {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 self.with_dmap_mut(|map| {
if let Some(node) = map.get_node(directory)? {
// A node without a `DirstateEntry` was created to hold child
// nodes, and is therefore a directory.
let state = node.state()?;
Ok(state.is_none() && node.descendants_with_entry_count() > 0)
} else {
Ok(false)
}
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 #[timed]
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn pack_v1(
Simon Sapin
rust: Serializing a DirstateMap does not mutate it anymore...
r49244 &self,
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 parents: DirstateParents,
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 ) -> Result<Vec<u8>, DirstateError> {
Simon Sapin
rust: Serializing a DirstateMap does not mutate it anymore...
r49244 let map = self.get_map();
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
// reallocations
let mut size = parents.as_bytes().len();
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 for node in map.iter_nodes() {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 let node = node?;
dirstate: remove need_delay logic...
r49221 if node.entry()?.is_some() {
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 size += packed_entry_size(
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 node.full_path(map.on_disk)?,
node.copy_source(map.on_disk)?,
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 );
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 }
}
let mut packed = Vec::with_capacity(size);
packed.extend(parents.as_bytes());
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 for node in map.iter_nodes() {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 let node = node?;
if let Some(entry) = node.entry()? {
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 pack_entry(
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 node.full_path(map.on_disk)?,
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 &entry,
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 node.copy_source(map.on_disk)?,
Simon Sapin
dirstate-tree: Serialize to disk...
r47872 &mut packed,
);
}
}
Ok(packed)
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate-v2: Move fixed-size tree metadata into the docket file...
r48482 /// Returns new data and metadata together with whether that data should be
/// appended to the existing data file whose content is at
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 /// `map.on_disk` (true), instead of written to a new data file
Simon Sapin
dirstate-v2: Move fixed-size tree metadata into the docket file...
r48482 /// (false).
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 #[timed]
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn pack_v2(
Simon Sapin
rust: Serializing a DirstateMap does not mutate it anymore...
r49244 &self,
Simon Sapin
dirstate-v2: Support appending to the same data file...
r48478 can_append: bool,
Simon Sapin
rhg: Add Repo::write_dirstate...
r49249 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
Simon Sapin
rust: Serializing a DirstateMap does not mutate it anymore...
r49244 let map = self.get_map();
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 on_disk::write(map, can_append)
Simon Sapin
dirstate-v2: Change the on-disk format when the requirement is enabled...
r48055 }
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 /// `callback` allows the caller to process and do something with the
/// results of the status. This is needed to do so efficiently (i.e.
/// without cloning the `DirstateStatus` object with its paths) because
/// we need to borrow from `Self`.
pub fn with_status<R>(
&mut self,
matcher: &(dyn Matcher + Sync),
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 root_dir: PathBuf,
ignore_files: Vec<PathBuf>,
options: StatusOptions,
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 callback: impl for<'r> FnOnce(
Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
) -> R,
) -> R {
self.with_dmap_mut(|map| {
callback(super::status::status(
map,
matcher,
root_dir,
ignore_files,
options,
))
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn copy_map_len(&self) -> usize {
let map = self.get_map();
map.nodes_with_copy_source_count as usize
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
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))
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 } else {
None
})
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 }))
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn copy_map_contains_key(
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 &self,
key: &HgPath,
) -> Result<bool, DirstateV2ParseError> {
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 let map = self.get_map();
Ok(if let Some(node) = map.get_node(key)? {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 node.has_copy_source()
Simon Sapin
dirstate-tree: Add map `get` and `contains_key` methods...
r47869 } else {
false
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 })
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn copy_map_get(
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 &self,
key: &HgPath,
) -> Result<Option<&HgPath>, DirstateV2ParseError> {
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 let map = self.get_map();
if let Some(node) = map.get_node(key)? {
if let Some(source) = node.copy_source(map.on_disk)? {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 return Ok(Some(source));
}
}
Ok(None)
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn copy_map_remove(
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 &mut self,
key: &HgPath,
) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 self.with_dmap_mut(|map| {
let count = &mut map.nodes_with_copy_source_count;
let unreachable_bytes = &mut map.unreachable_bytes;
Ok(DirstateMap::get_node_mut(
map.on_disk,
unreachable_bytes,
&mut map.root,
key,
)?
.and_then(|node| {
if let Some(source) = &node.copy_source {
*count -= 1;
DirstateMap::count_dropped_path(unreachable_bytes, source);
}
node.copy_source.take().map(Cow::into_owned)
}))
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn copy_map_insert(
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 &mut self,
Raphaël Gomès
rust-dirstatemap: use `&HgPath` instead of `HgPathBuf` in `copy_map_insert`...
r50015 key: &HgPath,
value: &HgPath,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 self.with_dmap_mut(|map| {
let node = DirstateMap::get_or_insert_node(
map.on_disk,
&mut map.unreachable_bytes,
&mut map.root,
&key,
WithBasename::to_cow_owned,
|_ancestor| {},
)?;
if node.copy_source.is_none() {
map.nodes_with_copy_source_count += 1
}
Raphaël Gomès
rust-dirstatemap: use `&HgPath` instead of `HgPathBuf` in `copy_map_insert`...
r50015 Ok(node
.copy_source
.replace(value.to_owned().into())
.map(Cow::into_owned))
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 })
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn len(&self) -> usize {
let map = self.get_map();
map.nodes_with_entry_count as usize
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn contains_key(
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 &self,
key: &HgPath,
) -> Result<bool, DirstateV2ParseError> {
Ok(self.get(key)?.is_some())
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn get(
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 &self,
key: &HgPath,
) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 let map = self.get_map();
Ok(if let Some(node) = map.get_node(key)? {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 node.entry()?
} else {
None
})
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn iter(&self) -> StateMapIter<'_> {
let map = self.get_map();
Box::new(filter_map_results(map.iter_nodes(), move |node| {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Ok(if let Some(entry) = node.entry()? {
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 Some((node.full_path(map.on_disk)?, entry))
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 } else {
None
})
Simon Sapin
dirstate-tree: Add tree traversal/iteration...
r47870 }))
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Simon Sapin
dirstate-v2: Add --dirs to debugdirstate command...
r48140
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn iter_tracked_dirs(
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483 &mut self,
) -> Result<
Box<
dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
+ Send
+ '_,
>,
DirstateError,
> {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 let map = self.get_map();
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 let on_disk = map.on_disk;
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483 Ok(Box::new(filter_map_results(
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 map.iter_nodes(),
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483 move |node| {
Ok(if node.tracked_descendants_count() > 0 {
Some(node.full_path(on_disk)?)
} else {
None
})
},
)))
}
Raphaël Gomès
rust-dirstatemap: implement part of the `setparents` logic...
r50011 /// Only public because it needs to be exposed to the Python layer.
/// It is not the full `setparents` logic, only the parts that mutate the
/// entries.
pub fn setparents_fixup(
&mut self,
) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
// XXX
// All the copying and re-querying is quite inefficient, but this is
// still a lot better than doing it from Python.
//
// The better solution is to develop a mechanism for `iter_mut`,
// which will be a lot more involved: we're dealing with a lazy,
// append-mostly, tree-like data structure. This will do for now.
let mut copies = vec![];
let mut files_with_p2_info = vec![];
for res in self.iter() {
let (path, entry) = res?;
if entry.p2_info() {
files_with_p2_info.push(path.to_owned())
}
}
self.with_dmap_mut(|map| {
for path in files_with_p2_info.iter() {
let node = map.get_or_insert(path)?;
let entry =
node.data.as_entry_mut().expect("entry should exist");
entry.drop_merge_data();
if let Some(source) = node.copy_source.take().as_deref() {
copies.push((path.to_owned(), source.to_owned()));
}
}
Ok(copies)
})
}
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 pub fn debug_iter(
Simon Sapin
dirstate-v2: Add --dirs to debugdirstate command...
r48140 &self,
Simon Sapin
debugstate: Always call dirstatemap.debug_iter()...
r48835 all: bool,
Simon Sapin
dirstate-v2: Add --dirs to debugdirstate command...
r48140 ) -> Box<
dyn Iterator<
Item = Result<
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483 (&HgPath, (u8, i32, i32, i32)),
Simon Sapin
dirstate-v2: Add --dirs to debugdirstate command...
r48140 DirstateV2ParseError,
>,
> + Send
+ '_,
> {
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 let map = self.get_map();
Box::new(filter_map_results(map.iter_nodes(), move |node| {
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483 let debug_tuple = if let Some(entry) = node.entry()? {
entry.debug_tuple()
Simon Sapin
debugstate: Always call dirstatemap.debug_iter()...
r48835 } else if !all {
return Ok(None);
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 } else if let Some(mtime) = node.cached_directory_mtime()? {
(b' ', 0, -1, mtime.truncated_seconds() as i32)
Simon Sapin
dirstate-v2: Add --dirs to debugdirstate command...
r48140 } else {
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483 (b' ', 0, -1, -1)
};
Simon Sapin
dirstate: Remove the Rust abstraction DirstateMapMethods...
r48883 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
Simon Sapin
dirstate-v2: Add --dirs to debugdirstate command...
r48140 }))
}
Simon Sapin
dirstate-tree: Empty shell for a second Rust DirstateMap implementation...
r47865 }
Raphaël Gomès
rust-dirstatemap: add unit tests...
r50017 #[cfg(test)]
mod tests {
use super::*;
/// Shortcut to return tracked descendants of a path.
/// Panics if the path does not exist.
fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
let path = dbg!(HgPath::new(path));
let node = map.get_map().get_node(path);
node.unwrap().unwrap().tracked_descendants_count()
}
/// Shortcut to return descendants with an entry.
/// Panics if the path does not exist.
fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
let path = dbg!(HgPath::new(path));
let node = map.get_map().get_node(path);
node.unwrap().unwrap().descendants_with_entry_count()
}
fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
let path = dbg!(HgPath::new(path));
let node = map.get_map().get_node(path);
assert!(node.unwrap().is_none());
}
/// Shortcut for path creation in tests
fn p(b: &[u8]) -> &HgPath {
HgPath::new(b)
}
/// Test the very simple case a single tracked file
#[test]
fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
let mut map = OwningDirstateMap::new_empty(vec![]);
assert_eq!(map.len(), 0);
map.set_tracked(p(b"some/nested/path"))?;
assert_eq!(map.len(), 1);
assert_eq!(tracked_descendants(&map, b"some"), 1);
assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
map.set_untracked(p(b"some/nested/path"))?;
assert_eq!(map.len(), 0);
assert!(map.get_map().get_node(p(b"some"))?.is_none());
Ok(())
}
/// Test the simple case of all tracked, but multiple files
#[test]
fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
let mut map = OwningDirstateMap::new_empty(vec![]);
map.set_tracked(p(b"some/nested/path"))?;
map.set_tracked(p(b"some/nested/file"))?;
// one layer without any files to test deletion cascade
map.set_tracked(p(b"some/other/nested/path"))?;
map.set_tracked(p(b"root_file"))?;
map.set_tracked(p(b"some/file"))?;
map.set_tracked(p(b"some/file2"))?;
map.set_tracked(p(b"some/file3"))?;
assert_eq!(map.len(), 7);
assert_eq!(tracked_descendants(&map, b"some"), 6);
assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
assert_eq!(tracked_descendants(&map, b"some/other"), 1);
assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
map.set_untracked(p(b"some/nested/path"))?;
assert_eq!(map.len(), 6);
assert_eq!(tracked_descendants(&map, b"some"), 5);
assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
assert_eq!(tracked_descendants(&map, b"some/other"), 1);
assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
map.set_untracked(p(b"some/nested/file"))?;
assert_eq!(map.len(), 5);
assert_eq!(tracked_descendants(&map, b"some"), 4);
assert_eq!(tracked_descendants(&map, b"some/other"), 1);
assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
assert_does_not_exist(&map, b"some_nested");
map.set_untracked(p(b"some/other/nested/path"))?;
assert_eq!(map.len(), 4);
assert_eq!(tracked_descendants(&map, b"some"), 3);
assert_does_not_exist(&map, b"some/other");
map.set_untracked(p(b"root_file"))?;
assert_eq!(map.len(), 3);
assert_eq!(tracked_descendants(&map, b"some"), 3);
assert_does_not_exist(&map, b"root_file");
map.set_untracked(p(b"some/file"))?;
assert_eq!(map.len(), 2);
assert_eq!(tracked_descendants(&map, b"some"), 2);
assert_does_not_exist(&map, b"some/file");
map.set_untracked(p(b"some/file2"))?;
assert_eq!(map.len(), 1);
assert_eq!(tracked_descendants(&map, b"some"), 1);
assert_does_not_exist(&map, b"some/file2");
map.set_untracked(p(b"some/file3"))?;
assert_eq!(map.len(), 0);
assert_does_not_exist(&map, b"some/file3");
Ok(())
}
/// Check with a mix of tracked and non-tracked items
#[test]
fn test_tracked_descendants_different() -> Result<(), DirstateError> {
let mut map = OwningDirstateMap::new_empty(vec![]);
// A file that was just added
map.set_tracked(p(b"some/nested/path"))?;
// This has no information, the dirstate should ignore it
map.reset_state(p(b"some/file"), false, false, false, false, None)?;
assert_does_not_exist(&map, b"some/file");
// A file that was removed
map.reset_state(
p(b"some/nested/file"),
false,
true,
false,
false,
None,
)?;
assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
// Only present in p2
map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
// A file that was merged
map.reset_state(p(b"root_file"), true, true, true, false, None)?;
assert!(map.get(p(b"root_file"))?.unwrap().tracked());
// A file that is added, with info from p2
// XXX is that actually possible?
map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
// A clean file
// One layer without any files to test deletion cascade
map.reset_state(
p(b"some/other/nested/path"),
true,
true,
false,
false,
None,
)?;
assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
assert_eq!(map.len(), 6);
assert_eq!(tracked_descendants(&map, b"some"), 3);
assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
assert_eq!(
descendants_with_an_entry(&map, b"some/other/nested/path"),
0
);
assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
// might as well check this
map.set_untracked(p(b"path/does/not/exist"))?;
assert_eq!(map.len(), 6);
map.set_untracked(p(b"some/other/nested/path"))?;
// It is set untracked but not deleted since it held other information
assert_eq!(map.len(), 6);
assert_eq!(tracked_descendants(&map, b"some"), 2);
assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
map.set_untracked(p(b"some/nested/path"))?;
// It is set untracked *and* deleted since it was only added
assert_eq!(map.len(), 5);
assert_eq!(tracked_descendants(&map, b"some"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
assert_does_not_exist(&map, b"some/nested/path");
map.set_untracked(p(b"root_file"))?;
// Untracked but not deleted
assert_eq!(map.len(), 5);
assert!(map.get(p(b"root_file"))?.is_some());
map.set_untracked(p(b"some/file2"))?;
assert_eq!(map.len(), 5);
assert_eq!(tracked_descendants(&map, b"some"), 0);
assert!(map.get(p(b"some/file2"))?.is_some());
map.set_untracked(p(b"some/file3"))?;
assert_eq!(map.len(), 5);
assert_eq!(tracked_descendants(&map, b"some"), 0);
assert!(map.get(p(b"some/file3"))?.is_some());
Ok(())
}
/// Check that copies counter is correctly updated
#[test]
fn test_copy_source() -> Result<(), DirstateError> {
let mut map = OwningDirstateMap::new_empty(vec![]);
// Clean file
map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
// Merged file
map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
// Removed file
map.reset_state(p(b"removed"), false, true, false, false, None)?;
// Added file
map.reset_state(p(b"files/added"), true, false, false, false, None)?;
// Add copy
map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
assert_eq!(map.copy_map_len(), 1);
// Copy override
map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
assert_eq!(map.copy_map_len(), 1);
// Multiple copies
map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
assert_eq!(map.copy_map_len(), 2);
map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
assert_eq!(map.copy_map_len(), 3);
// Added, so the entry is completely removed
map.set_untracked(p(b"files/added"))?;
assert_does_not_exist(&map, b"files/added");
assert_eq!(map.copy_map_len(), 2);
// Removed, so the entry is kept around, so is its copy
map.set_untracked(p(b"removed"))?;
assert!(map.get(p(b"removed"))?.is_some());
assert_eq!(map.copy_map_len(), 2);
// Clean, so the entry is kept around, but not its copy
map.set_untracked(p(b"files/clean"))?;
assert!(map.get(p(b"files/clean"))?.is_some());
assert_eq!(map.copy_map_len(), 1);
map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
assert_eq!(map.copy_map_len(), 2);
// Info from p2, so its copy source info is kept around
map.set_untracked(p(b"files/from_p2"))?;
assert!(map.get(p(b"files/from_p2"))?.is_some());
assert_eq!(map.copy_map_len(), 2);
Ok(())
}
/// Test with "on disk" data. For the sake of this test, the "on disk" data
/// does not actually come from the disk, but it's opaque to the code being
/// tested.
#[test]
fn test_on_disk() -> Result<(), DirstateError> {
// First let's create some data to put "on disk"
let mut map = OwningDirstateMap::new_empty(vec![]);
// A file that was just added
map.set_tracked(p(b"some/nested/added"))?;
map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
// A file that was removed
map.reset_state(
p(b"some/nested/removed"),
false,
true,
false,
false,
None,
)?;
// Only present in p2
map.reset_state(
p(b"other/p2_info_only"),
false,
false,
true,
false,
None,
)?;
map.copy_map_insert(
p(b"other/p2_info_only"),
p(b"other/p2_info_copy_source"),
)?;
// A file that was merged
map.reset_state(p(b"merged"), true, true, true, false, None)?;
// A file that is added, with info from p2
// XXX is that actually possible?
map.reset_state(
p(b"other/added_with_p2"),
true,
false,
true,
false,
None,
)?;
// One layer without any files to test deletion cascade
// A clean file
map.reset_state(
p(b"some/other/nested/clean"),
true,
true,
false,
false,
None,
)?;
let (packed, metadata, _should_append) = map.pack_v2(false)?;
let packed_len = packed.len();
assert!(packed_len > 0);
// Recreate "from disk"
let mut map = OwningDirstateMap::new_v2(
packed,
packed_len,
metadata.as_bytes(),
)?;
// Check that everything is accounted for
assert!(map.contains_key(p(b"some/nested/added"))?);
assert!(map.contains_key(p(b"some/nested/removed"))?);
assert!(map.contains_key(p(b"merged"))?);
assert!(map.contains_key(p(b"other/p2_info_only"))?);
assert!(map.contains_key(p(b"other/added_with_p2"))?);
assert!(map.contains_key(p(b"some/other/nested/clean"))?);
assert_eq!(
map.copy_map_get(p(b"some/nested/added"))?,
Some(p(b"added_copy_source"))
);
assert_eq!(
map.copy_map_get(p(b"other/p2_info_only"))?,
Some(p(b"other/p2_info_copy_source"))
);
assert_eq!(tracked_descendants(&map, b"some"), 2);
assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
assert_eq!(tracked_descendants(&map, b"other"), 1);
assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
assert_eq!(tracked_descendants(&map, b"some/other"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
assert_eq!(map.len(), 6);
assert_eq!(map.get_map().unreachable_bytes, 0);
assert_eq!(map.copy_map_len(), 2);
// Shouldn't change anything since it's already not tracked
map.set_untracked(p(b"some/nested/removed"))?;
assert_eq!(map.get_map().unreachable_bytes, 0);
match map.get_map().root {
ChildNodes::InMemory(_) => {
panic!("root should not have been mutated")
}
_ => (),
}
// We haven't mutated enough (nothing, actually), we should still be in
// the append strategy
assert!(map.get_map().write_should_append());
// But this mutates the structure, so there should be unreachable_bytes
assert!(map.set_untracked(p(b"some/nested/added"))?);
let unreachable_bytes = map.get_map().unreachable_bytes;
assert!(unreachable_bytes > 0);
match map.get_map().root {
ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
_ => (),
}
// This should not mutate the structure either, since `root` has
// already been mutated along with its direct children.
map.set_untracked(p(b"merged"))?;
assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
NodeRef::InMemory(_, _) => {
panic!("'other/added_with_p2' should not have been mutated")
}
_ => (),
}
// But this should, since it's in a different path
// than `<root>some/nested/add`
map.set_untracked(p(b"other/added_with_p2"))?;
assert!(map.get_map().unreachable_bytes > unreachable_bytes);
match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
NodeRef::OnDisk(_) => {
panic!("'other/added_with_p2' should have been mutated")
}
_ => (),
}
// We have rewritten most of the tree, we should create a new file
assert!(!map.get_map().write_should_append());
Ok(())
}
}