55
55
let commit_id = repo. rev_parse_single ( rev_spec) ?. detach ( ) ;
56
56
let mut string_heap = BTreeSet :: < & ' static [ u8 ] > :: new ( ) ;
57
57
58
- let ( commit_authors, is_shallow) = {
58
+ let ( commit_authors, stats , is_shallow) = {
59
59
let stat_progress = stats. then ( || progress. add_child ( "extract stats" ) ) . map ( |mut p| {
60
60
p. init ( None , progress:: count ( "commits" ) ) ;
61
61
p
@@ -65,14 +65,14 @@ where
65
65
let mut progress = progress. add_child ( "traverse commit graph" ) ;
66
66
progress. init ( None , progress:: count ( "commits" ) ) ;
67
67
68
- std:: thread:: scope ( |scope| -> anyhow:: Result < ( Vec < actor :: SignatureRef < ' static > > , bool ) > {
68
+ std:: thread:: scope ( |scope| -> anyhow:: Result < _ > {
69
69
let start = Instant :: now ( ) ;
70
- let ( tx, rx) = std:: sync:: mpsc:: channel :: < Vec < u8 > > ( ) ;
70
+ let ( tx, rx) = std:: sync:: mpsc:: channel :: < ( u32 , Vec < u8 > ) > ( ) ;
71
71
let mailmap = repo. open_mailmap ( ) ;
72
72
73
- let commit_thread = scope. spawn ( move || -> anyhow:: Result < Vec < actor :: SignatureRef < ' static > > > {
73
+ let commit_thread = scope. spawn ( move || -> anyhow:: Result < Vec < _ > > {
74
74
let mut out = Vec :: new ( ) ;
75
- for commit_data in rx {
75
+ for ( commit_idx , commit_data) in rx {
76
76
if let Some ( author) = git:: objs:: CommitRefIter :: from_bytes ( & commit_data)
77
77
. author ( )
78
78
. map ( |author| mailmap. resolve_cow ( author. trim ( ) ) )
@@ -91,19 +91,22 @@ where
91
91
let name = string_ref ( author. name . as_ref ( ) ) ;
92
92
let email = string_ref ( & author. email . as_ref ( ) ) ;
93
93
94
- out. push ( actor:: SignatureRef {
95
- name,
96
- email,
97
- time : author. time ,
98
- } ) ;
94
+ out. push ( (
95
+ commit_idx,
96
+ actor:: SignatureRef {
97
+ name,
98
+ email,
99
+ time : author. time ,
100
+ } ,
101
+ ) ) ;
99
102
}
100
103
}
101
104
out. shrink_to_fit ( ) ;
102
105
out. sort_by ( |a, b| {
103
- a. email . cmp ( & b. email ) . then (
104
- a. time
106
+ a. 1 . email . cmp ( & b. 1 . email ) . then (
107
+ a. 1 . time
105
108
. seconds_since_unix_epoch
106
- . cmp ( & b. time . seconds_since_unix_epoch )
109
+ . cmp ( & b. 1 . time . seconds_since_unix_epoch )
107
110
. reverse ( ) ,
108
111
)
109
112
} ) ;
@@ -181,18 +184,18 @@ where
181
184
let commit_iter = interrupt:: Iter :: new (
182
185
commit_id. ancestors ( |oid, buf| {
183
186
progress. inc ( ) ;
184
- repo. objects . find ( oid, buf) . map ( |o | {
185
- tx. send ( o . data . to_owned ( ) ) . ok ( ) ;
187
+ repo. objects . find ( oid, buf) . map ( |obj | {
188
+ tx. send ( ( commit_idx , obj . data . to_owned ( ) ) ) . ok ( ) ;
186
189
if let Some ( ( tx_tree, first_parent, commit) ) = tx_tree_id. as_ref ( ) . and_then ( |tx| {
187
- git:: objs:: CommitRefIter :: from_bytes ( o . data )
190
+ git:: objs:: CommitRefIter :: from_bytes ( obj . data )
188
191
. parent_ids ( )
189
192
. next ( )
190
193
. map ( |first_parent| ( tx, Some ( first_parent) , oid. to_owned ( ) ) )
191
194
} ) {
192
195
tx_tree. send ( ( commit_idx, first_parent, commit) ) . ok ( ) ;
193
196
}
194
- commit_idx += 1 ;
195
- git:: objs:: CommitRefIter :: from_bytes ( o . data )
197
+ commit_idx = commit_idx . checked_add ( 1 ) . expect ( "less then 4 billion commits" ) ;
198
+ git:: objs:: CommitRefIter :: from_bytes ( obj . data )
196
199
} )
197
200
} ) ,
198
201
|| anyhow ! ( "Cancelled by user" ) ,
@@ -213,20 +216,25 @@ where
213
216
progress. show_throughput ( start) ;
214
217
drop ( progress) ;
215
218
216
- let _stats_by_commit_idx = match stat_progress {
219
+ let stats_by_commit_idx = match stat_progress {
217
220
Some ( mut progress) => {
218
221
progress. set_max ( Some ( commit_idx as usize ) ) ;
219
222
let mut stats = Vec :: new ( ) ;
220
223
for handle in stat_threads {
221
224
stats. extend ( handle. join ( ) . expect ( "no panic" ) ?) ;
222
225
}
226
+ stats. sort_by_key ( |t| t. 0 ) ;
223
227
progress. show_throughput ( start) ;
224
228
stats
225
229
}
226
230
None => Vec :: new ( ) ,
227
231
} ;
228
232
229
- Ok ( ( commit_thread. join ( ) . expect ( "no panic" ) ?, is_shallow) )
233
+ Ok ( (
234
+ commit_thread. join ( ) . expect ( "no panic" ) ?,
235
+ stats_by_commit_idx,
236
+ is_shallow,
237
+ ) )
230
238
} ) ?
231
239
} ;
232
240
@@ -235,13 +243,13 @@ where
235
243
}
236
244
237
245
let start = Instant :: now ( ) ;
238
- let mut current_email = & commit_authors[ 0 ] . email ;
246
+ let mut current_email = & commit_authors[ 0 ] . 1 . email ;
239
247
let mut slice_start = 0 ;
240
248
let mut results_by_hours = Vec :: new ( ) ;
241
249
let mut ignored_bot_commits = 0_u32 ;
242
- for ( idx, elm) in commit_authors. iter ( ) . enumerate ( ) {
250
+ for ( idx, ( _ , elm) ) in commit_authors. iter ( ) . enumerate ( ) {
243
251
if elm. email != * current_email {
244
- let estimate = estimate_hours ( & commit_authors[ slice_start..idx] ) ;
252
+ let estimate = estimate_hours ( & commit_authors[ slice_start..idx] , & stats ) ;
245
253
slice_start = idx;
246
254
current_email = & elm. email ;
247
255
if ignore_bots && estimate. name . contains_str ( b"[bot]" ) {
@@ -252,7 +260,7 @@ where
252
260
}
253
261
}
254
262
if let Some ( commits) = commit_authors. get ( slice_start..) {
255
- results_by_hours. push ( estimate_hours ( commits) ) ;
263
+ results_by_hours. push ( estimate_hours ( commits, & stats ) ) ;
256
264
}
257
265
258
266
let num_authors = results_by_hours. len ( ) ;
@@ -275,15 +283,16 @@ where
275
283
) ) ;
276
284
277
285
let num_unique_authors = results_by_hours. len ( ) ;
278
- let ( total_hours, total_commits) = results_by_hours
286
+ let ( total_hours, total_commits, total_stats ) = results_by_hours
279
287
. iter ( )
280
- . map ( |e| ( e. hours , e. num_commits ) )
281
- . reduce ( |a, b| ( a. 0 + b. 0 , a. 1 + b. 1 ) )
288
+ . map ( |e| ( e. hours , e. num_commits , e . stats ) )
289
+ . reduce ( |a, b| ( a. 0 + b. 0 , a. 1 + b. 1 , a . 2 . clone ( ) . added ( & b . 2 ) ) )
282
290
. expect ( "at least one commit at this point" ) ;
283
291
if show_pii {
284
292
results_by_hours. sort_by ( |a, b| a. hours . partial_cmp ( & b. hours ) . unwrap_or ( std:: cmp:: Ordering :: Equal ) ) ;
293
+ let show_stats = !stats. is_empty ( ) ;
285
294
for entry in results_by_hours. iter ( ) {
286
- entry. write_to ( total_hours, & mut out) ?;
295
+ entry. write_to ( total_hours, show_stats , & mut out) ?;
287
296
writeln ! ( out) ?;
288
297
}
289
298
}
@@ -296,6 +305,13 @@ where
296
305
is_shallow. then( || " (shallow)" ) . unwrap_or_default( ) ,
297
306
num_authors
298
307
) ?;
308
+ if !stats. is_empty ( ) {
309
+ writeln ! (
310
+ out,
311
+ "total files added/removed/modified: {}/{}/{}" ,
312
+ total_stats. added, total_stats. removed, total_stats. modified
313
+ ) ?;
314
+ }
299
315
if !omit_unify_identities {
300
316
writeln ! (
301
317
out,
@@ -318,30 +334,42 @@ where
318
334
const MINUTES_PER_HOUR : f32 = 60.0 ;
319
335
const HOURS_PER_WORKDAY : f32 = 8.0 ;
320
336
321
- fn estimate_hours ( commits : & [ actor:: SignatureRef < ' static > ] ) -> WorkByEmail {
337
+ fn estimate_hours ( commits : & [ ( u32 , actor:: SignatureRef < ' static > ) ] , stats : & [ ( u32 , Stats ) ] ) -> WorkByEmail {
322
338
assert ! ( !commits. is_empty( ) ) ;
323
339
const MAX_COMMIT_DIFFERENCE_IN_MINUTES : f32 = 2.0 * MINUTES_PER_HOUR ;
324
340
const FIRST_COMMIT_ADDITION_IN_MINUTES : f32 = 2.0 * MINUTES_PER_HOUR ;
325
341
326
- let hours = FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0
327
- + commits. iter ( ) . rev ( ) . tuple_windows ( ) . fold (
328
- 0_f32 ,
329
- |hours, ( cur, next) : ( & actor:: SignatureRef < ' _ > , & actor:: SignatureRef < ' _ > ) | {
330
- let change_in_minutes =
331
- ( next. time . seconds_since_unix_epoch - cur. time . seconds_since_unix_epoch ) as f32 / MINUTES_PER_HOUR ;
332
- if change_in_minutes < MAX_COMMIT_DIFFERENCE_IN_MINUTES {
333
- hours + change_in_minutes as f32 / MINUTES_PER_HOUR
334
- } else {
335
- hours + ( FIRST_COMMIT_ADDITION_IN_MINUTES / MINUTES_PER_HOUR )
336
- }
337
- } ,
338
- ) ;
339
- let author = & commits[ 0 ] ;
342
+ let hours_for_commits = commits. iter ( ) . map ( |t| & t. 1 ) . rev ( ) . tuple_windows ( ) . fold (
343
+ 0_f32 ,
344
+ |hours, ( cur, next) : ( & actor:: SignatureRef < ' _ > , & actor:: SignatureRef < ' _ > ) | {
345
+ let change_in_minutes = ( next
346
+ . time
347
+ . seconds_since_unix_epoch
348
+ . saturating_sub ( cur. time . seconds_since_unix_epoch ) ) as f32
349
+ / MINUTES_PER_HOUR ;
350
+ if change_in_minutes < MAX_COMMIT_DIFFERENCE_IN_MINUTES {
351
+ hours + change_in_minutes as f32 / MINUTES_PER_HOUR
352
+ } else {
353
+ hours + ( FIRST_COMMIT_ADDITION_IN_MINUTES / MINUTES_PER_HOUR )
354
+ }
355
+ } ,
356
+ ) ;
357
+
358
+ let author = & commits[ 0 ] . 1 ;
340
359
WorkByEmail {
341
360
name : author. name ,
342
361
email : author. email ,
343
- hours,
362
+ hours : FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 + hours_for_commits ,
344
363
num_commits : commits. len ( ) as u32 ,
364
+ stats : commits. iter ( ) . map ( |t| & t. 0 ) . fold ( Stats :: default ( ) , |mut acc, id| {
365
+ match stats. binary_search_by ( |t| t. 0 . cmp ( id) ) {
366
+ Ok ( idx) => {
367
+ acc. add ( & stats[ idx] . 1 ) ;
368
+ acc
369
+ }
370
+ Err ( _) => acc,
371
+ }
372
+ } ) ,
345
373
}
346
374
}
347
375
@@ -378,6 +406,7 @@ struct WorkByPerson {
378
406
email : Vec < & ' static BStr > ,
379
407
hours : f32 ,
380
408
num_commits : u32 ,
409
+ stats : Stats ,
381
410
}
382
411
383
412
impl < ' a > WorkByPerson {
@@ -390,6 +419,7 @@ impl<'a> WorkByPerson {
390
419
}
391
420
self . num_commits += other. num_commits ;
392
421
self . hours += other. hours ;
422
+ self . stats . add ( & other. stats ) ;
393
423
}
394
424
}
395
425
@@ -400,12 +430,13 @@ impl<'a> From<&'a WorkByEmail> for WorkByPerson {
400
430
email : vec ! [ w. email] ,
401
431
hours : w. hours ,
402
432
num_commits : w. num_commits ,
433
+ stats : w. stats ,
403
434
}
404
435
}
405
436
}
406
437
407
438
impl WorkByPerson {
408
- fn write_to ( & self , total_hours : f32 , mut out : impl std:: io:: Write ) -> std:: io:: Result < ( ) > {
439
+ fn write_to ( & self , total_hours : f32 , show_stats : bool , mut out : impl std:: io:: Write ) -> std:: io:: Result < ( ) > {
409
440
writeln ! (
410
441
out,
411
442
"{} <{}>" ,
@@ -419,7 +450,15 @@ impl WorkByPerson {
419
450
self . hours,
420
451
self . hours / HOURS_PER_WORKDAY ,
421
452
( self . hours / total_hours) * 100.0
422
- )
453
+ ) ?;
454
+ if show_stats {
455
+ writeln ! (
456
+ out,
457
+ "total files added/removed/modified: {}/{}/{}" ,
458
+ self . stats. added, self . stats. removed, self . stats. modified
459
+ ) ?;
460
+ }
461
+ Ok ( ( ) )
423
462
}
424
463
}
425
464
@@ -429,10 +468,11 @@ struct WorkByEmail {
429
468
email : & ' static BStr ,
430
469
hours : f32 ,
431
470
num_commits : u32 ,
471
+ stats : Stats ,
432
472
}
433
473
434
474
/// Statistics for a particular commit.
435
- #[ derive( Debug , Default ) ]
475
+ #[ derive( Debug , Default , Copy , Clone ) ]
436
476
struct Stats {
437
477
/// amount of added files
438
478
added : usize ,
@@ -441,3 +481,18 @@ struct Stats {
441
481
/// amount of modified files
442
482
modified : usize ,
443
483
}
484
+
485
+ impl Stats {
486
+ fn add ( & mut self , other : & Stats ) -> & mut Self {
487
+ self . added += other. added ;
488
+ self . removed += other. removed ;
489
+ self . modified += other. modified ;
490
+ self
491
+ }
492
+
493
+ fn added ( & self , other : & Stats ) -> Self {
494
+ let mut a = * self ;
495
+ a. add ( other) ;
496
+ a
497
+ }
498
+ }
0 commit comments