Skip to content

Commit 5a99560

Browse files
committed
filter out ignore globs
1 parent 08514aa commit 5a99560

File tree

8 files changed

+103
-30
lines changed

8 files changed

+103
-30
lines changed

Cargo.lock

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ path = "src/lib.rs"
1212
[dependencies]
1313
clap = { version = "4.5.20", features = ["derive"] }
1414
clap_derive = "4.5.18"
15+
crossbeam-channel = "0.5.15"
1516
error-stack = "0.5.0"
1617
enum_dispatch = "0.3.13"
1718
fast-glob = "1.0.0"

src/config.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ pub struct Config {
2121

2222
#[serde(default = "default_cache_directory")]
2323
pub cache_directory: String,
24+
25+
#[serde(default = "default_ignore_globs")]
26+
pub ignore_globs: Vec<String>,
2427
}
2528

2629
#[allow(dead_code)]
@@ -57,6 +60,23 @@ fn vendored_gems_path() -> String {
5760
"vendored/".to_string()
5861
}
5962

63+
fn default_ignore_globs() -> Vec<String> {
64+
vec![
65+
"/.cursor".to_owned(),
66+
"/.git".to_owned(),
67+
"/.idea".to_owned(),
68+
"/.vscode".to_owned(),
69+
"/.yarn".to_owned(),
70+
"/ar_doc".to_owned(),
71+
"/db".to_owned(),
72+
"/helm".to_owned(),
73+
"/log".to_owned(),
74+
"/node_modules".to_owned(),
75+
"/sorbet".to_owned(),
76+
"/tmp".to_owned(),
77+
]
78+
}
79+
6080
#[cfg(test)]
6181
mod tests {
6282
use std::{

src/ignore.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pub(crate) fn matches_path(ignore_globs: &[String], path: &str) -> bool {
2+
ignore_globs.iter().any(|rule| glob_match(rule, path))
3+
}
4+
5+
fn glob_match(glob: &str, path: &str) -> bool {
6+
let glob = glob::Pattern::new(glob).unwrap();
7+
glob.matches(path)
8+
}
9+
10+
#[cfg(test)]
11+
mod tests {
12+
use super::*;
13+
14+
#[test]
15+
fn test_matches_path() {
16+
assert!(matches_path(&["**/*.rs".to_string()], "src/main.rs"));
17+
assert!(matches_path(&["src/**".to_string()], "src/main.rs"));
18+
}
19+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod cache;
22
pub(crate) mod common_test;
33
pub mod config;
4+
pub(crate) mod ignore;
45
pub mod ownership;
56
pub(crate) mod project;
67
pub mod project_builder;

src/ownership/for_file_fast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ mod tests {
303303
unowned_globs: vec![],
304304
vendored_gems_path: vendored_path.to_string(),
305305
cache_directory: "tmp/cache/codeowners".to_string(),
306+
ignore_globs: vec![],
306307
}
307308
}
308309

src/project_builder.rs

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::{
44
sync::{Arc, Mutex},
55
};
66

7-
use error_stack::{Result, ResultExt};
7+
use error_stack::{Report, Result, ResultExt};
88
use fast_glob::glob_match;
9-
use ignore::{WalkBuilder, WalkParallel, WalkState};
9+
use ignore::{DirEntry, WalkBuilder, WalkParallel, WalkState};
1010
use rayon::iter::{IntoParallelIterator, ParallelIterator};
1111
use tracing::{instrument, warn};
1212

@@ -51,53 +51,77 @@ impl<'a> ProjectBuilder<'a> {
5151

5252
#[instrument(level = "debug", skip_all)]
5353
pub fn build(&mut self) -> Result<Project, Error> {
54-
let mut entry_types = Vec::with_capacity(INITIAL_VECTOR_CAPACITY);
5554
let mut builder = WalkBuilder::new(&self.base_path);
5655
builder.hidden(false);
56+
builder.follow_links(false);
57+
// Prune traversal early: skip heavy and irrelevant directories
58+
let ignore_globs = self.config.ignore_globs.clone();
59+
let base_path = self.base_path.clone();
60+
61+
builder.filter_entry(move |entry: &DirEntry| {
62+
if let Ok(relative_path) = entry.path().strip_prefix(&base_path) {
63+
if let Some(relative_path_str) = relative_path.to_str() {
64+
if crate::ignore::matches_path(&ignore_globs, relative_path_str) {
65+
return false;
66+
}
67+
}
68+
}
69+
70+
true
71+
});
72+
5773
let walk_parallel: WalkParallel = builder.build_parallel();
5874

59-
let collected = Arc::new(Mutex::new(Vec::with_capacity(INITIAL_VECTOR_CAPACITY)));
60-
let collected_for_threads = Arc::clone(&collected);
75+
let (tx, rx) = crossbeam_channel::unbounded::<EntryType>();
76+
let error_holder: Arc<Mutex<Option<Report<Error>>>> = Arc::new(Mutex::new(None));
77+
let error_holder_for_threads = Arc::clone(&error_holder);
78+
79+
let this: &ProjectBuilder<'a> = self;
6180

6281
walk_parallel.run(move || {
63-
let collected = Arc::clone(&collected_for_threads);
82+
let error_holder = Arc::clone(&error_holder_for_threads);
83+
let tx = tx.clone();
6484
Box::new(move |res| {
6585
if let Ok(entry) = res {
66-
if let Ok(mut v) = collected.lock() {
67-
v.push(entry);
86+
match this.build_entry_type(entry) {
87+
Ok(entry_type) => {
88+
let _ = tx.send(entry_type);
89+
}
90+
Err(report) => {
91+
if let Ok(mut slot) = error_holder.lock() {
92+
if slot.is_none() {
93+
*slot = Some(report);
94+
}
95+
}
96+
}
6897
}
6998
}
7099
WalkState::Continue
71100
})
72101
});
73102

74-
// Process sequentially with &mut self without panicking on Arc/Mutex unwraps
75-
let collected_entries = match Arc::try_unwrap(collected) {
76-
// We are the sole owner of the Arc
103+
// Take ownership of the collected entry types
104+
let entry_types: Vec<EntryType> = rx.iter().collect();
105+
106+
// If any error occurred while building entry types, return it
107+
let maybe_error = match Arc::try_unwrap(error_holder) {
77108
Ok(mutex) => match mutex.into_inner() {
78-
// Mutex not poisoned
79-
Ok(entries) => entries,
80-
// Recover entries even if the mutex was poisoned
109+
Ok(err_opt) => err_opt,
81110
Err(poisoned) => poisoned.into_inner(),
82111
},
83-
// There are still other Arc references; lock and take the contents
84112
Err(arc) => match arc.lock() {
85-
Ok(mut guard) => std::mem::take(&mut *guard),
86-
// Recover guard even if poisoned, then take contents
87-
Err(poisoned) => {
88-
let mut guard = poisoned.into_inner();
89-
std::mem::take(&mut *guard)
90-
}
113+
Ok(mut guard) => guard.take(),
114+
Err(poisoned) => poisoned.into_inner().take(),
91115
},
92116
};
93-
for entry in collected_entries {
94-
entry_types.push(self.build_entry_type(entry)?);
117+
if let Some(report) = maybe_error {
118+
return Err(report);
95119
}
96120

97121
self.build_project_from_entry_types(entry_types)
98122
}
99123

100-
fn build_entry_type(&mut self, entry: ignore::DirEntry) -> Result<EntryType, Error> {
124+
fn build_entry_type(&self, entry: ignore::DirEntry) -> Result<EntryType, Error> {
101125
let absolute_path = entry.path();
102126

103127
let is_dir = entry.file_type().ok_or(Error::Io).change_context(Error::Io)?.is_dir();

src/project_file_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<'a> ProjectFileBuilder<'a> {
2121
Self { global_cache }
2222
}
2323

24-
pub(crate) fn build(&mut self, path: PathBuf) -> ProjectFile {
24+
pub(crate) fn build(&self, path: PathBuf) -> ProjectFile {
2525
if let Ok(Some(cached_project_file)) = self.get_project_file_from_cache(&path) {
2626
return cached_project_file;
2727
}

0 commit comments

Comments
 (0)