Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions gitoxide-core/src/repository/mailmap.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use anyhow::bail;
use gix::bstr::{BString, ByteSlice};
use std::io;

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

Ok(())
}

pub fn check(
repo: gix::Repository,
format: OutputFormat,
contacts: Vec<BString>,
mut out: impl io::Write,
mut err: impl io::Write,
) -> anyhow::Result<()> {
if format != OutputFormat::Human {
bail!("Only human output is supported right now");
}
if contacts.is_empty() {
bail!("specify at least one contact to run through the mailmap")
}

let mut mailmap = gix::mailmap::Snapshot::default();
if let Err(err) = repo.open_mailmap_into(&mut mailmap) {
bail!(err);
}

let mut buf = Vec::new();
for contact in contacts {
let actor = match gix::actor::IdentityRef::from_bytes::<()>(&contact) {
Ok(a) => a,
Err(_) => {
let Some(email) = contact
.trim_start()
.strip_prefix(b"<")
.and_then(|rest| rest.trim_end().strip_suffix(b">"))
else {
writeln!(err, "Failed to parse contact '{contact}' - skipping")?;
continue;
};
gix::actor::IdentityRef {
name: "".into(),
email: email.into(),
}
}
};
let resolved = mailmap.resolve_cow(gix::actor::SignatureRef {
name: actor.name,
email: actor.email,
time: Default::default(),
});
let resolved = gix::actor::IdentityRef {
name: resolved.name.as_ref(),
email: resolved.email.as_ref(),
};
buf.clear();
resolved.write_to(&mut buf)?;

out.write_all(&buf)?;
out.write_all(b"\n")?;
}
Ok(())
}
19 changes: 17 additions & 2 deletions gix-mailmap/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,37 @@ impl<'a> Entry<'a> {
/// Constructors indicating what kind of mapping is created.
///
/// Only these combinations of values are valid.
#[allow(missing_docs)]
impl<'a> Entry<'a> {
/// An entry that changes the name by an email.
pub fn change_name_by_email(proper_name: impl Into<&'a BStr>, commit_email: impl Into<&'a BStr>) -> Self {
Entry {
new_name: Some(proper_name.into()),
old_email: commit_email.into(),
..Default::default()
}
}
/// An entry that changes the email by an email.
pub fn change_email_by_email(proper_email: impl Into<&'a BStr>, commit_email: impl Into<&'a BStr>) -> Self {
Entry {
new_email: Some(proper_email.into()),
old_email: commit_email.into(),
..Default::default()
}
}
/// An entry that changes the email by a name and email.
pub fn change_email_by_name_and_email(
proper_email: impl Into<&'a BStr>,
commit_name: impl Into<&'a BStr>,
commit_email: impl Into<&'a BStr>,
) -> Self {
Entry {
new_email: Some(proper_email.into()),
old_email: commit_email.into(),
old_name: Some(commit_name.into()),
..Default::default()
}
}
/// An entry that changes a name and the email by an email.
pub fn change_name_and_email_by_email(
proper_name: impl Into<&'a BStr>,
proper_email: impl Into<&'a BStr>,
Expand All @@ -53,7 +68,7 @@ impl<'a> Entry<'a> {
..Default::default()
}
}

/// An entry that changes a name and email by a name and email.
pub fn change_name_and_email_by_name_and_email(
proper_name: impl Into<&'a BStr>,
proper_email: impl Into<&'a BStr>,
Expand Down
3 changes: 3 additions & 0 deletions gix-mailmap/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ fn parse_line(line: &BStr, line_number: usize) -> Result<Entry<'_>, Error> {
(Some(proper_name), Some(proper_email), Some(commit_name), Some(commit_email)) => {
Entry::change_name_and_email_by_name_and_email(proper_name, proper_email, commit_name, commit_email)
}
(None, Some(proper_email), Some(commit_name), Some(commit_email)) => {
Entry::change_email_by_name_and_email(proper_email, commit_name, commit_email)
}
_ => {
return Err(Error::Malformed {
line_number,
Expand Down
1 change: 1 addition & 0 deletions gix-mailmap/tests/fixtures/typical.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Joe R. Developer <joe@example.com> Joe <bugs@example.com>
Jane Doe <jane@example.com> <jane@laptop.(none)>
Jane Doe <jane@example.com> <jane@desktop.(none)>
Jane Doe <jane@example.com> Jane <bugs@example.com>
<jane@example.com> Jane <Jane@ipad.(none)>
5 changes: 3 additions & 2 deletions gix-mailmap/tests/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn a_typical_mailmap() {
Entry::change_name_and_email_by_email("Jane Doe", "jane@example.com", "jane@laptop.(none)"),
Entry::change_name_and_email_by_email("Jane Doe", "jane@example.com", "jane@desktop.(none)"),
Entry::change_name_and_email_by_name_and_email("Jane Doe", "jane@example.com", "Jane", "bugs@example.com"),
Entry::change_email_by_name_and_email("jane@example.com", "Jane", "Jane@ipad.(none)"),
]
);
}
Expand Down Expand Up @@ -76,8 +77,8 @@ fn valid_entries() {
Entry::change_name_and_email_by_email("proper name", "proper email", "commit-email")
);
assert_eq!(
line(" proper name <proper email>\tcommit name\t<commit-email>\t"),
Entry::change_name_and_email_by_name_and_email("proper name", "proper email", "commit name", "commit-email")
line("<proper-email> commit name <commit-email>"),
Entry::change_email_by_name_and_email("proper-email", "commit name", "commit-email")
);
}

Expand Down
7 changes: 6 additions & 1 deletion gix-mailmap/tests/snapshot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ fn try_resolve() {
Some(signature("Jane Doe", "jane@example.com")),
"name and email can be mapped specifically, case insensitive matching of name"
);
assert_eq!(
snapshot.resolve(signature("janE", "jane@ipad.(none)").to_ref()),
signature("janE", "jane@example.com"),
"an email can be mapped by name and email specifically, both match case-insensitively"
);

let sig = signature("Jane", "other@example.com");
assert_eq!(snapshot.try_resolve(sig.to_ref()), None, "unmatched email");
Expand All @@ -49,7 +54,7 @@ fn try_resolve() {
);
assert_eq!(snapshot.resolve(sig.to_ref()), sig);

assert_eq!(snapshot.entries().len(), 5);
assert_eq!(snapshot.entries().len(), 6);
}

#[test]
Expand Down
11 changes: 11 additions & 0 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,17 @@ pub fn main() -> Result<()> {
core::repository::mailmap::entries(repository(Mode::Lenient)?, format, out, err)
},
),
mailmap::Subcommands::Check { contacts } => prepare_and_run(
"mailmap-check",
trace,
verbose,
progress,
progress_keep_open,
None,
move |_progress, out, err| {
core::repository::mailmap::check(repository(Mode::Lenient)?, format, contacts, out, err)
},
),
},
Subcommands::Attributes(cmd) => match cmd {
attributes::Subcommands::Query { statistics, pathspec } => prepare_and_run(
Expand Down
7 changes: 7 additions & 0 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,17 @@ pub mod remote {
}

pub mod mailmap {
use gix::bstr::BString;

#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Print all entries in configured mailmaps, inform about errors as well.
Entries,
/// Print the canonical form of contacts according to the configured mailmaps.
Check {
/// One or more `Name <email>` or `<email>` to pass through the mailmap.
contacts: Vec<BString>,
},
}
}

Expand Down