diff --git a/Cargo.lock b/Cargo.lock index 256a1e6..6162ce3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,12 +110,12 @@ dependencies = [ "cargo_metadata", "cargo_toml", "grep", + "ignore", "log", "pretty_env_logger", "rayon", "serde", "toml_edit", - "walkdir", ] [[package]] @@ -383,6 +383,23 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index e08804d..0c72eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,12 @@ argh = "0.1.9" cargo_metadata = "0.18.0" cargo_toml = "0.16.2" grep = "0.2.8" +ignore = "0.4.20" log = "0.4.16" pretty_env_logger = "0.5.0" rayon = "1.5.2" serde = "1.0.136" toml_edit = "0.20.0" -walkdir = "2.3.2" # Uncomment this for profiling. #[profile.release] diff --git a/src/main.rs b/src/main.rs index ec9e76b..a2f05e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ use rayon::prelude::*; use std::path::Path; use std::str::FromStr; use std::{fs, path::PathBuf}; -use walkdir::WalkDir; #[derive(Clone, Copy)] pub(crate) enum UseCargoMetadata { @@ -62,6 +61,10 @@ struct MacheteArgs { #[argh(switch)] fix: bool, + /// respect ignore files (.gitignore, .ignore, etc.) when searching for files. + #[argh(switch)] + ignore: bool, + /// print version. #[argh(switch)] version: bool, @@ -71,21 +74,28 @@ struct MacheteArgs { paths: Vec, } -fn collect_paths(path: &Path, skip_target_dir: bool) -> Result, walkdir::Error> { +fn collect_paths( + path: &Path, + skip_target_dir: bool, + respect_ignore_files: bool, +) -> Result, ignore::Error> { // Find directory entries. - let walker = WalkDir::new(path).into_iter(); + let mut builder = ignore::WalkBuilder::new(path); - let manifest_path_entries = if skip_target_dir { - walker - .filter_entry(|entry| !entry.path().ends_with("target")) - .collect() - } else { - walker.collect::>() - }; + builder.git_exclude(respect_ignore_files); + builder.git_global(respect_ignore_files); + builder.git_ignore(respect_ignore_files); + builder.ignore(respect_ignore_files); + + if skip_target_dir { + builder.filter_entry(|entry| !entry.path().ends_with("target")); + } + + let walker = builder.build(); // Keep only errors and `Cargo.toml` files (filter), then map correct paths into owned // `PathBuf`. - manifest_path_entries + walker .into_iter() .filter(|entry| match entry { Ok(entry) => entry.file_name() == "Cargo.toml", @@ -142,7 +152,7 @@ fn run_machete() -> anyhow::Result { let mut walkdir_errors = Vec::new(); for path in args.paths { - let manifest_path_entries = match collect_paths(&path, args.skip_target_dir) { + let manifest_path_entries = match collect_paths(&path, args.skip_target_dir, args.ignore) { Ok(entries) => entries, Err(err) => { walkdir_errors.push(err); @@ -288,15 +298,25 @@ const TOP_LEVEL: &str = concat!(env!("CARGO_MANIFEST_DIR")); #[test] fn test_ignore_target() { + pretty_env_logger::init(); let entries = collect_paths( &PathBuf::from(TOP_LEVEL).join("./integration-tests/with-target/"), true, + false, ); assert!(entries.unwrap().is_empty()); let entries = collect_paths( &PathBuf::from(TOP_LEVEL).join("./integration-tests/with-target/"), false, + true, + ); + assert!(entries.unwrap().is_empty()); + + let entries = collect_paths( + &PathBuf::from(TOP_LEVEL).join("./integration-tests/with-target/"), + false, + false, ); assert!(!entries.unwrap().is_empty()); } diff --git a/src/search_unused.rs b/src/search_unused.rs index 478631b..6885259 100644 --- a/src/search_unused.rs +++ b/src/search_unused.rs @@ -11,7 +11,6 @@ use std::{ error::{self, Error}, path::{Path, PathBuf}, }; -use walkdir::WalkDir; use crate::UseCargoMetadata; #[cfg(test)] @@ -146,10 +145,23 @@ fn collect_paths(dir_path: &Path, analysis: &PackageAnalysis) -> Vec { trace!("adding src/ since paths was empty"); } + // Unfortunately WalkBuilder does not implement `std::iter::FromIterator`. + let walk_builder = { + let mut roots = root_paths.into_iter().map(|p| dir_path.join(p)); + let builder = ignore::WalkBuilder::new( + roots + .next() + .unwrap_or_else(|| unreachable!("root_paths should contain at least one entry")), + ); + roots.fold(builder, |mut builder, root| { + builder.add(root); + builder + }) + }; + // Collect all final paths for the crate first. - let paths: Vec = root_paths - .iter() - .flat_map(|root| WalkDir::new(dir_path.join(root)).into_iter()) + let paths = walk_builder + .build() .filter_map(|result| { let dir_entry = match result { Ok(dir_entry) => dir_entry, @@ -158,7 +170,7 @@ fn collect_paths(dir_path: &Path, analysis: &PackageAnalysis) -> Vec { return None; } }; - if !dir_entry.file_type().is_file() { + if !dir_entry.file_type()?.is_file() { return None; } if dir_entry