Skip to content

Commit 1240ece

Browse files
committed
parallel walk dir - 25% faster
1 parent fee60a7 commit 1240ece

File tree

4 files changed

+59
-19
lines changed

4 files changed

+59
-19
lines changed

src/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ enum Command {
2727
visible_alias = "g"
2828
)]
2929
Generate {
30-
#[arg(long, short,default_value = "false", help = "Skip staging the CODEOWNERS file")]
30+
#[arg(long, short, default_value = "false", help = "Skip staging the CODEOWNERS file")]
3131
skip_stage: bool,
3232
},
3333

@@ -39,7 +39,7 @@ enum Command {
3939

4040
#[clap(about = "Chains both `generate` and `validate` commands.", visible_alias = "gv")]
4141
GenerateAndValidate {
42-
#[arg(long, short,default_value = "false", help = "Skip staging the CODEOWNERS file")]
42+
#[arg(long, short, default_value = "false", help = "Skip staging the CODEOWNERS file")]
4343
skip_stage: bool,
4444
},
4545

src/project_builder.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::{
22
fs::File,
33
path::{Path, PathBuf},
4+
sync::{Arc, Mutex},
45
};
56

67
use error_stack::{Result, ResultExt};
78
use fast_glob::glob_match;
8-
use ignore::WalkBuilder;
9+
use ignore::{WalkBuilder, WalkParallel, WalkState};
910
use rayon::iter::{IntoParallelIterator, ParallelIterator};
1011
use tracing::{instrument, warn};
1112

@@ -53,12 +54,29 @@ impl<'a> ProjectBuilder<'a> {
5354
let mut entry_types = Vec::with_capacity(INITIAL_VECTOR_CAPACITY);
5455
let mut builder = WalkBuilder::new(&self.base_path);
5556
builder.hidden(false);
56-
let walkdir = builder.build();
57+
let walk_parallel: WalkParallel = builder.build_parallel();
5758

58-
for entry in walkdir {
59-
let entry = entry.change_context(Error::Io)?;
59+
let collected = Arc::new(Mutex::new(Vec::with_capacity(INITIAL_VECTOR_CAPACITY)));
60+
let collected_for_threads = Arc::clone(&collected);
61+
62+
walk_parallel.run(move || {
63+
let collected = Arc::clone(&collected_for_threads);
64+
Box::new(move |res| {
65+
if let Ok(entry) = res {
66+
if let Ok(mut v) = collected.lock() {
67+
v.push(entry);
68+
}
69+
}
70+
WalkState::Continue
71+
})
72+
});
73+
74+
// Process sequentially with &mut self
75+
let collected_entries = Arc::try_unwrap(collected).unwrap().into_inner().unwrap();
76+
for entry in collected_entries {
6077
entry_types.push(self.build_entry_type(entry)?);
6178
}
79+
6280
self.build_project_from_entry_types(entry_types)
6381
}
6482

src/runner.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,9 @@ impl Runner {
244244
})
245245
}
246246

247-
pub fn validate(&self ) -> RunResult {
247+
pub fn validate(&self) -> RunResult {
248248
match self.ownership.validate() {
249-
Ok(_) => {
250-
RunResult::default()
251-
},
249+
Ok(_) => RunResult::default(),
252250
Err(err) => RunResult {
253251
validation_errors: vec![format!("{}", err)],
254252
..Default::default()
@@ -267,7 +265,7 @@ impl Runner {
267265
self.git_stage();
268266
}
269267
RunResult::default()
270-
},
268+
}
271269
Err(err) => RunResult {
272270
io_errors: vec![err.to_string()],
273271
..Default::default()

tests/git_stage_test.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,25 @@ fn init_git_repo(path: &Path) {
3030
.current_dir(path)
3131
.output()
3232
.expect("failed to run git init");
33-
assert!(status.status.success(), "git init failed: {}", String::from_utf8_lossy(&status.stderr));
33+
assert!(
34+
status.status.success(),
35+
"git init failed: {}",
36+
String::from_utf8_lossy(&status.stderr)
37+
);
3438

3539
// Configure a dummy identity to appease git if commits ever happen; not strictly needed for staging
36-
let _ = Command::new("git").arg("config").arg("user.email").arg("[email protected]").current_dir(path).output();
37-
let _ = Command::new("git").arg("config").arg("user.name").arg("Test User").current_dir(path).output();
40+
let _ = Command::new("git")
41+
.arg("config")
42+
.arg("user.email")
43+
44+
.current_dir(path)
45+
.output();
46+
let _ = Command::new("git")
47+
.arg("config")
48+
.arg("user.name")
49+
.arg("Test User")
50+
.current_dir(path)
51+
.output();
3852
}
3953

4054
fn is_file_staged(repo_root: &Path, rel_path: &str) -> bool {
@@ -46,7 +60,11 @@ fn is_file_staged(repo_root: &Path, rel_path: &str) -> bool {
4660
.current_dir(repo_root)
4761
.output()
4862
.expect("failed to run git diff --cached");
49-
assert!(output.status.success(), "git diff failed: {}", String::from_utf8_lossy(&output.stderr));
63+
assert!(
64+
output.status.success(),
65+
"git diff failed: {}",
66+
String::from_utf8_lossy(&output.stderr)
67+
);
5068
let stdout = String::from_utf8_lossy(&output.stdout);
5169
stdout.lines().any(|line| line.trim() == rel_path)
5270
}
@@ -77,7 +95,11 @@ fn test_generate_stages_codeowners() {
7795
let run_config = build_run_config(&temp_root, ".github/CODEOWNERS");
7896
let result = runner::generate(&run_config, true);
7997
assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors);
80-
assert!(result.validation_errors.is_empty(), "validation_errors: {:?}", result.validation_errors);
98+
assert!(
99+
result.validation_errors.is_empty(),
100+
"validation_errors: {:?}",
101+
result.validation_errors
102+
);
81103

82104
// Assert CODEOWNERS file exists and is staged
83105
let rel_path = ".github/CODEOWNERS";
@@ -99,12 +121,14 @@ fn test_generate_and_validate_stages_codeowners() {
99121
let run_config = build_run_config(&temp_root, ".github/CODEOWNERS");
100122
let result = runner::generate_and_validate(&run_config, vec![], true);
101123
assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors);
102-
assert!(result.validation_errors.is_empty(), "validation_errors: {:?}", result.validation_errors);
124+
assert!(
125+
result.validation_errors.is_empty(),
126+
"validation_errors: {:?}",
127+
result.validation_errors
128+
);
103129

104130
// Assert CODEOWNERS file exists and is staged
105131
let rel_path = ".github/CODEOWNERS";
106132
assert!(run_config.codeowners_file_path.exists(), "CODEOWNERS file was not created");
107133
assert!(is_file_staged(&run_config.project_root, rel_path), "CODEOWNERS file was not staged");
108134
}
109-
110-

0 commit comments

Comments
 (0)