From f8ae334ca9416aae06eed600fec0eb94118a04ea Mon Sep 17 00:00:00 2001 From: William Correia Date: Sun, 8 Aug 2021 17:38:24 -0400 Subject: [PATCH 01/22] Add --no-ignore-parent flag - Flag toggles parent checking in the `ignore` crate. This should affect both git and non-git ignore files. - Updated Changelog. --- CHANGELOG.md | 2 ++ src/app.rs | 10 ++++++++++ src/main.rs | 4 ++++ src/options.rs | 3 +++ src/walk.rs | 2 +- 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08e4e60..16d6e20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Features +- Add new `--no-ignore-parent` flag, see #787 (@will459) + ## Bugfixes - Set default path separator to `/` in MSYS, see #537 and #730 (@aswild) diff --git a/src/app.rs b/src/app.rs index 2d6ec94..fcfbebe 100644 --- a/src/app.rs +++ b/src/app.rs @@ -49,6 +49,16 @@ pub fn build_app() -> App<'static, 'static> { ignored by '.gitignore' files.", ), ) + .arg( + Arg::with_name("no-ignore-parent") + .long("no-ignore-parent") + .overrides_with("no-ignore-parent") + .hidden_short_help(true) + .long_help( + "Show search results from files and directories that would otherwise be \ + ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.", + ), + ) .arg( Arg::with_name("no-global-ignore-file") .long("no-global-ignore-file") diff --git a/src/main.rs b/src/main.rs index 8654688..f3cda2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -338,6 +338,10 @@ fn run() -> Result { read_vcsignore: !(matches.is_present("no-ignore") || matches.is_present("rg-alias-hidden-ignore") || matches.is_present("no-ignore-vcs")), + read_parent_ignore: !(matches.is_present("no-ignore") + || matches.is_present("rg-alias-hidden-ignore") + || matches.is_present("no-ignore-vcs") + || matches.is_present("no-ignore-parent")), read_global_ignore: !(matches.is_present("no-ignore") || matches.is_present("rg-alias-hidden-ignore") || matches.is_present("no-global-ignore-file")), diff --git a/src/options.rs b/src/options.rs index aa9b5ea..cf7d28d 100644 --- a/src/options.rs +++ b/src/options.rs @@ -24,6 +24,9 @@ pub struct Options { /// Whether to respect `.fdignore` files or not. pub read_fdignore: bool, + /// Whether to respect ignore files in parent directories or not. + pub read_parent_ignore: bool, + /// Whether to respect VCS ignore files (`.gitignore`, ..) or not. pub read_vcsignore: bool, diff --git a/src/walk.rs b/src/walk.rs index 0529191..d970d2c 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -68,7 +68,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc, config: Arc) -> walker .hidden(config.ignore_hidden) .ignore(config.read_fdignore) - .parents(config.read_fdignore || config.read_vcsignore) + .parents(config.read_parent_ignore) .git_ignore(config.read_vcsignore) .git_global(config.read_vcsignore) .git_exclude(config.read_vcsignore) From 43f5c8adc984303aca260857f028256bd380bc63 Mon Sep 17 00:00:00 2001 From: William Correia Date: Fri, 27 Aug 2021 22:04:53 -0400 Subject: [PATCH 02/22] Add tests for --no-ignore-parent --- tests/tests.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 7fe2c70..4642f28 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -497,6 +497,78 @@ fn test_gitignore_and_fdignore() { ); } +/// Ignore parent ignore files (--no-ignore-parent) +#[test] +fn test_no_ignore_parent() { + let dirs = &["inner"]; + let files = &[ + "inner/parent-ignored", + "inner/child-ignored", + "inner/not-ignored", + ]; + let te = TestEnv::new(dirs, files); + + // Ignore 'parent-ignored' in root + fs::File::create(te.test_root().join(".gitignore")) + .unwrap() + .write_all(b"parent-ignored") + .unwrap(); + // Ignore 'child-ignored' in inner + fs::File::create(te.test_root().join("inner/.gitignore")) + .unwrap() + .write_all(b"child-ignored") + .unwrap(); + + te.assert_output_subdirectory("inner", &[], "not-ignored"); + + te.assert_output_subdirectory( + "inner", + &["--no-ignore-parent"], + "parent-ignored + not-ignored", + ); +} + +/// Ignore parent ignore files (--no-ignore-parent) with an inner git repo +#[test] +fn test_no_ignore_parent_inner_git() { + let dirs = &["inner"]; + let files = &[ + "inner/parent-ignored", + "inner/child-ignored", + "inner/not-ignored", + ]; + let te = TestEnv::new(dirs, files); + + // Make the inner folder also appear as a git repo + fs::create_dir_all(te.test_root().join("inner/.git")).unwrap(); + + // Ignore 'parent-ignored' in root + fs::File::create(te.test_root().join(".gitignore")) + .unwrap() + .write_all(b"parent-ignored") + .unwrap(); + // Ignore 'child-ignored' in inner + fs::File::create(te.test_root().join("inner/.gitignore")) + .unwrap() + .write_all(b"child-ignored") + .unwrap(); + + te.assert_output_subdirectory( + "inner", + &[], + "not-ignored + parent-ignored", + ); + + te.assert_output_subdirectory( + "inner", + &["--no-ignore-parent"], + "not-ignored + parent-ignored", + ); +} + /// Precedence of .fdignore files #[test] fn test_custom_ignore_precedence() { From 3c619afe30a55456bd2e6725e318eadbd2c0f179 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Wed, 1 Sep 2021 17:28:52 -0600 Subject: [PATCH 03/22] Update dependencies in Cargo.lock and Cargo.toml --- Cargo.lock | 235 ++++++++++++++++++++--------------------------------- Cargo.toml | 8 +- 2 files changed, 92 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85e7279..ee93a6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,21 +31,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.31" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" [[package]] name = "atty" @@ -60,15 +48,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" @@ -76,37 +58,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "blake2b_simd" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - [[package]] name = "bstr" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ "memchr", ] [[package]] name = "cc" -version = "1.0.53" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" [[package]] name = "cfg-if" @@ -129,9 +94,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.1" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "ansi_term 0.11.0", "atty", @@ -143,28 +108,21 @@ dependencies = [ "vec_map", ] -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "autocfg", - "cfg-if 0.1.10", + "cfg-if", "lazy_static", ] [[package]] name = "ctrlc" -version = "3.1.4" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7" +checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1" dependencies = [ "nix", "winapi", @@ -182,15 +140,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", @@ -228,13 +186,13 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "redox_syscall 0.2.10", + "redox_syscall", "winapi", ] @@ -246,9 +204,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fs_extra" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "fuchsia-cprng" @@ -258,20 +216,20 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "getrandom" -version = "0.1.14" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "globset" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" dependencies = [ "aho-corasick", "bstr", @@ -282,24 +240,24 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.13" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "humantime" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "ignore" -version = "0.4.15" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128b9e89d15a3faa642ee164c998fd4fae3d89d054463cddb2c25a7baad3a352" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" dependencies = [ "crossbeam-utils", "globset", @@ -342,45 +300,54 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.70" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "log" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] name = "lscolors" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f77452267149eac960ded529fe5f5460ddf792845a1d71b5d0cfcee5642e47e" +checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e" dependencies = [ "ansi_term 0.12.1", ] [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] [[package]] name = "nix" -version = "0.17.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +checksum = "e7555d6c7164cc913be1ce7f95cbecdabda61eb2ccd89008524af306fb7f5031" dependencies = [ "bitflags", "cc", - "cfg-if 0.1.10", + "cfg-if", "libc", - "void", + "memoffset", ] [[package]] @@ -394,9 +361,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", @@ -404,9 +371,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] @@ -421,6 +388,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "rand" version = "0.4.6" @@ -458,12 +431,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - [[package]] name = "redox_syscall" version = "0.2.10" @@ -475,13 +442,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom", - "redox_syscall 0.1.56", - "rust-argon2", + "redox_syscall", ] [[package]] @@ -503,25 +469,13 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] -[[package]] -name = "rust-argon2" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" -dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - [[package]] name = "same-file" version = "1.0.6" @@ -569,29 +523,28 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.0.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] name = "time" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "users" @@ -611,21 +564,15 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", @@ -634,21 +581,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", diff --git a/Cargo.toml b/Cargo.toml index f7e0f67..20bb1f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,8 +41,8 @@ lazy_static = "1.1.0" num_cpus = "1.8" regex = "1.5.4" regex-syntax = "0.6" -ctrlc = "3.1" -humantime = "2.0" +ctrlc = "3.2" +humantime = "2.1" lscolors = "0.7" globset = "0.4" anyhow = "1.0" @@ -51,7 +51,7 @@ normpath = "0.3" chrono = "0.4" [dependencies.clap] -version = "2.31.2" +version = "2.31.3" features = ["suggestions", "color", "wrap_help"] [target.'cfg(unix)'.dependencies] @@ -69,7 +69,7 @@ jemallocator = "0.3.0" [dev-dependencies] diff = "0.1" tempdir = "0.3" -filetime = "0.2.14" +filetime = "0.2.15" [profile.release] lto = true From a5f17db53af9be802a0b19e593b9b65db538f1d5 Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Mon, 23 Aug 2021 14:30:14 +0200 Subject: [PATCH 04/22] CI: Run clippy on stable and use msrv setting in `clippy.toml` --- .github/workflows/CICD.yml | 26 ++++++++++++++++++++------ clippy.toml | 1 + 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 clippy.toml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 3bf1da4..3e448c4 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -14,17 +14,16 @@ on: - '*' jobs: - min_version: - name: Minimum supported rust version + code_quality: + name: Code quality runs-on: ubuntu-20.04 steps: - name: Checkout source code uses: actions/checkout@v2 - - - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }}) + - name: Install rust toolchain uses: actions-rs/toolchain@v1 with: - toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }} + toolchain: stable default: true profile: minimal # minimal component installation (ie, no documentation) components: clippy, rustfmt @@ -33,11 +32,26 @@ jobs: with: command: fmt args: -- --check - - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) + - name: Ensure MSRV is set in `clippy.toml` + run: grep "^msrv = \"${{ env.MIN_SUPPORTED_RUST_VERSION }}\"\$" clippy.toml + - name: Run clippy uses: actions-rs/cargo@v1 with: command: clippy args: --locked --all-targets --all-features + + min_version: + name: Minimum supported rust version + runs-on: ubuntu-20.04 + steps: + - name: Checkout source code + uses: actions/checkout@v2 + - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }} + default: true + profile: minimal # minimal component installation (ie, no documentation) - name: Run tests uses: actions-rs/cargo@v1 with: diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..f97e544 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.42.0" From c3f786db43fbbaacef27a5036931cb2fc339c643 Mon Sep 17 00:00:00 2001 From: exploide Date: Sun, 3 Oct 2021 17:06:09 +0200 Subject: [PATCH 05/22] added missing help messages to various options despite they have hidden_short_help set to true, a short help message is still useful for auto-generated completions --- src/app.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index d2e8167..b26593b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -44,6 +44,7 @@ pub fn build_app() -> App<'static, 'static> { .long("no-ignore-vcs") .overrides_with("no-ignore-vcs") .hidden_short_help(true) + .help("Do not respect .gitignore files") .long_help( "Show search results from files and directories that would otherwise be \ ignored by '.gitignore' files.", @@ -54,6 +55,7 @@ pub fn build_app() -> App<'static, 'static> { .long("no-ignore-parent") .overrides_with("no-ignore-parent") .hidden_short_help(true) + .help("Do not respect .(git|fd)ignore files in parent directories") .long_help( "Show search results from files and directories that would otherwise be \ ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.", @@ -63,6 +65,7 @@ pub fn build_app() -> App<'static, 'static> { Arg::with_name("no-global-ignore-file") .long("no-global-ignore-file") .hidden(true) + .help("Do not respect the global ignore file") .long_help("Do not respect the global ignore file."), ) .arg( @@ -71,6 +74,7 @@ pub fn build_app() -> App<'static, 'static> { .long("unrestricted") .multiple(true) .hidden_short_help(true) + .help("Alias for '--no-ignore', and '--hidden' when given twice") .long_help( "Alias for '--no-ignore'. Can be repeated. '-uu' is an alias for \ '--no-ignore --hidden'.", @@ -114,6 +118,7 @@ pub fn build_app() -> App<'static, 'static> { .long("regex") .overrides_with_all(&["glob", "regex"]) .hidden_short_help(true) + .help("Regular-expression based search (default)") .long_help( "Perform a regular-expression based search (default). This can be used to \ override --glob.", @@ -126,6 +131,7 @@ pub fn build_app() -> App<'static, 'static> { .alias("literal") .overrides_with("fixed-strings") .hidden_short_help(true) + .help("Treat pattern as literal string instead of regex") .long_help( "Treat the pattern as a literal string instead of a regular expression. Note \ that this also performs substring comparison. If you want to match on an \ @@ -210,6 +216,7 @@ pub fn build_app() -> App<'static, 'static> { .long("maxdepth") .hidden(true) .takes_value(true) + .help("Set maximum search depth (default: none)") ) .arg( Arg::with_name("min-depth") @@ -217,6 +224,7 @@ pub fn build_app() -> App<'static, 'static> { .takes_value(true) .value_name("depth") .hidden_short_help(true) + .help("Only show results starting at given depth") .long_help( "Only show search results starting at the given depth. \ See also: '--max-depth' and '--exact-depth'", @@ -229,6 +237,7 @@ pub fn build_app() -> App<'static, 'static> { .value_name("depth") .hidden_short_help(true) .conflicts_with_all(&["max-depth", "min-depth"]) + .help("Only show results at exact given depth") .long_help( "Only show search results at the exact given depth. This is an alias for \ '--min-depth --max-depth '.", @@ -239,6 +248,7 @@ pub fn build_app() -> App<'static, 'static> { .long("prune") .conflicts_with_all(&["size", "exact-depth"]) .hidden_short_help(true) + .help("Do not traverse into matching directories") .long_help("Do not traverse into matching directories.") ) .arg( @@ -381,6 +391,7 @@ pub fn build_app() -> App<'static, 'static> { .number_of_values(1) .multiple(true) .hidden_short_help(true) + .help("Add custom ignore-file in '.gitignore' format") .long_help( "Add a custom ignore-file in '.gitignore' format. These files have a low \ precedence.", @@ -409,6 +420,7 @@ pub fn build_app() -> App<'static, 'static> { .takes_value(true) .value_name("num") .hidden_short_help(true) + .help("Set number of threads") .long_help( "Set number of threads to use for searching & executing (default: number \ of available CPU cores)", @@ -422,7 +434,7 @@ pub fn build_app() -> App<'static, 'static> { .number_of_values(1) .allow_hyphen_values(true) .multiple(true) - .help("Limit results based on the size of files.") + .help("Limit results based on the size of files") .long_help( "Limit results based on the size of files using the format <+->.\n \ '+': file size must be greater than or equal to this\n \ @@ -447,6 +459,7 @@ pub fn build_app() -> App<'static, 'static> { .long("max-buffer-time") .takes_value(true) .hidden(true) + .help("Milliseconds to buffer before streaming search results to console") .long_help( "Amount of time in milliseconds to buffer, before streaming the search \ results to the console.", @@ -503,6 +516,7 @@ pub fn build_app() -> App<'static, 'static> { // the files they saw in the previous search. .conflicts_with_all(&["exec", "exec-batch", "list-details"]) .hidden_short_help(true) + .help("Limit number of search results") .long_help("Limit the number of search results to 'count' and quit immediately."), ) .arg( @@ -511,6 +525,7 @@ pub fn build_app() -> App<'static, 'static> { .hidden_short_help(true) .overrides_with("max-results") .conflicts_with_all(&["exec", "exec-batch", "list-details"]) + .help("Limit search to a single result") .long_help("Limit the search to a single result and quit immediately. \ This is an alias for '--max-results=1'.") ) @@ -521,6 +536,7 @@ pub fn build_app() -> App<'static, 'static> { .alias("has-results") .hidden_short_help(true) .conflicts_with_all(&["exec", "exec-batch", "list-details", "max-results"]) + .help("Print nothing, exit code 0 if match found, 1 otherwise") .long_help( "When the flag is present, the program does not print anything and will \ return with an exit code of 0 if there is at least one match. Otherwise, the \ @@ -533,6 +549,7 @@ pub fn build_app() -> App<'static, 'static> { .long("show-errors") .hidden_short_help(true) .overrides_with("show-errors") + .help("Show filesystem errors") .long_help( "Enable the display of filesystem errors for situations such as \ insufficient permissions or dead symlinks.", @@ -545,6 +562,7 @@ pub fn build_app() -> App<'static, 'static> { .value_name("path") .number_of_values(1) .hidden_short_help(true) + .help("Change current working directory") .long_help( "Change the current working directory of fd to the provided path. This \ means that search results will be shown with respect to the given base \ @@ -568,6 +586,7 @@ pub fn build_app() -> App<'static, 'static> { .value_name("separator") .long("path-separator") .hidden_short_help(true) + .help("Set path separator when printing file paths") .long_help( "Set the path separator to use when printing file paths. The default is \ the OS-specific separator ('/' on Unix, '\\' on Windows).", @@ -590,6 +609,7 @@ pub fn build_app() -> App<'static, 'static> { .multiple(true) .hidden_short_help(true) .number_of_values(1) + .help("Provide paths to search as an alternative to the positional ") .long_help( "Provide paths to search as an alternative to the positional \ argument. Changes the usage to `fd [FLAGS/OPTIONS] --search-path \ @@ -626,6 +646,7 @@ pub fn build_app() -> App<'static, 'static> { .long("one-file-system") .aliases(&["mount", "xdev"]) .hidden_short_help(true) + .help("Do not descend into a different file system") .long_help( "By default, fd will traverse the file system tree as far as other options \ dictate. With this flag, fd ensures that it does not descend into a \ From fd1c3d376eedf4a281daada8484739af2b2e6f2d Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Wed, 6 Oct 2021 18:38:50 +0200 Subject: [PATCH 06/22] Fix typos --- README.md | 2 +- contrib/completion/_fd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22361cb..2fa4eab 100644 --- a/README.md +++ b/README.md @@ -625,7 +625,7 @@ You can install [the fd-find package](https://www.freshports.org/sysutils/fd) fr pkg install fd-find ``` -### From NPM +### From npm On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package: diff --git a/contrib/completion/_fd b/contrib/completion/_fd index 3f43b0f..28b37ee 100644 --- a/contrib/completion/_fd +++ b/contrib/completion/_fd @@ -220,7 +220,7 @@ _fd() { _fd "$@" # ------------------------------------------------------------------------------ -# Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users +# Copyright (c) 2011 GitHub zsh-users - http://github.com/zsh-users # All rights reserved. # # Redistribution and use in source and binary forms, with or without From c06efe131787384f081f22421b63a2d4fe2efe22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:02:35 +0000 Subject: [PATCH 07/22] Bump anyhow from 1.0.43 to 1.0.44 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.43 to 1.0.44. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.43...1.0.44) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee93a6f..019bee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] name = "atty" From feb969881b8dc0ff3ed7de118570da39e347501d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:02:40 +0000 Subject: [PATCH 08/22] Bump libc from 0.2.101 to 0.2.103 Bumps [libc](https://github.com/rust-lang/libc) from 0.2.101 to 0.2.103. - [Release notes](https://github.com/rust-lang/libc/releases) - [Commits](https://github.com/rust-lang/libc/compare/0.2.101...0.2.103) --- updated-dependencies: - dependency-name: libc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 019bee1..71a75eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,9 +300,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "log" From 00eb6461cb2da687d1d0750a45efe9380c7e39b2 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Tue, 12 Oct 2021 00:46:15 -0600 Subject: [PATCH 09/22] Remove an unnecessary reference. (#864) This was caught by Code Quality github action with the message: > this expression borrows a reference (`&walk::DirEntry`) that is immediately dereferenced by the compiler --- src/filetypes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filetypes.rs b/src/filetypes.rs index 2baf0f2..3f9281d 100644 --- a/src/filetypes.rs +++ b/src/filetypes.rs @@ -39,7 +39,7 @@ impl FileTypes { .metadata() .map(|m| filesystem::is_executable(&m)) .unwrap_or(false)) - || (self.empty_only && !filesystem::is_empty(&entry)) + || (self.empty_only && !filesystem::is_empty(entry)) || !(entry_type.is_file() || entry_type.is_dir() || entry_type.is_symlink() From a64a607fd89a4454203dc4ecf32de114e7b6e36b Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Sat, 9 Oct 2021 16:20:03 +0200 Subject: [PATCH 10/22] Bump MSRV to 1.53 --- .github/workflows/CICD.yml | 2 +- README.md | 2 +- build.rs | 2 +- clippy.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 3e448c4..03d59a2 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,7 +1,7 @@ name: CICD env: - MIN_SUPPORTED_RUST_VERSION: "1.42.0" + MIN_SUPPORTED_RUST_VERSION: "1.53.0" CICD_INTERMEDIATES_DIR: "_cicd-intermediates" on: diff --git a/README.md b/README.md index 2fa4eab..e7b56d3 100644 --- a/README.md +++ b/README.md @@ -639,7 +639,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can ``` cargo install fd-find ``` -Note that rust version *1.42.0* or later is required. +Note that rust version *1.53.0* or later is required. `make` is also needed for the build. diff --git a/build.rs b/build.rs index ddf9e15..d2d9566 100644 --- a/build.rs +++ b/build.rs @@ -5,7 +5,7 @@ use clap::Shell; include!("src/app.rs"); fn main() { - let min_version = "1.42"; + let min_version = "1.53"; match version_check::is_min_version(min_version) { Some(true) => {} diff --git a/clippy.toml b/clippy.toml index f97e544..e51b4f3 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.42.0" +msrv = "1.53.0" From 45a86459b2275814bd455bf2710506644b7061c1 Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Sat, 21 Aug 2021 22:43:17 +0200 Subject: [PATCH 11/22] Refactor: `merge_exit_codes` now takes an `impl IntoIterator` This way, callers don't need to collect into a slice / vec. --- src/exec/job.rs | 2 +- src/exit_codes.rs | 20 ++++++++++---------- src/walk.rs | 11 +++++------ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/exec/job.rs b/src/exec/job.rs index c43be5b..83abf1a 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -42,7 +42,7 @@ pub fn job( results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output)) } // Returns error in case of any error. - merge_exitcodes(&results) + merge_exitcodes(results) } pub fn batch( diff --git a/src/exit_codes.rs b/src/exit_codes.rs index 2083b32..4f8a974 100644 --- a/src/exit_codes.rs +++ b/src/exit_codes.rs @@ -23,8 +23,8 @@ impl ExitCode { } } -pub fn merge_exitcodes(results: &[ExitCode]) -> ExitCode { - if results.iter().any(|&c| ExitCode::is_error(c)) { +pub fn merge_exitcodes(results: impl IntoIterator) -> ExitCode { + if results.into_iter().any(ExitCode::is_error) { return ExitCode::GeneralError; } ExitCode::Success @@ -36,38 +36,38 @@ mod tests { #[test] fn success_when_no_results() { - assert_eq!(merge_exitcodes(&[]), ExitCode::Success); + assert_eq!(merge_exitcodes([]), ExitCode::Success); } #[test] fn general_error_if_at_least_one_error() { assert_eq!( - merge_exitcodes(&[ExitCode::GeneralError]), + merge_exitcodes([ExitCode::GeneralError]), ExitCode::GeneralError ); assert_eq!( - merge_exitcodes(&[ExitCode::KilledBySigint]), + merge_exitcodes([ExitCode::KilledBySigint]), ExitCode::GeneralError ); assert_eq!( - merge_exitcodes(&[ExitCode::KilledBySigint, ExitCode::Success]), + merge_exitcodes([ExitCode::KilledBySigint, ExitCode::Success]), ExitCode::GeneralError ); assert_eq!( - merge_exitcodes(&[ExitCode::Success, ExitCode::GeneralError]), + merge_exitcodes([ExitCode::Success, ExitCode::GeneralError]), ExitCode::GeneralError ); assert_eq!( - merge_exitcodes(&[ExitCode::GeneralError, ExitCode::KilledBySigint]), + merge_exitcodes([ExitCode::GeneralError, ExitCode::KilledBySigint]), ExitCode::GeneralError ); } #[test] fn success_if_no_error() { - assert_eq!(merge_exitcodes(&[ExitCode::Success]), ExitCode::Success); + assert_eq!(merge_exitcodes([ExitCode::Success]), ExitCode::Success); assert_eq!( - merge_exitcodes(&[ExitCode::Success, ExitCode::Success]), + merge_exitcodes([ExitCode::Success, ExitCode::Success]), ExitCode::Success ); } diff --git a/src/walk.rs b/src/walk.rs index 0d91b91..1cc1ab5 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -205,12 +205,11 @@ fn spawn_receiver( } // Wait for all threads to exit before exiting the program. - let mut results: Vec = Vec::new(); - for h in handles { - results.push(h.join().unwrap()); - } - - merge_exitcodes(&results) + let exit_codes = handles + .into_iter() + .map(|handle| handle.join().unwrap()) + .collect::>(); + merge_exitcodes(exit_codes) } } else { let start = time::Instant::now(); From 3de948ae0dcc6894580ccd1a0b433f0bd7e859ab Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Sat, 21 Aug 2021 22:44:35 +0200 Subject: [PATCH 12/22] Refactor: use some nice Rust methods in buffering code --- src/walk.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/walk.rs b/src/walk.rs index 1cc1ab5..8236e4e 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -40,6 +40,8 @@ pub enum WorkerResult { /// Maximum size of the output buffer before flushing results to the console pub const MAX_BUFFER_LENGTH: usize = 1000; +/// Default duration until output buffering switches to streaming. +pub const DEFAULT_MAX_BUFFER_TIME: time::Duration = time::Duration::from_millis(100); /// Recursively scan the given search path for files / pathnames matching the pattern. /// @@ -220,9 +222,7 @@ fn spawn_receiver( let mut mode = ReceiverMode::Buffering; // Maximum time to wait before we start streaming to the console. - let max_buffer_time = config - .max_buffer_time - .unwrap_or_else(|| time::Duration::from_millis(100)); + let max_buffer_time = config.max_buffer_time.unwrap_or(DEFAULT_MAX_BUFFER_TIME); let stdout = io::stdout(); let mut stdout = stdout.lock(); @@ -242,7 +242,7 @@ fn spawn_receiver( // Have we reached the maximum buffer size or maximum buffering time? if buffer.len() > MAX_BUFFER_LENGTH - || time::Instant::now() - start > max_buffer_time + || start.elapsed() > max_buffer_time { // Flush the buffer for v in &buffer { @@ -265,6 +265,11 @@ fn spawn_receiver( } num_results += 1; + if let Some(max_results) = config.max_results { + if num_results >= max_results { + break; + } + } } WorkerResult::Error(err) => { if show_filesystem_errors { @@ -272,21 +277,13 @@ fn spawn_receiver( } } } - - if let Some(max_results) = config.max_results { - if num_results >= max_results { - break; - } - } } // If we have finished fast enough (faster than max_buffer_time), we haven't streamed // anything to the console, yet. In this case, sort the results and print them: - if !buffer.is_empty() { - buffer.sort(); - for value in buffer { - output::print_entry(&mut stdout, &value, &config, &wants_to_quit); - } + buffer.sort(); + for value in buffer { + output::print_entry(&mut stdout, &value, &config, &wants_to_quit); } if config.quiet { From b8c575cc8f6e3e00537fe4c436805af099320fc0 Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Sat, 21 Aug 2021 23:00:23 +0200 Subject: [PATCH 13/22] Refactor: extract some methods out of `run` and reorder `main.rs` Now, the top method is `main`, then comes `run`, then the methods used in `run` follow. Generally, a method is always declared somewhere after its first use. This way, you can read the file from top to bottom with a decreasing level of abstraction (you start with very high-level processes like setting the current dir and logic for which ls command to use only comes furher down). --- src/main.rs | 504 +++++++++++++++++++++++++++++----------------------- 1 file changed, 283 insertions(+), 221 deletions(-) diff --git a/src/main.rs b/src/main.rs index 76408a9..3adb532 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,10 +50,38 @@ ow=0:or=0;38;5;16;48;5;203:no=0:ex=1;38;5;203:cd=0;38;5;203;48;5;236:mi=0;38;5;1 38;5;185:*.jpg=0;38;5;208:*.mir=0;38;5;48:*.sxi=0;38;5;186:*.bz2=4;38;5;203:*.odt=0;38;5;186:*.mov=0;38;5;208:*.toc=0;38;5;243:*.bat=1;38;5;203:*.asa=0;38;5;48:*.awk=0;38;5;48:*.sbt=0;38;5;48:*.vcd=4;38;5;203:*.kts=0;38;5;48:*.arj=4;38;5;203:*.blg=0;38;5;243:*.c++=0;38;5;48:*.odp=0;38;5;186:*.bbl=0;38;5;243:*.idx=0;38;5;243:*.com=1;38;5;203:*.mp3=0;38;5;208:*.avi=0;38;5;208:*.def=0;38;5;48:*.cgi=0;38;5;48:*.zip=4;38;5;203:*.ttf=0;38;5;208:*.ppt=0;38;5;186:*.tml=0;38;5;149:*.fsx=0;38;5;48:*.h++=0;38;5;48:*.rtf=0;38;5;186:*.inl=0;38;5;48:*.yaml=0;38;5;149:*.html=0;38;5;185:*.mpeg=0;38;5;208:*.java=0;38;5;48:*.hgrc=0;38;5;149:*.orig=0;38;5;243:*.conf=0;38;5;149:*.dart=0;38;5;48:*.psm1=0;38;5;48:*.rlib=0;38;5;243:*.fish=0;38;5;48:*.bash=0;38;5;48:*.make=0;38;5;149:*.docx=0;38;5;186:*.json=0;38;5;149:*.psd1=0;38;5;48:*.lisp=0;38;5;48:*.tbz2=4;38;5;203:*.diff=0;38;5;48:*.epub=0;38;5;186:*.xlsx=0;38;5;186:*.pptx=0;38;5;186:*.toml=0;38;5;149:*.h264=0;38;5;208:*.purs=0;38;5;48:*.flac=0;38;5;208:*.tiff=0;38;5;208:*.jpeg=0;38;5;208:*.lock=0;38;5;243:*.less=0;38;5;48:*.dyn_o=0;38;5;243:*.scala=0;38;5;48:*.mdown=0;38;5;185:*.shtml=0;38;5;185:*.class=0;38;5;243:*.cache=0;38;5;243:*.cmake=0;38;5;149:*passwd=0;38;5;149:*.swift=0;38;5;48:*shadow=0;38;5;149:*.xhtml=0;38;5;185:*.patch=0;38;5;48:*.cabal=0;38;5;48:*README=0;38;5;16;48;5;186:*.toast=4;38;5;203:*.ipynb=0;38;5;48:*COPYING=0;38;5;249:*.gradle=0;38;5;48:*.matlab=0;38;5;48:*.config=0;38;5;149:*LICENSE=0;38;5;249:*.dyn_hi=0;38;5;243:*.flake8=0;38;5;149:*.groovy=0;38;5;48:*INSTALL=0;38;5;16;48;5;186:*TODO.md=1:*.ignore=0;38;5;149:*Doxyfile=0;38;5;149:*TODO.txt=1:*setup.py=0;38;5;149:*Makefile=0;38;5;149:*.gemspec=0;38;5;149:*.desktop=0;38;5;149:*.rgignore=0;38;5;149:*.markdown=0;38;5;185:*COPYRIGHT=0;38;5;249:*configure=0;38;5;149:*.DS_Store=0;38;5;243:*.kdevelop=0;38;5;149:*.fdignore=0;38;5;149:*README.md=0;38;5;16;48;5;186:*.cmake.in=0;38;5;149:*SConscript=0;38;5;149:*CODEOWNERS=0;38;5;149:*.localized=0;38;5;243:*.gitignore=0;38;5;149:*Dockerfile=0;38;5;149:*.gitconfig=0;38;5;149:*INSTALL.md=0;38;5;16;48;5;186:*README.txt=0;38;5;16;48;5;186:*SConstruct=0;38;5;149:*.scons_opt=0;38;5;243:*.travis.yml=0;38;5;186:*.gitmodules=0;38;5;149:*.synctex.gz=0;38;5;243:*LICENSE-MIT=0;38;5;249:*MANIFEST.in=0;38;5;149:*Makefile.in=0;38;5;243:*Makefile.am=0;38;5;149:*INSTALL.txt=0;38;5;16;48;5;186:*configure.ac=0;38;5;149:*.applescript=0;38;5;48:*appveyor.yml=0;38;5;186:*.fdb_latexmk=0;38;5;243:*CONTRIBUTORS=0;38;5;16;48;5;186:*.clang-format=0;38;5;149:*LICENSE-APACHE=0;38;5;249:*CMakeLists.txt=0;38;5;149:*CMakeCache.txt=0;38;5;243:*.gitattributes=0;38;5;149:*CONTRIBUTORS.md=0;38;5;16;48;5;186:*.sconsign.dblite=0;38;5;243:*requirements.txt=0;38;5;149:*CONTRIBUTORS.txt=0;38;5;16;48;5;186:*package-lock.json=0;38;5;243:*.CFUserTextEncoding=0;38;5;243 "; +fn main() { + let result = run(); + match result { + Ok(exit_code) => { + process::exit(exit_code.into()); + } + Err(err) => { + eprintln!("[fd error]: {:#}", err); + process::exit(ExitCode::GeneralError.into()); + } + } +} + fn run() -> Result { let matches = app::build_app().get_matches_from(env::args_os()); - // Set the current working directory of the process + set_working_dir(&matches)?; + let current_directory = Path::new("."); + ensure_current_directory_exists(current_directory)?; + let search_paths = extract_search_paths(&matches, current_directory)?; + + let pattern = extract_search_pattern(&matches)?; + ensure_search_pattern_is_not_a_path(&matches, pattern)?; + let pattern_regex = build_pattern_regex(&matches, pattern)?; + + let config = construct_options(matches, &pattern_regex)?; + ensure_use_hidden_option_for_leading_dot_pattern(&config, &pattern_regex)?; + let re = build_regex(pattern_regex, &config)?; + walk::scan(&search_paths, Arc::new(re), Arc::new(config)) +} + +fn set_working_dir(matches: &clap::ArgMatches) -> Result<()> { if let Some(base_directory) = matches.value_of_os("base-directory") { let base_directory = Path::new(base_directory); if !filesystem::is_existing_directory(base_directory) { @@ -69,15 +97,20 @@ fn run() -> Result { ) })?; } + Ok(()) +} - let current_directory = Path::new("."); - if !filesystem::is_existing_directory(current_directory) { - return Err(anyhow!( +fn ensure_current_directory_exists(current_directory: &Path) -> Result<()> { + if filesystem::is_existing_directory(current_directory) { + Ok(()) + } else { + Err(anyhow!( "Could not retrieve current directory (has it been deleted?)." - )); + )) } +} - // Get the search pattern +fn extract_search_pattern<'a>(matches: &'a clap::ArgMatches) -> Result<&'a str> { let pattern = matches .value_of_os("pattern") .map(|p| { @@ -86,54 +119,57 @@ fn run() -> Result { }) .transpose()? .unwrap_or(""); + Ok(pattern) +} - // Get one or more root directories to search. - let passed_arguments = matches +fn extract_search_paths( + matches: &clap::ArgMatches, + current_directory: &Path, +) -> Result> { + let mut search_paths = matches .values_of_os("path") - .or_else(|| matches.values_of_os("search-path")); - - let mut search_paths = if let Some(paths) = passed_arguments { - let mut directories = vec![]; - for path in paths { - let path_buffer = PathBuf::from(path); - if filesystem::is_existing_directory(&path_buffer) { - directories.push(path_buffer); - } else { - print_error(format!( - "Search path '{}' is not a directory.", - path_buffer.to_string_lossy() - )); - } - } - - directories - } else { - vec![current_directory.to_path_buf()] - }; - - // Check if we have no valid search paths. + .or_else(|| matches.values_of_os("search-path")) + .map_or_else( + || vec![current_directory.to_path_buf()], + |paths| { + paths + .filter_map(|path| { + let path_buffer = PathBuf::from(path); + if filesystem::is_existing_directory(&path_buffer) { + Some(path_buffer) + } else { + print_error(format!( + "Search path '{}' is not a directory.", + path_buffer.to_string_lossy() + )); + None + } + }) + .collect() + }, + ); if search_paths.is_empty() { return Err(anyhow!("No valid search paths given.")); } - if matches.is_present("absolute-path") { - search_paths = search_paths - .iter() - .map(|path_buffer| { - path_buffer - .normalize() - .and_then(|pb| filesystem::absolute_path(pb.as_path())) - .unwrap() - }) - .collect(); + update_to_absolute_paths(&mut search_paths); } + Ok(search_paths) +} - // Detect if the user accidentally supplied a path instead of a search pattern +fn update_to_absolute_paths(search_paths: &mut [PathBuf]) { + for buffer in search_paths.iter_mut() { + *buffer = filesystem::absolute_path(buffer.normalize().unwrap().as_path()).unwrap(); + } +} + +/// Detect if the user accidentally supplied a path instead of a search pattern +fn ensure_search_pattern_is_not_a_path(matches: &clap::ArgMatches, pattern: &str) -> Result<()> { if !matches.is_present("full-path") && pattern.contains(std::path::MAIN_SEPARATOR) && Path::new(pattern).is_dir() { - return Err(anyhow!( + Err(anyhow!( "The search pattern '{pattern}' contains a path-separation character ('{sep}') \ and will not lead to any search results.\n\n\ If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \ @@ -142,10 +178,14 @@ fn run() -> Result { fd --full-path '{pattern}'", pattern = pattern, sep = std::path::MAIN_SEPARATOR, - )); + )) + } else { + Ok(()) } +} - let pattern_regex = if matches.is_present("glob") && !pattern.is_empty() { +fn build_pattern_regex(matches: &clap::ArgMatches, pattern: &str) -> Result { + Ok(if matches.is_present("glob") && !pattern.is_empty() { let glob = GlobBuilder::new(pattern).literal_separator(true).build()?; glob.regex().to_owned() } else if matches.is_present("fixed-strings") { @@ -153,17 +193,46 @@ fn run() -> Result { regex::escape(pattern) } else { String::from(pattern) - }; + }) +} +fn check_path_separator_length(path_separator: Option<&str>) -> Result<()> { + match (cfg!(windows), path_separator) { + (true, Some(sep)) if sep.len() > 1 => Err(anyhow!( + "A path separator must be exactly one byte, but \ + the given separator is {} bytes: '{}'.\n\ + In some shells on Windows, '/' is automatically \ + expanded. Try to use '//' instead.", + sep.len(), + sep + )), + _ => Ok(()), + } +} + +fn construct_options(matches: clap::ArgMatches, pattern_regex: &str) -> Result { // The search will be case-sensitive if the command line flag is set or // if the pattern has an uppercase character (smart case). let case_sensitive = !matches.is_present("ignore-case") - && (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex)); + && (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern_regex)); + + let path_separator = matches + .value_of("path-separator") + .map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned())); + check_path_separator_length(path_separator.as_deref())?; + + let size_limits = extract_size_limits(&matches)?; + let time_constraints = extract_time_constraints(&matches)?; + #[cfg(unix)] + let owner_constraint = matches + .value_of("owner") + .map(OwnerFilter::from_string) + .transpose()? + .flatten(); #[cfg(windows)] let ansi_colors_support = ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some(); - #[cfg(not(windows))] let ansi_colors_support = true; @@ -174,163 +243,14 @@ fn run() -> Result { _ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal, }; - let path_separator = matches - .value_of("path-separator") - .map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned())); - - #[cfg(windows)] - { - if let Some(ref sep) = path_separator { - if sep.len() > 1 { - return Err(anyhow!( - "A path separator must be exactly one byte, but \ - the given separator is {} bytes: '{}'.\n\ - In some shells on Windows, '/' is automatically \ - expanded. Try to use '//' instead.", - sep.len(), - sep - )); - }; - }; - } - let ls_colors = if colored_output { Some(LsColors::from_env().unwrap_or_else(|| LsColors::from_string(DEFAULT_LS_COLORS))) } else { None }; + let command = extract_command(&matches, path_separator.as_deref(), colored_output)?; - let command = if let Some(args) = matches.values_of("exec") { - Some(CommandTemplate::new(args, path_separator.clone())) - } else if let Some(args) = matches.values_of("exec-batch") { - Some(CommandTemplate::new_batch(args, path_separator.clone())?) - } else if matches.is_present("list-details") { - let color = matches.value_of("color").unwrap_or("auto"); - let color_arg = ["--color=", color].concat(); - - #[allow(unused)] - let gnu_ls = |command_name| { - // Note: we use short options here (instead of --long-options) to support more - // platforms (like BusyBox). - vec![ - command_name, - "-l", // long listing format - "-h", // human readable file sizes - "-d", // list directories themselves, not their contents - &color_arg, - ] - }; - - let cmd: Vec<&str> = if cfg!(unix) { - if !cfg!(any( - target_os = "macos", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - )) { - // Assume ls is GNU ls - gnu_ls("ls") - } else { - // MacOS, DragonFlyBSD, FreeBSD - use std::process::{Command, Stdio}; - - // Use GNU ls, if available (support for --color=auto, better LS_COLORS support) - let gnu_ls_exists = Command::new("gls") - .arg("--version") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .is_ok(); - - if gnu_ls_exists { - gnu_ls("gls") - } else { - let mut cmd = vec![ - "ls", // BSD version of ls - "-l", // long listing format - "-h", // '--human-readable' is not available, '-h' is - "-d", // '--directory' is not available, but '-d' is - ]; - - if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output { - // -G is not available in NetBSD's and OpenBSD's ls - cmd.push("-G"); - } - - cmd - } - } - } else if cfg!(windows) { - use std::process::{Command, Stdio}; - - // Use GNU ls, if available - let gnu_ls_exists = Command::new("ls") - .arg("--version") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .is_ok(); - - if gnu_ls_exists { - gnu_ls("ls") - } else { - return Err(anyhow!( - "'fd --list-details' is not supported on Windows unless GNU 'ls' is installed." - )); - } - } else { - return Err(anyhow!( - "'fd --list-details' is not supported on this platform." - )); - }; - - Some(CommandTemplate::new_batch(&cmd, path_separator.clone()).unwrap()) - } else { - None - }; - - let size_limits = if let Some(vs) = matches.values_of("size") { - vs.map(|sf| { - SizeFilter::from_string(sf) - .ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf)) - }) - .collect::>>()? - } else { - vec![] - }; - - let now = time::SystemTime::now(); - let mut time_constraints: Vec = Vec::new(); - if let Some(t) = matches.value_of("changed-within") { - if let Some(f) = TimeFilter::after(&now, t) { - time_constraints.push(f); - } else { - return Err(anyhow!( - "'{}' is not a valid date or duration. See 'fd --help'.", - t - )); - } - } - if let Some(t) = matches.value_of("changed-before") { - if let Some(f) = TimeFilter::before(&now, t) { - time_constraints.push(f); - } else { - return Err(anyhow!( - "'{}' is not a valid date or duration. See 'fd --help'.", - t - )); - } - } - - #[cfg(unix)] - let owner_constraint = if let Some(s) = matches.value_of("owner") { - OwnerFilter::from_string(s)? - } else { - None - }; - - let config = Options { + Ok(Options { case_sensitive, search_full_path: matches.is_present("full-path"), ignore_hidden: !(matches.is_present("hidden") @@ -455,20 +375,177 @@ fn run() -> Result { None } }), - }; + }) +} - if cfg!(unix) - && config.ignore_hidden - && pattern_matches_strings_with_leading_dot(&pattern_regex) - { +fn extract_command( + matches: &clap::ArgMatches, + path_separator: Option<&str>, + colored_output: bool, +) -> Result> { + None.or_else(|| { + matches.values_of("exec").map(|args| { + Ok(CommandTemplate::new( + args, + path_separator.map(str::to_string), + )) + }) + }) + .or_else(|| { + matches + .values_of("exec-batch") + .map(|args| CommandTemplate::new_batch(args, path_separator.map(str::to_string))) + }) + .or_else(|| { + if !matches.is_present("list-details") { + return None; + } + + let color = matches.value_of("color").unwrap_or("auto"); + let color_arg = format!("--color={}", color); + + let res = determine_ls_command(&color_arg, colored_output).map(|cmd| { + CommandTemplate::new_batch(cmd, path_separator.map(str::to_string)).unwrap() + }); + + Some(res) + }) + .transpose() +} + +fn determine_ls_command(color_arg: &str, colored_output: bool) -> Result> { + #[allow(unused)] + let gnu_ls = |command_name| { + // Note: we use short options here (instead of --long-options) to support more + // platforms (like BusyBox). + vec![ + command_name, + "-l", // long listing format + "-h", // human readable file sizes + "-d", // list directories themselves, not their contents + color_arg, + ] + }; + let cmd: Vec<&str> = if cfg!(unix) { + if !cfg!(any( + target_os = "macos", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )) { + // Assume ls is GNU ls + gnu_ls("ls") + } else { + // MacOS, DragonFlyBSD, FreeBSD + use std::process::{Command, Stdio}; + + // Use GNU ls, if available (support for --color=auto, better LS_COLORS support) + let gnu_ls_exists = Command::new("gls") + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok(); + + if gnu_ls_exists { + gnu_ls("gls") + } else { + let mut cmd = vec![ + "ls", // BSD version of ls + "-l", // long listing format + "-h", // '--human-readable' is not available, '-h' is + "-d", // '--directory' is not available, but '-d' is + ]; + + if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output { + // -G is not available in NetBSD's and OpenBSD's ls + cmd.push("-G"); + } + + cmd + } + } + } else if cfg!(windows) { + use std::process::{Command, Stdio}; + + // Use GNU ls, if available + let gnu_ls_exists = Command::new("ls") + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok(); + + if gnu_ls_exists { + gnu_ls("ls") + } else { + return Err(anyhow!( + "'fd --list-details' is not supported on Windows unless GNU 'ls' is installed." + )); + } + } else { return Err(anyhow!( + "'fd --list-details' is not supported on this platform." + )); + }; + Ok(cmd) +} + +fn extract_size_limits(matches: &clap::ArgMatches) -> Result> { + matches.values_of("size").map_or(Ok(Vec::new()), |vs| { + vs.map(|sf| { + SizeFilter::from_string(sf) + .ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf)) + }) + .collect::>>() + }) +} + +fn extract_time_constraints(matches: &clap::ArgMatches) -> Result> { + let now = time::SystemTime::now(); + let mut time_constraints: Vec = Vec::new(); + if let Some(t) = matches.value_of("changed-within") { + if let Some(f) = TimeFilter::after(&now, t) { + time_constraints.push(f); + } else { + return Err(anyhow!( + "'{}' is not a valid date or duration. See 'fd --help'.", + t + )); + } + } + if let Some(t) = matches.value_of("changed-before") { + if let Some(f) = TimeFilter::before(&now, t) { + time_constraints.push(f); + } else { + return Err(anyhow!( + "'{}' is not a valid date or duration. See 'fd --help'.", + t + )); + } + } + Ok(time_constraints) +} + +fn ensure_use_hidden_option_for_leading_dot_pattern( + config: &Options, + pattern_regex: &str, +) -> Result<()> { + if cfg!(unix) && config.ignore_hidden && pattern_matches_strings_with_leading_dot(pattern_regex) + { + Err(anyhow!( "The pattern seems to only match files with a leading dot, but hidden files are \ filtered by default. Consider adding -H/--hidden to search hidden files as well \ or adjust your search pattern." - )); + )) + } else { + Ok(()) } +} - let re = RegexBuilder::new(&pattern_regex) +fn build_regex(pattern_regex: String, config: &Options) -> Result { + RegexBuilder::new(&pattern_regex) .case_insensitive(!config.case_sensitive) .dot_matches_new_line(true) .build() @@ -479,20 +556,5 @@ fn run() -> Result { also use the '--glob' option to match on a glob pattern.", e.to_string() ) - })?; - - walk::scan(&search_paths, Arc::new(re), Arc::new(config)) -} - -fn main() { - let result = run(); - match result { - Ok(exit_code) => { - process::exit(exit_code.into()); - } - Err(err) => { - eprintln!("[fd error]: {:#}", err); - process::exit(ExitCode::GeneralError.into()); - } - } + }) } From 02e98501125f199e78f555856ef81577053f93bf Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Mon, 23 Aug 2021 13:31:01 +0200 Subject: [PATCH 14/22] Refactor: Rename `Options` to `Config` --- src/{options.rs => config.rs} | 2 +- src/main.rs | 14 +++++++------- src/output.rs | 12 ++++++------ src/walk.rs | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) rename src/{options.rs => config.rs} (99%) diff --git a/src/options.rs b/src/config.rs similarity index 99% rename from src/options.rs rename to src/config.rs index 71900df..a053e6e 100644 --- a/src/options.rs +++ b/src/config.rs @@ -10,7 +10,7 @@ use crate::filter::OwnerFilter; use crate::filter::{SizeFilter, TimeFilter}; /// Configuration options for *fd*. -pub struct Options { +pub struct Config { /// Whether the search is case-sensitive or case-insensitive. pub case_sensitive: bool, diff --git a/src/main.rs b/src/main.rs index 3adb532..da5fcd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ mod app; +mod config; mod error; mod exec; mod exit_codes; mod filesystem; mod filetypes; mod filter; -mod options; mod output; mod regex_helper; mod walk; @@ -23,6 +23,7 @@ use lscolors::LsColors; use normpath::PathExt; use regex::bytes::{RegexBuilder, RegexSetBuilder}; +use crate::config::Config; use crate::error::print_error; use crate::exec::CommandTemplate; use crate::exit_codes::ExitCode; @@ -30,7 +31,6 @@ use crate::filetypes::FileTypes; #[cfg(unix)] use crate::filter::OwnerFilter; use crate::filter::{SizeFilter, TimeFilter}; -use crate::options::Options; use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot}; // We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481 @@ -75,7 +75,7 @@ fn run() -> Result { ensure_search_pattern_is_not_a_path(&matches, pattern)?; let pattern_regex = build_pattern_regex(&matches, pattern)?; - let config = construct_options(matches, &pattern_regex)?; + let config = construct_config(matches, &pattern_regex)?; ensure_use_hidden_option_for_leading_dot_pattern(&config, &pattern_regex)?; let re = build_regex(pattern_regex, &config)?; walk::scan(&search_paths, Arc::new(re), Arc::new(config)) @@ -210,7 +210,7 @@ fn check_path_separator_length(path_separator: Option<&str>) -> Result<()> { } } -fn construct_options(matches: clap::ArgMatches, pattern_regex: &str) -> Result { +fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result { // The search will be case-sensitive if the command line flag is set or // if the pattern has an uppercase character (smart case). let case_sensitive = !matches.is_present("ignore-case") @@ -250,7 +250,7 @@ fn construct_options(matches: clap::ArgMatches, pattern_regex: &str) -> Result Result Result<()> { if cfg!(unix) && config.ignore_hidden && pattern_matches_strings_with_leading_dot(pattern_regex) @@ -544,7 +544,7 @@ fn ensure_use_hidden_option_for_leading_dot_pattern( } } -fn build_regex(pattern_regex: String, config: &Options) -> Result { +fn build_regex(pattern_regex: String, config: &Config) -> Result { RegexBuilder::new(&pattern_regex) .case_insensitive(!config.case_sensitive) .dot_matches_new_line(true) diff --git a/src/output.rs b/src/output.rs index 471b8fb..536626d 100644 --- a/src/output.rs +++ b/src/output.rs @@ -6,10 +6,10 @@ use std::sync::Arc; use lscolors::{LsColors, Style}; +use crate::config::Config; use crate::error::print_error; use crate::exit_codes::ExitCode; use crate::filesystem::strip_current_dir; -use crate::options::Options; fn replace_path_separator(path: &str, new_path_separator: &str) -> String { path.replace(std::path::MAIN_SEPARATOR, new_path_separator) @@ -19,7 +19,7 @@ fn replace_path_separator(path: &str, new_path_separator: &str) -> String { pub fn print_entry( stdout: &mut StdoutLock, entry: &Path, - config: &Options, + config: &Config, wants_to_quit: &Arc, ) { let path = if entry.is_absolute() { @@ -49,7 +49,7 @@ pub fn print_entry( fn print_entry_colorized( stdout: &mut StdoutLock, path: &Path, - config: &Options, + config: &Config, ls_colors: &LsColors, wants_to_quit: &Arc, ) -> io::Result<()> { @@ -85,7 +85,7 @@ fn print_entry_colorized( fn print_entry_uncolorized_base( stdout: &mut StdoutLock, path: &Path, - config: &Options, + config: &Config, ) -> io::Result<()> { let separator = if config.null_separator { "\0" } else { "\n" }; @@ -100,7 +100,7 @@ fn print_entry_uncolorized_base( fn print_entry_uncolorized( stdout: &mut StdoutLock, path: &Path, - config: &Options, + config: &Config, ) -> io::Result<()> { print_entry_uncolorized_base(stdout, path, config) } @@ -109,7 +109,7 @@ fn print_entry_uncolorized( fn print_entry_uncolorized( stdout: &mut StdoutLock, path: &Path, - config: &Options, + config: &Config, ) -> io::Result<()> { use std::os::unix::ffi::OsStrExt; diff --git a/src/walk.rs b/src/walk.rs index 8236e4e..cb50c4d 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -15,11 +15,11 @@ use ignore::overrides::OverrideBuilder; use ignore::{self, WalkBuilder}; use regex::bytes::Regex; +use crate::config::Config; use crate::error::print_error; use crate::exec; use crate::exit_codes::{merge_exitcodes, ExitCode}; use crate::filesystem; -use crate::options::Options; use crate::output; /// The receiver thread can either be buffering results or directly streaming to the console. @@ -48,7 +48,7 @@ pub const DEFAULT_MAX_BUFFER_TIME: time::Duration = time::Duration::from_millis( /// If the `--exec` argument was supplied, this will create a thread pool for executing /// jobs in parallel from a given command line and the discovered paths. Otherwise, each /// path will simply be written to standard output. -pub fn scan(path_vec: &[PathBuf], pattern: Arc, config: Arc) -> Result { +pub fn scan(path_vec: &[PathBuf], pattern: Arc, config: Arc) -> Result { let mut path_iter = path_vec.iter(); let first_path_buf = path_iter .next() @@ -163,7 +163,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc, config: Arc) -> } fn spawn_receiver( - config: &Arc, + config: &Arc, wants_to_quit: &Arc, rx: Receiver, ) -> thread::JoinHandle { @@ -333,7 +333,7 @@ impl DirEntry { } fn spawn_senders( - config: &Arc, + config: &Arc, wants_to_quit: &Arc, pattern: Arc, parallel_walker: ignore::WalkParallel, From 7b5b3ec47b98984121e2665c7bad5274cb8db796 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 11 Oct 2021 12:21:27 -0400 Subject: [PATCH 15/22] walk: Add a cache for DirEntry metadata --- CHANGELOG.md | 7 ++++++ Cargo.lock | 1 + Cargo.toml | 1 + src/filetypes.rs | 2 +- src/walk.rs | 64 +++++++++++++++++++++++++++++++----------------- tests/tests.rs | 16 +++++++++++- 6 files changed, 67 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72119e2..d48c317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Upcoming release +## Performance improvements + +- File metadata is now cached between the different filters that require it (e.g. `--owner`, + `--size`), reducing the number of `stat` syscalls when multiple filters are used; see #863 + ## Features - Don't buffer command output from `--exec` when using a single thread. See #522 @@ -15,6 +20,8 @@ - Properly handle write errors to devices that are full, see #737 - Use local time zone for time functions (`--change-newer-than`, `--change-older-than`), see #631 (@jacobmischka) - Support `--list-details` on more platforms (like BusyBox), see #783 +- The filters `--owner`, `--size`, and `--changed-{within,before}` now apply to symbolic links + themselves, rather than the link target, except when `--follow` is specified; see #863 ## Changes diff --git a/Cargo.lock b/Cargo.lock index 71a75eb..7cfa182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,7 @@ dependencies = [ "lscolors", "normpath", "num_cpus", + "once_cell", "regex", "regex-syntax", "tempdir", diff --git a/Cargo.toml b/Cargo.toml index 20bb1f7..93fe845 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ anyhow = "1.0" dirs-next = "2.0" normpath = "0.3" chrono = "0.4" +once_cell = "1.8.0" [dependencies.clap] version = "2.31.3" diff --git a/src/filetypes.rs b/src/filetypes.rs index 3f9281d..10872a0 100644 --- a/src/filetypes.rs +++ b/src/filetypes.rs @@ -37,7 +37,7 @@ impl FileTypes { || (self.executables_only && !entry .metadata() - .map(|m| filesystem::is_executable(&m)) + .map(|m| filesystem::is_executable(m)) .unwrap_or(false)) || (self.empty_only && !filesystem::is_empty(entry)) || !(entry_type.is_file() diff --git a/src/walk.rs b/src/walk.rs index cb50c4d..7850ad7 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -13,6 +13,7 @@ use std::time; use anyhow::{anyhow, Result}; use ignore::overrides::OverrideBuilder; use ignore::{self, WalkBuilder}; +use once_cell::unsync::OnceCell; use regex::bytes::Regex; use crate::config::Config; @@ -295,39 +296,58 @@ fn spawn_receiver( }) } -pub enum DirEntry { +enum DirEntryInner { Normal(ignore::DirEntry), BrokenSymlink(PathBuf), } +pub struct DirEntry { + inner: DirEntryInner, + metadata: OnceCell>, +} + impl DirEntry { + fn normal(e: ignore::DirEntry) -> Self { + Self { + inner: DirEntryInner::Normal(e), + metadata: OnceCell::new(), + } + } + + fn broken_symlink(path: PathBuf) -> Self { + Self { + inner: DirEntryInner::BrokenSymlink(path), + metadata: OnceCell::new(), + } + } + pub fn path(&self) -> &Path { - match self { - DirEntry::Normal(e) => e.path(), - DirEntry::BrokenSymlink(pathbuf) => pathbuf.as_path(), + match &self.inner { + DirEntryInner::Normal(e) => e.path(), + DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(), } } pub fn file_type(&self) -> Option { - match self { - DirEntry::Normal(e) => e.file_type(), - DirEntry::BrokenSymlink(pathbuf) => { - pathbuf.symlink_metadata().map(|m| m.file_type()).ok() - } + match &self.inner { + DirEntryInner::Normal(e) => e.file_type(), + DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()), } } - pub fn metadata(&self) -> Option { - match self { - DirEntry::Normal(e) => e.metadata().ok(), - DirEntry::BrokenSymlink(_) => None, - } + pub fn metadata(&self) -> Option<&Metadata> { + self.metadata + .get_or_init(|| match &self.inner { + DirEntryInner::Normal(e) => e.metadata().ok(), + DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(), + }) + .as_ref() } pub fn depth(&self) -> Option { - match self { - DirEntry::Normal(e) => Some(e.depth()), - DirEntry::BrokenSymlink(_) => None, + match &self.inner { + DirEntryInner::Normal(e) => Some(e.depth()), + DirEntryInner::BrokenSymlink(_) => None, } } } @@ -355,7 +375,7 @@ fn spawn_senders( // Skip the root directory entry. return ignore::WalkState::Continue; } - Ok(e) => DirEntry::Normal(e), + Ok(e) => DirEntry::normal(e), Err(ignore::Error::WithPath { path, err: inner_err, @@ -367,7 +387,7 @@ fn spawn_senders( .ok() .map_or(false, |m| m.file_type().is_symlink()) => { - DirEntry::BrokenSymlink(path) + DirEntry::broken_symlink(path) } _ => { return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath { @@ -436,7 +456,7 @@ fn spawn_senders( #[cfg(unix)] { if let Some(ref owner_constraint) = config.owner_constraint { - if let Ok(ref metadata) = entry_path.metadata() { + if let Some(metadata) = entry.metadata() { if !owner_constraint.matches(metadata) { return ignore::WalkState::Continue; } @@ -449,7 +469,7 @@ fn spawn_senders( // Filter out unwanted sizes if it is a file and we have been given size constraints. if !config.size_constraints.is_empty() { if entry_path.is_file() { - if let Ok(metadata) = entry_path.metadata() { + if let Some(metadata) = entry.metadata() { let file_size = metadata.len(); if config .size_constraints @@ -469,7 +489,7 @@ fn spawn_senders( // Filter out unwanted modification times if !config.time_constraints.is_empty() { let mut matched = false; - if let Ok(metadata) = entry_path.metadata() { + if let Some(metadata) = entry.metadata() { if let Ok(modified) = metadata.modified() { matched = config .time_constraints diff --git a/tests/tests.rs b/tests/tests.rs index 6278afb..a7c04f4 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1652,9 +1652,22 @@ fn create_file_with_modified>(path: P, duration_in_secs: u64) { filetime::set_file_times(&path, ft, ft).expect("time modification failed"); } +#[cfg(test)] +fn remove_symlink>(path: P) { + #[cfg(unix)] + fs::remove_file(path).expect("remove symlink"); + + // On Windows, symlinks remember whether they point to files or directories, so try both + #[cfg(windows)] + fs::remove_file(path.as_ref()) + .or_else(|_| fs::remove_dir(path.as_ref())) + .expect("remove symlink"); +} + #[test] fn test_modified_relative() { let te = TestEnv::new(&[], &[]); + remove_symlink(te.test_root().join("symlink")); create_file_with_modified(te.test_root().join("foo_0_now"), 0); create_file_with_modified(te.test_root().join("bar_1_min"), 60); create_file_with_modified(te.test_root().join("foo_10_min"), 600); @@ -1692,8 +1705,9 @@ fn change_file_modified>(path: P, iso_date: &str) { } #[test] -fn test_modified_asolute() { +fn test_modified_absolute() { let te = TestEnv::new(&[], &["15mar2018", "30dec2017"]); + remove_symlink(te.test_root().join("symlink")); change_file_modified(te.test_root().join("15mar2018"), "2018-03-15T12:00:00Z"); change_file_modified(te.test_root().join("30dec2017"), "2017-12-30T23:59:00Z"); From 17dd2a6dfec1ca2086c3d279f5c458758bc9a71b Mon Sep 17 00:00:00 2001 From: Devon Hollowood Date: Thu, 21 Oct 2021 23:05:13 -0700 Subject: [PATCH 16/22] Implement `--batch-size` (#866) --- CHANGELOG.md | 2 ++ contrib/completion/_fd | 1 + doc/fd.1 | 6 ++++++ src/app.rs | 15 +++++++++++++++ src/config.rs | 4 ++++ src/exec/job.rs | 15 ++++++++++++++- src/main.rs | 6 ++++++ src/walk.rs | 8 +++++++- tests/tests.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 97 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d48c317..d016699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - Add new `--no-ignore-parent` flag, see #787 (@will459) +- Add new `--batch-size` flag, see #410 (@devonhollowood) + ## Bugfixes - Set default path separator to `/` in MSYS, see #537 and #730 (@aswild) diff --git a/contrib/completion/_fd b/contrib/completion/_fd index 28b37ee..a17c748 100644 --- a/contrib/completion/_fd +++ b/contrib/completion/_fd @@ -138,6 +138,7 @@ _fd() { + '(exec-cmds)' # execute command '(long-listing max-results)'{-x+,--exec=}'[execute command for each search result]:command: _command_names -e:*\;::program arguments: _normal' '(long-listing max-results)'{-X+,--exec-batch=}'[execute command for all search results at once]:command: _command_names -e:*\;::program arguments: _normal' + '(long-listing max-results)'{--batch-size=}'[max number of args for each -X call]:size' + other '!(--max-buffer-time)--max-buffer-time=[set amount of time to buffer before showing output]:time (ms)' diff --git a/doc/fd.1 b/doc/fd.1 index 69e8438..66413cc 100644 --- a/doc/fd.1 +++ b/doc/fd.1 @@ -405,5 +405,11 @@ $ fd -e py .TP .RI "Open all search results with vim:" $ fd pattern -X vim +.TP +.BI "\-\-batch\-size " size +Pass at most +.I size +arguments to each call to the command given with -X. +.TP .SH SEE ALSO .BR find (1) diff --git a/src/app.rs b/src/app.rs index b26593b..3f5bc2b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -365,6 +365,21 @@ pub fn build_app() -> App<'static, 'static> { " ), ) + .arg( + Arg::with_name("batch-size") + .long("batch-size") + .takes_value(true) + .value_name("size") + .hidden_short_help(true) + .requires("exec-batch") + .help("Max number of arguments to run as a batch with -X") + .long_help( + "Maximum number of arguments to pass to the command given with -X. \ + If the number of results is greater than the given size, \ + the command given with -X is run again with remaining arguments. \ + A batch size of zero means there is no limit.", + ), + ) .arg( Arg::with_name("exclude") .long("exclude") diff --git a/src/config.rs b/src/config.rs index a053e6e..c11f88b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -85,6 +85,10 @@ pub struct Config { /// If a value is supplied, each item found will be used to generate and execute commands. pub command: Option>, + /// Maximum number of search results to pass to each `command`. If zero, the number is + /// unlimited. + pub batch_size: usize, + /// A list of glob patterns that should be excluded from the search. pub exclude_patterns: Vec, diff --git a/src/exec/job.rs b/src/exec/job.rs index 83abf1a..aa8164c 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -50,6 +50,7 @@ pub fn batch( cmd: &CommandTemplate, show_filesystem_errors: bool, buffer_output: bool, + limit: usize, ) -> ExitCode { let paths = rx.iter().filter_map(|value| match value { WorkerResult::Entry(val) => Some(val), @@ -60,5 +61,17 @@ pub fn batch( None } }); - cmd.generate_and_execute_batch(paths, buffer_output) + if limit == 0 { + // no limit + return cmd.generate_and_execute_batch(paths, buffer_output); + } + + let mut exit_codes = Vec::new(); + let mut peekable = paths.peekable(); + while peekable.peek().is_some() { + let limited = peekable.by_ref().take(limit); + let exit_code = cmd.generate_and_execute_batch(limited, buffer_output); + exit_codes.push(exit_code); + } + merge_exitcodes(exit_codes) } diff --git a/src/main.rs b/src/main.rs index da5fcd9..321df54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -348,6 +348,12 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result()) + .transpose() + .context("Failed to parse --batch-size argument")? + .unwrap_or_default(), exclude_patterns: matches .values_of("exclude") .map(|v| v.map(|p| String::from("!") + p).collect()) diff --git a/src/walk.rs b/src/walk.rs index 7850ad7..789a500 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -179,7 +179,13 @@ fn spawn_receiver( // This will be set to `Some` if the `--exec` argument was supplied. if let Some(ref cmd) = config.command { if cmd.in_batch_mode() { - exec::batch(rx, cmd, show_filesystem_errors, enable_output_buffering) + exec::batch( + rx, + cmd, + show_filesystem_errors, + enable_output_buffering, + config.batch_size, + ) } else { let shared_rx = Arc::new(Mutex::new(rx)); diff --git a/tests/tests.rs b/tests/tests.rs index a7c04f4..1baf15e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1418,6 +1418,48 @@ fn test_exec_batch() { } } +#[test] +fn test_exec_batch_with_limit() { + // TODO Test for windows + if cfg!(windows) { + return; + } + + let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); + + te.assert_output( + &["foo", "--batch-size", "0", "--exec-batch", "echo", "{}"], + "a.foo one/b.foo one/two/C.Foo2 one/two/c.foo one/two/three/d.foo one/two/three/directory_foo", + ); + + let output = te.assert_success_and_get_output( + ".", + &["foo", "--batch-size=2", "--exec-batch", "echo", "{}"], + ); + let stdout = String::from_utf8_lossy(&output.stdout); + + for line in stdout.lines() { + assert_eq!(2, line.split_whitespace().count()); + } + + let mut paths: Vec<_> = stdout + .lines() + .flat_map(|line| line.split_whitespace()) + .collect(); + paths.sort_unstable(); + assert_eq!( + &paths, + &[ + "a.foo", + "one/b.foo", + "one/two/C.Foo2", + "one/two/c.foo", + "one/two/three/d.foo", + "one/two/three/directory_foo" + ], + ); +} + /// Shell script execution (--exec) with a custom --path-separator #[test] fn test_exec_with_separator() { From 1236b1dbcfce14a77e9470ab53a702ef08acaabc Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Mon, 1 Nov 2021 22:41:56 -0600 Subject: [PATCH 17/22] Update some dependencies Combination of dependabot prs. --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cfa182..c3bfee7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,24 +54,24 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "memchr", ] [[package]] name = "cc" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-if" @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ "nix", "winapi", @@ -301,9 +301,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.103" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" [[package]] name = "log" @@ -316,9 +316,9 @@ dependencies = [ [[package]] name = "lscolors" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e" +checksum = "bd0aa49b10c47f9a4391a99198b5e65c74f9ca771c0dcc856bb75a3f46c8627d" dependencies = [ "ansi_term 0.12.1", ] @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7555d6c7164cc913be1ce7f95cbecdabda61eb2ccd89008524af306fb7f5031" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" dependencies = [ "bitflags", "cc", @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "normpath" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e6e8f70e9fbbe3752d330d769e3424f24b9458ce266df93a3b456902fd696a" +checksum = "640c20e9df4a2d4a5adad5b47e17d76dac3e824346b181931c3ec9f7a85687b1" dependencies = [ "winapi", ] @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "users" diff --git a/Cargo.toml b/Cargo.toml index 93fe845..c715091 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ regex = "1.5.4" regex-syntax = "0.6" ctrlc = "3.2" humantime = "2.1" -lscolors = "0.7" +lscolors = "0.8" globset = "0.4" anyhow = "1.0" dirs-next = "2.0" From fd493eb7093f8d7a83afda7712b956a56081bdca Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Mon, 1 Nov 2021 23:05:33 -0600 Subject: [PATCH 18/22] Change bug_report from an issue template to an issue form --- .github/ISSUE_TEMPLATE/bug_report.md | 28 ------------------ .github/ISSUE_TEMPLATE/bug_report.yaml | 41 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 28 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 58eb9f2..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug Report -about: Report a bug. -title: "" -labels: bug -assignees: '' - ---- - -**Describe the bug you encountered:** - - - - -**Describe what you expected to happen:** - - -**What version of `fd` are you using?** - - -**Which operating system / distribution are you on?** - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000..778408d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,41 @@ +name: Bug Report +description: Report a bug. +title: "[BUG] " +labels: bug +body: + - type: markdown + attributes: + value: | + Please check out the [troubleshooting section](https://github.com/sharkdp/fd#troubleshooting) first. + - type: checkboxes + attributes: + options: + - label: I have read the troubleshooting section and still think this is a bug. + required: true + - type: textarea + id: bug + attributes: + label: "Describe the bug you encountered:" + validations: + required: true + - type: textarea + id: expected + attributes: + label: "Describe what you expected to happen:" + - type: input + id: version + attributes: + label: "What version of `fd` are you using?" + placeholder: "paste the output of `fd --version` here" + validations: + required: true + - type: textarea + id: os + attributes: + label: Which operating system / distribution are you on? + placeholder: | + Unix: paste the output of `uname -srm` and lsb_release -a` here. + Windows: please tell us your Windows version + render: shell + validations: + required: true From 653bc0e55ddc43b03e1b9acf3b39f6dbc00ba8ab Mon Sep 17 00:00:00 2001 From: Joseph Lee Date: Thu, 11 Nov 2021 23:30:01 +0800 Subject: [PATCH 19/22] fix zsh completion --- contrib/completion/_fd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/_fd b/contrib/completion/_fd index a17c748..f8521a7 100644 --- a/contrib/completion/_fd +++ b/contrib/completion/_fd @@ -138,7 +138,7 @@ _fd() { + '(exec-cmds)' # execute command '(long-listing max-results)'{-x+,--exec=}'[execute command for each search result]:command: _command_names -e:*\;::program arguments: _normal' '(long-listing max-results)'{-X+,--exec-batch=}'[execute command for all search results at once]:command: _command_names -e:*\;::program arguments: _normal' - '(long-listing max-results)'{--batch-size=}'[max number of args for each -X call]:size' + '(long-listing max-results)--batch-size=[max number of args for each -X call]:size' + other '!(--max-buffer-time)--max-buffer-time=[set amount of time to buffer before showing output]:time (ms)' From 828649a30d7c419ac1c83d309126d90bc0deee66 Mon Sep 17 00:00:00 2001 From: David Peter Date: Sun, 14 Nov 2021 13:58:57 +0100 Subject: [PATCH 20/22] Revert "Add pemistahl as a maintainer" This reverts commit c06c9952b61f35a7881b399cd21d0a4f821e7055. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e7b56d3..f10d961 100644 --- a/README.md +++ b/README.md @@ -667,7 +667,6 @@ cargo install --path . - [sharkdp](https://github.com/sharkdp) - [tmccombs](https://github.com/tmccombs) - [tavianator](https://github.com/tavianator) -- [pemistahl](https://github.com/pemistahl/) ## License From 690976380db1f11f6ed848761a2931e1424318d4 Mon Sep 17 00:00:00 2001 From: David Peter Date: Sun, 14 Nov 2021 16:21:24 +0100 Subject: [PATCH 21/22] Add section concerning aliases, shell functions closes #870 --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f10d961..e0fc261 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,14 @@ use a character class with a single hyphen character: > fd '[-]pattern' ``` +### "Command not found" for `alias`es or shell functions + +Shell `alias`es and shell functions can not be used for command execution via `fd -x` or +`fd -X`. In `zsh`, you can make the alias global via `alias -g myalias="…"`. In `bash`, +you can use `export -f my_function` to make available to child processes. You would still +need to call `fd -x bash -c 'my_function "$1"' bash`. For other use cases or shells, use +a (temporary) shell script. + ## Integration with other programs ### Using fd with `fzf` From 2570fbd04e058ed449e95b2583e7145c40e9c925 Mon Sep 17 00:00:00 2001 From: David Peter Date: Sun, 14 Nov 2021 16:29:10 +0100 Subject: [PATCH 22/22] Add longer help text for --prune, closes #727 --- src/app.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 3f5bc2b..efd11d7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -249,7 +249,8 @@ pub fn build_app() -> App<'static, 'static> { .conflicts_with_all(&["size", "exact-depth"]) .hidden_short_help(true) .help("Do not traverse into matching directories") - .long_help("Do not traverse into matching directories.") + .long_help("Do not traverse into directories that match the search criteria. If \ + you want to exclude specific directories, use the '--exclude=…' option.") ) .arg( Arg::with_name("file-type")