gitoxide_core/repository/
mailmap.rs1use anyhow::bail;
2use gix::bstr::{BString, ByteSlice};
3use std::io;
4
5#[cfg(feature = "serde")]
6use gix::mailmap::Entry;
7
8use crate::OutputFormat;
9
10#[cfg(feature = "serde")]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12struct JsonEntry {
13 new_name: Option<String>,
14 new_email: Option<String>,
15 old_name: Option<String>,
16 old_email: String,
17}
18
19#[cfg(feature = "serde")]
20impl<'a> From<Entry<'a>> for JsonEntry {
21 fn from(v: Entry<'a>) -> Self {
22 use gix::bstr::ByteSlice;
23 JsonEntry {
24 new_name: v.new_name().map(|s| s.to_str_lossy().into_owned()),
25 new_email: v.new_email().map(|s| s.to_str_lossy().into_owned()),
26 old_name: v.old_name().map(|s| s.to_str_lossy().into_owned()),
27 old_email: v.old_email().to_str_lossy().into_owned(),
28 }
29 }
30}
31
32pub fn entries(
33 repo: gix::Repository,
34 format: OutputFormat,
35 #[cfg_attr(not(feature = "serde"), allow(unused_variables))] out: impl io::Write,
36 mut err: impl io::Write,
37) -> anyhow::Result<()> {
38 if format == OutputFormat::Human {
39 writeln!(err, "Defaulting to JSON as human format isn't implemented").ok();
40 }
41
42 let mut mailmap = gix::mailmap::Snapshot::default();
43 if let Err(e) = repo.open_mailmap_into(&mut mailmap) {
44 writeln!(err, "Error while loading mailmap, the first error is: {e}").ok();
45 }
46
47 #[cfg(feature = "serde")]
48 serde_json::to_writer_pretty(out, &mailmap.iter().map(JsonEntry::from).collect::<Vec<_>>())?;
49
50 Ok(())
51}
52
53pub fn check(
54 repo: gix::Repository,
55 format: OutputFormat,
56 contacts: Vec<BString>,
57 mut out: impl io::Write,
58 mut err: impl io::Write,
59) -> anyhow::Result<()> {
60 if format != OutputFormat::Human {
61 bail!("Only human output is supported right now");
62 }
63 if contacts.is_empty() {
64 bail!("specify at least one contact to run through the mailmap")
65 }
66
67 let mut mailmap = gix::mailmap::Snapshot::default();
68 if let Err(err) = repo.open_mailmap_into(&mut mailmap) {
69 bail!(err);
70 }
71
72 let mut buf = Vec::new();
73 for contact in contacts {
74 let actor = match gix::actor::IdentityRef::from_bytes::<()>(&contact) {
75 Ok(a) => a,
76 Err(_) => {
77 let Some(email) = contact
78 .trim_start()
79 .strip_prefix(b"<")
80 .and_then(|rest| rest.trim_end().strip_suffix(b">"))
81 else {
82 writeln!(err, "Failed to parse contact '{contact}' - skipping")?;
83 continue;
84 };
85 gix::actor::IdentityRef {
86 name: "".into(),
87 email: email.into(),
88 }
89 }
90 };
91 let resolved = mailmap.resolve_cow(gix::actor::SignatureRef {
92 name: actor.name,
93 email: actor.email,
94 time: Default::default(),
95 });
96 let resolved = gix::actor::IdentityRef {
97 name: resolved.name.as_ref(),
98 email: resolved.email.as_ref(),
99 };
100 buf.clear();
101 resolved.write_to(&mut buf)?;
102
103 out.write_all(&buf)?;
104 out.write_all(b"\n")?;
105 }
106 Ok(())
107}