From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- vendor/gix/src/commit.rs | 238 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 vendor/gix/src/commit.rs (limited to 'vendor/gix/src/commit.rs') diff --git a/vendor/gix/src/commit.rs b/vendor/gix/src/commit.rs new file mode 100644 index 000000000..10fa6f675 --- /dev/null +++ b/vendor/gix/src/commit.rs @@ -0,0 +1,238 @@ +//! + +/// An empty array of a type usable with the `gix::easy` API to help declaring no parents should be used +pub const NO_PARENT_IDS: [gix_hash::ObjectId; 0] = []; + +/// The error returned by [`commit(…)`][crate::Repository::commit()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + ParseTime(#[from] crate::config::time::Error), + #[error("Committer identity is not configured")] + CommitterMissing, + #[error("Author identity is not configured")] + AuthorMissing, + #[error(transparent)] + ReferenceNameValidation(#[from] gix_ref::name::Error), + #[error(transparent)] + WriteObject(#[from] crate::object::write::Error), + #[error(transparent)] + ReferenceEdit(#[from] crate::reference::edit::Error), +} + +/// +pub mod describe { + use std::borrow::Cow; + + use gix_hash::ObjectId; + use gix_hashtable::HashMap; + use gix_odb::Find; + + use crate::{bstr::BStr, ext::ObjectIdExt, Repository}; + + /// The result of [try_resolve()][Platform::try_resolve()]. + pub struct Resolution<'repo> { + /// The outcome of the describe operation. + pub outcome: gix_revision::describe::Outcome<'static>, + /// The id to describe. + pub id: crate::Id<'repo>, + } + + impl<'repo> Resolution<'repo> { + /// Turn this instance into something displayable + pub fn format(self) -> Result, Error> { + let prefix = self.id.shorten()?; + Ok(self.outcome.into_format(prefix.hex_len())) + } + } + + /// The error returned by [try_format()][Platform::try_format()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Describe(#[from] gix_revision::describe::Error), + #[error("Could not produce an unambiguous shortened id for formatting.")] + ShortId(#[from] crate::id::shorten::Error), + #[error(transparent)] + RefIter(#[from] crate::reference::iter::Error), + #[error(transparent)] + RefIterInit(#[from] crate::reference::iter::init::Error), + } + + /// A selector to choose what kind of references should contribute to names. + #[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] + pub enum SelectRef { + /// Only use annotated tags for names. + AnnotatedTags, + /// Use all tags for names, annotated or plain reference. + AllTags, + /// Use all references, including local branch names. + AllRefs, + } + + impl SelectRef { + fn names(&self, repo: &Repository) -> Result>, Error> { + let platform = repo.references()?; + + Ok(match self { + SelectRef::AllTags | SelectRef::AllRefs => { + let mut refs: Vec<_> = match self { + SelectRef::AllRefs => platform.all()?, + SelectRef::AllTags => platform.tags()?, + _ => unreachable!(), + } + .filter_map(Result::ok) + .filter_map(|mut r: crate::Reference<'_>| { + let target_id = r.target().try_id().map(ToOwned::to_owned); + let peeled_id = r.peel_to_id_in_place().ok()?; + let (prio, tag_time) = match target_id { + Some(target_id) if peeled_id != *target_id => { + let tag = repo.find_object(target_id).ok()?.try_into_tag().ok()?; + (1, tag.tagger().ok()??.time.seconds_since_unix_epoch) + } + _ => (0, 0), + }; + ( + peeled_id.inner, + prio, + tag_time, + Cow::from(r.inner.name.shorten().to_owned()), + ) + .into() + }) + .collect(); + // By priority, then by time ascending, then lexicographically. + // More recent entries overwrite older ones due to collection into hashmap. + refs.sort_by( + |(_a_peeled_id, a_prio, a_time, a_name), (_b_peeled_id, b_prio, b_time, b_name)| { + a_prio + .cmp(b_prio) + .then_with(|| a_time.cmp(b_time)) + .then_with(|| b_name.cmp(a_name)) + }, + ); + refs.into_iter().map(|(a, _, _, b)| (a, b)).collect() + } + SelectRef::AnnotatedTags => { + let mut peeled_commits_and_tag_date: Vec<_> = platform + .tags()? + .filter_map(Result::ok) + .filter_map(|r: crate::Reference<'_>| { + // TODO: we assume direct refs for tags, which is the common case, but it doesn't have to be + // so rather follow symrefs till the first object and then peel tags after the first object was found. + let tag = r.try_id()?.object().ok()?.try_into_tag().ok()?; + let tag_time = tag + .tagger() + .ok() + .and_then(|s| s.map(|s| s.time.seconds_since_unix_epoch)) + .unwrap_or(0); + let commit_id = tag.target_id().ok()?.object().ok()?.try_into_commit().ok()?.id; + Some((commit_id, tag_time, Cow::::from(r.name().shorten().to_owned()))) + }) + .collect(); + // Sort by time ascending, then lexicographically. + // More recent entries overwrite older ones due to collection into hashmap. + peeled_commits_and_tag_date.sort_by(|(_a_id, a_time, a_name), (_b_id, b_time, b_name)| { + a_time.cmp(b_time).then_with(|| b_name.cmp(a_name)) + }); + peeled_commits_and_tag_date + .into_iter() + .map(|(a, _, c)| (a, c)) + .collect() + } + }) + } + } + + impl Default for SelectRef { + fn default() -> Self { + SelectRef::AnnotatedTags + } + } + + /// A support type to allow configuring a `git describe` operation + pub struct Platform<'repo> { + pub(crate) id: gix_hash::ObjectId, + pub(crate) repo: &'repo crate::Repository, + pub(crate) select: SelectRef, + pub(crate) first_parent: bool, + pub(crate) id_as_fallback: bool, + pub(crate) max_candidates: usize, + } + + impl<'repo> Platform<'repo> { + /// Configure which names to `select` from which describe can chose. + pub fn names(mut self, select: SelectRef) -> Self { + self.select = select; + self + } + + /// If true, shorten the graph traversal time by just traversing the first parent of merge commits. + pub fn traverse_first_parent(mut self, first_parent: bool) -> Self { + self.first_parent = first_parent; + self + } + + /// Only consider the given amount of candidates, instead of the default of 10. + pub fn max_candidates(mut self, candidates: usize) -> Self { + self.max_candidates = candidates; + self + } + + /// If true, even if no candidate is available a format will always be produced. + pub fn id_as_fallback(mut self, use_fallback: bool) -> Self { + self.id_as_fallback = use_fallback; + self + } + + /// Try to find a name for the configured commit id using all prior configuration, returning `Some(describe::Format)` + /// if one was found. + /// + /// Note that there will always be `Some(format)` + pub fn try_format(&self) -> Result>, Error> { + self.try_resolve()?.map(|r| r.format()).transpose() + } + + /// Try to find a name for the configured commit id using all prior configuration, returning `Some(Outcome)` + /// if one was found. + /// + /// The outcome provides additional information, but leaves the caller with the burden + /// + /// # Performance + /// + /// It is greatly recommended to [assure an object cache is set][crate::Repository::object_cache_size_if_unset()] + /// to save ~40% of time. + pub fn try_resolve(&self) -> Result>, Error> { + // TODO: dirty suffix with respective dirty-detection + let outcome = gix_revision::describe( + &self.id, + |id, buf| { + Ok(self + .repo + .objects + .try_find(id, buf)? + .and_then(|d| d.try_into_commit_iter())) + }, + gix_revision::describe::Options { + name_by_oid: self.select.names(self.repo)?, + fallback_to_oid: self.id_as_fallback, + first_parent: self.first_parent, + max_candidates: self.max_candidates, + }, + )?; + + Ok(outcome.map(|outcome| crate::commit::describe::Resolution { + outcome, + id: self.id.attach(self.repo), + })) + } + + /// Like [`try_format()`][Platform::try_format()], but turns `id_as_fallback()` on to always produce a format. + pub fn format(&mut self) -> Result, Error> { + self.id_as_fallback = true; + Ok(self.try_format()?.expect("BUG: fallback must always produce a format")) + } + } +} -- cgit v1.2.3