1+ use std:: collections:: BTreeSet ;
12use std:: {
23 collections:: { hash_map:: Entry , HashMap } ,
3- ffi:: OsStr ,
44 io,
55 path:: Path ,
66 time:: Instant ,
77} ;
88
99use anyhow:: { anyhow, bail} ;
1010use git_repository as git;
11- use git_repository:: {
12- actor,
13- bstr:: { BString , ByteSlice } ,
14- interrupt, objs,
15- prelude:: * ,
16- progress,
17- refs:: file:: ReferenceExt ,
18- Progress ,
19- } ;
11+ use git_repository:: bstr:: { BStr , BString } ;
12+ use git_repository:: { actor, bstr:: ByteSlice , interrupt, objs, prelude:: * , progress, Progress } ;
2013use itertools:: Itertools ;
21- use rayon:: prelude:: * ;
2214
2315/// Additional configuration for the hours estimation functionality.
2416pub struct Context < W > {
@@ -41,7 +33,7 @@ pub struct Context<W> {
4133/// * _progress_ - A way to provide progress and performance information
4234pub fn estimate < W , P > (
4335 working_dir : & Path ,
44- refname : & OsStr ,
36+ rev_spec : & BStr ,
4537 mut progress : P ,
4638 Context {
4739 show_pii,
@@ -55,69 +47,92 @@ where
5547 P : Progress ,
5648{
5749 let repo = git:: discover ( working_dir) ?. apply_environment ( ) ;
58- let commit_id = repo
59- . refs
60- . find ( refname. to_string_lossy ( ) . as_ref ( ) ) ?
61- . peel_to_id_in_place ( & repo. refs , |oid, buf| {
62- repo. objects
63- . try_find ( oid, buf)
64- . map ( |obj| obj. map ( |obj| ( obj. kind , obj. data ) ) )
65- } ) ?
66- . to_owned ( ) ;
50+ let commit_id = repo. rev_parse_single ( rev_spec) ?. detach ( ) ;
51+ let mut string_heap = BTreeSet :: < & ' static [ u8 ] > :: new ( ) ;
6752
6853 let ( all_commits, is_shallow) = {
69- let start = Instant :: now ( ) ;
7054 let mut progress = progress. add_child ( "Traverse commit graph" ) ;
71- progress. init ( None , progress:: count ( "commits" ) ) ;
72- let mut commits: Vec < Vec < u8 > > = Vec :: new ( ) ;
73- let commit_iter = interrupt:: Iter :: new (
74- commit_id. ancestors ( |oid, buf| {
75- progress. inc ( ) ;
76- repo. objects . find ( oid, buf) . map ( |o| {
77- commits. push ( o. data . to_owned ( ) ) ;
78- objs:: CommitRefIter :: from_bytes ( o. data )
79- } )
80- } ) ,
81- || anyhow ! ( "Cancelled by user" ) ,
82- ) ;
83- let mut is_shallow = false ;
84- for c in commit_iter {
85- match c? {
86- Ok ( c) => c,
87- Err ( git:: traverse:: commit:: ancestors:: Error :: FindExisting { .. } ) => {
88- is_shallow = true ;
89- break ;
55+ let string_heap = & mut string_heap;
56+ std:: thread:: scope (
57+ move |scope| -> anyhow:: Result < ( Vec < actor:: SignatureRef < ' static > > , bool ) > {
58+ let start = Instant :: now ( ) ;
59+ progress. init ( None , progress:: count ( "commits" ) ) ;
60+ let ( tx, rx) = std:: sync:: mpsc:: channel :: < Vec < u8 > > ( ) ;
61+ let mailmap = repo. open_mailmap ( ) ;
62+
63+ let handle = scope. spawn ( move || -> anyhow:: Result < Vec < actor:: SignatureRef < ' static > > > {
64+ let mut out = Vec :: new ( ) ;
65+ for commit_data in rx {
66+ if let Some ( author) = objs:: CommitRefIter :: from_bytes ( & commit_data)
67+ . author ( )
68+ . map ( |author| mailmap. resolve ( author. trim ( ) ) )
69+ . ok ( )
70+ {
71+ let mut string_ref = |s : & BString | -> & ' static BStr {
72+ match string_heap. get ( s. as_slice ( ) ) {
73+ Some ( n) => n. as_bstr ( ) ,
74+ None => {
75+ let sv: Vec < u8 > = s. clone ( ) . into ( ) ;
76+ string_heap. insert ( Box :: leak ( sv. into_boxed_slice ( ) ) ) ;
77+ ( * string_heap. get ( s. as_slice ( ) ) . expect ( "present" ) ) . as_ref ( )
78+ }
79+ }
80+ } ;
81+ let name = string_ref ( & author. name ) ;
82+ let email = string_ref ( & author. email ) ;
83+
84+ out. push ( actor:: SignatureRef {
85+ name,
86+ email,
87+ time : author. time ,
88+ } ) ;
89+ }
90+ }
91+ out. shrink_to_fit ( ) ;
92+ out. sort_by ( |a, b| {
93+ a. email . cmp ( & b. email ) . then (
94+ a. time
95+ . seconds_since_unix_epoch
96+ . cmp ( & b. time . seconds_since_unix_epoch )
97+ . reverse ( ) ,
98+ )
99+ } ) ;
100+ Ok ( out)
101+ } ) ;
102+
103+ let commit_iter = interrupt:: Iter :: new (
104+ commit_id. ancestors ( |oid, buf| {
105+ progress. inc ( ) ;
106+ repo. objects . find ( oid, buf) . map ( |o| {
107+ tx. send ( o. data . to_owned ( ) ) . ok ( ) ;
108+ objs:: CommitRefIter :: from_bytes ( o. data )
109+ } )
110+ } ) ,
111+ || anyhow ! ( "Cancelled by user" ) ,
112+ ) ;
113+ let mut is_shallow = false ;
114+ for c in commit_iter {
115+ match c? {
116+ Ok ( c) => c,
117+ Err ( git:: traverse:: commit:: ancestors:: Error :: FindExisting { .. } ) => {
118+ is_shallow = true ;
119+ break ;
120+ }
121+ Err ( err) => return Err ( err. into ( ) ) ,
122+ } ;
90123 }
91- Err ( err ) => return Err ( err . into ( ) ) ,
92- } ;
93- }
94- progress . show_throughput ( start ) ;
95- ( commits , is_shallow )
124+ drop ( tx ) ;
125+ progress . show_throughput ( start ) ;
126+ Ok ( ( handle . join ( ) . expect ( "no panic" ) ? , is_shallow ) )
127+ } ,
128+ ) ?
96129 } ;
97130
98- let mailmap = repo. open_mailmap ( ) ;
99- let start = Instant :: now ( ) ;
100- #[ allow( clippy:: redundant_closure) ]
101- let mut all_commits: Vec < actor:: Signature > = all_commits
102- . into_par_iter ( )
103- . filter_map ( |commit_data : Vec < u8 > | {
104- objs:: CommitRefIter :: from_bytes ( & commit_data)
105- . author ( )
106- . map ( |author| mailmap. resolve ( author. trim ( ) ) )
107- . ok ( )
108- } )
109- . collect :: < Vec < _ > > ( ) ;
110- all_commits. sort_by ( |a, b| {
111- a. email . cmp ( & b. email ) . then (
112- a. time
113- . seconds_since_unix_epoch
114- . cmp ( & b. time . seconds_since_unix_epoch )
115- . reverse ( ) ,
116- )
117- } ) ;
118131 if all_commits. is_empty ( ) {
119132 bail ! ( "No commits to process" ) ;
120133 }
134+
135+ let start = Instant :: now ( ) ;
121136 let mut current_email = & all_commits[ 0 ] . email ;
122137 let mut slice_start = 0 ;
123138 let mut results_by_hours = Vec :: new ( ) ;
@@ -201,15 +216,15 @@ where
201216const MINUTES_PER_HOUR : f32 = 60.0 ;
202217const HOURS_PER_WORKDAY : f32 = 8.0 ;
203218
204- fn estimate_hours ( commits : & [ actor:: Signature ] ) -> WorkByEmail {
219+ fn estimate_hours ( commits : & [ actor:: SignatureRef < ' static > ] ) -> WorkByEmail {
205220 assert ! ( !commits. is_empty( ) ) ;
206221 const MAX_COMMIT_DIFFERENCE_IN_MINUTES : f32 = 2.0 * MINUTES_PER_HOUR ;
207222 const FIRST_COMMIT_ADDITION_IN_MINUTES : f32 = 2.0 * MINUTES_PER_HOUR ;
208223
209224 let hours = FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0
210225 + commits. iter ( ) . rev ( ) . tuple_windows ( ) . fold (
211226 0_f32 ,
212- |hours, ( cur, next) : ( & actor:: Signature , & actor:: Signature ) | {
227+ |hours, ( cur, next) : ( & actor:: SignatureRef < ' _ > , & actor:: SignatureRef < ' _ > ) | {
213228 let change_in_minutes =
214229 ( next. time . seconds_since_unix_epoch - cur. time . seconds_since_unix_epoch ) as f32 / MINUTES_PER_HOUR ;
215230 if change_in_minutes < MAX_COMMIT_DIFFERENCE_IN_MINUTES {
@@ -221,19 +236,19 @@ fn estimate_hours(commits: &[actor::Signature]) -> WorkByEmail {
221236 ) ;
222237 let author = & commits[ 0 ] ;
223238 WorkByEmail {
224- name : author. name . to_owned ( ) ,
225- email : author. email . to_owned ( ) ,
239+ name : author. name ,
240+ email : author. email ,
226241 hours,
227242 num_commits : commits. len ( ) as u32 ,
228243 }
229244}
230245
231- fn deduplicate_identities ( persons : & [ WorkByEmail ] ) -> Vec < WorkByPerson < ' _ > > {
232- let mut email_to_index = HashMap :: < & BString , usize > :: with_capacity ( persons. len ( ) ) ;
233- let mut name_to_index = HashMap :: < & BString , usize > :: with_capacity ( persons. len ( ) ) ;
234- let mut out = Vec :: < WorkByPerson < ' _ > > :: with_capacity ( persons. len ( ) ) ;
246+ fn deduplicate_identities ( persons : & [ WorkByEmail ] ) -> Vec < WorkByPerson > {
247+ let mut email_to_index = HashMap :: < & ' static BStr , usize > :: with_capacity ( persons. len ( ) ) ;
248+ let mut name_to_index = HashMap :: < & ' static BStr , usize > :: with_capacity ( persons. len ( ) ) ;
249+ let mut out = Vec :: < WorkByPerson > :: with_capacity ( persons. len ( ) ) ;
235250 for person_by_email in persons {
236- match email_to_index. entry ( & person_by_email. email ) {
251+ match email_to_index. entry ( person_by_email. email ) {
237252 Entry :: Occupied ( email_entry) => {
238253 out[ * email_entry. get ( ) ] . merge ( person_by_email) ;
239254 name_to_index. insert ( & person_by_email. name , * email_entry. get ( ) ) ;
@@ -256,14 +271,14 @@ fn deduplicate_identities(persons: &[WorkByEmail]) -> Vec<WorkByPerson<'_>> {
256271}
257272
258273#[ derive( Debug ) ]
259- struct WorkByPerson < ' a > {
260- name : Vec < & ' a BString > ,
261- email : Vec < & ' a BString > ,
274+ struct WorkByPerson {
275+ name : Vec < & ' static BStr > ,
276+ email : Vec < & ' static BStr > ,
262277 hours : f32 ,
263278 num_commits : u32 ,
264279}
265280
266- impl < ' a > WorkByPerson < ' a > {
281+ impl < ' a > WorkByPerson {
267282 fn merge ( & mut self , other : & ' a WorkByEmail ) {
268283 if !self . name . contains ( & & other. name ) {
269284 self . name . push ( & other. name ) ;
@@ -276,18 +291,18 @@ impl<'a> WorkByPerson<'a> {
276291 }
277292}
278293
279- impl < ' a > From < & ' a WorkByEmail > for WorkByPerson < ' a > {
294+ impl < ' a > From < & ' a WorkByEmail > for WorkByPerson {
280295 fn from ( w : & ' a WorkByEmail ) -> Self {
281296 WorkByPerson {
282- name : vec ! [ & w. name] ,
283- email : vec ! [ & w. email] ,
297+ name : vec ! [ w. name] ,
298+ email : vec ! [ w. email] ,
284299 hours : w. hours ,
285300 num_commits : w. num_commits ,
286301 }
287302 }
288303}
289304
290- impl < ' a > WorkByPerson < ' a > {
305+ impl WorkByPerson {
291306 fn write_to ( & self , total_hours : f32 , mut out : impl std:: io:: Write ) -> std:: io:: Result < ( ) > {
292307 writeln ! (
293308 out,
@@ -308,8 +323,8 @@ impl<'a> WorkByPerson<'a> {
308323
309324#[ derive( Debug ) ]
310325struct WorkByEmail {
311- name : BString ,
312- email : BString ,
326+ name : & ' static BStr ,
327+ email : & ' static BStr ,
313328 hours : f32 ,
314329 num_commits : u32 ,
315330}
0 commit comments