@@ -32,6 +32,12 @@ pub struct FileOwner {
3232 pub sources : Vec < Source > ,
3333}
3434
35+ #[ derive( Debug , Default , Clone , PartialEq ) ]
36+ pub struct TeamOwnership {
37+ pub heading : String ,
38+ pub globs : Vec < String > ,
39+ }
40+
3541impl Display for FileOwner {
3642 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
3743 let sources = self
@@ -121,6 +127,14 @@ impl Ownership {
121127 . collect ( ) )
122128 }
123129
130+ #[ instrument( level = "debug" , skip_all) ]
131+ pub fn for_team ( & self , team_name : & str ) -> Result < Vec < TeamOwnership > , Box < dyn std:: error:: Error > > {
132+ info ! ( "getting team ownership for {}" , team_name) ;
133+ let team = self . project . get_team ( team_name) . ok_or ( "Team not found" ) ?;
134+
135+ parse_for_team ( team. github_team , & self . project . codeowners_file )
136+ }
137+
124138 #[ instrument( level = "debug" , skip_all) ]
125139 pub fn generate_file ( & self ) -> String {
126140 info ! ( "generating codeowners file" ) ;
@@ -141,9 +155,42 @@ impl Ownership {
141155 }
142156}
143157
158+ fn parse_for_team ( team_name : String , codeowners_file : & str ) -> Result < Vec < TeamOwnership > , Box < dyn std:: error:: Error > > {
159+ let mut output = vec ! [ ] ;
160+ let mut current_section = TeamOwnership :: default ( ) ;
161+ let input: String = codeowners_file. replace ( & FileGenerator :: disclaimer ( ) . join ( "\n " ) , "" ) ;
162+
163+ for line in input. trim_start ( ) . lines ( ) {
164+ match line {
165+ comment if comment. starts_with ( "#" ) => {
166+ current_section. heading = comment. to_string ( ) ;
167+ }
168+ "" => {
169+ if current_section != TeamOwnership :: default ( ) {
170+ output. push ( current_section. clone ( ) ) ;
171+ current_section = TeamOwnership :: default ( ) ;
172+ }
173+ }
174+ team_line if team_line. ends_with ( & team_name) => {
175+ let v: Vec < & str > = team_line. split ( ' ' ) . collect ( ) ;
176+ let s = v. first ( ) . ok_or ( "malformed team line" ) ?;
177+
178+ current_section. globs . push ( s. to_string ( ) ) ;
179+ }
180+ _ => { }
181+ }
182+ }
183+ if current_section != TeamOwnership :: default ( ) {
184+ output. push ( current_section. clone ( ) ) ;
185+ }
186+
187+ Ok ( output)
188+ }
189+
144190#[ cfg( test) ]
145191mod tests {
146- use crate :: common_test:: tests:: build_ownership_with_all_mappers;
192+ use super :: * ;
193+ use crate :: common_test:: tests:: { build_ownership_with_all_mappers, vecs_match} ;
147194
148195 #[ test]
149196 fn test_for_file_owner ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
@@ -162,4 +209,133 @@ mod tests {
162209 assert_eq ! ( file_owners. len( ) , 0 ) ;
163210 Ok ( ( ) )
164211 }
212+
213+ #[ test]
214+ fn test_for_team_not_found ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
215+ let ownership = build_ownership_with_all_mappers ( ) ?;
216+ let team_ownership = ownership. for_team ( "Nope" ) ;
217+ assert ! ( team_ownership. is_err( ) , "Team not found" ) ;
218+ Ok ( ( ) )
219+ }
220+
221+ #[ test]
222+ fn test_for_team ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
223+ let ownership = build_ownership_with_all_mappers ( ) ?;
224+ let team_ownership = ownership. for_team ( "Bar" ) ;
225+ assert ! ( team_ownership. is_ok( ) ) ;
226+ Ok ( ( ) )
227+ }
228+
229+ #[ test]
230+ fn test_parse_for_team_trims_header ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
231+ let codeownership_file = r#"
232+ # STOP! - DO NOT EDIT THIS FILE MANUALLY
233+ # This file was automatically generated by "bin/codeownership validate".
234+ #
235+ # CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub
236+ # teams. This is useful when developers create Pull Requests since the
237+ # code/file owner is notified. Reference GitHub docs for more details:
238+ # https://help.github.com/en/articles/about-code-owners
239+
240+
241+ "# ;
242+
243+ let team_ownership = parse_for_team ( "@Bar" . to_string ( ) , & codeownership_file) ?;
244+ assert ! ( team_ownership. is_empty( ) ) ;
245+ Ok ( ( ) )
246+ }
247+
248+ #[ test]
249+ fn test_parse_for_team_includes_owned_globs ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
250+ let codeownership_file = r#"
251+ # First Section
252+ /path/to/owned/**/* @Foo
253+ /path/to/not/owned/**/* @Bar
254+
255+ # Last Section
256+ /another/owned/path @Foo
257+ "# ;
258+
259+ let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , & codeownership_file) ?;
260+ vecs_match (
261+ & team_ownership,
262+ & vec ! [
263+ TeamOwnership {
264+ heading: "# First Section" . to_string( ) ,
265+ globs: vec![ "/path/to/owned/**/*" . to_string( ) ] ,
266+ } ,
267+ TeamOwnership {
268+ heading: "# Last Section" . to_string( ) ,
269+ globs: vec![ "/another/owned/path" . to_string( ) ] ,
270+ } ,
271+ ] ,
272+ ) ;
273+ Ok ( ( ) )
274+ }
275+
276+ #[ test]
277+ fn test_parse_for_team_with_partial_team_match ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
278+ let codeownership_file = r#"
279+ # First Section
280+ /path/to/owned @Foo
281+ /path/to/not/owned @FooBar
282+ "# ;
283+
284+ let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , & codeownership_file) ?;
285+ vecs_match (
286+ & team_ownership,
287+ & vec ! [ TeamOwnership {
288+ heading: "# First Section" . to_string( ) ,
289+ globs: vec![ "/path/to/owned" . to_string( ) ] ,
290+ } ] ,
291+ ) ;
292+ Ok ( ( ) )
293+ }
294+
295+ #[ test]
296+ fn test_parse_for_team_with_trailing_newlines ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
297+ let codeownership_file = r#"
298+ # First Section
299+ /path/to/owned @Foo
300+
301+ # Last Section
302+ /another/owned/path @Foo
303+
304+
305+
306+ "# ;
307+
308+ let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , & codeownership_file) ?;
309+ vecs_match (
310+ & team_ownership,
311+ & vec ! [
312+ TeamOwnership {
313+ heading: "# First Section" . to_string( ) ,
314+ globs: vec![ "/path/to/owned" . to_string( ) ] ,
315+ } ,
316+ TeamOwnership {
317+ heading: "# Last Section" . to_string( ) ,
318+ globs: vec![ "/another/owned/path" . to_string( ) ] ,
319+ } ,
320+ ] ,
321+ ) ;
322+ Ok ( ( ) )
323+ }
324+
325+ #[ test]
326+ fn test_parse_for_team_without_trailing_newline ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
327+ let codeownership_file = r#"
328+ # First Section
329+ /path/to/owned @Foo"# ;
330+
331+ let team_ownership = parse_for_team ( "@Foo" . to_string ( ) , & codeownership_file) ?;
332+ vecs_match (
333+ & team_ownership,
334+ & vec ! [ TeamOwnership {
335+ heading: "# First Section" . to_string( ) ,
336+ globs: vec![ "/path/to/owned" . to_string( ) ] ,
337+ } ] ,
338+ ) ;
339+ Ok ( ( ) )
340+ }
165341}
0 commit comments