Skip to content

Commit f107014

Browse files
committed
Merge branch 'fix-mailmap'
2 parents 78b8e41 + 3e08fa3 commit f107014

File tree

8 files changed

+106
-5
lines changed

8 files changed

+106
-5
lines changed

Diff for: gitoxide-core/src/repository/mailmap.rs

+58
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use anyhow::bail;
2+
use gix::bstr::{BString, ByteSlice};
13
use std::io;
24

35
#[cfg(feature = "serde")]
@@ -50,3 +52,59 @@ pub fn entries(
5052

5153
Ok(())
5254
}
55+
56+
pub fn check(
57+
repo: gix::Repository,
58+
format: OutputFormat,
59+
contacts: Vec<BString>,
60+
mut out: impl io::Write,
61+
mut err: impl io::Write,
62+
) -> anyhow::Result<()> {
63+
if format != OutputFormat::Human {
64+
bail!("Only human output is supported right now");
65+
}
66+
if contacts.is_empty() {
67+
bail!("specify at least one contact to run through the mailmap")
68+
}
69+
70+
let mut mailmap = gix::mailmap::Snapshot::default();
71+
if let Err(err) = repo.open_mailmap_into(&mut mailmap) {
72+
bail!(err);
73+
}
74+
75+
let mut buf = Vec::new();
76+
for contact in contacts {
77+
let actor = match gix::actor::IdentityRef::from_bytes::<()>(&contact) {
78+
Ok(a) => a,
79+
Err(_) => {
80+
let Some(email) = contact
81+
.trim_start()
82+
.strip_prefix(b"<")
83+
.and_then(|rest| rest.trim_end().strip_suffix(b">"))
84+
else {
85+
writeln!(err, "Failed to parse contact '{contact}' - skipping")?;
86+
continue;
87+
};
88+
gix::actor::IdentityRef {
89+
name: "".into(),
90+
email: email.into(),
91+
}
92+
}
93+
};
94+
let resolved = mailmap.resolve_cow(gix::actor::SignatureRef {
95+
name: actor.name,
96+
email: actor.email,
97+
time: Default::default(),
98+
});
99+
let resolved = gix::actor::IdentityRef {
100+
name: resolved.name.as_ref(),
101+
email: resolved.email.as_ref(),
102+
};
103+
buf.clear();
104+
resolved.write_to(&mut buf)?;
105+
106+
out.write_all(&buf)?;
107+
out.write_all(b"\n")?;
108+
}
109+
Ok(())
110+
}

Diff for: gix-mailmap/src/entry.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,37 @@ impl<'a> Entry<'a> {
2525
/// Constructors indicating what kind of mapping is created.
2626
///
2727
/// Only these combinations of values are valid.
28-
#[allow(missing_docs)]
2928
impl<'a> Entry<'a> {
29+
/// An entry that changes the name by an email.
3030
pub fn change_name_by_email(proper_name: impl Into<&'a BStr>, commit_email: impl Into<&'a BStr>) -> Self {
3131
Entry {
3232
new_name: Some(proper_name.into()),
3333
old_email: commit_email.into(),
3434
..Default::default()
3535
}
3636
}
37+
/// An entry that changes the email by an email.
3738
pub fn change_email_by_email(proper_email: impl Into<&'a BStr>, commit_email: impl Into<&'a BStr>) -> Self {
3839
Entry {
3940
new_email: Some(proper_email.into()),
4041
old_email: commit_email.into(),
4142
..Default::default()
4243
}
4344
}
45+
/// An entry that changes the email by a name and email.
46+
pub fn change_email_by_name_and_email(
47+
proper_email: impl Into<&'a BStr>,
48+
commit_name: impl Into<&'a BStr>,
49+
commit_email: impl Into<&'a BStr>,
50+
) -> Self {
51+
Entry {
52+
new_email: Some(proper_email.into()),
53+
old_email: commit_email.into(),
54+
old_name: Some(commit_name.into()),
55+
..Default::default()
56+
}
57+
}
58+
/// An entry that changes a name and the email by an email.
4459
pub fn change_name_and_email_by_email(
4560
proper_name: impl Into<&'a BStr>,
4661
proper_email: impl Into<&'a BStr>,
@@ -53,7 +68,7 @@ impl<'a> Entry<'a> {
5368
..Default::default()
5469
}
5570
}
56-
71+
/// An entry that changes a name and email by a name and email.
5772
pub fn change_name_and_email_by_name_and_email(
5873
proper_name: impl Into<&'a BStr>,
5974
proper_email: impl Into<&'a BStr>,

Diff for: gix-mailmap/src/parse.rs

+3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ fn parse_line(line: &BStr, line_number: usize) -> Result<Entry<'_>, Error> {
7777
(Some(proper_name), Some(proper_email), Some(commit_name), Some(commit_email)) => {
7878
Entry::change_name_and_email_by_name_and_email(proper_name, proper_email, commit_name, commit_email)
7979
}
80+
(None, Some(proper_email), Some(commit_name), Some(commit_email)) => {
81+
Entry::change_email_by_name_and_email(proper_email, commit_name, commit_email)
82+
}
8083
_ => {
8184
return Err(Error::Malformed {
8285
line_number,

Diff for: gix-mailmap/tests/fixtures/typical.txt

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Joe R. Developer <[email protected]> Joe <[email protected]>
66
Jane Doe <[email protected]> <jane@laptop.(none)>
77
Jane Doe <[email protected]> <jane@desktop.(none)>
88
9+
<[email protected]> Jane <Jane@ipad.(none)>

Diff for: gix-mailmap/tests/parse/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fn a_typical_mailmap() {
3131
Entry::change_name_and_email_by_email("Jane Doe", "[email protected]", "jane@laptop.(none)"),
3232
Entry::change_name_and_email_by_email("Jane Doe", "[email protected]", "jane@desktop.(none)"),
3333
Entry::change_name_and_email_by_name_and_email("Jane Doe", "[email protected]", "Jane", "[email protected]"),
34+
Entry::change_email_by_name_and_email("[email protected]", "Jane", "Jane@ipad.(none)"),
3435
]
3536
);
3637
}
@@ -76,8 +77,8 @@ fn valid_entries() {
7677
Entry::change_name_and_email_by_email("proper name", "proper email", "commit-email")
7778
);
7879
assert_eq!(
79-
line(" proper name <proper email>\tcommit name\t<commit-email>\t"),
80-
Entry::change_name_and_email_by_name_and_email("proper name", "proper email", "commit name", "commit-email")
80+
line("<proper-email> commit name <commit-email>"),
81+
Entry::change_email_by_name_and_email("proper-email", "commit name", "commit-email")
8182
);
8283
}
8384

Diff for: gix-mailmap/tests/snapshot/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ fn try_resolve() {
3131
Some(signature("Jane Doe", "[email protected]")),
3232
"name and email can be mapped specifically, case insensitive matching of name"
3333
);
34+
assert_eq!(
35+
snapshot.resolve(signature("janE", "jane@ipad.(none)").to_ref()),
36+
signature("janE", "[email protected]"),
37+
"an email can be mapped by name and email specifically, both match case-insensitively"
38+
);
3439

3540
let sig = signature("Jane", "[email protected]");
3641
assert_eq!(snapshot.try_resolve(sig.to_ref()), None, "unmatched email");
@@ -49,7 +54,7 @@ fn try_resolve() {
4954
);
5055
assert_eq!(snapshot.resolve(sig.to_ref()), sig);
5156

52-
assert_eq!(snapshot.entries().len(), 5);
57+
assert_eq!(snapshot.entries().len(), 6);
5358
}
5459

5560
#[test]

Diff for: src/plumbing/main.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,17 @@ pub fn main() -> Result<()> {
12161216
core::repository::mailmap::entries(repository(Mode::Lenient)?, format, out, err)
12171217
},
12181218
),
1219+
mailmap::Subcommands::Check { contacts } => prepare_and_run(
1220+
"mailmap-check",
1221+
trace,
1222+
verbose,
1223+
progress,
1224+
progress_keep_open,
1225+
None,
1226+
move |_progress, out, err| {
1227+
core::repository::mailmap::check(repository(Mode::Lenient)?, format, contacts, out, err)
1228+
},
1229+
),
12191230
},
12201231
Subcommands::Attributes(cmd) => match cmd {
12211232
attributes::Subcommands::Query { statistics, pathspec } => prepare_and_run(

Diff for: src/plumbing/options/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -512,10 +512,17 @@ pub mod remote {
512512
}
513513

514514
pub mod mailmap {
515+
use gix::bstr::BString;
516+
515517
#[derive(Debug, clap::Subcommand)]
516518
pub enum Subcommands {
517519
/// Print all entries in configured mailmaps, inform about errors as well.
518520
Entries,
521+
/// Print the canonical form of contacts according to the configured mailmaps.
522+
Check {
523+
/// One or more `Name <email>` or `<email>` to pass through the mailmap.
524+
contacts: Vec<BString>,
525+
},
519526
}
520527
}
521528

0 commit comments

Comments
 (0)