Skip to content

Commit 4d53a81

Browse files
committed
subtracting team globs
1 parent 5f4a201 commit 4d53a81

File tree

6 files changed

+125
-54
lines changed

6 files changed

+125
-54
lines changed

src/common_test.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ pub mod tests {
279279
})
280280
}
281281

282+
pub fn build_ownership_with_subtracted_globs_team_glob_codeowners() -> Result<Ownership, Box<dyn Error>> {
283+
ownership!(TestProjectFile {
284+
relative_path: "config/teams/baz.yml".to_owned(),
285+
content: "name: Baz\ngithub:\n team: \"@Baz\"\n members:\n - Baz member\nowned_globs:\n - \"packs/bar/**\"\nunowned_globs:\n - \"packs/bar/excluded/**\"\n".to_owned(),
286+
})
287+
}
288+
282289
pub fn build_ownership_with_team_yml_codeowners() -> Result<Ownership, Box<dyn Error>> {
283290
let temp_dir = tempdir()?;
284291

src/ownership/mapper.rs

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,17 @@ pub enum OwnerMatcher {
7878
impl OwnerMatcher {
7979
pub fn new_glob_with_candidate_subtracted_globs(
8080
glob: String,
81-
candidate_subtracted_globs: Vec<String>,
81+
candidate_subtracted_globs: &[String],
8282
team_name: TeamName,
8383
source: Source,
8484
) -> Self {
85-
let subtracted_globs = candidate_subtracted_globs.iter().filter(|candidate_subtracted_glob| {
86-
glob_match(candidate_subtracted_glob, &glob) || glob_match(&glob, candidate_subtracted_glob)
87-
}).cloned().collect();
85+
let subtracted_globs = candidate_subtracted_globs
86+
.iter()
87+
.filter(|candidate_subtracted_glob| {
88+
glob_match(candidate_subtracted_glob, &glob) || glob_match(&glob, candidate_subtracted_glob)
89+
})
90+
.cloned()
91+
.collect();
8892
OwnerMatcher::Glob {
8993
glob,
9094
subtracted_globs,
@@ -109,13 +113,10 @@ impl OwnerMatcher {
109113
subtracted_globs,
110114
team_name,
111115
source,
112-
} => {
113-
if glob_match(glob, relative_path.to_str().unwrap()) {
114-
(Some(team_name), source)
115-
} else {
116-
(None, source)
117-
}
118-
}
116+
} => relative_path
117+
.to_str()
118+
.filter(|path| glob_match(glob, path) && !subtracted_globs.iter().any(|subtracted| glob_match(subtracted, path)))
119+
.map_or((None, source), |_| (Some(team_name), source)),
119120
OwnerMatcher::ExactMatches(path_to_team, source) => (path_to_team.get(relative_path), source),
120121
}
121122
}
@@ -125,15 +126,15 @@ impl OwnerMatcher {
125126
mod tests {
126127
use super::*;
127128

128-
fn assert_owner_for(glob: &str, relative_path: &str, expect_match: bool) {
129+
fn assert_owner_for(glob: &str, subtracted_globs: &[&str], relative_path: &str, expect_match: bool) {
129130
let source = Source::Directory("packs/bam".to_string());
130131
let team_name = "team1".to_string();
131-
let owner_matcher = OwnerMatcher::Glob {
132-
glob: glob.to_string(),
133-
subtracted_globs: vec![],
134-
team_name: team_name.clone(),
135-
source: source.clone(),
136-
};
132+
let owner_matcher = OwnerMatcher::new_glob_with_candidate_subtracted_globs(
133+
glob.to_string(),
134+
&subtracted_globs.iter().map(|s| s.to_string()).collect::<Vec<String>>(),
135+
team_name.clone(),
136+
source.clone(),
137+
);
137138
let response = owner_matcher.owner_for(Path::new(relative_path));
138139
if expect_match {
139140
assert_eq!(response, (Some(&team_name), &source));
@@ -144,26 +145,50 @@ mod tests {
144145

145146
#[test]
146147
fn owner_for_without_brackets_in_glob() {
147-
assert_owner_for("packs/bam/**/**", "packs/bam/app/components/sidebar.jsx", true);
148-
assert_owner_for("packs/bam/**/**", "packs/baz/app/components/sidebar.jsx", false);
149-
assert_owner_for("packs/bam/**/**", "packs/bam/app/[components]/gadgets/sidebar.jsx", true);
150-
assert_owner_for("packs/bam/**/**", "packs/bam/app/sidebar_[component].jsx", true);
148+
assert_owner_for("packs/bam/**/**", &[], "packs/bam/app/components/sidebar.jsx", true);
149+
assert_owner_for("packs/bam/**/**", &[], "packs/baz/app/components/sidebar.jsx", false);
150+
assert_owner_for("packs/bam/**/**", &[], "packs/bam/app/[components]/gadgets/sidebar.jsx", true);
151+
assert_owner_for("packs/bam/**/**", &[], "packs/bam/app/sidebar_[component].jsx", true);
152+
assert_owner_for(
153+
"packs/bam/**/**",
154+
&["packs/bam/app/excluded/**"],
155+
"packs/bam/app/excluded/sidebar_[component].jsx",
156+
false,
157+
);
158+
}
159+
160+
#[test]
161+
fn subtracted_globs() {
162+
assert_owner_for(
163+
"packs/bam/**/**",
164+
&["packs/bam/app/excluded/**"],
165+
"packs/bam/app/excluded/some_file.rb",
166+
false,
167+
);
168+
assert_owner_for(
169+
"packs/bam/**/**",
170+
&["packs/bam/app/excluded/**"],
171+
"packs/bam/app/not_excluded/some_file.rb",
172+
true,
173+
);
151174
}
152175

153176
#[test]
154177
fn owner_for_with_brackets_in_glob() {
155178
assert_owner_for(
156179
"packs/bam/app/\\[components\\]/**/**",
180+
&[],
157181
"packs/bam/app/[components]/gadgets/sidebar.jsx",
158182
true,
159183
);
160-
assert_owner_for("packs/\\[bam\\]/**/**", "packs/[bam]/app/components/sidebar.jsx", true);
184+
assert_owner_for("packs/\\[bam\\]/**/**", &[], "packs/[bam]/app/components/sidebar.jsx", true);
161185
}
162186

163187
#[test]
164188
fn owner_for_with_multiple_brackets_in_glob() {
165189
assert_owner_for(
166190
"packs/\\[bam\\]/bar/\\[foo\\]/**/**",
191+
&[],
167192
"packs/[bam]/bar/[foo]/app/components/sidebar.jsx",
168193
true,
169194
);
@@ -192,17 +217,25 @@ mod tests {
192217
fn test_new_glob_with_candidate_subtracted_globs() {
193218
assert_new_glob_with_candidate_subtracted_globs("packs/bam/**/**", &[], &[]);
194219
assert_new_glob_with_candidate_subtracted_globs("packs/bam/**/**", &["packs/bam/app/**/**"], &["packs/bam/app/**/**"]);
195-
assert_new_glob_with_candidate_subtracted_globs("packs/bam/**/**", &["packs/bam/app/an/exceptional/path/it.rb"], &["packs/bam/app/an/exceptional/path/it.rb"]);
220+
assert_new_glob_with_candidate_subtracted_globs(
221+
"packs/bam/**/**",
222+
&["packs/bam/app/an/exceptional/path/it.rb"],
223+
&["packs/bam/app/an/exceptional/path/it.rb"],
224+
);
196225
assert_new_glob_with_candidate_subtracted_globs("packs/bam/**/**", &["packs/bam.rb"], &[]);
197226
assert_new_glob_with_candidate_subtracted_globs("packs/bam/**/**", &["packs/nope/app/**/**"], &[]);
198227
assert_new_glob_with_candidate_subtracted_globs("packs/**", &["packs/yep/app/**/**"], &["packs/yep/app/**/**"]);
199228
assert_new_glob_with_candidate_subtracted_globs("packs/foo.yml", &["packs/foo/**/**"], &[]);
200229
}
201230

202-
fn assert_new_glob_with_candidate_subtracted_globs(glob: &str, candidate_subtracted_globs: &[&str], expected_subtracted_globs: &[&str]) {
231+
fn assert_new_glob_with_candidate_subtracted_globs(
232+
glob: &str,
233+
candidate_subtracted_globs: &[&str],
234+
expected_subtracted_globs: &[&str],
235+
) {
203236
let owner_matcher = OwnerMatcher::new_glob_with_candidate_subtracted_globs(
204237
glob.to_string(),
205-
candidate_subtracted_globs.iter().map(|s| s.to_string()).collect(),
238+
&candidate_subtracted_globs.iter().map(|s| s.to_string()).collect::<Vec<String>>(),
206239
"team1".to_string(),
207240
Source::TeamGlob(glob.to_string()),
208241
);

src/ownership/mapper/team_glob_mapper.rs

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,42 @@ impl TeamGlobMapper {
1212
pub fn build(project: Arc<Project>) -> Self {
1313
Self { project }
1414
}
15-
16-
fn iter_team_globs(&self) -> impl Iterator<Item = (&str, &str, &str, bool)> + '_ {
17-
self.project.teams.iter().flat_map(|team| {
18-
team.owned_globs
19-
.iter()
20-
.map(move |glob| (glob.as_str(), team.github_team.as_str(), team.name.as_str(), team.avoid_ownership))
21-
})
22-
}
2315
}
2416

2517
impl Mapper for TeamGlobMapper {
2618
fn entries(&self) -> Vec<Entry> {
27-
self.iter_team_globs()
28-
.map(|(glob, github_team, team_name, disabled)| Entry {
29-
path: glob.to_owned(),
30-
github_team: github_team.to_owned(),
31-
team_name: team_name.to_owned(),
32-
disabled,
33-
})
34-
.collect()
19+
let mut entries: Vec<Entry> = Vec::new();
20+
21+
for team in &self.project.teams {
22+
for owned_glob in &team.owned_globs {
23+
entries.push(Entry {
24+
path: owned_glob.to_owned(),
25+
github_team: team.github_team.to_owned(),
26+
team_name: team.name.to_owned(),
27+
disabled: team.avoid_ownership,
28+
});
29+
}
30+
}
31+
32+
entries
3533
}
3634

3735
fn owner_matchers(&self) -> Vec<OwnerMatcher> {
38-
self.iter_team_globs()
39-
.map(|(glob, github_team, _, _)| {
40-
OwnerMatcher::new_glob_with_candidate_subtracted_globs(
41-
glob.to_owned(),
42-
vec![],
43-
github_team.to_owned(),
44-
Source::TeamGlob(glob.to_owned()),
45-
)
46-
})
47-
.collect()
36+
let mut owner_matchers: Vec<OwnerMatcher> = Vec::new();
37+
38+
for team in &self.project.teams {
39+
let team_subtracted_globs = team.subtracted_globs.clone();
40+
for owned_glob in &team.owned_globs {
41+
owner_matchers.push(OwnerMatcher::new_glob_with_candidate_subtracted_globs(
42+
owned_glob.clone(),
43+
&team_subtracted_globs,
44+
team.github_team.clone(),
45+
Source::TeamGlob(owned_glob.clone()),
46+
))
47+
}
48+
}
49+
50+
owner_matchers
4851
}
4952

5053
fn name(&self) -> String {
@@ -56,7 +59,10 @@ impl Mapper for TeamGlobMapper {
5659
mod tests {
5760
use std::error::Error;
5861

59-
use crate::common_test::tests::{build_ownership_with_all_mappers, build_ownership_with_team_glob_codeowners, vecs_match};
62+
use crate::common_test::tests::{
63+
build_ownership_with_all_mappers, build_ownership_with_subtracted_globs_team_glob_codeowners,
64+
build_ownership_with_team_glob_codeowners, vecs_match,
65+
};
6066

6167
use super::*;
6268
#[test]
@@ -81,8 +87,26 @@ mod tests {
8187
let mapper = TeamGlobMapper::build(ownership.project.clone());
8288
vecs_match(
8389
&mapper.owner_matchers(),
84-
&vec![OwnerMatcher::new_glob(
90+
&vec![OwnerMatcher::new_glob_with_candidate_subtracted_globs(
91+
"packs/bar/**".to_owned(),
92+
&[],
93+
"@Baz".to_owned(),
94+
Source::TeamGlob("packs/bar/**".to_owned()),
95+
)],
96+
);
97+
Ok(())
98+
}
99+
100+
#[test]
101+
fn test_owner_matchers_with_subtracted_globs() -> Result<(), Box<dyn Error>> {
102+
let ownership = build_ownership_with_subtracted_globs_team_glob_codeowners()?;
103+
104+
let mapper = TeamGlobMapper::build(ownership.project.clone());
105+
vecs_match(
106+
&mapper.owner_matchers(),
107+
&vec![OwnerMatcher::new_glob_with_candidate_subtracted_globs(
85108
"packs/bar/**".to_owned(),
109+
&["packs/bar/excluded/**".to_owned()],
86110
"@Baz".to_owned(),
87111
Source::TeamGlob("packs/bar/**".to_owned()),
88112
)],

src/project.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub struct Team {
3737
pub name: String,
3838
pub github_team: String,
3939
pub owned_globs: Vec<String>,
40+
pub subtracted_globs: Vec<String>,
4041
pub owned_gems: Vec<String>,
4142
pub avoid_ownership: bool,
4243
}
@@ -50,6 +51,7 @@ impl Team {
5051
name: deserializer.name,
5152
github_team: deserializer.github.team,
5253
owned_globs: deserializer.owned_globs,
54+
subtracted_globs: deserializer.subtracted_globs,
5355
owned_gems: deserializer.ruby.map(|ruby| ruby.owned_gems).unwrap_or_default(),
5456
avoid_ownership: deserializer.github.do_not_add_to_codeowners_file,
5557
})
@@ -132,6 +134,9 @@ pub mod deserializers {
132134

133135
#[serde(default = "empty_string_vec")]
134136
pub owned_globs: Vec<String>,
137+
138+
#[serde(alias = "unowned_globs", default = "empty_string_vec")]
139+
pub subtracted_globs: Vec<String>,
135140
}
136141

137142
fn empty_string_vec() -> Vec<String> {

tests/fixtures/valid_project/.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Owner in .codeowner
1919
/javascript/packages/items/**/** @PayrollTeam
2020
/javascript/packages/items/(special)/**/** @PaymentsTeam
21+
/ruby/app/payments/foo/**/** @PayrollTeam
2122
/ruby/app/payroll/**/** @PayrollTeam
2223

2324
# Owner metadata key in package.yml

tests/valid_project_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ fn test_for_team() -> Result<(), Box<dyn Error>> {
201201
202202
## Owner in .codeowner
203203
/javascript/packages/items/**/**
204+
/ruby/app/payments/foo/**/**
204205
/ruby/app/payroll/**/**
205206
206207
## Owner metadata key in package.yml

0 commit comments

Comments
 (0)