summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2024-01-03 10:27:14 +0100
committerSebastian Thiel <sebastian.thiel@icloud.com>2024-01-03 10:27:14 +0100
commit49f98f537bf0ac41a7b1992094103f6d36f135f8 (patch)
treead9563ea0fbfe4e8a08cfd3abe340ca1cee9240c
parente992659db17f275b48e555afd6b18df737401f01 (diff)
parent93f0f61b3042b933f099714e3a6d336497eb18ba (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.rs91
-rw-r--r--src/interactive/app/tests/utils.rs2
-rw-r--r--src/main.rs6
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")]