Skip to content

Commit 5deeb8b

Browse files
committed
new file_owners_for_file so consumers can view reason for ownership
1 parent 94c9bdf commit 5deeb8b

File tree

5 files changed

+138
-91
lines changed

5 files changed

+138
-91
lines changed

src/common_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub mod tests {
2828
}};
2929
}
3030

31-
const DEFAULT_CODE_OWNERSHIP_YML: &str = indoc! {"
31+
pub const DEFAULT_CODE_OWNERSHIP_YML: &str = indoc! {"
3232
---
3333
owned_globs:
3434
- \"{app,components,config,frontend,lib,packs,spec,ruby}/**/*.{rb,rake,js,jsx,ts,tsx,json,yml,erb}\"

src/ownership/for_file_fast.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn find_file_owners(project_root: &Path, config: &Config, file_path: &Path)
3636
&& !is_config_unowned
3737
&& let Some(team) = teams_by_name.get(&team_name)
3838
{
39-
sources_by_team.entry(team.name.clone()).or_default().push(Source::TeamFile);
39+
sources_by_team.entry(team.name.clone()).or_default().push(Source::AnnotatedFile);
4040
}
4141
}
4242
}
@@ -277,7 +277,7 @@ fn vendored_gem_owner(relative_file_path: &Path, config: &Config, teams: &[Team]
277277
fn source_priority(source: &Source) -> u8 {
278278
match source {
279279
// Highest confidence first
280-
Source::TeamFile => 0,
280+
Source::AnnotatedFile => 0,
281281
Source::Directory(_) => 1,
282282
Source::Package(_, _) => 2,
283283
Source::TeamGlob(_) => 3,

src/ownership/mapper.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub type TeamName = String;
3333
#[derive(Debug, PartialEq, Clone)]
3434
pub enum Source {
3535
Directory(String),
36-
TeamFile,
36+
AnnotatedFile,
3737
TeamGem,
3838
TeamGlob(String),
3939
Package(String, String),
@@ -44,7 +44,7 @@ impl Display for Source {
4444
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4545
match self {
4646
Source::Directory(path) => write!(f, "Owner specified in `{}/.codeowner`", path),
47-
Source::TeamFile => write!(f, "Owner annotation at the top of the file"),
47+
Source::AnnotatedFile => write!(f, "Owner annotation at the top of the file"),
4848
Source::TeamGem => write!(f, "Owner specified in Team YML's `owned_gems`"),
4949
Source::TeamGlob(glob) => write!(f, "Owner specified in Team YML as an owned_glob `{}`", glob),
5050
Source::Package(package_path, glob) => {
@@ -200,7 +200,7 @@ mod tests {
200200
Source::Directory("packs/bam".to_string()).to_string(),
201201
"Owner specified in `packs/bam/.codeowner`"
202202
);
203-
assert_eq!(Source::TeamFile.to_string(), "Owner annotation at the top of the file");
203+
assert_eq!(Source::AnnotatedFile.to_string(), "Owner annotation at the top of the file");
204204
assert_eq!(Source::TeamGem.to_string(), "Owner specified in Team YML's `owned_gems`");
205205
assert_eq!(
206206
Source::TeamGlob("a/glob/**".to_string()).to_string(),

src/ownership/mapper/team_file_mapper.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl Mapper for TeamFileMapper {
5959
}
6060
}
6161

62-
vec![OwnerMatcher::ExactMatches(path_to_team, Source::TeamFile)]
62+
vec![OwnerMatcher::ExactMatches(path_to_team, Source::AnnotatedFile)]
6363
}
6464

6565
fn name(&self) -> String {
@@ -150,7 +150,7 @@ mod tests {
150150
(PathBuf::from("ruby/app/views/foos/show.html.erb"), "Bar".to_owned()),
151151
(PathBuf::from("ruby/app/views/foos/_row.html.erb"), "Bam".to_owned()),
152152
]),
153-
Source::TeamFile,
153+
Source::AnnotatedFile,
154154
)];
155155
assert_eq!(owner_matchers, expected_owner_matchers);
156156
Ok(())

src/runner.rs

Lines changed: 130 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -43,97 +43,19 @@ pub fn for_file(run_config: &RunConfig, file_path: &str, from_codeowners: bool)
4343
for_file_optimized(run_config, file_path)
4444
}
4545

46-
fn for_file_codeowners_only(run_config: &RunConfig, file_path: &str) -> RunResult {
47-
match team_for_file_from_codeowners(run_config, file_path) {
48-
Ok(Some(team)) => {
49-
let relative_team_path = team
50-
.path
51-
.strip_prefix(&run_config.project_root)
52-
.unwrap_or(team.path.as_path())
53-
.to_string_lossy()
54-
.to_string();
55-
RunResult {
56-
info_messages: vec![format!(
57-
"Team: {}\nGithub Team: {}\nTeam YML: {}\nDescription:\n- Owner inferred from codeowners file",
58-
team.name, team.github_team, relative_team_path
59-
)],
60-
..Default::default()
61-
}
62-
}
63-
Ok(None) => RunResult::default(),
64-
Err(err) => RunResult {
65-
io_errors: vec![err.to_string()],
66-
..Default::default()
67-
},
68-
}
69-
}
70-
pub fn team_for_file_from_codeowners(run_config: &RunConfig, file_path: &str) -> Result<Option<Team>, Error> {
46+
pub fn file_owners_for_file(run_config: &RunConfig, file_path: &str) -> Result<Vec<FileOwner>, Error> {
7147
let config = config_from_path(&run_config.config_path)?;
72-
let relative_file_path = Path::new(file_path)
73-
.strip_prefix(&run_config.project_root)
74-
.unwrap_or(Path::new(file_path));
48+
use crate::ownership::for_file_fast::find_file_owners;
49+
let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)).map_err(Error::Io)?;
7550

76-
let parser = crate::ownership::parser::Parser {
77-
project_root: run_config.project_root.clone(),
78-
codeowners_file_path: run_config.codeowners_file_path.clone(),
79-
team_file_globs: config.team_file_glob.clone(),
80-
};
81-
Ok(parser
82-
.team_from_file_path(Path::new(relative_file_path))
83-
.map_err(|e| Error::Io(e.to_string()))?)
51+
Ok(owners)
8452
}
8553

8654
pub fn team_for_file(run_config: &RunConfig, file_path: &str) -> Result<Option<Team>, Error> {
87-
let config = config_from_path(&run_config.config_path)?;
88-
use crate::ownership::for_file_fast::find_file_owners;
89-
let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)).map_err(Error::Io)?;
90-
55+
let owners = file_owners_for_file(run_config, file_path)?;
9156
Ok(owners.first().map(|fo| fo.team.clone()))
9257
}
9358

94-
// (imports below intentionally trimmed after refactor)
95-
96-
fn for_file_optimized(run_config: &RunConfig, file_path: &str) -> RunResult {
97-
let config = match config_from_path(&run_config.config_path) {
98-
Ok(c) => c,
99-
Err(err) => {
100-
return RunResult {
101-
io_errors: vec![err.to_string()],
102-
..Default::default()
103-
};
104-
}
105-
};
106-
107-
use crate::ownership::for_file_fast::find_file_owners;
108-
let file_owners = match find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)) {
109-
Ok(v) => v,
110-
Err(err) => {
111-
return RunResult {
112-
io_errors: vec![err],
113-
..Default::default()
114-
};
115-
}
116-
};
117-
118-
let info_messages: Vec<String> = match file_owners.len() {
119-
0 => vec![format!("{}", FileOwner::default())],
120-
1 => vec![format!("{}", file_owners[0])],
121-
_ => {
122-
let mut error_messages = vec!["Error: file is owned by multiple teams!".to_string()];
123-
for file_owner in file_owners {
124-
error_messages.push(format!("\n{}", file_owner));
125-
}
126-
return RunResult {
127-
validation_errors: error_messages,
128-
..Default::default()
129-
};
130-
}
131-
};
132-
RunResult {
133-
info_messages,
134-
..Default::default()
135-
}
136-
}
13759

13860
pub fn version() -> String {
13961
env!("CARGO_PKG_VERSION").to_string()
@@ -336,12 +258,137 @@ impl Runner {
336258
}
337259
}
338260

261+
262+
fn for_file_codeowners_only(run_config: &RunConfig, file_path: &str) -> RunResult {
263+
match team_for_file_from_codeowners(run_config, file_path) {
264+
Ok(Some(team)) => {
265+
let relative_team_path = team
266+
.path
267+
.strip_prefix(&run_config.project_root)
268+
.unwrap_or(team.path.as_path())
269+
.to_string_lossy()
270+
.to_string();
271+
RunResult {
272+
info_messages: vec![format!(
273+
"Team: {}\nGithub Team: {}\nTeam YML: {}\nDescription:\n- Owner inferred from codeowners file",
274+
team.name, team.github_team, relative_team_path
275+
)],
276+
..Default::default()
277+
}
278+
}
279+
Ok(None) => RunResult::default(),
280+
Err(err) => RunResult {
281+
io_errors: vec![err.to_string()],
282+
..Default::default()
283+
},
284+
}
285+
}
286+
pub fn team_for_file_from_codeowners(run_config: &RunConfig, file_path: &str) -> Result<Option<Team>, Error> {
287+
let config = config_from_path(&run_config.config_path)?;
288+
let relative_file_path = Path::new(file_path)
289+
.strip_prefix(&run_config.project_root)
290+
.unwrap_or(Path::new(file_path));
291+
292+
let parser = crate::ownership::parser::Parser {
293+
project_root: run_config.project_root.clone(),
294+
codeowners_file_path: run_config.codeowners_file_path.clone(),
295+
team_file_globs: config.team_file_glob.clone(),
296+
};
297+
Ok(parser
298+
.team_from_file_path(Path::new(relative_file_path))
299+
.map_err(|e| Error::Io(e.to_string()))?)
300+
}
301+
302+
fn for_file_optimized(run_config: &RunConfig, file_path: &str) -> RunResult {
303+
let config = match config_from_path(&run_config.config_path) {
304+
Ok(c) => c,
305+
Err(err) => {
306+
return RunResult {
307+
io_errors: vec![err.to_string()],
308+
..Default::default()
309+
};
310+
}
311+
};
312+
313+
use crate::ownership::for_file_fast::find_file_owners;
314+
let file_owners = match find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)) {
315+
Ok(v) => v,
316+
Err(err) => {
317+
return RunResult {
318+
io_errors: vec![err],
319+
..Default::default()
320+
};
321+
}
322+
};
323+
324+
let info_messages: Vec<String> = match file_owners.len() {
325+
0 => vec![format!("{}", FileOwner::default())],
326+
1 => vec![format!("{}", file_owners[0])],
327+
_ => {
328+
let mut error_messages = vec!["Error: file is owned by multiple teams!".to_string()];
329+
for file_owner in file_owners {
330+
error_messages.push(format!("\n{}", file_owner));
331+
}
332+
return RunResult {
333+
validation_errors: error_messages,
334+
..Default::default()
335+
};
336+
}
337+
};
338+
RunResult {
339+
info_messages,
340+
..Default::default()
341+
}
342+
}
343+
339344
#[cfg(test)]
340345
mod tests {
346+
use tempfile::tempdir;
347+
348+
use crate::{common_test, ownership::mapper::Source};
349+
341350
use super::*;
342351

343352
#[test]
344353
fn test_version() {
345354
assert_eq!(version(), env!("CARGO_PKG_VERSION").to_string());
346355
}
356+
fn write_file(temp_dir: &Path, file_path: &str, content: &str) {
357+
let file_path = temp_dir.join(file_path);
358+
let _ = std::fs::create_dir_all(file_path.parent().unwrap());
359+
std::fs::write(file_path, content).unwrap();
360+
}
361+
362+
#[test]
363+
fn test_file_owners_for_file() {
364+
let temp_dir = tempdir().unwrap();
365+
write_file(&temp_dir.path(), "config/code_ownership.yml", &common_test::tests::DEFAULT_CODE_OWNERSHIP_YML);
366+
["a", "b", "c"].iter().for_each(|name| {
367+
let team_yml = format!("name: {}\ngithub:\n team: \"@{}\"\n members:\n - {}member\n", name, name, name);
368+
write_file(&temp_dir.path(), &format!("config/teams/{}.yml", name), &team_yml);
369+
});
370+
write_file(&temp_dir.path(), "app/consumers/deep/nesting/nestdir/deep_file.rb", "# @team b\nclass DeepFile end;");
371+
372+
let run_config = RunConfig {
373+
project_root: temp_dir.path().to_path_buf(),
374+
codeowners_file_path: temp_dir.path().join(".github/CODEOWNERS").to_path_buf(),
375+
config_path: temp_dir.path().join("config/code_ownership.yml").to_path_buf(),
376+
no_cache: false,
377+
};
378+
379+
380+
let file_owners = file_owners_for_file(&run_config, "app/consumers/deep/nesting/nestdir/deep_file.rb").unwrap();
381+
assert_eq!(file_owners.len(), 1);
382+
assert_eq!(file_owners[0].team.name, "b");
383+
assert_eq!(file_owners[0].team.github_team, "@b");
384+
assert_eq!(file_owners[0].team.path.to_string_lossy().ends_with("config/teams/b.yml"), true);
385+
assert_eq!(file_owners[0].sources.len(), 1);
386+
assert_eq!(file_owners[0].sources, vec![Source::AnnotatedFile]);
387+
388+
389+
let team = team_for_file(&run_config, "app/consumers/deep/nesting/nestdir/deep_file.rb").unwrap().unwrap();
390+
assert_eq!(team.name, "b");
391+
assert_eq!(team.github_team, "@b");
392+
assert_eq!(team.path.to_string_lossy().ends_with("config/teams/b.yml"), true);
393+
}
347394
}

0 commit comments

Comments
 (0)