Skip to content

Commit 831de00

Browse files
committed
first try..slow
1 parent f947d86 commit 831de00

File tree

2 files changed

+104
-24
lines changed

2 files changed

+104
-24
lines changed

src/ownership/codeowners_file_parser.rs

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
use fast_glob::glob_match;
66
use memoize::memoize;
77
use regex::Regex;
8+
use rayon::prelude::*;
89
use std::{
910
collections::HashMap,
1011
error::Error,
@@ -23,32 +24,69 @@ pub struct Parser {
2324

2425
impl Parser {
2526
pub fn teams_from_files_paths(&self, file_paths: &[PathBuf]) -> Result<HashMap<String, Team>, Box<dyn Error>> {
26-
todo!()
27-
}
28-
29-
pub fn team_from_file_path(&self, file_path: &Path) -> Result<Option<Team>, Box<dyn Error>> {
30-
let file_path_str = file_path
31-
.to_str()
32-
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid file path"))?;
33-
let slash_prefixed = if file_path_str.starts_with("/") {
34-
file_path_str.to_string()
35-
} else {
36-
format!("/{}", file_path_str)
37-
};
38-
39-
let codeowners_lines_in_priorty = build_codeowners_lines_in_priority(self.codeowners_file_path.to_string_lossy().into_owned());
40-
for line in codeowners_lines_in_priorty {
41-
let (glob, team_name) = line
42-
.split_once(' ')
43-
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid line"))?;
44-
if glob_match(glob, &slash_prefixed) {
45-
let tbn = teams_by_github_team_name(self.absolute_team_files_globs());
46-
let team: Option<Team> = tbn.get(team_name.to_string().as_str()).cloned();
47-
return Ok(team);
48-
}
27+
let mut file_inputs: Vec<(String, String)> = Vec::with_capacity(file_paths.len());
28+
for path in file_paths {
29+
let file_path_str = path
30+
.to_str()
31+
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid file path"))?;
32+
let key = file_path_str.to_string();
33+
let slash_prefixed = if file_path_str.starts_with('/') {
34+
file_path_str.to_string()
35+
} else {
36+
format!("/{}", file_path_str)
37+
};
38+
file_inputs.push((key, slash_prefixed));
39+
}
40+
41+
if file_inputs.is_empty() {
42+
return Ok(HashMap::new());
43+
}
44+
45+
let codeowners_lines_in_priority = build_codeowners_lines_in_priority(
46+
self.codeowners_file_path.to_string_lossy().into_owned(),
47+
);
48+
// Pre-parse lines once to avoid repeated split and to handle malformed lines early
49+
let codeowners_entries: Vec<(String, String)> = codeowners_lines_in_priority
50+
.iter()
51+
.map(|line| {
52+
line
53+
.split_once(' ')
54+
.map(|(glob, team_name)| (glob.to_string(), team_name.to_string()))
55+
.ok_or_else(|| IoError::new(std::io::ErrorKind::InvalidInput, "Invalid line"))
56+
})
57+
.collect::<Result<_, IoError>>()
58+
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
59+
let teams_by_name = teams_by_github_team_name(self.absolute_team_files_globs());
60+
61+
// Parallelize across files: for each file, scan lines in priority order
62+
let result_pairs: Vec<(String, Team)> = file_inputs
63+
.par_iter()
64+
.filter_map(|(key, prefixed)| {
65+
for (glob, team_name) in &codeowners_entries {
66+
if glob_match(glob, prefixed) {
67+
// Stop at first match (highest priority). If team missing, treat as unowned.
68+
if let Some(team) = teams_by_name.get(team_name) {
69+
return Some((key.clone(), team.clone()));
70+
} else {
71+
return None;
72+
}
73+
}
74+
}
75+
None
76+
})
77+
.collect();
78+
79+
let mut result: HashMap<String, Team> = HashMap::with_capacity(result_pairs.len());
80+
for (k, t) in result_pairs {
81+
result.insert(k, t);
4982
}
5083

51-
Ok(None)
84+
Ok(result)
85+
}
86+
87+
pub fn team_from_file_path(&self, file_path: &Path) -> Result<Option<Team>, Box<dyn Error>> {
88+
let teams = self.teams_from_files_paths(&[file_path.to_path_buf()])?;
89+
Ok(teams.get(file_path.to_string_lossy().into_owned().as_str()).cloned())
5290
}
5391

5492
fn absolute_team_files_globs(&self) -> Vec<String> {

src/runner.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ mod tests {
363363
use tempfile::tempdir;
364364

365365
use crate::{common_test, ownership::mapper::Source};
366+
use ignore::{DirEntry, WalkBuilder, WalkParallel, WalkState};
366367

367368
use super::*;
368369

@@ -417,4 +418,45 @@ mod tests {
417418
assert_eq!(team.github_team, "@b");
418419
assert!(team.path.to_string_lossy().ends_with("config/teams/b.yml"));
419420
}
421+
422+
#[test]
423+
fn test_teams_for_files_from_codeowners() {
424+
let project_root = Path::new("/Users/perryhertler/workspace/zenpayroll");
425+
let codeowners_file_path = project_root.join(".github/CODEOWNERS");
426+
let config_path = project_root.join("config/code_ownership.yml");
427+
let run_config = RunConfig {
428+
project_root: project_root.to_path_buf(),
429+
codeowners_file_path: codeowners_file_path.to_path_buf(),
430+
config_path: config_path.to_path_buf(),
431+
no_cache: false,
432+
};
433+
434+
// Collect all files in packs and frontend directories recursively
435+
let mut file_paths = Vec::new();
436+
for dir in ["packs", "frontend"] {
437+
let dir_path = project_root.join(dir);
438+
if dir_path.exists() && dir_path.is_dir() {
439+
for entry in WalkBuilder::new(&dir_path)
440+
.filter_entry(|e| {
441+
let name = e.file_name().to_str().unwrap_or("");
442+
!(name == "node_modules" || name == "dist" || name == ".git")
443+
})
444+
.build()
445+
.filter_map(|e| e.ok())
446+
.filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
447+
.filter_map(|e| e.path().strip_prefix(project_root).ok().map(|p| p.to_string_lossy().to_string()))
448+
{
449+
file_paths.push(entry);
450+
}
451+
}
452+
}
453+
454+
let start_time = std::time::Instant::now();
455+
let teams = teams_for_files_from_codeowners(&run_config, &file_paths).unwrap();
456+
let end_time = std::time::Instant::now();
457+
println!("Time taken: {:?}", end_time.duration_since(start_time));
458+
println!("Teams: {:?}", teams);
459+
assert_eq!(teams.len(), 1);
460+
461+
}
420462
}

0 commit comments

Comments
 (0)