1#![allow(clippy::empty_docs)]
3
4use std::convert::Infallible;
5
6pub const NO_PARENT_IDS: [gix_hash::ObjectId; 0] = [];
8
9#[derive(Debug, thiserror::Error)]
11#[allow(missing_docs)]
12pub enum Error {
13 #[error(transparent)]
14 ParseTime(#[from] crate::config::time::Error),
15 #[error("Committer identity is not configured")]
16 CommitterMissing,
17 #[error("Author identity is not configured")]
18 AuthorMissing,
19 #[error(transparent)]
20 ReferenceNameValidation(#[from] gix_ref::name::Error),
21 #[error(transparent)]
22 WriteObject(#[from] crate::object::write::Error),
23 #[error(transparent)]
24 ReferenceEdit(#[from] crate::reference::edit::Error),
25}
26
27impl From<std::convert::Infallible> for Error {
28 fn from(_value: Infallible) -> Self {
29 unreachable!("cannot be invoked")
30 }
31}
32
33#[cfg(feature = "revision")]
35pub mod describe {
36 use gix_error::Exn;
37 use gix_hash::ObjectId;
38 use gix_hashtable::HashMap;
39 use std::borrow::Cow;
40
41 use crate::{bstr::BStr, ext::ObjectIdExt, Repository};
42
43 pub struct Resolution<'repo> {
45 pub outcome: gix_revision::describe::Outcome<'static>,
47 pub id: crate::Id<'repo>,
49 }
50
51 impl Resolution<'_> {
52 pub fn format(self) -> Result<gix_revision::describe::Format<'static>, Error> {
54 let prefix = self.id.shorten()?;
55 Ok(self.outcome.into_format(prefix.hex_len()))
56 }
57
58 #[cfg(feature = "status")]
65 pub fn format_with_dirty_suffix(
66 self,
67 dirty_suffix: impl Into<Option<String>>,
68 ) -> Result<gix_revision::describe::Format<'static>, Error> {
69 let prefix = self.id.shorten()?;
70 let mut dirty_suffix = dirty_suffix.into();
71 if dirty_suffix.is_some() && !self.id.repo.is_dirty()? {
72 dirty_suffix.take();
73 }
74 let mut format = self.outcome.into_format(prefix.hex_len());
75 format.dirty_suffix = dirty_suffix;
76 Ok(format)
77 }
78 }
79
80 #[derive(Debug, thiserror::Error)]
82 #[allow(missing_docs)]
83 pub enum Error {
84 #[error(transparent)]
85 OpenCache(#[from] crate::repository::commit_graph_if_enabled::Error),
86 #[error(transparent)]
87 Describe(#[from] gix_revision::describe::Error),
88 #[error("Could not produce an unambiguous shortened id for formatting.")]
89 ShortId(#[from] crate::id::shorten::Error),
90 #[error(transparent)]
91 RefIter(#[from] crate::reference::iter::Error),
92 #[error(transparent)]
93 RefIterInit(#[from] crate::reference::iter::init::Error),
94 #[error(transparent)]
95 #[cfg(feature = "status")]
96 DetermineIsDirty(#[from] crate::status::is_dirty::Error),
97 }
98
99 #[derive(Default, Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
101 pub enum SelectRef {
102 #[default]
104 AnnotatedTags,
105 AllTags,
107 AllRefs,
109 }
110
111 impl SelectRef {
112 fn names(&self, repo: &Repository) -> Result<HashMap<ObjectId, Cow<'static, BStr>>, Error> {
113 let platform = repo.references()?;
114
115 Ok(match self {
116 SelectRef::AllTags | SelectRef::AllRefs => {
117 let mut refs: Vec<_> = match self {
118 SelectRef::AllRefs => platform.all()?,
119 SelectRef::AllTags => platform.tags()?,
120 _ => unreachable!(),
121 }
122 .filter_map(Result::ok)
123 .filter_map(|mut r: crate::Reference<'_>| {
124 let target_id = r.target().try_id().map(ToOwned::to_owned);
125 let peeled_id = r.peel_to_id().ok()?;
126 let (prio, tag_time) = match target_id {
127 Some(target_id) if peeled_id != *target_id => {
128 let tag = repo.find_object(target_id).ok()?.try_into_tag().ok()?;
129 let tag_time = tag.tagger().ok().and_then(|s| s.map(|s| s.seconds())).unwrap_or(0);
130 (1, tag_time)
131 }
132 _ => (0, 0),
133 };
134 (
135 peeled_id.inner,
136 prio,
137 tag_time,
138 Cow::from(r.inner.name.shorten().to_owned()),
139 )
140 .into()
141 })
142 .collect();
143 refs.sort_by(
146 |(_a_peeled_id, a_prio, a_time, a_name), (_b_peeled_id, b_prio, b_time, b_name)| {
147 a_prio
148 .cmp(b_prio)
149 .then_with(|| a_time.cmp(b_time))
150 .then_with(|| b_name.cmp(a_name))
151 },
152 );
153 refs.into_iter().map(|(a, _, _, b)| (a, b)).collect()
154 }
155 SelectRef::AnnotatedTags => {
156 let mut peeled_commits_and_tag_date: Vec<_> = platform
157 .tags()?
158 .filter_map(Result::ok)
159 .filter_map(|r: crate::Reference<'_>| {
160 let tag = r.try_id()?.object().ok()?.try_into_tag().ok()?;
163 let tag_time = tag.tagger().ok().and_then(|s| s.map(|s| s.seconds())).unwrap_or(0);
164 let commit_id = tag.target_id().ok()?.object().ok()?.try_into_commit().ok()?.id;
165 Some((commit_id, tag_time, Cow::<BStr>::from(r.name().shorten().to_owned())))
166 })
167 .collect();
168 peeled_commits_and_tag_date.sort_by(|(_a_id, a_time, a_name), (_b_id, b_time, b_name)| {
171 a_time.cmp(b_time).then_with(|| b_name.cmp(a_name))
172 });
173 peeled_commits_and_tag_date
174 .into_iter()
175 .map(|(a, _, c)| (a, c))
176 .collect()
177 }
178 })
179 }
180 }
181
182 pub struct Platform<'repo> {
184 pub(crate) id: gix_hash::ObjectId,
185 pub repo: &'repo crate::Repository,
187 pub(crate) select: SelectRef,
188 pub(crate) first_parent: bool,
189 pub(crate) id_as_fallback: bool,
190 pub(crate) max_candidates: usize,
191 }
192
193 impl<'repo> Platform<'repo> {
194 pub fn names(mut self, select: SelectRef) -> Self {
196 self.select = select;
197 self
198 }
199
200 pub fn traverse_first_parent(mut self, first_parent: bool) -> Self {
202 self.first_parent = first_parent;
203 self
204 }
205
206 pub fn max_candidates(mut self, candidates: usize) -> Self {
208 self.max_candidates = candidates;
209 self
210 }
211
212 pub fn id_as_fallback(mut self, use_fallback: bool) -> Self {
214 self.id_as_fallback = use_fallback;
215 self
216 }
217
218 pub fn try_format(&self) -> Result<Option<gix_revision::describe::Format<'static>>, Error> {
221 self.try_resolve()?.map(Resolution::format).transpose()
222 }
223
224 pub fn try_resolve_with_cache(
234 &self,
235 cache: Option<&'_ gix_commitgraph::Graph>,
236 ) -> Result<Option<Resolution<'repo>>, Error> {
237 let mut graph = self.repo.revision_graph(cache);
238 let outcome = gix_revision::describe(
239 &self.id,
240 &mut graph,
241 gix_revision::describe::Options {
242 name_by_oid: self.select.names(self.repo)?,
243 fallback_to_oid: self.id_as_fallback,
244 first_parent: self.first_parent,
245 max_candidates: self.max_candidates,
246 },
247 )
248 .map_err(Exn::into_inner)?;
249
250 Ok(outcome.map(|outcome| Resolution {
251 outcome,
252 id: self.id.attach(self.repo),
253 }))
254 }
255
256 pub fn try_resolve(&self) -> Result<Option<Resolution<'repo>>, Error> {
262 let cache = self.repo.commit_graph_if_enabled()?;
263 self.try_resolve_with_cache(cache.as_ref())
264 }
265
266 pub fn format(&mut self) -> Result<gix_revision::describe::Format<'static>, Error> {
268 self.id_as_fallback = true;
269 Ok(self.try_format()?.expect("BUG: fallback must always produce a format"))
270 }
271 }
272}