1
- use clap:: { App , Arg , ArgMatches , SubCommand } ;
1
+ use clap:: { App , Arg , SubCommand } ;
2
2
use codeowners:: Owner ;
3
3
use codeowners:: Owners ;
4
4
use git2:: { Error , Repository } ;
5
- use std:: collections:: HashSet ;
5
+ use std:: collections:: { HashMap , HashSet } ;
6
6
use std:: env:: current_dir;
7
7
use std:: io:: { self , BufRead } ;
8
8
use std:: path:: Path ;
9
9
use std:: path:: PathBuf ;
10
10
11
11
type ExitError = ( Option < String > , i32 ) ;
12
+ type Settings = ( bool , bool , bool ) ;
12
13
13
14
fn main ( ) {
14
15
if let Err ( ( msg, code) ) = run ( ) {
@@ -96,26 +97,33 @@ fn run() -> Result<(), ExitError> {
96
97
97
98
let owners = codeowners:: from_path ( ownersfile) ;
98
99
100
+ let settings = (
101
+ matches. is_present ( "teams" ) ,
102
+ matches. is_present ( "users" ) ,
103
+ matches. is_present ( "emails" ) ,
104
+ ) ;
105
+
99
106
match matches. subcommand ( ) {
100
107
( "path" , Some ( matches) ) => match matches. value_of ( "path" ) . unwrap ( ) . as_ref ( ) {
101
108
"-" => {
102
109
let stdin = io:: stdin ( ) ;
103
110
for path in stdin. lock ( ) . lines ( ) . filter_map ( Result :: ok) {
104
- if !resolve ( & owners, & matches , & path) {
111
+ if !resolve ( & owners, settings , & path) {
105
112
return Err ( ( None , 2 ) ) ;
106
113
}
107
114
}
108
115
}
109
116
path => {
110
- if !resolve ( & owners, & matches , path) {
117
+ if !resolve ( & owners, settings , path) {
111
118
return Err ( ( None , 2 ) ) ;
112
119
}
113
120
}
114
121
} ,
115
122
( "log" , Some ( matches) ) => {
116
123
let repo = Repository :: discover ( "." ) . expect ( "dir" ) ;
117
124
for revspec in matches. values_of ( "revspec" ) . expect ( "required" ) {
118
- print_for_revspec ( & repo, revspec) . map_err ( |e| ( Some ( format ! ( "{:?}" , e) ) , 1 ) ) ?;
125
+ print_for_revspec ( & repo, & owners, settings, revspec)
126
+ . map_err ( |e| ( Some ( format ! ( "{:?}" , e) ) , 1 ) ) ?;
119
127
}
120
128
}
121
129
( _, _) => unreachable ! ( "invalid subcommand" ) ,
@@ -124,17 +132,13 @@ fn run() -> Result<(), ExitError> {
124
132
Ok ( ( ) )
125
133
}
126
134
127
- fn resolve ( owners : & Owners , matches : & ArgMatches , path : & str ) -> bool {
128
- let ( teams, users, emails) = (
129
- matches. occurrences_of ( "teams" ) > 0 ,
130
- matches. occurrences_of ( "users" ) > 0 ,
131
- matches. occurrences_of ( "emails" ) > 0 ,
132
- ) ;
135
+ fn find_owners < P : AsRef < Path > > ( owners : & Owners , settings : Settings , path : P ) -> Vec < String > {
136
+ let ( teams, users, emails) = settings;
133
137
let owners = match owners. of ( path) {
134
138
Some ( owners) => owners,
135
- None => return false ,
139
+ None => return Vec :: new ( ) ,
136
140
} ;
137
- let owned = owners
141
+ owners
138
142
. iter ( )
139
143
. filter_map ( |owner| {
140
144
if teams {
@@ -156,7 +160,11 @@ fn resolve(owners: &Owners, matches: &ArgMatches, path: &str) -> bool {
156
160
Some ( owner. to_string ( ) )
157
161
}
158
162
} )
159
- . collect :: < Vec < _ > > ( ) ;
163
+ . collect ( )
164
+ }
165
+
166
+ fn resolve ( owners : & Owners , settings : Settings , path : & str ) -> bool {
167
+ let owned = find_owners ( owners, settings, path) ;
160
168
161
169
if owned. is_empty ( ) {
162
170
return false ;
@@ -192,9 +200,26 @@ fn discover_codeowners() -> Option<PathBuf> {
192
200
codeowners:: locate ( & curr_dir)
193
201
}
194
202
195
- fn print_for_revspec ( repo : & git2:: Repository , revspec : & str ) -> Result < ( ) , Error > {
203
+ #[ derive( Default , Debug ) ]
204
+ struct Stats {
205
+ files : u64 ,
206
+ commits : u64 ,
207
+ example : String ,
208
+ }
209
+
210
+ fn print_for_revspec (
211
+ repo : & git2:: Repository ,
212
+ owners : & Owners ,
213
+ settings : Settings ,
214
+ revspec : & str ,
215
+ ) -> Result < ( ) , Error > {
196
216
let mut revwalk = repo. revwalk ( ) ?;
197
217
revwalk. push_range ( revspec) ?;
218
+
219
+ let mut summary = HashMap :: with_capacity ( 2 ) ;
220
+
221
+ let mut unowned = Stats :: default ( ) ;
222
+
198
223
while let Some ( commit) = revwalk. next ( ) {
199
224
let commit = commit?;
200
225
let commit = repo. find_commit ( commit) . expect ( "commit" ) ;
@@ -215,7 +240,77 @@ fn print_for_revspec(repo: &git2::Repository, revspec: &str) -> Result<(), Error
215
240
files. insert ( path. to_path_buf ( ) ) ;
216
241
}
217
242
}
218
- println ! ( "{:?} touched files: {:?}" , commit. id( ) , files) ;
243
+
244
+ let hash = & format ! ( "{}" , commit. id( ) ) [ ..6 ] ;
245
+ let first_line = commit
246
+ . message ( )
247
+ . unwrap_or ( "" )
248
+ . split ( "\n " )
249
+ . next ( )
250
+ . unwrap_or ( "" ) ;
251
+
252
+ let commit = format ! ( "{} {}" , hash, first_line) ;
253
+
254
+ if files. is_empty ( ) {
255
+ println ! ( "{}" , commit) ;
256
+ continue ;
257
+ }
258
+
259
+ println ! ( "{}" , commit) ;
260
+
261
+ // repo.branches(None)?.next().unwrap().unwrap().0.get().peel_to_commit()
262
+
263
+ let mut files = files. into_iter ( ) . collect :: < Vec < _ > > ( ) ;
264
+ files. sort ( ) ;
265
+
266
+ let mut commit_owners = HashSet :: with_capacity ( files. len ( ) ) ;
267
+
268
+ for file in files {
269
+ let owners = find_owners ( owners, settings, & file) ;
270
+ println ! ( " * {:?} {}" , file, owners. join( " " ) ) ;
271
+ commit_owners. extend ( owners. iter ( ) . cloned ( ) ) ;
272
+
273
+ if owners. is_empty ( ) {
274
+ unowned. files += 1 ;
275
+ if unowned. example . is_empty ( ) {
276
+ unowned. example = format ! ( "{:?} in {}" , file, commit) ;
277
+ }
278
+ }
279
+
280
+ for owner in owners {
281
+ let stats = summary. entry ( owner) . or_insert_with ( Stats :: default) ;
282
+ stats. files += 1 ;
283
+
284
+ if stats. example . is_empty ( ) {
285
+ stats. example = format ! ( "{:?} in {}" , file, commit) ;
286
+ }
287
+ }
288
+ }
289
+
290
+ for owner in commit_owners {
291
+ summary. entry ( owner) . or_insert_with ( Stats :: default) . commits += 1 ;
292
+ }
293
+
294
+ println ! ( ) ;
295
+ }
296
+
297
+ println ! ( ) ;
298
+ println ! ( "Summary:" ) ;
299
+ let mut summary = summary. into_iter ( ) . collect :: < Vec < _ > > ( ) ;
300
+ summary. sort_by_key ( |( owner, _) | owner. to_string ( ) ) ;
301
+
302
+ for ( owner, stats) in summary {
303
+ println ! (
304
+ " * {}: {} files in {} commits, including: {}" ,
305
+ owner, stats. files, stats. commits, stats. example
306
+ ) ;
307
+ }
308
+
309
+ if unowned. files != 0 {
310
+ println ! (
311
+ " * no owner: {} files, including: {}" ,
312
+ unowned. files, unowned. example
313
+ ) ;
219
314
}
220
315
221
316
Ok ( ( ) )
0 commit comments