| use bstr::ByteSlice; |
| use gix_actor::SignatureRef; |
| |
| use crate::Snapshot; |
| |
| mod signature; |
| pub use signature::{ResolvedSignature, Signature}; |
| |
| mod util; |
| use util::EncodedStringRef; |
| |
| mod entry; |
| pub(crate) use entry::EmailEntry; |
| |
| impl Snapshot { |
| /// Create a new snapshot from the given bytes buffer, ignoring all parse errors that may occur on a line-by-line basis. |
| /// |
| /// This is similar to what git does. |
| pub fn from_bytes(buf: &[u8]) -> Self { |
| Self::new(crate::parse_ignore_errors(buf)) |
| } |
| |
| /// Create a new instance from `entries`. |
| /// |
| /// These can be obtained using [`crate::parse()`]. |
| pub fn new<'a>(entries: impl IntoIterator<Item = crate::Entry<'a>>) -> Self { |
| let mut snapshot = Self::default(); |
| snapshot.merge(entries); |
| snapshot |
| } |
| |
| /// Merge the given `entries` into this instance, possibly overwriting existing mappings with |
| /// new ones should they collide. |
| pub fn merge<'a>(&mut self, entries: impl IntoIterator<Item = crate::Entry<'a>>) -> &mut Self { |
| for entry in entries { |
| let old_email: EncodedStringRef<'_> = entry.old_email.into(); |
| assert!( |
| entry.new_name.is_some() || entry.new_email.is_some(), |
| "BUG: encountered entry without any mapped/new name or email." |
| ); |
| match self |
| .entries_by_old_email |
| .binary_search_by(|e| e.old_email.cmp_ref(old_email)) |
| { |
| Ok(pos) => self.entries_by_old_email[pos].merge(entry), |
| Err(insert_pos) => { |
| self.entries_by_old_email.insert(insert_pos, entry.into()); |
| } |
| }; |
| } |
| self |
| } |
| |
| /// Transform our acceleration structure into a list of entries. |
| /// |
| /// Note that the order is different from how they were obtained initially, and are explicitly ordered by |
| /// (`old_email`, `old_name`). |
| pub fn entries(&self) -> Vec<crate::Entry<'_>> { |
| let mut out = Vec::with_capacity(self.entries_by_old_email.len()); |
| for entry in &self.entries_by_old_email { |
| if entry.new_email.is_some() || entry.new_name.is_some() { |
| out.push(crate::Entry { |
| new_name: entry.new_name.as_ref().map(|b| b.as_bstr()), |
| new_email: entry.new_email.as_ref().map(|b| b.as_bstr()), |
| old_name: None, |
| old_email: entry.old_email.as_bstr(), |
| }); |
| } |
| |
| for name_entry in &entry.entries_by_old_name { |
| out.push(crate::Entry { |
| new_name: name_entry.new_name.as_ref().map(|b| b.as_bstr()), |
| new_email: name_entry.new_email.as_ref().map(|b| b.as_bstr()), |
| old_name: name_entry.old_name.as_bstr().into(), |
| old_email: entry.old_email.as_bstr(), |
| }); |
| } |
| } |
| out |
| } |
| |
| /// Try to resolve `signature` by its contained email and name and provide resolved/mapped names as reference. |
| /// Return `None` if no such mapping was found. |
| /// |
| /// Note that opposed to what git seems to do, we also normalize the case of email addresses to match the one |
| /// given in the mailmap. That is, if `[email protected]` is the current email, it will be matched and replaced with |
| /// `[email protected]`. This leads to better mapping results and saves entries in the mailmap. |
| /// |
| /// This is the fastest possible lookup as there is no allocation. |
| pub fn try_resolve_ref(&self, signature: gix_actor::SignatureRef<'_>) -> Option<ResolvedSignature<'_>> { |
| let email: EncodedStringRef<'_> = signature.email.into(); |
| let pos = self |
| .entries_by_old_email |
| .binary_search_by(|e| e.old_email.cmp_ref(email)) |
| .ok()?; |
| let entry = &self.entries_by_old_email[pos]; |
| |
| let name: EncodedStringRef<'_> = signature.name.into(); |
| |
| match entry.entries_by_old_name.binary_search_by(|e| e.old_name.cmp_ref(name)) { |
| Ok(pos) => { |
| let name_entry = &entry.entries_by_old_name[pos]; |
| ResolvedSignature::try_new( |
| name_entry.new_email.as_ref(), |
| entry.old_email.as_bstr(), |
| signature.email, |
| name_entry.new_name.as_ref(), |
| ) |
| } |
| Err(_) => ResolvedSignature::try_new( |
| entry.new_email.as_ref(), |
| entry.old_email.as_bstr(), |
| signature.email, |
| entry.new_name.as_ref(), |
| ), |
| } |
| } |
| |
| /// Try to resolve `signature` by its contained email and name and provide resolved/mapped names as owned signature, |
| /// with the mapped name and/or email replaced accordingly. |
| /// |
| /// Return `None` if no such mapping was found. |
| pub fn try_resolve(&self, signature: gix_actor::SignatureRef<'_>) -> Option<gix_actor::Signature> { |
| self.try_resolve_ref(signature) |
| .map(|new| enriched_signature(signature, new).into()) |
| } |
| |
| /// Like [`try_resolve()`][Snapshot::try_resolve()], but always returns an owned signature, which might be a copy |
| /// of `signature` if no mapping was found. |
| /// |
| /// Note that this method will always allocate. |
| pub fn resolve(&self, signature: gix_actor::SignatureRef<'_>) -> gix_actor::Signature { |
| self.try_resolve(signature).unwrap_or_else(|| signature.to_owned()) |
| } |
| |
| /// Like [`try_resolve()`][Snapshot::try_resolve()], but always returns a special copy-on-write signature, which contains |
| /// changed names or emails as `Cow::Owned`, or `Cow::Borrowed` if no mapping was found. |
| pub fn resolve_cow<'a>(&self, signature: gix_actor::SignatureRef<'a>) -> Signature<'a> { |
| self.try_resolve_ref(signature) |
| .map_or_else(|| signature.into(), |new| enriched_signature(signature, new)) |
| } |
| } |
| |
| fn enriched_signature<'a>( |
| SignatureRef { name, email, time }: SignatureRef<'a>, |
| new: ResolvedSignature<'_>, |
| ) -> Signature<'a> { |
| match (new.email, new.name) { |
| (Some(new_email), Some(new_name)) => Signature { |
| email: new_email.to_owned().into(), |
| name: new_name.to_owned().into(), |
| time, |
| }, |
| (Some(new_email), None) => Signature { |
| email: new_email.to_owned().into(), |
| name: name.into(), |
| time, |
| }, |
| (None, Some(new_name)) => Signature { |
| email: email.into(), |
| name: new_name.to_owned().into(), |
| time, |
| }, |
| (None, None) => unreachable!("BUG: ResolvedSignatures don't exist here when nothing is set"), |
| } |
| } |