Skip to content

Commit

Permalink
feat: ein tool hours --stat to collect additional statistics per au…
Browse files Browse the repository at this point in the history
…thor. (#470)

Note that these are expensive and unconditionally use threads to speed
up these computations.
  • Loading branch information
Byron committed Sep 19, 2022
1 parent 1027be9 commit 28c4cae
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 68 deletions.
136 changes: 68 additions & 68 deletions gitoxide-core/src/hours.rs
Expand Up @@ -18,6 +18,8 @@ pub struct Context<W> {
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,
Expand All @@ -38,6 +40,7 @@ pub fn estimate<W, P>(
Context {
show_pii,
ignore_bots,
stats: _,
omit_unify_identities,
mut out,
}: Context<W>,
Expand All @@ -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<actor::SignatureRef<'static>>, bool)> {
let start = Instant::now();
progress.init(None, progress::count("commits"));
let (tx, rx) = std::sync::mpsc::channel::<Vec<u8>>();
let mailmap = repo.open_mailmap();
std::thread::scope(|scope| -> anyhow::Result<(Vec<actor::SignatureRef<'static>>, bool)> {
let start = Instant::now();
progress.init(None, progress::count("commits"));
let (tx, rx) = std::sync::mpsc::channel::<Vec<u8>>();
let mailmap = repo.open_mailmap();

let handle = scope.spawn(move || -> anyhow::Result<Vec<actor::SignatureRef<'static>>> {
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<u8> = 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<Vec<actor::SignatureRef<'static>>> {
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<u8> = 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() {
Expand Down
2 changes: 2 additions & 0 deletions src/porcelain/main.rs
Expand Up @@ -40,6 +40,7 @@ pub fn main() -> Result<()> {
working_dir,
rev_spec,
no_bots,
stats,
show_pii,
omit_unify_identities,
}) => {
Expand All @@ -58,6 +59,7 @@ pub fn main() -> Result<()> {
hours::Context {
show_pii,
ignore_bots: no_bots,
stats,
omit_unify_identities,
out,
},
Expand Down
3 changes: 3 additions & 0 deletions src/porcelain/options.rs
Expand Up @@ -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,
Expand Down

0 comments on commit 28c4cae

Please sign in to comment.