diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2024-01-03 10:27:14 +0100 |
---|---|---|
committer | Sebastian Thiel <sebastian.thiel@icloud.com> | 2024-01-03 10:27:14 +0100 |
commit | 49f98f537bf0ac41a7b1992094103f6d36f135f8 (patch) | |
tree | ad9563ea0fbfe4e8a08cfd3abe340ca1cee9240c | |
parent | e992659db17f275b48e555afd6b18df737401f01 (diff) | |
parent | 93f0f61b3042b933f099714e3a6d336497eb18ba (diff) |
fix: `--ignore-dirs` now work as expected. (#196)
Previously they would need to be specified as relative to the traversal root, which
was unintuitive and would lead to ignores not working for many.
Even though this was done for performance to avoid canonicalization, we do now
perform a more performance version of canonicalization so the overall performance
should be acceptable nonetheless.
Also note that ignored directories are now logged when using a `--log-file`.
-rw-r--r-- | src/common.rs | 91 | ||||
-rw-r--r-- | src/interactive/app/tests/utils.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 6 |
3 files changed, 90 insertions, 9 deletions
diff --git a/src/common.rs b/src/common.rs index 66e2277..7f4f89e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,7 @@ use crate::crossdev; use crate::traverse::{EntryData, Tree, TreeIndex}; use byte_unit::{n_gb_bytes, n_gib_bytes, n_mb_bytes, n_mib_bytes, ByteUnit}; -use log::info; +use std::collections::BTreeSet; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -171,14 +171,15 @@ pub struct WalkOptions { pub apparent_size: bool, pub sorting: TraversalSorting, pub cross_filesystems: bool, - pub ignore_dirs: Vec<PathBuf>, + pub ignore_dirs: BTreeSet<PathBuf>, } type WalkDir = jwalk::WalkDirGeneric<((), Option<Result<std::fs::Metadata, jwalk::Error>>)>; impl WalkOptions { pub(crate) fn iter_from_path(&self, root: &Path, root_device_id: u64) -> WalkDir { - info!("root path={:?}", root); + let ignore_dirs = self.ignore_dirs.clone(); + let cwd = std::env::current_dir().unwrap_or_else(|_| root.to_owned()); WalkDir::new(root) .follow_links(false) .sort(match self.sorting { @@ -187,7 +188,6 @@ impl WalkOptions { }) .skip_hidden(false) .process_read_dir({ - let ignore_dirs = self.ignore_dirs.clone(); let cross_filesystems = self.cross_filesystems; move |_, _, _, dir_entry_results| { dir_entry_results.iter_mut().for_each(|dir_entry_result| { @@ -200,7 +200,9 @@ impl WalkOptions { .as_ref() .map(|m| crossdev::is_same_device(root_device_id, m)) .unwrap_or(true); - if !ok_for_fs || ignore_dirs.contains(&dir_entry.path()) { + if !ok_for_fs + || ignore_directory(&dir_entry.path(), &ignore_dirs, &cwd) + { dir_entry.read_children_path = None; } } @@ -241,3 +243,82 @@ impl WalkResult { i32::from(self.num_errors > 0) } } + +pub fn canonicalize_ignore_dirs(ignore_dirs: &[PathBuf]) -> BTreeSet<PathBuf> { + let dirs = ignore_dirs + .iter() + .map(gix_path::realpath) + .filter_map(Result::ok) + .collect(); + log::info!("Ignoring canonicalized {dirs:?}"); + dirs +} + +fn ignore_directory(path: &Path, ignore_dirs: &BTreeSet<PathBuf>, cwd: &Path) -> bool { + if ignore_dirs.is_empty() { + return false; + } + let path = gix_path::realpath_opts(path, cwd, 32); + path.map(|path| { + let ignored = ignore_dirs.contains(&path); + if ignored { + log::debug!("Ignored {path:?}"); + } + ignored + }) + .unwrap_or(false) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ignore_directories() { + let cwd = std::env::current_dir().unwrap(); + #[cfg(unix)] + let mut parameters = vec![ + ("/usr", vec!["/usr"], true), + ("/usr/local", vec!["/usr"], false), + ("/smth", vec!["/usr"], false), + ("/usr/local/..", vec!["/usr/local/.."], true), + ("/usr", vec!["/usr/local/.."], true), + ("/usr/local/share/../..", vec!["/usr"], true), + ]; + + #[cfg(windows)] + let mut parameters = vec![ + ("C:\\Windows", vec!["C:\\Windows"], true), + ("C:\\Windows\\System", vec!["C:\\Windows"], false), + ("C:\\Smth", vec!["C:\\Windows"], false), + ( + "C:\\Windows\\System\\..", + vec!["C:\\Windows\\System\\.."], + true, + ), + ("C:\\Windows", vec!["C:\\Windows\\System\\.."], true), + ( + "C:\\Windows\\System\\Speech\\..\\..", + vec!["C:\\Windows"], + true, + ), + ]; + + parameters.extend([ + ("src", vec!["src"], true), + ("src/interactive", vec!["src"], false), + ("src/interactive/..", vec!["src"], true), + ]); + + for (path, ignore_dirs, expected_result) in parameters { + let ignore_dirs = canonicalize_ignore_dirs( + &ignore_dirs.into_iter().map(Into::into).collect::<Vec<_>>(), + ); + assert_eq!( + ignore_directory(path.as_ref(), &ignore_dirs, &cwd), + expected_result, + "result='{expected_result}' for path='{path}' and ignore_dir='{ignore_dirs:?}' " + ); + } + } +} diff --git a/src/interactive/app/tests/utils.rs b/src/interactive/app/tests/utils.rs index f6bc354..79defab 100644 --- a/src/interactive/app/tests/utils.rs +++ b/src/interactive/app/tests/utils.rs @@ -185,7 +185,7 @@ pub fn initialized_app_and_terminal_with_closure( count_hard_links: false, sorting: TraversalSorting::AlphabeticalByFileName, cross_filesystems: false, - ignore_dirs: Vec::new(), + ignore_dirs: Default::default(), }, input_paths, Interaction::None, diff --git a/src/main.rs b/src/main.rs index 818d971..9943270 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code, rust_2018_idioms, unsafe_code)] use anyhow::Result; use clap::Parser; -use dua::TraversalSorting; +use dua::{canonicalize_ignore_dirs, TraversalSorting}; use log::info; use simplelog::{Config, LevelFilter, WriteLogger}; use std::fs::OpenOptions; @@ -28,7 +28,7 @@ fn main() -> Result<()> { if let Some(log_file) = &opt.log_file { log_panics::init(); WriteLogger::init( - LevelFilter::Info, + LevelFilter::Debug, Config::default(), OpenOptions::new() .write(true) @@ -46,7 +46,7 @@ fn main() -> Result<()> { count_hard_links: opt.count_hard_links, sorting: TraversalSorting::None, cross_filesystems: !opt.stay_on_filesystem, - ignore_dirs: opt.ignore_dirs, + ignore_dirs: canonicalize_ignore_dirs(&opt.ignore_dirs), }; let res = match opt.command { #[cfg(feature = "tui-crossplatform")] |