diff --git a/CHANGELOG.md b/CHANGELOG.md index 6170274..67ea1ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Upcoming release ## Features - +- Add `--ignore-contain` option to ignore directories containing a named entry (e.g. to ignore [`CACHEDIR.TAG`](https://bford.info/cachedir/)); see #1727 (@fischman). ## Bugfixes diff --git a/doc/fd.1 b/doc/fd.1 index df42b17..278c429 100644 --- a/doc/fd.1 +++ b/doc/fd.1 @@ -265,6 +265,10 @@ Examples: \-\-exclude '*.pyc' \-\-exclude node_modules .TP +.BI "\-\-ignore-contain " name +Exclude directories that (directly) contain the given name. +This option can be specified multiple times. +.TP .BI "\-\-ignore-file " path Add a custom ignore-file in '.gitignore' format. These files have a low precedence. diff --git a/src/cli.rs b/src/cli.rs index d517468..4dbe547 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -528,6 +528,10 @@ pub struct Opts { )] pub hyperlink: HyperlinkWhen, + /// Ignore directories containing the named entry. + #[arg(long, value_name = "name")] + pub ignore_contain: Vec, + /// Set number of threads to use for searching & executing (default: number /// of available CPU cores) #[arg(long, short = 'j', value_name = "num", hide_short_help = true, value_parser = str::parse::)] diff --git a/src/config.rs b/src/config.rs index 9e18120..708a993 100644 --- a/src/config.rs +++ b/src/config.rs @@ -130,6 +130,9 @@ pub struct Config { /// Whether or not to use hyperlinks on paths pub hyperlink: bool, + + /// Names that should stop traversal down their parent. (e.g. https://bford.info/cachedir/). + pub ignore_contain: Vec, } impl Config { diff --git a/src/main.rs b/src/main.rs index fafb3b9..80e380f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -326,6 +326,7 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result { + if let Ok(e) = &entry { + let entry_path = e.path(); + if entry_path.is_dir() + && config + .ignore_contain + .iter() + .any(|ic| entry_path.join(ic).exists()) + { + return WalkState::Skip; + } + if e.depth() == 0 { // Skip the root directory entry. return WalkState::Continue; } + } + let entry = match entry { Ok(e) => DirEntry::normal(e), Err(ignore::Error::WithPath { path, diff --git a/tests/tests.rs b/tests/tests.rs index 722f054..c125d3a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2707,3 +2707,54 @@ fn test_hyperlink() { te.assert_output(&["--hyperlink=always", "a.foo"], &expected); } + +#[test] +fn test_ignore_contain() { + let te = TestEnv::new( + &["include", "exclude", "exclude/sub", "other"], + &[ + "top", + "include/foo", + "exclude/CACHEDIR.TAG", + "exclude/sub/nope", + "other/ignoremyparent", + ], + ); + let expected = "include/ + include/foo + symlink + top"; + te.assert_output( + &[ + "--ignore-contain=CACHEDIR.TAG", + "--ignore-contain=ignoremyparent", + ".", + ], + expected, + ); +} + +#[test] +fn test_ignore_contain_precedence_over_depth_check() { + let te = TestEnv::new( + &["include", "exclude", "exclude/sub"], + &[ + "top", + "include/foo", + "exclude/CACHEDIR.TAG", + "exclude/sub/nope", + ], + ); + let expected = "include/foo"; + te.assert_output( + &["--ignore-contain=CACHEDIR.TAG", "--min-depth=2", "."], + expected, + ); +} + +#[test] +fn test_ignore_contain_precedence_over_root_check() { + let te = TestEnv::new(&["include"], &["CACHEDIR.TAG", "top", "include/foo"]); + let expected = ""; + te.assert_output(&["--ignore-contain=CACHEDIR.TAG", "."], expected); +}