Skip to content

Commit 75c56ab

Browse files
committed
memoize codeowners
1 parent f708aa9 commit 75c56ab

File tree

5 files changed

+182
-44
lines changed

5 files changed

+182
-44
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/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

+60-24
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,76 @@
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-
};
19-
20-
let codeowners_lines_in_priorty = build_codeowners_lines_in_priority(codeowners_file_path)?;
21-
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()));
15+
pub struct Parser {
16+
pub project_root: PathBuf,
17+
pub codeowners_file_path: PathBuf,
18+
pub team_file_globs: Vec<String>,
19+
}
20+
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+
}
46+
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+
let mut teams = HashMap::new();
58+
for glob in team_file_glob {
59+
let paths = glob::glob(&glob).expect("Failed to read glob pattern").filter_map(Result::ok);
60+
61+
for path in paths {
62+
let team = Team::from_team_file_path(path).unwrap();
63+
teams.insert(team.github_team.clone(), team);
2864
}
2965
}
3066

31-
Ok(None)
67+
teams
3268
}
3369

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)
70+
#[memoize]
71+
fn build_codeowners_lines_in_priority(codeowners_file_path: String) -> Vec<String> {
72+
let codeowners_file = fs::read_to_string(codeowners_file_path).unwrap();
73+
stripped_lines_by_priority(&codeowners_file)
3874
}
3975

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

src/runner.rs

+31-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use core::fmt;
2-
use std::{fs::File, path::PathBuf};
2+
use std::{
3+
fs::File,
4+
path::{Path, PathBuf},
5+
};
36

47
use error_stack::{Context, Result, ResultExt};
58
use serde::{Deserialize, Serialize};
69

710
use crate::{
811
cache::{Cache, Caching, file::GlobalCache, noop::NoopCache},
912
config::Config,
10-
ownership::{FileOwner, Ownership, fast_team_name_from_file_path},
13+
ownership::{FileOwner, Ownership},
1114
project_builder::ProjectBuilder,
1215
};
1316

@@ -35,12 +38,33 @@ pub fn for_file(run_config: &RunConfig, file_path: &str, verbose: bool) -> RunRe
3538
if verbose {
3639
run_with_runner(run_config, |runner| runner.for_file(file_path))
3740
} else {
38-
let result = fast_team_name_from_file_path(file_path, &run_config.codeowners_file_path);
41+
let config = match config_from_path(&run_config.config_path) {
42+
Ok(config) => config,
43+
Err(err) => {
44+
return RunResult {
45+
io_errors: vec![err.to_string()],
46+
..Default::default()
47+
};
48+
}
49+
};
50+
let parser = crate::ownership::parser::Parser {
51+
project_root: run_config.project_root.clone(),
52+
codeowners_file_path: run_config.codeowners_file_path.clone(),
53+
team_file_globs: config.team_file_glob.clone(),
54+
};
55+
let result = parser.team_from_file_path(Path::new(file_path));
3956
match result {
40-
Ok(Some(team_name)) => RunResult {
41-
info_messages: vec![format!("{}", team_name)],
42-
..Default::default()
43-
},
57+
Ok(Some(team)) => {
58+
let relative_team_yml_path = team.path.strip_prefix(&run_config.project_root).unwrap_or(&team.path);
59+
60+
RunResult {
61+
info_messages: vec![
62+
format!("Team: {}", team.name),
63+
format!("Team YML: {}", relative_team_yml_path.display()),
64+
],
65+
..Default::default()
66+
}
67+
}
4468
Ok(None) => RunResult {
4569
info_messages: vec!["No team found".to_string()],
4670
..Default::default()

0 commit comments

Comments
 (0)