gitoxide_core/repository/
mailmap.rs

1use std::io;
2
3use anyhow::bail;
4use gix::bstr::{BString, ByteSlice};
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}