Skip to content

Commit db7046b

Browse files
committed
memoize codeowners
1 parent f708aa9 commit db7046b

8 files changed

+271
-60
lines changed

Cargo.lock

+87-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ clap_derive = "4.5.18"
1515
error-stack = "0.5.0"
1616
enum_dispatch = "0.3.13"
1717
fast-glob = "0.4.0"
18+
glob = "0.3.2"
1819
ignore = "0.4.23"
1920
itertools = "0.14.0"
2021
lazy_static = "1.5.0"
22+
memoize = "0.5.1"
2123
path-clean = "1.0.1"
2224
rayon = "1.10.0"
2325
regex = "1.11.1"
@@ -30,7 +32,6 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
3032

3133
[dev-dependencies]
3234
assert_cmd = "2.0.16"
33-
glob = "0.3.1"
3435
rusty-hook = "^0.11.2"
3536
predicates = "3.1.2"
3637
pretty_assertions = "1.4.1" # Shows a more readable diff when comparing objects

src/cli.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ use std::path::{Path, PathBuf};
99
enum Command {
1010
#[clap(about = "Finds the owner of a given file.", visible_alias = "f")]
1111
ForFile {
12-
#[arg(short, long, default_value = "false")]
13-
verbose: bool,
12+
#[arg(
13+
short,
14+
long,
15+
default_value = "false",
16+
help = "Find the owner from the CODEOWNERS file and just return the team name and yml path"
17+
)]
18+
fast: bool,
1419
name: String,
1520
},
1621

@@ -98,7 +103,7 @@ pub fn cli() -> Result<RunResult, RunnerError> {
98103
Command::Validate => runner::validate(&run_config, vec![]),
99104
Command::Generate => runner::generate(&run_config),
100105
Command::GenerateAndValidate => runner::generate_and_validate(&run_config, vec![]),
101-
Command::ForFile { name, verbose } => runner::for_file(&run_config, &name, verbose),
106+
Command::ForFile { name, fast } => runner::for_file(&run_config, &name, fast),
102107
Command::ForTeam { name } => runner::for_team(&run_config, &name),
103108
Command::DeleteCache => runner::delete_cache(&run_config),
104109
};

src/ownership.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use mapper::{OwnerMatcher, Source, TeamName};
44
use std::{
55
error::Error,
66
fmt::{self, Display},
7-
path::{Path, PathBuf},
7+
path::Path,
88
sync::Arc,
99
};
1010
use tracing::{info, instrument};
1111

1212
mod file_generator;
1313
mod file_owner_finder;
1414
pub(crate) mod mapper;
15-
mod parser;
15+
pub(crate) mod parser;
1616
mod validator;
1717

1818
use crate::{
@@ -178,10 +178,6 @@ impl Ownership {
178178
}
179179
}
180180

181-
pub fn fast_team_name_from_file_path(file_path: &str, code_owners_file_path: &PathBuf) -> Result<Option<String>, Box<dyn Error>> {
182-
parser::team_name_from_file_path(Path::new(file_path), code_owners_file_path)
183-
}
184-
185181
#[cfg(test)]
186182
mod tests {
187183
use super::*;

src/ownership/parser.rs

+73-22
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,91 @@
1-
use crate::ownership::{FileGenerator, TeamOwnership};
1+
use crate::{
2+
ownership::{FileGenerator, TeamOwnership},
3+
project::Team,
4+
};
25
use fast_glob::glob_match;
6+
use memoize::memoize;
37
use std::{
8+
collections::HashMap,
49
error::Error,
510
fs,
611
io::Error as IoError,
712
path::{Path, PathBuf},
813
};
914

10-
pub fn team_name_from_file_path(file_path: &Path, codeowners_file_path: &PathBuf) -> Result<Option<String>, Box<dyn Error>> {
11-
let file_path_str = file_path
12-
.to_str()
13-
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid file path"))?;
14-
let slash_prefixed = if file_path_str.starts_with("/") {
15-
file_path_str.to_string()
16-
} else {
17-
format!("/{}", file_path_str)
18-
};
15+
pub struct Parser {
16+
pub project_root: PathBuf,
17+
pub codeowners_file_path: PathBuf,
18+
pub team_file_globs: Vec<String>,
19+
}
1920

20-
let codeowners_lines_in_priorty = build_codeowners_lines_in_priority(codeowners_file_path)?;
21+
impl Parser {
22+
pub fn team_from_file_path(&self, file_path: &Path) -> Result<Option<Team>, Box<dyn Error>> {
23+
let file_path_str = file_path
24+
.to_str()
25+
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid file path"))?;
26+
let slash_prefixed = if file_path_str.starts_with("/") {
27+
file_path_str.to_string()
28+
} else {
29+
format!("/{}", file_path_str)
30+
};
31+
32+
let codeowners_lines_in_priorty = build_codeowners_lines_in_priority(self.codeowners_file_path.to_string_lossy().into_owned());
33+
for line in codeowners_lines_in_priorty {
34+
let (glob, team_name) = line
35+
.split_once(' ')
36+
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid line"))?;
37+
if glob_match(glob, &slash_prefixed) {
38+
let tbn = teams_by_github_team_name(self.absolute_team_files_globs());
39+
let team: Option<Team> = tbn.get(team_name.to_string().as_str()).cloned();
40+
return Ok(team);
41+
}
42+
}
43+
44+
Ok(None)
45+
}
2146

22-
for line in codeowners_lines_in_priorty {
23-
let (glob, team_name) = line
24-
.split_once(' ')
25-
.ok_or(IoError::new(std::io::ErrorKind::InvalidInput, "Invalid line"))?;
26-
if glob_match(glob, &slash_prefixed) {
27-
return Ok(Some(team_name.to_string()));
47+
fn absolute_team_files_globs(&self) -> Vec<String> {
48+
self.team_file_globs
49+
.iter()
50+
.map(|glob| format!("{}/{}", self.project_root.display(), glob))
51+
.collect()
52+
}
53+
}
54+
55+
#[memoize]
56+
fn teams_by_github_team_name(team_file_glob: Vec<String>) -> HashMap<String, Team> {
57+
dbg!("in teams_by_github_team_name");
58+
let mut teams = HashMap::new();
59+
for glob in team_file_glob {
60+
let paths = glob::glob(&glob).expect("Failed to read glob pattern").filter_map(Result::ok);
61+
62+
for path in paths {
63+
let team = match Team::from_team_file_path(path) {
64+
Ok(team) => team,
65+
Err(e) => {
66+
eprintln!("Error parsing team file: {}", e);
67+
continue;
68+
}
69+
};
70+
teams.insert(team.github_team.clone(), team);
2871
}
2972
}
3073

31-
Ok(None)
74+
teams
3275
}
3376

34-
fn build_codeowners_lines_in_priority(codeowners_file_path: &PathBuf) -> Result<Vec<String>, Box<dyn Error>> {
35-
let codeowners_file = fs::read_to_string(codeowners_file_path)?;
36-
let stripped_lines = stripped_lines_by_priority(&codeowners_file);
37-
Ok(stripped_lines)
77+
#[memoize]
78+
fn build_codeowners_lines_in_priority(codeowners_file_path: String) -> Vec<String> {
79+
dbg!("in build_codeowners_lines_in_priority");
80+
let codeowners_file = match fs::read_to_string(codeowners_file_path) {
81+
Ok(codeowners_file) => codeowners_file,
82+
Err(e) => {
83+
// we can't return the error because it's not clonable
84+
eprintln!("Error reading codeowners file: {}", e);
85+
return vec![];
86+
}
87+
};
88+
stripped_lines_by_priority(&codeowners_file)
3889
}
3990

4091
fn stripped_lines_by_priority(codeowners_file: &str) -> Vec<String> {

0 commit comments

Comments
 (0)