Skip to content

Commit 3c7c9a7

Browse files
committed
change: ein tool hours -s was split into -f|--file-stats and -l|line-stats. (#470)
That way more information is generated at increasingly high costs.
1 parent ffd4f0f commit 3c7c9a7

File tree

3 files changed

+89
-33
lines changed

3 files changed

+89
-33
lines changed

gitoxide-core/src/hours.rs

+77-28
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ pub struct Context<W> {
2020
pub ignore_bots: bool,
2121
/// Show personally identifiable information before the summary. Includes names and email addresses.
2222
pub show_pii: bool,
23-
/// Collect additional information like tree changes and changed lines.
24-
pub stats: bool,
23+
/// Collect how many files have been added, removed and modified (without rename tracking).
24+
pub file_stats: bool,
25+
/// Collect how many lines in files have been added, removed and modified (without rename tracking).
26+
pub line_stats: bool,
2527
/// Omit unifying identities by name and email which can lead to the same author appear multiple times
2628
/// due to using different names or email addresses.
2729
pub omit_unify_identities: bool,
@@ -42,7 +44,8 @@ pub fn estimate<W, P>(
4244
Context {
4345
show_pii,
4446
ignore_bots,
45-
stats,
47+
file_stats,
48+
line_stats,
4649
omit_unify_identities,
4750
mut out,
4851
}: Context<W>,
@@ -56,7 +59,7 @@ where
5659
let mut string_heap = BTreeSet::<&'static [u8]>::new();
5760

5861
let (commit_authors, stats, is_shallow) = {
59-
let stat_progress = stats.then(|| progress.add_child("extract stats")).map(|mut p| {
62+
let stat_progress = file_stats.then(|| progress.add_child("extract stats")).map(|mut p| {
6063
p.init(None, progress::count("commits"));
6164
p
6265
});
@@ -113,7 +116,7 @@ where
113116
Ok(out)
114117
});
115118

116-
let (tx_tree_id, stat_threads) = stats
119+
let (tx_tree_id, stat_threads) = (file_stats || line_stats)
117120
.then(|| {
118121
let num_threads = num_cpus::get().saturating_sub(1 /*main thread*/).max(1);
119122
let (tx, rx) = flume::unbounded::<(u32, Option<git::hash::ObjectId>, git::hash::ObjectId)>();
@@ -130,7 +133,7 @@ where
130133
if let Some(c) = counter.as_ref() {
131134
c.fetch_add(1, Ordering::SeqCst);
132135
}
133-
let mut stat = Stats::default();
136+
let mut stat = FileStats::default();
134137
let from = match parent_commit {
135138
Some(id) => {
136139
match repo.find_object(id).ok().and_then(|c| c.peel_to_tree().ok()) {
@@ -283,16 +286,15 @@ where
283286
));
284287

285288
let num_unique_authors = results_by_hours.len();
286-
let (total_hours, total_commits, total_stats) = results_by_hours
289+
let (total_hours, total_commits, total_files, total_lines) = results_by_hours
287290
.iter()
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)))
291+
.map(|e| (e.hours, e.num_commits, e.files, e.lines))
292+
.reduce(|a, b| (a.0 + b.0, a.1 + b.1, a.2.clone().added(&b.2), a.3.clone().added(&b.3)))
290293
.expect("at least one commit at this point");
291294
if show_pii {
292295
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();
294296
for entry in results_by_hours.iter() {
295-
entry.write_to(total_hours, show_stats, &mut out)?;
297+
entry.write_to(total_hours, file_stats, line_stats, &mut out)?;
296298
writeln!(out)?;
297299
}
298300
}
@@ -305,11 +307,18 @@ where
305307
is_shallow.then(|| " (shallow)").unwrap_or_default(),
306308
num_authors
307309
)?;
308-
if !stats.is_empty() {
310+
if file_stats {
309311
writeln!(
310312
out,
311313
"total files added/removed/modified: {}/{}/{}",
312-
total_stats.added, total_stats.removed, total_stats.modified
314+
total_files.added, total_files.removed, total_files.modified
315+
)?;
316+
}
317+
if line_stats {
318+
writeln!(
319+
out,
320+
"total lines added/removed: {}/{}",
321+
total_lines.added, total_lines.removed
313322
)?;
314323
}
315324
if !omit_unify_identities {
@@ -334,7 +343,7 @@ where
334343
const MINUTES_PER_HOUR: f32 = 60.0;
335344
const HOURS_PER_WORKDAY: f32 = 8.0;
336345

337-
fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32, Stats)]) -> WorkByEmail {
346+
fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32, FileStats)]) -> WorkByEmail {
338347
assert!(!commits.is_empty());
339348
const MAX_COMMIT_DIFFERENCE_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR;
340349
const FIRST_COMMIT_ADDITION_IN_MINUTES: f32 = 2.0 * MINUTES_PER_HOUR;
@@ -361,9 +370,9 @@ fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32
361370
email: author.email,
362371
hours: FIRST_COMMIT_ADDITION_IN_MINUTES / 60.0 + hours_for_commits,
363372
num_commits: commits.len() as u32,
364-
stats: (!stats.is_empty())
373+
files: (!stats.is_empty())
365374
.then(|| {
366-
commits.iter().map(|t| &t.0).fold(Stats::default(), |mut acc, id| {
375+
commits.iter().map(|t| &t.0).fold(FileStats::default(), |mut acc, id| {
367376
match stats.binary_search_by(|t| t.0.cmp(id)) {
368377
Ok(idx) => {
369378
acc.add(&stats[idx].1);
@@ -374,6 +383,7 @@ fn estimate_hours(commits: &[(u32, actor::SignatureRef<'static>)], stats: &[(u32
374383
})
375384
})
376385
.unwrap_or_default(),
386+
lines: Default::default(),
377387
}
378388
}
379389

@@ -410,7 +420,8 @@ struct WorkByPerson {
410420
email: Vec<&'static BStr>,
411421
hours: f32,
412422
num_commits: u32,
413-
stats: Stats,
423+
files: FileStats,
424+
lines: LineStats,
414425
}
415426

416427
impl<'a> WorkByPerson {
@@ -423,7 +434,7 @@ impl<'a> WorkByPerson {
423434
}
424435
self.num_commits += other.num_commits;
425436
self.hours += other.hours;
426-
self.stats.add(&other.stats);
437+
self.files.add(&other.files);
427438
}
428439
}
429440

@@ -434,13 +445,20 @@ impl<'a> From<&'a WorkByEmail> for WorkByPerson {
434445
email: vec![w.email],
435446
hours: w.hours,
436447
num_commits: w.num_commits,
437-
stats: w.stats,
448+
files: w.files,
449+
lines: w.lines,
438450
}
439451
}
440452
}
441453

442454
impl WorkByPerson {
443-
fn write_to(&self, total_hours: f32, show_stats: bool, mut out: impl std::io::Write) -> std::io::Result<()> {
455+
fn write_to(
456+
&self,
457+
total_hours: f32,
458+
show_files: bool,
459+
show_lines: bool,
460+
mut out: impl std::io::Write,
461+
) -> std::io::Result<()> {
444462
writeln!(
445463
out,
446464
"{} <{}>",
@@ -455,11 +473,18 @@ impl WorkByPerson {
455473
self.hours / HOURS_PER_WORKDAY,
456474
(self.hours / total_hours) * 100.0
457475
)?;
458-
if show_stats {
476+
if show_files {
459477
writeln!(
460478
out,
461479
"total files added/removed/modified: {}/{}/{}",
462-
self.stats.added, self.stats.removed, self.stats.modified
480+
self.files.added, self.files.removed, self.files.modified
481+
)?;
482+
}
483+
if show_lines {
484+
writeln!(
485+
out,
486+
"total lines added/removed: {}/{}",
487+
self.lines.added, self.lines.removed
463488
)?;
464489
}
465490
Ok(())
@@ -472,12 +497,13 @@ struct WorkByEmail {
472497
email: &'static BStr,
473498
hours: f32,
474499
num_commits: u32,
475-
stats: Stats,
500+
files: FileStats,
501+
lines: LineStats,
476502
}
477503

478-
/// Statistics for a particular commit.
504+
/// File statistics for a particular commit.
479505
#[derive(Debug, Default, Copy, Clone)]
480-
struct Stats {
506+
struct FileStats {
481507
/// amount of added files
482508
added: usize,
483509
/// amount of removed files
@@ -486,15 +512,38 @@ struct Stats {
486512
modified: usize,
487513
}
488514

489-
impl Stats {
490-
fn add(&mut self, other: &Stats) -> &mut Self {
515+
/// Line statistics for a particular commit.
516+
#[derive(Debug, Default, Copy, Clone)]
517+
struct LineStats {
518+
/// amount of added lines
519+
added: usize,
520+
/// amount of removed lines
521+
removed: usize,
522+
}
523+
524+
impl FileStats {
525+
fn add(&mut self, other: &FileStats) -> &mut Self {
491526
self.added += other.added;
492527
self.removed += other.removed;
493528
self.modified += other.modified;
494529
self
495530
}
496531

497-
fn added(&self, other: &Stats) -> Self {
532+
fn added(&self, other: &FileStats) -> Self {
533+
let mut a = *self;
534+
a.add(other);
535+
a
536+
}
537+
}
538+
539+
impl LineStats {
540+
fn add(&mut self, other: &LineStats) -> &mut Self {
541+
self.added += other.added;
542+
self.removed += other.removed;
543+
self
544+
}
545+
546+
fn added(&self, other: &LineStats) -> Self {
498547
let mut a = *self;
499548
a.add(other);
500549
a

src/porcelain/main.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ pub fn main() -> Result<()> {
4040
working_dir,
4141
rev_spec,
4242
no_bots,
43-
stats,
43+
file_stats,
44+
line_stats,
4445
show_pii,
4546
omit_unify_identities,
4647
}) => {
@@ -59,7 +60,8 @@ pub fn main() -> Result<()> {
5960
hours::Context {
6061
show_pii,
6162
ignore_bots: no_bots,
62-
stats,
63+
file_stats,
64+
line_stats,
6365
omit_unify_identities,
6466
out,
6567
},

src/porcelain/options.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,14 @@ pub struct EstimateHours {
101101
/// Ignore github bots which match the `[bot]` search string.
102102
#[clap(short = 'b', long)]
103103
pub no_bots: bool,
104-
/// Collect additional information about file modifications, additions and deletions.
105-
#[clap(short = 's', long)]
106-
pub stats: bool,
104+
/// Collect additional information about file modifications, additions and deletions (without rename tracking).
105+
#[clap(short = 'f', long)]
106+
pub file_stats: bool,
107+
/// Collect additional information about lines added and deleted (without rename tracking).
108+
///
109+
/// Note that this implies the work to be done for file-stats, so it should be set as well.
110+
#[clap(short = 'l', long)]
111+
pub line_stats: bool,
107112
/// Show personally identifiable information before the summary. Includes names and email addresses.
108113
#[clap(short = 'p', long)]
109114
pub show_pii: bool,

0 commit comments

Comments
 (0)