use std::{
borrow::Cow,
fmt::{Display, Formatter},
};
use bstr::BStr;
use gix_hashtable::HashMap;
#[derive(Debug, Clone)]
pub struct Outcome<'name> {
pub name: Option<Cow<'name, BStr>>,
pub id: gix_hash::ObjectId,
pub depth: u32,
pub name_by_oid: HashMap<gix_hash::ObjectId, Cow<'name, BStr>>,
pub commits_seen: 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: Option<Cow<'a, BStr>>,
pub id: gix_hash::ObjectId,
pub hex_len: usize,
pub depth: u32,
pub long: bool,
pub dirty_suffix: Option<String>,
}
impl Format<'_> {
pub fn is_exact_match(&self) -> bool {
self.depth == 0
}
pub fn long(&mut self, long: bool) -> &mut Self {
self.long = long;
self
}
}
impl Display for Format<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(name) = self.name.as_deref() {
if !self.long && self.is_exact_match() {
name.fmt(f)?;
} else {
write!(f, "{}-{}-g{}", name, self.depth, self.id.to_hex_with_len(self.hex_len))?;
}
} else {
self.id.to_hex_with_len(self.hex_len).fmt(f)?;
}
if let Some(suffix) = &self.dirty_suffix {
write!(f, "-{suffix}")?;
}
Ok(())
}
}
pub type Flags = u32;
const MAX_CANDIDATES: usize = std::mem::size_of::<Flags>() * 8;
#[derive(Clone, Debug)]
pub struct Options<'name> {
pub name_by_oid: HashMap<gix_hash::ObjectId, Cow<'name, BStr>>,
pub max_candidates: usize,
pub fallback_to_oid: bool,
pub first_parent: bool,
}
impl Default for Options<'_> {
fn default() -> Self {
Options {
max_candidates: 10, name_by_oid: Default::default(),
fallback_to_oid: false,
first_parent: false,
}
}
}
pub type Error = gix_error::Message;
pub(crate) mod function {
use std::{borrow::Cow, cmp::Ordering};
use bstr::BStr;
use gix_error::{message, Exn, ResultExt};
use gix_hash::oid;
use super::{Error, Outcome};
use crate::{
describe::{CommitTime, Flags, Options, MAX_CANDIDATES},
Graph, PriorityQueue,
};
pub fn describe<'name>(
commit: &oid,
graph: &mut Graph<'_, '_, Flags>,
Options {
name_by_oid,
mut max_candidates,
fallback_to_oid,
first_parent,
}: Options<'name>,
) -> Result<Option<Outcome<'name>>, Exn<Error>> {
let _span = gix_trace::coarse!(
"gix_revision::describe()",
commit = %commit,
name_count = name_by_oid.len(),
max_candidates,
first_parent
);
max_candidates = max_candidates.min(MAX_CANDIDATES);
if let Some(name) = name_by_oid.get(commit) {
return Ok(Some(Outcome {
name: name.clone().into(),
id: commit.to_owned(),
depth: 0,
name_by_oid,
commits_seen: 0,
}));
}
if max_candidates == 0 || name_by_oid.is_empty() {
return if fallback_to_oid {
Ok(Some(Outcome {
id: commit.to_owned(),
name: None,
name_by_oid,
depth: 0,
commits_seen: 0,
}))
} else {
Ok(None)
};
}
let mut queue = PriorityQueue::from_iter(Some((u32::MAX, commit.to_owned())));
let mut candidates = Vec::new();
let mut commits_seen = 0;
let mut gave_up_on_commit = None;
graph.clear();
graph.insert(commit.to_owned(), 0u32);
while let Some(commit) = queue.pop_value() {
commits_seen += 1;
let flags = if let Some(name) = name_by_oid.get(&commit) {
if candidates.len() < max_candidates {
let identity_bit = 1 << candidates.len();
candidates.push(Candidate {
name: name.clone(),
commits_in_its_future: commits_seen - 1,
identity_bit,
order: candidates.len(),
});
let flags = graph.get_mut(&commit).expect("inserted");
*flags |= identity_bit;
*flags
} else {
gave_up_on_commit = Some(commit);
break;
}
} else {
graph[&commit]
};
for candidate in candidates
.iter_mut()
.filter(|c| (flags & c.identity_bit) != c.identity_bit)
{
candidate.commits_in_its_future += 1;
}
if queue.is_empty() && !candidates.is_empty() {
let mut shortest_depth = Flags::MAX;
let mut best_candidates_at_same_depth = 0_u32;
for candidate in &candidates {
match candidate.commits_in_its_future.cmp(&shortest_depth) {
Ordering::Less => {
shortest_depth = candidate.commits_in_its_future;
best_candidates_at_same_depth = candidate.identity_bit;
}
Ordering::Equal => {
best_candidates_at_same_depth |= candidate.identity_bit;
}
Ordering::Greater => {}
}
}
if (flags & best_candidates_at_same_depth) == best_candidates_at_same_depth {
break;
}
}
parents_by_date_onto_queue_and_track_names(graph, &mut queue, commit, flags, first_parent)?;
}
if candidates.is_empty() {
return if fallback_to_oid {
Ok(Some(Outcome {
id: commit.to_owned(),
name: None,
name_by_oid,
depth: 0,
commits_seen,
}))
} else {
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))
});
if let Some(commit_id) = gave_up_on_commit {
queue.insert(u32::MAX, commit_id);
commits_seen -= 1;
}
commits_seen += finish_depth_computation(
queue,
graph,
candidates.first_mut().expect("at least one candidate"),
first_parent,
)?;
Ok(candidates.into_iter().next().map(|c| Outcome {
name: c.name.into(),
id: commit.to_owned(),
depth: c.commits_in_its_future,
name_by_oid,
commits_seen,
}))
}
fn parents_by_date_onto_queue_and_track_names(
graph: &mut Graph<'_, '_, Flags>,
queue: &mut PriorityQueue<CommitTime, gix_hash::ObjectId>,
commit: gix_hash::ObjectId,
commit_flags: Flags,
first_parent: bool,
) -> Result<(), Exn<Error>> {
graph
.insert_parents(
&commit,
&mut |parent_id, parent_commit_date| {
queue.insert(parent_commit_date as u32, parent_id);
commit_flags
},
&mut |_parent_id, flags| *flags |= commit_flags,
first_parent,
)
.or_raise(|| message!("could not insert parents of commit {} into graph", commit.to_hex()))?;
Ok(())
}
fn finish_depth_computation(
mut queue: PriorityQueue<CommitTime, gix_hash::ObjectId>,
graph: &mut Graph<'_, '_, Flags>,
best_candidate: &mut Candidate<'_>,
first_parent: bool,
) -> Result<u32, Exn<Error>> {
let mut commits_seen = 0;
while let Some(commit) = queue.pop_value() {
commits_seen += 1;
let flags = graph[&commit];
if (flags & best_candidate.identity_bit) == best_candidate.identity_bit {
if queue
.iter_unordered()
.all(|id| (graph[id] & best_candidate.identity_bit) == best_candidate.identity_bit)
{
break;
}
} else {
best_candidate.commits_in_its_future += 1;
}
parents_by_date_onto_queue_and_track_names(graph, &mut queue, commit, flags, first_parent)?;
}
Ok(commits_seen)
}
#[derive(Debug)]
struct Candidate<'a> {
name: Cow<'a, BStr>,
commits_in_its_future: Flags,
identity_bit: Flags,
order: usize,
}
}
type CommitTime = u32;