Skip to content

Commit 397b4ae

Browse files
committed
Compute contributor identity using emails, lowercase, only
We cache the lower-case value only if it is different from what's there to speed up comparisons while using memory/allocations only when needed.
1 parent 04ff547 commit 397b4ae

File tree

1 file changed

+45
-13
lines changed

1 file changed

+45
-13
lines changed

src/info/repo.rs

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use git2::{Repository, RepositoryOpenFlags, Status, StatusOptions, StatusShow};
66
use git_repository as git;
77
use git_repository::bstr::ByteSlice;
88
use regex::Regex;
9+
use std::cmp::Ordering;
910
use std::collections::HashMap;
1011
use std::path::Path;
1112
use time::format_description::well_known::Rfc3339;
@@ -23,23 +24,53 @@ pub struct Repo<'a> {
2324
time_of_first_commit: git::actor::Time,
2425
}
2526

26-
#[derive(Hash, PartialEq, Eq, Ord, PartialOrd)]
27+
#[derive(Hash)]
2728
pub struct Sig {
2829
name: git::bstr::BString,
2930
email: git::bstr::BString,
31+
email_lowercase: Option<String>,
3032
}
3133

3234
impl From<git::actor::Signature> for Sig {
33-
fn from(
34-
git::actor::Signature {
35-
name, mut email, ..
36-
}: git::actor::Signature,
37-
) -> Self {
38-
// authors who aren't mail-mapped would otherwise seem dissimilar if the email case is different.
39-
// Explicitly lower-casing it normalizes for that.
40-
// This comes at the cost of displaying only lower-case emails as well, which seems beneficial.
41-
email.make_ascii_lowercase();
42-
Self { name, email }
35+
fn from(git::actor::Signature { name, email, .. }: git::actor::Signature) -> Self {
36+
let needs_lowercase = email.chars().any(|c| c.to_ascii_lowercase() != c);
37+
let email_lowercase = needs_lowercase.then(|| email.chars().collect::<String>());
38+
Self {
39+
name,
40+
email,
41+
email_lowercase,
42+
}
43+
}
44+
}
45+
46+
impl Eq for Sig {}
47+
48+
impl PartialEq<Self> for Sig {
49+
fn eq(&self, other: &Self) -> bool {
50+
self.cmp(other) == Ordering::Equal
51+
}
52+
}
53+
54+
impl PartialOrd<Self> for Sig {
55+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
56+
self.cmp(other).into()
57+
}
58+
}
59+
60+
impl Ord for Sig {
61+
fn cmp(&self, other: &Self) -> Ordering {
62+
self.email_lowercase
63+
.as_ref()
64+
.and_then(|a_email_lc| {
65+
other
66+
.email_lowercase
67+
.as_ref()
68+
.map(|b_email_lc| (a_email_lc, b_email_lc))
69+
})
70+
.map_or_else(
71+
|| self.email.cmp(&other.email),
72+
|(a_email_lc, b_email_lc)| a_email_lc.cmp(b_email_lc),
73+
)
4374
}
4475
}
4576

@@ -98,8 +129,9 @@ impl<'a> Repo<'a> {
98129
author_to_number_of_commits.into_iter().collect();
99130

100131
let total_num_authors = authors_by_number_of_commits.len();
101-
authors_by_number_of_commits
102-
.sort_by(|(sa, a_count), (sb, b_count)| b_count.cmp(a_count).then_with(|| sa.cmp(&sb)));
132+
authors_by_number_of_commits.sort_by(|(sa, a_count), (sb, b_count)| {
133+
b_count.cmp(a_count).then_with(|| sa.name.cmp(&sb.name))
134+
});
103135

104136
let authors: Vec<Author> = authors_by_number_of_commits
105137
.into_iter()

0 commit comments

Comments
 (0)