Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 26489d1

Browse files
committed
Merge branch 'hours-upgrade'
2 parents 429cccc + 4d0977d commit 26489d1

5 files changed

Lines changed: 109 additions & 94 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gitoxide-core/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ default = []
1818
## Discover all git repositories within a directory. Particularly useful with [skim](https://github.com/lotabout/skim).
1919
organize = ["git-url", "jwalk"]
2020
## Derive the amount of time invested into a git repository akin to [git-hours](https://github.com/kimmobrunfeldt/git-hours).
21-
estimate-hours = ["itertools", "rayon", "fs-err"]
21+
estimate-hours = ["itertools", "fs-err"]
2222

2323
#! ### Mutually Exclusive Networking
2424
#! If both are set, _blocking-client_ will take precedence, allowing `--all-features` to be used.
@@ -59,7 +59,6 @@ blocking = { version = "1.0.2", optional = true }
5959
git-url = { version = "^0.8.0", path = "../git-url", optional = true }
6060
jwalk = { version = "0.6.0", optional = true }
6161

62-
rayon = { version = "1.5.0", optional = true }
6362
itertools = { version = "0.10.1", optional = true }
6463
fs-err = { version = "2.6.0", optional = true }
6564

gitoxide-core/src/hours.rs

Lines changed: 100 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
1+
use std::collections::BTreeSet;
12
use std::{
23
collections::{hash_map::Entry, HashMap},
3-
ffi::OsStr,
44
io,
55
path::Path,
66
time::Instant,
77
};
88

99
use anyhow::{anyhow, bail};
1010
use 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};
2013
use itertools::Itertools;
21-
use rayon::prelude::*;
2214

2315
/// Additional configuration for the hours estimation functionality.
2416
pub struct Context<W> {
@@ -41,7 +33,7 @@ pub struct Context<W> {
4133
/// * _progress_ - A way to provide progress and performance information
4234
pub 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
201216
const MINUTES_PER_HOUR: f32 = 60.0;
202217
const 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)]
310325
struct WorkByEmail {
311-
name: BString,
312-
email: BString,
326+
name: &'static BStr,
327+
email: &'static BStr,
313328
hours: f32,
314329
num_commits: u32,
315330
}

src/porcelain/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub fn main() -> Result<()> {
3838
Subcommands::Tool(tool) => match tool {
3939
crate::porcelain::options::ToolCommands::EstimateHours(crate::porcelain::options::EstimateHours {
4040
working_dir,
41-
refname,
41+
rev_spec,
4242
no_bots,
4343
show_pii,
4444
omit_unify_identities,
@@ -53,7 +53,7 @@ pub fn main() -> Result<()> {
5353
move |progress, out, _err| {
5454
hours::estimate(
5555
&working_dir,
56-
&refname,
56+
rev_spec.as_ref(),
5757
progress,
5858
hours::Context {
5959
show_pii,

src/porcelain/options.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use std::{ffi::OsString, path::PathBuf};
1+
use git::bstr::BString;
2+
use git_repository as git;
3+
use std::path::PathBuf;
24

35
#[derive(Debug, clap::Parser)]
46
#[clap(about = "The rusty git", version = clap::crate_version!())]
@@ -93,9 +95,9 @@ pub struct EstimateHours {
9395
#[clap(validator_os = validator::is_repo)]
9496
#[clap(default_value = ".")]
9597
pub working_dir: PathBuf,
96-
/// The name of the ref like 'HEAD' or 'main' at which to start iterating the commit graph.
97-
#[clap(default_value("HEAD"))]
98-
pub refname: OsString,
98+
/// The name of the revision as spec, like 'HEAD' or 'main' at which to start iterating the commit graph.
99+
#[clap(default_value("HEAD"), parse(try_from_os_str = git::env::os_str_to_bstring))]
100+
pub rev_spec: BString,
99101
/// Ignore github bots which match the `[bot]` search string.
100102
#[clap(short = 'b', long)]
101103
pub no_bots: bool,

0 commit comments

Comments
 (0)