use std::{ borrow::Cow, fmt::{Display, Formatter}, }; use git_object::bstr::BStr; #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Outcome<'a> { pub name: Cow<'a, BStr>, pub id: git_hash::ObjectId, pub depth: u32, } impl<'a> Outcome<'a> { pub fn into_format(self, hex_len: usize) -> Format<'a> { Format { name: self.name, id: self.id, hex_len, depth: self.depth, long: false, dirty_suffix: None, } } } #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Format<'a> { pub name: Cow<'a, BStr>, pub id: git_hash::ObjectId, pub hex_len: usize, pub depth: u32, pub long: bool, pub dirty_suffix: Option, } impl<'a> Format<'a> { pub fn is_exact_match(&self) -> bool { self.depth == 0 } pub fn long(&mut self) -> &mut Self { self.long = true; self } pub fn short(&mut self) -> &mut Self { self.long = false; self } } impl<'a> Display for Format<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if !self.long && self.is_exact_match() { self.name.fmt(f)?; } else { write!( f, "{}-{}-g{}", self.name, self.depth, self.id.to_hex_with_len(self.hex_len) )?; } if let Some(suffix) = &self.dirty_suffix { write!(f, "-{}", suffix)?; } Ok(()) } } pub(crate) mod function { use std::{ borrow::Cow, collections::{hash_map, HashMap, VecDeque}, iter::FromIterator, }; use git_hash::{oid, ObjectId}; use git_object::{bstr::BStr, CommitRefIter}; use super::Outcome; #[allow(clippy::result_unit_err)] pub fn describe<'a, Find, E>( commit: &oid, mut find: Find, name_set: &HashMap>, ) -> Result>, E> where Find: for<'b> FnMut(&oid, &'b mut Vec) -> Result, E>, E: std::error::Error + Send + Sync + 'static, { if let Some(name) = name_set.get(commit) { return Ok(Some(Outcome { name: name.clone(), id: commit.to_owned(), depth: 0, })); } let mut buf = Vec::new(); let mut parent_buf = Vec::new(); let mut parents = Vec::new(); let mut queue = VecDeque::from_iter(Some(commit.to_owned())); let mut candidates = Vec::new(); let mut seen_commits = 0; let mut _gave_up_on_commit = None; let mut seen = hash_hasher::HashedMap::default(); seen.insert(commit.to_owned(), 0u32); const MAX_CANDIDATES: usize = std::mem::size_of::() * 8; while let Some(commit) = queue.pop_front() { seen_commits += 1; if let Some(name) = name_set.get(&commit) { if candidates.len() < MAX_CANDIDATES { let identity_bit = 1 << candidates.len(); candidates.push(Candidate { name: name.clone(), commits_in_its_future: seen_commits - 1, identity_bit, order: candidates.len(), }); *seen.get_mut(&commit).expect("inserted") |= identity_bit; } else { _gave_up_on_commit = Some(commit); break; } } let flags = seen[&commit]; for candidate in candidates .iter_mut() .filter(|c| (flags & c.identity_bit) != c.identity_bit) { candidate.commits_in_its_future += 1; } let commit_iter = find(&commit, &mut buf)?; parents.clear(); for token in commit_iter { match token { Ok(git_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(git_object::commit::ref_iter::Token::Parent { id: parent_id }) => { match seen.entry(parent_id) { hash_map::Entry::Vacant(entry) => { let parent = find(&parent_id, &mut parent_buf)?; // TODO: figure out if not having a date is a hard error. let parent_commit_date = parent .committer() .map(|committer| committer.time.seconds_since_unix_epoch) .unwrap_or_default(); entry.insert(flags); parents.push((parent_id, parent_commit_date)); } hash_map::Entry::Occupied(mut entry) => { *entry.get_mut() |= flags; } } } Ok(_unused_token) => break, Err(_err) => todo!("return a decode error"), } } if !parents.is_empty() { parents.sort_by(|a, b| a.1.cmp(&b.1).reverse()); seen.extend(parents.iter().map(|e| (e.0, flags))); queue.extend(parents.iter().map(|e| e.0)); } } if candidates.is_empty() { return Ok(None); } candidates.sort_by(|a, b| { a.commits_in_its_future .cmp(&b.commits_in_its_future) .then_with(|| a.order.cmp(&b.order)) }); Ok(candidates.into_iter().next().map(|c| Outcome { name: c.name, id: commit.to_owned(), depth: c.commits_in_its_future, })) } type Flags = u32; #[derive(Debug)] struct Candidate<'a> { name: Cow<'a, BStr>, commits_in_its_future: Flags, /// A single bit identifying this candidate uniquely in a bitset identity_bit: Flags, /// The order at which we found the candidate, first one has order = 0 order: usize, } }