@@ -547,14 +547,12 @@
) -> Ref<'a, RustDirstateMap> {
self.inner(py).borrow()
}
- #[cfg(not(feature = "dirstate-tree"))]
fn translate_key(
py: Python,
res: (&HgPathBuf, &DirstateEntry),
) -> PyResult<Option<PyBytes>> {
Ok(Some(PyBytes::new(py, res.0.as_bytes())))
}
- #[cfg(not(feature = "dirstate-tree"))]
fn translate_key_value(
py: Python,
res: (&HgPathBuf, &DirstateEntry),
@@ -565,24 +563,6 @@
make_dirstate_tuple(py, &entry)?,
)))
}
- #[cfg(feature = "dirstate-tree")]
- fn translate_key(
- py: Python,
- res: (HgPathBuf, DirstateEntry),
- ) -> PyResult<Option<PyBytes>> {
- Ok(Some(PyBytes::new(py, res.0.as_bytes())))
- }
- #[cfg(feature = "dirstate-tree")]
- fn translate_key_value(
- py: Python,
- res: (HgPathBuf, DirstateEntry),
- ) -> PyResult<Option<(PyBytes, PyObject)>> {
- let (f, entry) = res;
- Ok(Some((
- PyBytes::new(py, f.as_bytes()),
- make_dirstate_tuple(py, &entry)?,
- )))
- }
}
py_shared_iterator!(
@@ -10,7 +10,6 @@
[features]
default = ["python27"]
-dirstate-tree = ["hg-core/dirstate-tree"]
# Features to build an extension module:
python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"]
@@ -14,66 +14,6 @@
/// files.
pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>);
-#[cfg(feature = "dirstate-tree")]
-impl<'a, M: Matcher + Sync> Status<'a, M> {
- pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
- let (traversed_sender, traversed_receiver) =
- crossbeam_channel::unbounded();
-
- // Step 1: check the files explicitly mentioned by the user
- let (work, mut results) = self.walk_explicit(traversed_sender.clone());
-
- // Step 2: Check files in the dirstate
- if !self.matcher.is_exact() {
- self.extend_from_dmap(&mut results);
- }
- // Step 3: Check the working directory if listing unknowns
- if !work.is_empty() {
- // Hashmaps are quite a bit slower to build than vecs, so only
- // build it if needed.
- let mut old_results = None;
-
- // Step 2: recursively check the working directory for changes if
- // needed
- for (dir, dispatch) in work {
- match dispatch {
- Dispatch::Directory { was_file } => {
- if was_file {
- results.push((dir.to_owned(), Dispatch::Removed));
- }
- if self.options.list_ignored
- || self.options.list_unknown
- && !self.dir_ignore(&dir)
- {
- if old_results.is_none() {
- old_results =
- Some(results.iter().cloned().collect());
- }
- self.traverse(
- &dir,
- old_results
- .as_ref()
- .expect("old results should exist"),
- &mut results,
- traversed_sender.clone(),
- );
- }
- }
- _ => {
- unreachable!("There can only be directories in `work`")
- }
- }
- }
- }
-
- drop(traversed_sender);
- let traversed = traversed_receiver.into_iter().collect();
-
- Ok(build_response(results, traversed))
- }
-}
-
-#[cfg(not(feature = "dirstate-tree"))]
impl<'a, M: Matcher + Sync> Status<'a, M> {
pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
let (traversed_sender, traversed_receiver) =
@@ -9,9 +9,6 @@
//! It is currently missing a lot of functionality compared to the Python one
//! and will only be triggered in narrow cases.
-#[cfg(feature = "dirstate-tree")]
-use crate::dirstate::dirstate_tree::iter::StatusShortcut;
-#[cfg(not(feature = "dirstate-tree"))]
use crate::utils::path_auditor::PathAuditor;
use crate::{
dirstate::SIZE_FROM_OTHER_PARENT,
@@ -703,83 +700,6 @@
///
/// This takes a mutable reference to the results to account for the
/// `extend` in timings
- #[cfg(feature = "dirstate-tree")]
- #[timed]
- pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
- results.par_extend(
- self.dmap
- .fs_iter(self.root_dir.clone())
- .par_bridge()
- .filter(|(path, _)| self.matcher.matches(path))
- .map(move |(filename, shortcut)| {
- let entry = match shortcut {
- StatusShortcut::Entry(e) => e,
- StatusShortcut::Dispatch(d) => {
- return (Cow::Owned(filename), d)
- }
- };
- let filename_as_path = match hg_path_to_path_buf(&filename)
- {
- Ok(f) => f,
- Err(_) => {
- return (
- Cow::Owned(filename),
- INVALID_PATH_DISPATCH,
- )
- }
- };
- let meta = self
- .root_dir
- .join(filename_as_path)
- .symlink_metadata();
-
- match meta {
- Ok(m)
- if !(m.file_type().is_file()
- || m.file_type().is_symlink()) =>
- {
- (
- Cow::Owned(filename),
- dispatch_missing(entry.state),
- )
- }
- Ok(m) => {
- let dispatch = dispatch_found(
- &filename,
- entry,
- HgMetadata::from_metadata(m),
- &self.dmap.copy_map,
- self.options,
- );
- (Cow::Owned(filename), dispatch)
- }
- Err(e)
- if e.kind() == ErrorKind::NotFound
- || e.raw_os_error() == Some(20) =>
- {
- // Rust does not yet have an `ErrorKind` for
- // `NotADirectory` (errno 20)
- // It happens if the dirstate contains `foo/bar`
- // and foo is not a
- // directory
- (
- Cow::Owned(filename),
- dispatch_missing(entry.state),
- )
- }
- Err(e) => {
- (Cow::Owned(filename), dispatch_os_error(&e))
- }
- }
- }),
- );
- }
-
- /// Add the files in the dirstate to the results.
- ///
- /// This takes a mutable reference to the results to account for the
- /// `extend` in timings
- #[cfg(not(feature = "dirstate-tree"))]
#[timed]
pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
results.par_extend(
@@ -850,7 +770,6 @@
///
/// This takes a mutable reference to the results to account for the
/// `extend` in timings
- #[cfg(not(feature = "dirstate-tree"))]
#[timed]
pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
let to_visit: Vec<(&HgPath, &DirstateEntry)> =
@@ -73,7 +73,6 @@
}
/// `now` is the duration in seconds since the Unix epoch
-#[cfg(not(feature = "dirstate-tree"))]
pub fn pack_dirstate(
state_map: &mut StateMap,
copy_map: &CopyMap,
@@ -146,79 +145,6 @@
Ok(packed)
}
-/// `now` is the duration in seconds since the Unix epoch
-#[cfg(feature = "dirstate-tree")]
-pub fn pack_dirstate(
- state_map: &mut StateMap,
- copy_map: &CopyMap,
- parents: DirstateParents,
- now: Duration,
-) -> Result<Vec<u8>, DirstatePackError> {
- // TODO move away from i32 before 2038.
- let now: i32 = now.as_secs().try_into().expect("time overflow");
-
- let expected_size: usize = state_map
- .iter()
- .map(|(filename, _)| {
- let mut length = MIN_ENTRY_SIZE + filename.len();
- if let Some(copy) = copy_map.get(&filename) {
- length += copy.len() + 1;
- }
- length
- })
- .sum();
- let expected_size = expected_size + PARENT_SIZE * 2;
-
- let mut packed = Vec::with_capacity(expected_size);
- let mut new_state_map = vec![];
-
- packed.extend(&parents.p1);
- packed.extend(&parents.p2);
-
- for (filename, entry) in state_map.iter() {
- let new_filename = filename.to_owned();
- let mut new_mtime: i32 = entry.mtime;
- if entry.state == EntryState::Normal && entry.mtime == now {
- // The file was last modified "simultaneously" with the current
- // write to dirstate (i.e. within the same second for file-
- // systems with a granularity of 1 sec). This commonly happens
- // for at least a couple of files on 'update'.
- // The user could change the file without changing its size
- // within the same second. Invalidate the file's mtime in
- // dirstate, forcing future 'status' calls to compare the
- // contents of the file if the size is the same. This prevents
- // mistakenly treating such files as clean.
- new_mtime = -1;
- new_state_map.push((
- filename.to_owned(),
- DirstateEntry {
- mtime: new_mtime,
- ..entry
- },
- ));
- }
- let mut new_filename = new_filename.into_vec();
- if let Some(copy) = copy_map.get(&filename) {
- new_filename.push(b'\0');
- new_filename.extend(copy.bytes());
- }
-
- packed.write_u8(entry.state.into())?;
- packed.write_i32::<BigEndian>(entry.mode)?;
- packed.write_i32::<BigEndian>(entry.size)?;
- packed.write_i32::<BigEndian>(new_mtime)?;
- packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
- packed.extend(new_filename)
- }
-
- if packed.len() != expected_size {
- return Err(DirstatePackError::BadSize(expected_size, packed.len()));
- }
-
- state_map.extend(new_state_map);
-
- Ok(packed)
-}
#[cfg(test)]
mod tests {
deleted file mode 100644
@@ -1,682 +0,0 @@
-// tree.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-use super::iter::Iter;
-use super::node::{Directory, Node, NodeKind};
-use crate::dirstate::dirstate_tree::iter::FsIter;
-use crate::dirstate::dirstate_tree::node::{InsertResult, RemoveResult};
-use crate::utils::hg_path::{HgPath, HgPathBuf};
-use crate::DirstateEntry;
-use std::path::PathBuf;
-
-/// A specialized tree to represent the Mercurial dirstate.
-///
-/// # Advantages over a flat structure
-///
-/// The dirstate is inherently hierarchical, since it's a representation of the
-/// file structure of the project. The current dirstate format is flat, and
-/// while that affords us potentially great (unordered) iteration speeds, the
-/// need to retrieve a given path is great enough that you need some kind of
-/// hashmap or tree in a lot of cases anyway.
-///
-/// Going with a tree allows us to be smarter:
-/// - Skipping an ignored directory means we don't visit its entire subtree
-/// - Security auditing does not need to reconstruct paths backwards to check
-/// for symlinked directories, this can be done during the iteration in a
-/// very efficient fashion
-/// - We don't need to build the directory information in another struct,
-/// simplifying the code a lot, reducing the memory footprint and
-/// potentially going faster depending on the implementation.
-/// - We can use it to store a (platform-dependent) caching mechanism [1]
-/// - And probably other types of optimizations.
-///
-/// Only the first two items in this list are implemented as of this commit.
-///
-/// [1]: https://www.mercurial-scm.org/wiki/DirsCachePlan
-///
-///
-/// # Structure
-///
-/// It's a prefix (radix) tree with no fixed arity, with a granularity of a
-/// folder, allowing it to mimic a filesystem hierarchy:
-///
-/// ```text
-/// foo/bar
-/// foo/baz
-/// test
-/// ```
-/// Will be represented (simplified) by:
-///
-/// ```text
-/// Directory(root):
-/// - File("test")
-/// - Directory("foo"):
-/// - File("bar")
-/// - File("baz")
-/// ```
-///
-/// Moreover, it is special-cased for storing the dirstate and as such handles
-/// cases that a simple `HashMap` would handle, but while preserving the
-/// hierarchy.
-/// For example:
-///
-/// ```shell
-/// $ touch foo
-/// $ hg add foo
-/// $ hg commit -m "foo"
-/// $ hg remove foo
-/// $ rm foo
-/// $ mkdir foo
-/// $ touch foo/a
-/// $ hg add foo/a
-/// $ hg status
-/// R foo
-/// A foo/a
-/// ```
-/// To represent this in a tree, one needs to keep track of whether any given
-/// file was a directory and whether any given directory was a file at the last
-/// dirstate update. This tree stores that information, but only in the right
-/// circumstances by respecting the high-level rules that prevent nonsensical
-/// structures to exist:
-/// - a file can only be added as a child of another file if the latter is
-/// marked as `Removed`
-/// - a file cannot replace a folder unless all its descendents are removed
-///
-/// This second rule is not checked by the tree for performance reasons, and
-/// because high-level logic already prevents that state from happening.
-///
-/// # Ordering
-///
-/// It makes no guarantee of ordering for now.
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct Tree {
- pub root: Node,
- files_count: usize,
-}
-
-impl Tree {
- pub fn new() -> Self {
- Self {
- root: Node {
- kind: NodeKind::Directory(Directory {
- was_file: None,
- children: Default::default(),
- }),
- },
- files_count: 0,
- }
- }
-
- /// How many files (not directories) are stored in the tree, including ones
- /// marked as `Removed`.
- pub fn len(&self) -> usize {
- self.files_count
- }
-
- pub fn is_empty(&self) -> bool {
- self.len() == 0
- }
-
- /// Inserts a file in the tree and returns the previous entry if any.
- pub fn insert(
- &mut self,
- path: impl AsRef<HgPath>,
- kind: DirstateEntry,
- ) -> Option<DirstateEntry> {
- let old = self.insert_node(path, kind);
- match old?.kind {
- NodeKind::Directory(_) => None,
- NodeKind::File(f) => Some(f.entry),
- }
- }
-
- /// Low-level insertion method that returns the previous node (directories
- /// included).
- fn insert_node(
- &mut self,
- path: impl AsRef<HgPath>,
- kind: DirstateEntry,
- ) -> Option<Node> {
- let InsertResult {
- did_insert,
- old_entry,
- } = self.root.insert(path.as_ref().as_bytes(), kind);
- self.files_count += if did_insert { 1 } else { 0 };
- old_entry
- }
-
- /// Returns a reference to a node if it exists.
- pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> {
- self.root.get(path.as_ref().as_bytes())
- }
-
- /// Returns a reference to the entry corresponding to `path` if it exists.
- pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> {
- if let Some(node) = self.get_node(&path) {
- return match &node.kind {
- NodeKind::Directory(d) => {
- d.was_file.as_ref().map(|f| &f.entry)
- }
- NodeKind::File(f) => Some(&f.entry),
- };
- }
- None
- }
-
- /// Returns `true` if an entry is found for the given `path`.
- pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool {
- self.get(path).is_some()
- }
-
- /// Returns a mutable reference to the entry corresponding to `path` if it
- /// exists.
- pub fn get_mut(
- &mut self,
- path: impl AsRef<HgPath>,
- ) -> Option<&mut DirstateEntry> {
- if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) {
- return match kind {
- NodeKind::Directory(d) => {
- d.was_file.as_mut().map(|f| &mut f.entry)
- }
- NodeKind::File(f) => Some(&mut f.entry),
- };
- }
- None
- }
-
- /// Returns an iterator over the paths and corresponding entries in the
- /// tree.
- pub fn iter(&self) -> Iter {
- Iter::new(&self.root)
- }
-
- /// Returns an iterator of all entries in the tree, with a special
- /// filesystem handling for the directories containing said entries. See
- /// the documentation of `FsIter` for more.
- pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter {
- FsIter::new(&self.root, root_dir)
- }
-
- /// Remove the entry at `path` and returns it, if it exists.
- pub fn remove(
- &mut self,
- path: impl AsRef<HgPath>,
- ) -> Option<DirstateEntry> {
- let RemoveResult { old_entry, .. } =
- self.root.remove(path.as_ref().as_bytes());
- self.files_count = self
- .files_count
- .checked_sub(if old_entry.is_some() { 1 } else { 0 })
- .expect("removed too many files");
- old_entry
- }
-}
-
-impl<P: AsRef<HgPath>> Extend<(P, DirstateEntry)> for Tree {
- fn extend<T: IntoIterator<Item = (P, DirstateEntry)>>(&mut self, iter: T) {
- for (path, entry) in iter {
- self.insert(path, entry);
- }
- }
-}
-
-impl<'a> IntoIterator for &'a Tree {
- type Item = (HgPathBuf, DirstateEntry);
- type IntoIter = Iter<'a>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.iter()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::dirstate::dirstate_tree::node::File;
- use crate::{EntryState, FastHashMap};
- use pretty_assertions::assert_eq;
-
- impl Node {
- /// Shortcut for getting children of a node in tests.
- fn children(&self) -> Option<&FastHashMap<Vec<u8>, Node>> {
- match &self.kind {
- NodeKind::Directory(d) => Some(&d.children),
- NodeKind::File(_) => None,
- }
- }
- }
-
- #[test]
- fn test_dirstate_tree() {
- let mut tree = Tree::new();
-
- assert_eq!(
- tree.insert_node(
- HgPath::new(b"we/p"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 0,
- mtime: 0,
- size: 0
- }
- ),
- None
- );
- dbg!(&tree);
- assert!(tree.get_node(HgPath::new(b"we")).is_some());
- let entry = DirstateEntry {
- state: EntryState::Merged,
- mode: 41,
- mtime: 42,
- size: 43,
- };
- assert_eq!(tree.insert_node(HgPath::new(b"foo/bar"), entry), None);
- assert_eq!(
- tree.get_node(HgPath::new(b"foo/bar")),
- Some(&Node {
- kind: NodeKind::File(File {
- was_directory: None,
- entry
- })
- })
- );
- // We didn't override the first entry we made
- assert!(tree.get_node(HgPath::new(b"we")).is_some(),);
- // Inserting the same key again
- assert_eq!(
- tree.insert_node(HgPath::new(b"foo/bar"), entry),
- Some(Node {
- kind: NodeKind::File(File {
- was_directory: None,
- entry
- }),
- })
- );
- // Inserting the two levels deep
- assert_eq!(tree.insert_node(HgPath::new(b"foo/bar/baz"), entry), None);
- // Getting a file "inside a file" should return `None`
- assert_eq!(tree.get_node(HgPath::new(b"foo/bar/baz/bap"),), None);
-
- assert_eq!(
- tree.insert_node(HgPath::new(b"wasdir/subfile"), entry),
- None,
- );
- let removed_entry = DirstateEntry {
- state: EntryState::Removed,
- mode: 0,
- mtime: 0,
- size: 0,
- };
- assert!(tree
- .insert_node(HgPath::new(b"wasdir"), removed_entry)
- .is_some());
-
- assert_eq!(
- tree.get_node(HgPath::new(b"wasdir")),
- Some(&Node {
- kind: NodeKind::File(File {
- was_directory: Some(Box::new(Directory {
- was_file: None,
- children: [(
- b"subfile".to_vec(),
- Node {
- kind: NodeKind::File(File {
- was_directory: None,
- entry,
- })
- }
- )]
- .to_vec()
- .into_iter()
- .collect()
- })),
- entry: removed_entry
- })
- })
- );
-
- assert!(tree.get(HgPath::new(b"wasdir/subfile")).is_some())
- }
-
- #[test]
- fn test_insert_removed() {
- let mut tree = Tree::new();
- let entry = DirstateEntry {
- state: EntryState::Merged,
- mode: 1,
- mtime: 2,
- size: 3,
- };
- let removed_entry = DirstateEntry {
- state: EntryState::Removed,
- mode: 10,
- mtime: 20,
- size: 30,
- };
- assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None);
- assert_eq!(
- tree.insert_node(HgPath::new(b"foo/a"), removed_entry),
- None
- );
- // The insert should not turn `foo` into a directory as `foo` is not
- // `Removed`.
- match tree.get_node(HgPath::new(b"foo")).unwrap().kind {
- NodeKind::Directory(_) => panic!("should be a file"),
- NodeKind::File(_) => {}
- }
-
- let mut tree = Tree::new();
- let entry = DirstateEntry {
- state: EntryState::Merged,
- mode: 1,
- mtime: 2,
- size: 3,
- };
- let removed_entry = DirstateEntry {
- state: EntryState::Removed,
- mode: 10,
- mtime: 20,
- size: 30,
- };
- // The insert *should* turn `foo` into a directory as it is `Removed`.
- assert_eq!(tree.insert_node(HgPath::new(b"foo"), removed_entry), None);
- assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), entry), None);
- match tree.get_node(HgPath::new(b"foo")).unwrap().kind {
- NodeKind::Directory(_) => {}
- NodeKind::File(_) => panic!("should be a directory"),
- }
- }
-
- #[test]
- fn test_get() {
- let mut tree = Tree::new();
- let entry = DirstateEntry {
- state: EntryState::Merged,
- mode: 1,
- mtime: 2,
- size: 3,
- };
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
- assert_eq!(tree.files_count, 1);
- assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry));
- assert_eq!(tree.get(HgPath::new(b"a/b")), None);
- assert_eq!(tree.get(HgPath::new(b"a")), None);
- assert_eq!(tree.get(HgPath::new(b"a/b/c/d")), None);
- let entry2 = DirstateEntry {
- state: EntryState::Removed,
- mode: 0,
- mtime: 5,
- size: 1,
- };
- // was_directory
- assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None);
- assert_eq!(tree.files_count, 2);
- assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2));
- assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry));
-
- let mut tree = Tree::new();
-
- // was_file
- assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None);
- assert_eq!(tree.files_count, 1);
- assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None);
- assert_eq!(tree.files_count, 2);
- assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2));
- }
-
- #[test]
- fn test_get_mut() {
- let mut tree = Tree::new();
- let mut entry = DirstateEntry {
- state: EntryState::Merged,
- mode: 1,
- mtime: 2,
- size: 3,
- };
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
- assert_eq!(tree.files_count, 1);
- assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry));
- assert_eq!(tree.get_mut(HgPath::new(b"a/b")), None);
- assert_eq!(tree.get_mut(HgPath::new(b"a")), None);
- assert_eq!(tree.get_mut(HgPath::new(b"a/b/c/d")), None);
- let mut entry2 = DirstateEntry {
- state: EntryState::Removed,
- mode: 0,
- mtime: 5,
- size: 1,
- };
- // was_directory
- assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None);
- assert_eq!(tree.files_count, 2);
- assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2));
- assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry));
-
- let mut tree = Tree::new();
-
- // was_file
- assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None);
- assert_eq!(tree.files_count, 1);
- assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None);
- assert_eq!(tree.files_count, 2);
- assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2));
- }
-
- #[test]
- fn test_remove() {
- let mut tree = Tree::new();
- assert_eq!(tree.files_count, 0);
- assert_eq!(tree.remove(HgPath::new(b"foo")), None);
- assert_eq!(tree.files_count, 0);
-
- let entry = DirstateEntry {
- state: EntryState::Normal,
- mode: 0,
- mtime: 0,
- size: 0,
- };
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
- assert_eq!(tree.files_count, 1);
-
- assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry));
- assert_eq!(tree.files_count, 0);
-
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None);
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/y"), entry), None);
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/z"), entry), None);
- assert_eq!(tree.insert_node(HgPath::new(b"x"), entry), None);
- assert_eq!(tree.insert_node(HgPath::new(b"y"), entry), None);
- assert_eq!(tree.files_count, 5);
-
- assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry));
- assert_eq!(tree.files_count, 4);
- assert_eq!(tree.remove(HgPath::new(b"a/b/x")), None);
- assert_eq!(tree.files_count, 4);
- assert_eq!(tree.remove(HgPath::new(b"a/b/y")), Some(entry));
- assert_eq!(tree.files_count, 3);
- assert_eq!(tree.remove(HgPath::new(b"a/b/z")), Some(entry));
- assert_eq!(tree.files_count, 2);
-
- assert_eq!(tree.remove(HgPath::new(b"x")), Some(entry));
- assert_eq!(tree.files_count, 1);
- assert_eq!(tree.remove(HgPath::new(b"y")), Some(entry));
- assert_eq!(tree.files_count, 0);
-
- // `a` should have been cleaned up, no more files anywhere in its
- // descendents
- assert_eq!(tree.get_node(HgPath::new(b"a")), None);
- assert_eq!(tree.root.children().unwrap().len(), 0);
-
- let removed_entry = DirstateEntry {
- state: EntryState::Removed,
- ..entry
- };
- assert_eq!(tree.insert(HgPath::new(b"a"), removed_entry), None);
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None);
- assert_eq!(tree.files_count, 2);
- dbg!(&tree);
- assert_eq!(tree.remove(HgPath::new(b"a")), Some(removed_entry));
- assert_eq!(tree.files_count, 1);
- dbg!(&tree);
- assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry));
- assert_eq!(tree.files_count, 0);
-
- // The entire tree should have been cleaned up, no more files anywhere
- // in its descendents
- assert_eq!(tree.root.children().unwrap().len(), 0);
-
- let removed_entry = DirstateEntry {
- state: EntryState::Removed,
- ..entry
- };
- assert_eq!(tree.insert(HgPath::new(b"a"), entry), None);
- assert_eq!(
- tree.insert_node(HgPath::new(b"a/b/x"), removed_entry),
- None
- );
- assert_eq!(tree.files_count, 2);
- dbg!(&tree);
- assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry));
- assert_eq!(tree.files_count, 1);
- dbg!(&tree);
- assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry));
- assert_eq!(tree.files_count, 0);
-
- dbg!(&tree);
- // The entire tree should have been cleaned up, no more files anywhere
- // in its descendents
- assert_eq!(tree.root.children().unwrap().len(), 0);
-
- assert_eq!(tree.insert(HgPath::new(b"d"), entry), None);
- assert_eq!(tree.insert(HgPath::new(b"d/d/d"), entry), None);
- assert_eq!(tree.files_count, 2);
-
- // Deleting the nested file should not delete the top directory as it
- // used to be a file
- assert_eq!(tree.remove(HgPath::new(b"d/d/d")), Some(entry));
- assert_eq!(tree.files_count, 1);
- assert!(tree.get_node(HgPath::new(b"d")).is_some());
- assert!(tree.remove(HgPath::new(b"d")).is_some());
- assert_eq!(tree.files_count, 0);
-
- // Deleting the nested file should not delete the top file (other way
- // around from the last case)
- assert_eq!(tree.insert(HgPath::new(b"a/a"), entry), None);
- assert_eq!(tree.files_count, 1);
- assert_eq!(tree.insert(HgPath::new(b"a"), entry), None);
- assert_eq!(tree.files_count, 2);
- dbg!(&tree);
- assert_eq!(tree.remove(HgPath::new(b"a/a")), Some(entry));
- assert_eq!(tree.files_count, 1);
- dbg!(&tree);
- assert!(tree.get_node(HgPath::new(b"a")).is_some());
- assert!(tree.get_node(HgPath::new(b"a/a")).is_none());
- }
-
- #[test]
- fn test_was_directory() {
- let mut tree = Tree::new();
-
- let entry = DirstateEntry {
- state: EntryState::Removed,
- mode: 0,
- mtime: 0,
- size: 0,
- };
- assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
- assert_eq!(tree.files_count, 1);
-
- assert!(tree.insert_node(HgPath::new(b"a"), entry).is_some());
- let new_a = tree.root.children().unwrap().get(&b"a".to_vec()).unwrap();
-
- match &new_a.kind {
- NodeKind::Directory(_) => panic!(),
- NodeKind::File(f) => {
- let dir = f.was_directory.clone().unwrap();
- let c = dir
- .children
- .get(&b"b".to_vec())
- .unwrap()
- .children()
- .unwrap()
- .get(&b"c".to_vec())
- .unwrap();
-
- assert_eq!(
- match &c.kind {
- NodeKind::Directory(_) => panic!(),
- NodeKind::File(f) => f.entry,
- },
- entry
- );
- }
- }
- assert_eq!(tree.files_count, 2);
- dbg!(&tree);
- assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry));
- assert_eq!(tree.files_count, 1);
- dbg!(&tree);
- let a = tree.get_node(HgPath::new(b"a")).unwrap();
- match &a.kind {
- NodeKind::Directory(_) => panic!(),
- NodeKind::File(f) => {
- // Directory in `was_directory` was emptied, should be removed
- assert_eq!(f.was_directory, None);
- }
- }
- }
- #[test]
- fn test_extend() {
- let insertions = [
- (
- HgPathBuf::from_bytes(b"d"),
- DirstateEntry {
- state: EntryState::Added,
- mode: 0,
- mtime: -1,
- size: -1,
- },
- ),
- (
- HgPathBuf::from_bytes(b"b"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 33188,
- mtime: 1599647984,
- size: 2,
- },
- ),
- (
- HgPathBuf::from_bytes(b"a/a"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 33188,
- mtime: 1599647984,
- size: 2,
- },
- ),
- (
- HgPathBuf::from_bytes(b"d/d/d"),
- DirstateEntry {
- state: EntryState::Removed,
- mode: 0,
- mtime: 0,
- size: 0,
- },
- ),
- ]
- .to_vec();
- let mut tree = Tree::new();
-
- tree.extend(insertions.clone().into_iter());
-
- for (path, _) in &insertions {
- assert!(tree.contains_key(path), true);
- }
- assert_eq!(tree.files_count, 4);
- }
-}
deleted file mode 100644
@@ -1,398 +0,0 @@
-// node.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-use super::iter::Iter;
-use crate::utils::hg_path::HgPathBuf;
-use crate::{DirstateEntry, EntryState, FastHashMap};
-
-/// Represents a filesystem directory in the dirstate tree
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct Directory {
- /// Contains the old file information if it existed between changesets.
- /// Happens if a file `foo` is marked as removed, removed from the
- /// filesystem then a directory `foo` is created and at least one of its
- /// descendents is added to Mercurial.
- pub(super) was_file: Option<Box<File>>,
- pub(super) children: FastHashMap<Vec<u8>, Node>,
-}
-
-/// Represents a filesystem file (or symlink) in the dirstate tree
-#[derive(Debug, Clone, PartialEq)]
-pub struct File {
- /// Contains the old structure if it existed between changesets.
- /// Happens all descendents of `foo` marked as removed and removed from
- /// the filesystem, then a file `foo` is created and added to Mercurial.
- pub(super) was_directory: Option<Box<Directory>>,
- pub(super) entry: DirstateEntry,
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum NodeKind {
- Directory(Directory),
- File(File),
-}
-
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct Node {
- pub kind: NodeKind,
-}
-
-impl Default for NodeKind {
- fn default() -> Self {
- NodeKind::Directory(Default::default())
- }
-}
-
-impl Node {
- pub fn insert(
- &mut self,
- path: &[u8],
- new_entry: DirstateEntry,
- ) -> InsertResult {
- let mut split = path.splitn(2, |&c| c == b'/');
- let head = split.next().unwrap_or(b"");
- let tail = split.next().unwrap_or(b"");
-
- // Are we're modifying the current file ? Is the the end of the path ?
- let is_current_file = tail.is_empty() && head.is_empty();
-
- // Potentially Replace the current file with a directory if it's marked
- // as `Removed`
- if !is_current_file {
- if let NodeKind::File(file) = &mut self.kind {
- if file.entry.state == EntryState::Removed {
- self.kind = NodeKind::Directory(Directory {
- was_file: Some(Box::from(file.clone())),
- children: Default::default(),
- })
- }
- }
- }
- match &mut self.kind {
- NodeKind::Directory(directory) => {
- Node::insert_in_directory(directory, new_entry, head, tail)
- }
- NodeKind::File(file) => {
- if is_current_file {
- let new = Self {
- kind: NodeKind::File(File {
- entry: new_entry,
- ..file.clone()
- }),
- };
- InsertResult {
- did_insert: false,
- old_entry: Some(std::mem::replace(self, new)),
- }
- } else {
- match file.entry.state {
- EntryState::Removed => {
- unreachable!("Removed file turning into a directory was dealt with earlier")
- }
- _ => {
- Node::insert_in_file(
- file, new_entry, head, tail,
- )
- }
- }
- }
- }
- }
- }
-
- /// The current file still exists and is not marked as `Removed`.
- /// Insert the entry in its `was_directory`.
- fn insert_in_file(
- file: &mut File,
- new_entry: DirstateEntry,
- head: &[u8],
- tail: &[u8],
- ) -> InsertResult {
- if let Some(d) = &mut file.was_directory {
- Node::insert_in_directory(d, new_entry, head, tail)
- } else {
- let mut dir = Directory {
- was_file: None,
- children: FastHashMap::default(),
- };
- let res =
- Node::insert_in_directory(&mut dir, new_entry, head, tail);
- file.was_directory = Some(Box::new(dir));
- res
- }
- }
-
- /// Insert an entry in the subtree of `directory`
- fn insert_in_directory(
- directory: &mut Directory,
- new_entry: DirstateEntry,
- head: &[u8],
- tail: &[u8],
- ) -> InsertResult {
- let mut res = InsertResult::default();
-
- if let Some(node) = directory.children.get_mut(head) {
- // Node exists
- match &mut node.kind {
- NodeKind::Directory(subdir) => {
- if tail.is_empty() {
- let becomes_file = Self {
- kind: NodeKind::File(File {
- was_directory: Some(Box::from(subdir.clone())),
- entry: new_entry,
- }),
- };
- let old_entry = directory
- .children
- .insert(head.to_owned(), becomes_file);
- return InsertResult {
- did_insert: true,
- old_entry,
- };
- } else {
- res = node.insert(tail, new_entry);
- }
- }
- NodeKind::File(_) => {
- res = node.insert(tail, new_entry);
- }
- }
- } else if tail.is_empty() {
- // File does not already exist
- directory.children.insert(
- head.to_owned(),
- Self {
- kind: NodeKind::File(File {
- was_directory: None,
- entry: new_entry,
- }),
- },
- );
- res.did_insert = true;
- } else {
- // Directory does not already exist
- let mut nested = Self {
- kind: NodeKind::Directory(Directory {
- was_file: None,
- children: Default::default(),
- }),
- };
- res = nested.insert(tail, new_entry);
- directory.children.insert(head.to_owned(), nested);
- }
- res
- }
-
- /// Removes an entry from the tree, returns a `RemoveResult`.
- pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
- let empty_result = RemoveResult::default();
- if path.is_empty() {
- return empty_result;
- }
- let mut split = path.splitn(2, |&c| c == b'/');
- let head = split.next();
- let tail = split.next().unwrap_or(b"");
-
- let head = match head {
- None => {
- return empty_result;
- }
- Some(h) => h,
- };
- if head == path {
- match &mut self.kind {
- NodeKind::Directory(d) => {
- return Node::remove_from_directory(head, d);
- }
- NodeKind::File(f) => {
- if let Some(d) = &mut f.was_directory {
- let RemoveResult { old_entry, .. } =
- Node::remove_from_directory(head, d);
- return RemoveResult {
- cleanup: false,
- old_entry,
- };
- }
- }
- }
- empty_result
- } else {
- // Look into the dirs
- match &mut self.kind {
- NodeKind::Directory(d) => {
- if let Some(child) = d.children.get_mut(head) {
- let mut res = child.remove(tail);
- if res.cleanup {
- d.children.remove(head);
- }
- res.cleanup =
- d.children.is_empty() && d.was_file.is_none();
- res
- } else {
- empty_result
- }
- }
- NodeKind::File(f) => {
- if let Some(d) = &mut f.was_directory {
- if let Some(child) = d.children.get_mut(head) {
- let RemoveResult { cleanup, old_entry } =
- child.remove(tail);
- if cleanup {
- d.children.remove(head);
- }
- if d.children.is_empty() && d.was_file.is_none() {
- f.was_directory = None;
- }
-
- return RemoveResult {
- cleanup: false,
- old_entry,
- };
- }
- }
- empty_result
- }
- }
- }
- }
-
- fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
- if let Some(node) = d.children.get_mut(head) {
- return match &mut node.kind {
- NodeKind::Directory(d) => {
- if let Some(f) = &mut d.was_file {
- let entry = f.entry;
- d.was_file = None;
- RemoveResult {
- cleanup: false,
- old_entry: Some(entry),
- }
- } else {
- RemoveResult::default()
- }
- }
- NodeKind::File(f) => {
- let entry = f.entry;
- let mut cleanup = false;
- match &f.was_directory {
- None => {
- if d.children.len() == 1 {
- cleanup = true;
- }
- d.children.remove(head);
- }
- Some(dir) => {
- node.kind = NodeKind::Directory(*dir.clone());
- }
- }
-
- RemoveResult {
- cleanup,
- old_entry: Some(entry),
- }
- }
- };
- }
- RemoveResult::default()
- }
-
- pub fn get(&self, path: &[u8]) -> Option<&Node> {
- if path.is_empty() {
- return Some(&self);
- }
- let mut split = path.splitn(2, |&c| c == b'/');
- let head = split.next();
- let tail = split.next().unwrap_or(b"");
-
- let head = match head {
- None => {
- return Some(&self);
- }
- Some(h) => h,
- };
- match &self.kind {
- NodeKind::Directory(d) => {
- if let Some(child) = d.children.get(head) {
- return child.get(tail);
- }
- }
- NodeKind::File(f) => {
- if let Some(d) = &f.was_directory {
- if let Some(child) = d.children.get(head) {
- return child.get(tail);
- }
- }
- }
- }
-
- None
- }
-
- pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
- if path.is_empty() {
- return Some(&mut self.kind);
- }
- let mut split = path.splitn(2, |&c| c == b'/');
- let head = split.next();
- let tail = split.next().unwrap_or(b"");
-
- let head = match head {
- None => {
- return Some(&mut self.kind);
- }
- Some(h) => h,
- };
- match &mut self.kind {
- NodeKind::Directory(d) => {
- if let Some(child) = d.children.get_mut(head) {
- return child.get_mut(tail);
- }
- }
- NodeKind::File(f) => {
- if let Some(d) = &mut f.was_directory {
- if let Some(child) = d.children.get_mut(head) {
- return child.get_mut(tail);
- }
- }
- }
- }
-
- None
- }
-
- pub fn iter(&self) -> Iter {
- Iter::new(self)
- }
-}
-
-/// Information returned to the caller of an `insert` operation for integrity.
-#[derive(Debug, Default)]
-pub struct InsertResult {
- /// Whether the insertion resulted in an actual insertion and not an
- /// update
- pub(super) did_insert: bool,
- /// The entry that was replaced, if it exists
- pub(super) old_entry: Option<Node>,
-}
-
-/// Information returned to the caller of a `remove` operation integrity.
-#[derive(Debug, Default)]
-pub struct RemoveResult {
- /// If the caller needs to remove the current node
- pub(super) cleanup: bool,
- /// The entry that was replaced, if it exists
- pub(super) old_entry: Option<DirstateEntry>,
-}
-
-impl<'a> IntoIterator for &'a Node {
- type Item = (HgPathBuf, DirstateEntry);
- type IntoIter = Iter<'a>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.iter()
- }
-}
deleted file mode 100644
@@ -1,392 +0,0 @@
-// iter.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-use super::node::{Node, NodeKind};
-use super::tree::Tree;
-use crate::dirstate::dirstate_tree::node::Directory;
-use crate::dirstate::status::Dispatch;
-use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf};
-use crate::DirstateEntry;
-use std::borrow::Cow;
-use std::collections::VecDeque;
-use std::iter::{FromIterator, FusedIterator};
-use std::path::PathBuf;
-
-impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree {
- fn from_iter<T: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
- iter: T,
- ) -> Self {
- let mut tree = Self::new();
- for (path, entry) in iter {
- tree.insert(path, entry);
- }
- tree
- }
-}
-
-/// Iterator of all entries in the dirstate tree.
-///
-/// It has no particular ordering.
-pub struct Iter<'a> {
- to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
-}
-
-impl<'a> Iter<'a> {
- pub fn new(node: &'a Node) -> Iter<'a> {
- let mut to_visit = VecDeque::new();
- to_visit.push_back((Cow::Borrowed(&b""[..]), node));
- Self { to_visit }
- }
-}
-
-impl<'a> Iterator for Iter<'a> {
- type Item = (HgPathBuf, DirstateEntry);
-
- fn next(&mut self) -> Option<Self::Item> {
- while let Some((base_path, node)) = self.to_visit.pop_front() {
- match &node.kind {
- NodeKind::Directory(dir) => {
- add_children_to_visit(
- &mut self.to_visit,
- &base_path,
- &dir,
- );
- if let Some(file) = &dir.was_file {
- return Some((
- HgPathBuf::from_bytes(&base_path),
- file.entry,
- ));
- }
- }
- NodeKind::File(file) => {
- if let Some(dir) = &file.was_directory {
- add_children_to_visit(
- &mut self.to_visit,
- &base_path,
- &dir,
- );
- }
- return Some((
- HgPathBuf::from_bytes(&base_path),
- file.entry,
- ));
- }
- }
- }
- None
- }
-}
-
-impl<'a> FusedIterator for Iter<'a> {}
-
-/// Iterator of all entries in the dirstate tree, with a special filesystem
-/// handling for the directories containing said entries.
-///
-/// It checks every directory on-disk to see if it has become a symlink, to
-/// prevent a potential security issue.
-/// Using this information, it may dispatch `status` information early: it
-/// returns canonical paths along with `Shortcut`s, which are either a
-/// `DirstateEntry` or a `Dispatch`, if the fate of said path has already been
-/// determined.
-///
-/// Like `Iter`, it has no particular ordering.
-pub struct FsIter<'a> {
- root_dir: PathBuf,
- to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
- shortcuts: VecDeque<(HgPathBuf, StatusShortcut)>,
-}
-
-impl<'a> FsIter<'a> {
- pub fn new(node: &'a Node, root_dir: PathBuf) -> FsIter<'a> {
- let mut to_visit = VecDeque::new();
- to_visit.push_back((Cow::Borrowed(&b""[..]), node));
- Self {
- root_dir,
- to_visit,
- shortcuts: Default::default(),
- }
- }
-
- /// Mercurial tracks symlinks but *not* what they point to.
- /// If a directory is moved and symlinked:
- ///
- /// ```bash
- /// $ mkdir foo
- /// $ touch foo/a
- /// $ # commit...
- /// $ mv foo bar
- /// $ ln -s bar foo
- /// ```
- /// We need to dispatch the new symlink as `Unknown` and all the
- /// descendents of the directory it replace as `Deleted`.
- fn dispatch_symlinked_directory(
- &mut self,
- path: impl AsRef<HgPath>,
- node: &Node,
- ) {
- let path = path.as_ref();
- self.shortcuts.push_back((
- path.to_owned(),
- StatusShortcut::Dispatch(Dispatch::Unknown),
- ));
- for (file, _) in node.iter() {
- self.shortcuts.push_back((
- path.join(&file),
- StatusShortcut::Dispatch(Dispatch::Deleted),
- ));
- }
- }
-
- /// Returns `true` if the canonical `path` of a directory corresponds to a
- /// symlink on disk. It means it was moved and symlinked after the last
- /// dirstate update.
- ///
- /// # Special cases
- ///
- /// Returns `false` for the repository root.
- /// Returns `false` on io error, error handling is outside of the iterator.
- fn directory_became_symlink(&mut self, path: &HgPath) -> bool {
- if path.is_empty() {
- return false;
- }
- let filename_as_path = match hg_path_to_path_buf(&path) {
- Ok(p) => p,
- _ => return false,
- };
- let meta = self.root_dir.join(filename_as_path).symlink_metadata();
- match meta {
- Ok(ref m) if m.file_type().is_symlink() => true,
- _ => false,
- }
- }
-}
-
-/// Returned by `FsIter`, since the `Dispatch` of any given entry may already
-/// be determined during the iteration. This is necessary for performance
-/// reasons, since hierarchical information is needed to `Dispatch` an entire
-/// subtree efficiently.
-#[derive(Debug, Copy, Clone)]
-pub enum StatusShortcut {
- /// A entry in the dirstate for further inspection
- Entry(DirstateEntry),
- /// The result of the status of the corresponding file
- Dispatch(Dispatch),
-}
-
-impl<'a> Iterator for FsIter<'a> {
- type Item = (HgPathBuf, StatusShortcut);
-
- fn next(&mut self) -> Option<Self::Item> {
- // If any paths have already been `Dispatch`-ed, return them
- if let Some(res) = self.shortcuts.pop_front() {
- return Some(res);
- }
-
- while let Some((base_path, node)) = self.to_visit.pop_front() {
- match &node.kind {
- NodeKind::Directory(dir) => {
- let canonical_path = HgPath::new(&base_path);
- if self.directory_became_symlink(canonical_path) {
- // Potential security issue, don't do a normal
- // traversal, force the results.
- self.dispatch_symlinked_directory(
- canonical_path,
- &node,
- );
- continue;
- }
- add_children_to_visit(
- &mut self.to_visit,
- &base_path,
- &dir,
- );
- if let Some(file) = &dir.was_file {
- return Some((
- HgPathBuf::from_bytes(&base_path),
- StatusShortcut::Entry(file.entry),
- ));
- }
- }
- NodeKind::File(file) => {
- if let Some(dir) = &file.was_directory {
- add_children_to_visit(
- &mut self.to_visit,
- &base_path,
- &dir,
- );
- }
- return Some((
- HgPathBuf::from_bytes(&base_path),
- StatusShortcut::Entry(file.entry),
- ));
- }
- }
- }
-
- None
- }
-}
-
-impl<'a> FusedIterator for FsIter<'a> {}
-
-fn join_path<'a, 'b>(path: &'a [u8], other: &'b [u8]) -> Cow<'b, [u8]> {
- if path.is_empty() {
- other.into()
- } else {
- [path, &b"/"[..], other].concat().into()
- }
-}
-
-/// Adds all children of a given directory `dir` to the visit queue `to_visit`
-/// prefixed by a `base_path`.
-fn add_children_to_visit<'a>(
- to_visit: &mut VecDeque<(Cow<'a, [u8]>, &'a Node)>,
- base_path: &[u8],
- dir: &'a Directory,
-) {
- to_visit.extend(dir.children.iter().map(|(path, child)| {
- let full_path = join_path(&base_path, &path);
- (full_path, child)
- }));
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::utils::hg_path::HgPath;
- use crate::{EntryState, FastHashMap};
- use std::collections::HashSet;
-
- #[test]
- fn test_iteration() {
- let mut tree = Tree::new();
-
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"foo/bar"),
- DirstateEntry {
- state: EntryState::Merged,
- mode: 41,
- mtime: 42,
- size: 43,
- }
- ),
- None
- );
-
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"foo2"),
- DirstateEntry {
- state: EntryState::Merged,
- mode: 40,
- mtime: 41,
- size: 42,
- }
- ),
- None
- );
-
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"foo/baz"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 0,
- mtime: 0,
- size: 0,
- }
- ),
- None
- );
-
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"foo/bap/nested"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 0,
- mtime: 0,
- size: 0,
- }
- ),
- None
- );
-
- assert_eq!(tree.len(), 4);
-
- let results: HashSet<_> =
- tree.iter().map(|(c, _)| c.to_owned()).collect();
- dbg!(&results);
- assert!(results.contains(HgPath::new(b"foo2")));
- assert!(results.contains(HgPath::new(b"foo/bar")));
- assert!(results.contains(HgPath::new(b"foo/baz")));
- assert!(results.contains(HgPath::new(b"foo/bap/nested")));
-
- let mut iter = tree.iter();
- assert!(iter.next().is_some());
- assert!(iter.next().is_some());
- assert!(iter.next().is_some());
- assert!(iter.next().is_some());
- assert_eq!(None, iter.next());
- assert_eq!(None, iter.next());
- drop(iter);
-
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"foo/bap/nested/a"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 0,
- mtime: 0,
- size: 0,
- }
- ),
- None
- );
-
- let results: FastHashMap<_, _> = tree.iter().collect();
- assert!(results.contains_key(HgPath::new(b"foo2")));
- assert!(results.contains_key(HgPath::new(b"foo/bar")));
- assert!(results.contains_key(HgPath::new(b"foo/baz")));
- // Is a dir but `was_file`, so it's listed as a removed file
- assert!(results.contains_key(HgPath::new(b"foo/bap/nested")));
- assert!(results.contains_key(HgPath::new(b"foo/bap/nested/a")));
-
- // insert removed file (now directory) after nested file
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"a/a"),
- DirstateEntry {
- state: EntryState::Normal,
- mode: 0,
- mtime: 0,
- size: 0,
- }
- ),
- None
- );
-
- // `insert` returns `None` for a directory
- assert_eq!(
- tree.insert(
- HgPathBuf::from_bytes(b"a"),
- DirstateEntry {
- state: EntryState::Removed,
- mode: 0,
- mtime: 0,
- size: 0,
- }
- ),
- None
- );
-
- let results: FastHashMap<_, _> = tree.iter().collect();
- assert!(results.contains_key(HgPath::new(b"a")));
- assert!(results.contains_key(HgPath::new(b"a/a")));
- }
-}
deleted file mode 100644
@@ -1,14 +0,0 @@
-// dirstate_tree.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-//! Special-case radix tree that matches a filesystem hierarchy for use in the
-//! dirstate.
-//! It has not been optimized at all yet.
-
-pub mod iter;
-pub mod node;
-pub mod tree;
@@ -254,7 +254,6 @@
)
}
- #[cfg(not(feature = "dirstate-tree"))]
pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
if !force
&& self.non_normal_set.is_some()
@@ -283,34 +282,6 @@
self.non_normal_set = Some(non_normal);
self.other_parent_set = Some(other_parent);
}
- #[cfg(feature = "dirstate-tree")]
- pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
- if !force
- && self.non_normal_set.is_some()
- && self.other_parent_set.is_some()
- {
- return;
- }
- let mut non_normal = HashSet::new();
- let mut other_parent = HashSet::new();
-
- for (
- filename,
- DirstateEntry {
- state, size, mtime, ..
- },
- ) in self.state_map.iter()
- {
- if state != EntryState::Normal || mtime == MTIME_UNSET {
- non_normal.insert(filename.to_owned());
- }
- if state == EntryState::Normal && size == SIZE_FROM_OTHER_PARENT {
- other_parent.insert(filename.to_owned());
- }
- }
- self.non_normal_set = Some(non_normal);
- self.other_parent_set = Some(other_parent);
- }
/// Both of these setters and their uses appear to be the simplest way to
/// emulate a Python lazy property, but it is ugly and unidiomatic.
@@ -426,7 +397,6 @@
self.set_non_normal_other_parent_entries(true);
Ok(packed)
}
- #[cfg(not(feature = "dirstate-tree"))]
pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
if let Some(ref file_fold_map) = self.file_fold_map {
return file_fold_map;
@@ -442,22 +412,6 @@
self.file_fold_map = Some(new_file_fold_map);
self.file_fold_map.as_ref().unwrap()
}
- #[cfg(feature = "dirstate-tree")]
- pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
- if let Some(ref file_fold_map) = self.file_fold_map {
- return file_fold_map;
- }
- let mut new_file_fold_map = FileFoldMap::default();
-
- for (filename, DirstateEntry { state, .. }) in self.state_map.iter() {
- if state != EntryState::Removed {
- new_file_fold_map
- .insert(normalize_case(&filename), filename.to_owned());
- }
- }
- self.file_fold_map = Some(new_file_fold_map);
- self.file_fold_map.as_ref().unwrap()
- }
}
#[cfg(test)]
@@ -30,7 +30,6 @@
/// Initializes the multiset from a dirstate.
///
/// If `skip_state` is provided, skips dirstate entries with equal state.
- #[cfg(not(feature = "dirstate-tree"))]
pub fn from_dirstate(
dirstate: &StateMap,
skip_state: Option<EntryState>,
@@ -51,30 +50,6 @@
Ok(multiset)
}
- /// Initializes the multiset from a dirstate.
- ///
- /// If `skip_state` is provided, skips dirstate entries with equal state.
- #[cfg(feature = "dirstate-tree")]
- pub fn from_dirstate(
- dirstate: &StateMap,
- skip_state: Option<EntryState>,
- ) -> Result<Self, DirstateMapError> {
- let mut multiset = DirsMultiset {
- inner: FastHashMap::default(),
- };
- for (filename, DirstateEntry { state, .. }) in dirstate.iter() {
- // This `if` is optimized out of the loop
- if let Some(skip) = skip_state {
- if skip != state {
- multiset.add_path(filename)?;
- }
- } else {
- multiset.add_path(filename)?;
- }
- }
-
- Ok(multiset)
- }
/// Initializes the multiset from a manifest.
pub fn from_manifest(
@@ -14,8 +14,6 @@
pub mod dirs_multiset;
pub mod dirstate_map;
-#[cfg(feature = "dirstate-tree")]
-pub mod dirstate_tree;
pub mod parsers;
pub mod status;
@@ -52,15 +50,9 @@
/// merge.
pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
-#[cfg(not(feature = "dirstate-tree"))]
pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
-#[cfg(not(feature = "dirstate-tree"))]
pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
-#[cfg(feature = "dirstate-tree")]
-pub type StateMap = dirstate_tree::tree::Tree;
-#[cfg(feature = "dirstate-tree")]
-pub type StateMapIter<'a> = dirstate_tree::iter::Iter<'a>;
pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>;
pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
@@ -41,9 +41,3 @@
clap = "*"
pretty_assertions = "0.6.1"
tempfile = "3.1.0"
-
-[features]
-# Use a (still unoptimized) tree for the dirstate instead of the current flat
-# dirstate. This is not yet recommended for performance reasons. A future
-# version might make it the default, or make it a runtime option.
-dirstate-tree = []