//! use std::ops::Deref; use git_hash::{oid, ObjectId}; use crate::{object::find, Id, Object}; /// An [object id][ObjectId] infused with `Easy`. impl<'repo> Id<'repo> { /// Find the [`Object`] associated with this object id, and consider it an error if it doesn't exist. /// /// # Note /// /// There can only be one `ObjectRef` per `Easy`. To increase that limit, clone the `Easy`. pub fn object(&self) -> Result, find::existing::OdbError> { self.repo.find_object(self.inner) } /// Try to find the [`Object`] associated with this object id, and return `None` if it's not available locally. /// /// # Note /// /// There can only be one `ObjectRef` per `Easy`. To increase that limit, clone the `Easy`. pub fn try_object(&self) -> Result>, find::OdbError> { self.repo.try_find_object(self.inner) } /// Turn this object id into a shortened id with a length in hex as configured by `core.abbrev`. pub fn shorten(&self) -> Result { let hex_len = self .repo .config .hex_len .map_or_else( || self.repo.objects.packed_object_count().map(calculate_auto_hex_len), Ok, ) .map_err(shorten::Error::Find)?; let prefix = git_odb::find::PotentialPrefix::new(self.inner, hex_len) .expect("BUG: internal hex-len must always be valid"); self.repo .objects .disambiguate_prefix(prefix) .map_err(crate::object::find::existing::OdbError::Find)? .ok_or(crate::object::find::existing::OdbError::NotFound { oid: self.inner }) } } fn calculate_auto_hex_len(num_packed_objects: u64) -> usize { let mut len = 64 - num_packed_objects.leading_zeros(); len = (len + 1) / 2; len.max(7) as usize } /// pub mod shorten { /// Returned by [`Id::prefix()`][super::Id::shorten()]. pub type Error = crate::object::find::existing::OdbError; } impl<'repo> Deref for Id<'repo> { type Target = oid; fn deref(&self) -> &Self::Target { &self.inner } } impl<'repo> Id<'repo> { pub(crate) fn from_id(id: impl Into, repo: &'repo crate::Repository) -> Self { Id { inner: id.into(), repo } } /// Turn this instance into its bare [ObjectId]. pub fn detach(self) -> ObjectId { self.inner } } /// A platform to traverse commit ancestors, also referred to as commit history. pub struct Ancestors<'repo> { repo: &'repo crate::Repository, tips: Box>, sorting: git_traverse::commit::Sorting, parents: git_traverse::commit::Parents, } /// pub mod ancestors { use git_odb::FindExt; use crate::{ext::ObjectIdExt, id::Ancestors, Id}; impl<'repo> Id<'repo> { /// Obtain a platform for traversing ancestors of this commit. pub fn ancestors(&self) -> Ancestors<'repo> { Ancestors { repo: self.repo, tips: Box::new(Some(self.inner).into_iter()), sorting: Default::default(), parents: Default::default(), } } } impl<'repo> Ancestors<'repo> { /// Set the sort mode for commits to the given value. The default is to order by topology. pub fn sorting(mut self, sorting: git_traverse::commit::Sorting) -> Self { self.sorting = sorting; self } /// Only traverse the first parent of the commit graph. pub fn first_parent_only(mut self) -> Self { self.parents = git_traverse::commit::Parents::First; self } /// Return an iterator to traverse all commits in the history of the commit the parent [Id] is pointing to. pub fn all(&mut self) -> Result, git_traverse::commit::ancestors::Error> { let tips = std::mem::replace(&mut self.tips, Box::new(None.into_iter())); let parents = self.parents; let sorting = self.sorting; let repo = self.repo; Ok(Iter { repo, inner: Box::new( git_traverse::commit::Ancestors::new( tips, git_traverse::commit::ancestors::State::default(), move |oid, buf| repo.objects.find_commit_iter(oid, buf), ) .sorting(sorting)? .parents(parents), ), is_shallow: None, error_on_missing_commit: false, }) } } /// The iterator returned by [`Ancestors::all()`]. pub struct Iter<'repo> { repo: &'repo crate::Repository, inner: Box> + 'repo>, error_on_missing_commit: bool, // TODO: tests /// After iteration this flag is true if the iteration was stopped prematurely due to missing parent commits. /// Note that this flag won't be `Some` if any iteration error occours, which is the case if /// [`error_on_missing_commit()`][Iter::error_on_missing_commit()] was called. /// /// This happens if a repository is a shallow clone. /// Note that this value is `None` as long as the iteration isn't complete. pub is_shallow: Option, } impl<'repo> Iter<'repo> { // TODO: tests /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typicall happens /// when operating on a shallow clone and thus is non-critical by default. /// /// Check the [`is_shallow`][Iter::is_shallow] field once the iteration ended otherwise to learn if a shallow commit graph /// was encountered. pub fn error_on_missing_commit(mut self) -> Self { self.error_on_missing_commit = true; self } } impl<'repo> Iterator for Iter<'repo> { type Item = Result, git_traverse::commit::ancestors::Error>; fn next(&mut self) -> Option { match self.inner.next() { None => { self.is_shallow = Some(false); None } Some(Ok(oid)) => Some(Ok(oid.attach(self.repo))), Some(Err(err @ git_traverse::commit::ancestors::Error::FindExisting { .. })) => { if self.error_on_missing_commit { Some(Err(err)) } else { self.is_shallow = Some(true); None } } Some(Err(err)) => Some(Err(err)), } } } } mod impls { use std::{cmp::Ordering, hash::Hasher}; use git_hash::{oid, ObjectId}; use crate::{DetachedObject, Id, Object}; // Eq, Hash, Ord, PartialOrd, impl<'a> std::hash::Hash for Id<'a> { fn hash(&self, state: &mut H) { self.inner.hash(state) } } impl<'a> PartialOrd> for Id<'a> { fn partial_cmp(&self, other: &Id<'a>) -> Option { self.inner.partial_cmp(&other.inner) } } impl<'repo> PartialEq> for Id<'repo> { fn eq(&self, other: &Id<'repo>) -> bool { self.inner == other.inner } } impl<'repo> PartialEq for Id<'repo> { fn eq(&self, other: &ObjectId) -> bool { &self.inner == other } } impl<'repo> PartialEq for Id<'repo> { fn eq(&self, other: &oid) -> bool { self.inner == other } } impl<'repo> PartialEq> for Id<'repo> { fn eq(&self, other: &Object<'repo>) -> bool { self.inner == other.id } } impl<'repo> PartialEq for Id<'repo> { fn eq(&self, other: &DetachedObject) -> bool { self.inner == other.id } } impl<'repo> std::fmt::Debug for Id<'repo> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.inner.fmt(f) } } impl<'repo> AsRef for Id<'repo> { fn as_ref(&self) -> &oid { &self.inner } } impl<'repo> From> for ObjectId { fn from(v: Id<'repo>) -> Self { v.inner } } } #[cfg(test)] mod tests { use super::*; #[test] fn size_of_oid() { assert_eq!( std::mem::size_of::>(), 32, "size of oid shouldn't change without notice" ) } }