From 28c4cae70aab2bd5b479961fcc6ee91ff80f651b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 22:55:00 +0800 Subject: [PATCH] feat: `ein tool hours --stat` to collect additional statistics per author. (#470) Note that these are expensive and unconditionally use threads to speed up these computations. --- gitoxide-core/src/hours.rs | 136 ++++++++++++++++++------------------- src/porcelain/main.rs | 2 + src/porcelain/options.rs | 3 + 3 files changed, 73 insertions(+), 68 deletions(-) diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index cdc33f2ba7f..35035ffbfd7 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -18,6 +18,8 @@ pub struct Context { pub ignore_bots: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. pub show_pii: bool, + /// Collect additional information like tree changes and changed lines. + pub stats: bool, /// Omit unifying identities by name and email which can lead to the same author appear multiple times /// due to using different names or email addresses. pub omit_unify_identities: bool, @@ -38,6 +40,7 @@ pub fn estimate( Context { show_pii, ignore_bots, + stats: _, omit_unify_identities, mut out, }: Context, @@ -52,80 +55,77 @@ where let (all_commits, is_shallow) = { let mut progress = progress.add_child("Traverse commit graph"); - let string_heap = &mut string_heap; - std::thread::scope( - move |scope| -> anyhow::Result<(Vec>, bool)> { - let start = Instant::now(); - progress.init(None, progress::count("commits")); - let (tx, rx) = std::sync::mpsc::channel::>(); - let mailmap = repo.open_mailmap(); + std::thread::scope(|scope| -> anyhow::Result<(Vec>, bool)> { + let start = Instant::now(); + progress.init(None, progress::count("commits")); + let (tx, rx) = std::sync::mpsc::channel::>(); + let mailmap = repo.open_mailmap(); - let handle = scope.spawn(move || -> anyhow::Result>> { - let mut out = Vec::new(); - for commit_data in rx { - if let Some(author) = objs::CommitRefIter::from_bytes(&commit_data) - .author() - .map(|author| mailmap.resolve_cow(author.trim())) - .ok() - { - let mut string_ref = |s: &[u8]| -> &'static BStr { - match string_heap.get(s) { - Some(n) => n.as_bstr(), - None => { - let sv: Vec = s.to_owned().into(); - string_heap.insert(Box::leak(sv.into_boxed_slice())); - (*string_heap.get(s).expect("present")).as_ref() - } + let handle = scope.spawn(move || -> anyhow::Result>> { + let mut out = Vec::new(); + for commit_data in rx { + if let Some(author) = objs::CommitRefIter::from_bytes(&commit_data) + .author() + .map(|author| mailmap.resolve_cow(author.trim())) + .ok() + { + let mut string_ref = |s: &[u8]| -> &'static BStr { + match string_heap.get(s) { + Some(n) => n.as_bstr(), + None => { + let sv: Vec = s.to_owned().into(); + string_heap.insert(Box::leak(sv.into_boxed_slice())); + (*string_heap.get(s).expect("present")).as_ref() } - }; - let name = string_ref(author.name.as_ref()); - let email = string_ref(&author.email.as_ref()); + } + }; + let name = string_ref(author.name.as_ref()); + let email = string_ref(&author.email.as_ref()); - out.push(actor::SignatureRef { - name, - email, - time: author.time, - }); - } + out.push(actor::SignatureRef { + name, + email, + time: author.time, + }); } - out.shrink_to_fit(); - out.sort_by(|a, b| { - a.email.cmp(&b.email).then( - a.time - .seconds_since_unix_epoch - .cmp(&b.time.seconds_since_unix_epoch) - .reverse(), - ) - }); - Ok(out) + } + out.shrink_to_fit(); + out.sort_by(|a, b| { + a.email.cmp(&b.email).then( + a.time + .seconds_since_unix_epoch + .cmp(&b.time.seconds_since_unix_epoch) + .reverse(), + ) }); + Ok(out) + }); - let commit_iter = interrupt::Iter::new( - commit_id.ancestors(|oid, buf| { - progress.inc(); - repo.objects.find(oid, buf).map(|o| { - tx.send(o.data.to_owned()).ok(); - objs::CommitRefIter::from_bytes(o.data) - }) - }), - || anyhow!("Cancelled by user"), - ); - let mut is_shallow = false; - for c in commit_iter { - match c? { - Ok(c) => c, - Err(git::traverse::commit::ancestors::Error::FindExisting { .. }) => { - is_shallow = true; - break; - } - Err(err) => return Err(err.into()), - }; - } - drop(tx); - progress.show_throughput(start); - Ok((handle.join().expect("no panic")?, is_shallow)) - }, - )? + let commit_iter = interrupt::Iter::new( + commit_id.ancestors(|oid, buf| { + progress.inc(); + repo.objects.find(oid, buf).map(|o| { + tx.send(o.data.to_owned()).ok(); + objs::CommitRefIter::from_bytes(o.data) + }) + }), + || anyhow!("Cancelled by user"), + ); + let mut is_shallow = false; + for c in commit_iter { + match c? { + Ok(c) => c, + Err(git::traverse::commit::ancestors::Error::FindExisting { .. }) => { + is_shallow = true; + break; + } + Err(err) => return Err(err.into()), + }; + } + drop(tx); + progress.show_throughput(start); + Ok((handle.join().expect("no panic")?, is_shallow)) + })? }; if all_commits.is_empty() { diff --git a/src/porcelain/main.rs b/src/porcelain/main.rs index e0656617e30..ef1e2d575e5 100644 --- a/src/porcelain/main.rs +++ b/src/porcelain/main.rs @@ -40,6 +40,7 @@ pub fn main() -> Result<()> { working_dir, rev_spec, no_bots, + stats, show_pii, omit_unify_identities, }) => { @@ -58,6 +59,7 @@ pub fn main() -> Result<()> { hours::Context { show_pii, ignore_bots: no_bots, + stats, omit_unify_identities, out, }, diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 5ee5774321f..454d6eed4f1 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -101,6 +101,9 @@ pub struct EstimateHours { /// Ignore github bots which match the `[bot]` search string. #[clap(short = 'b', long)] pub no_bots: bool, + /// Collect additional information like tree changes and changed lines. + #[clap(short = 's', long)] + pub stats: bool, /// Show personally identifiable information before the summary. Includes names and email addresses. #[clap(short = 'p', long)] pub show_pii: bool,