//!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<Object<'repo>, 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<Option<Object<'repo>>, 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<git_hash::Prefix, shorten::Error> { 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<ObjectId>, 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<dyn Iterator<Item = ObjectId>>, 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<Iter<'repo>, 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<dyn Iterator<Item = Result<git_hash::ObjectId, git_traverse::commit::ancestors::Error>> + '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<bool>, } 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<Id<'repo>, git_traverse::commit::ancestors::Error>; fn next(&mut self) -> Option<Self::Item> { 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<H: Hasher>(&self, state: &mut H) { self.inner.hash(state) } } impl<'a> PartialOrd<Id<'a>> for Id<'a> { fn partial_cmp(&self, other: &Id<'a>) -> Option<Ordering> { self.inner.partial_cmp(&other.inner) } } impl<'repo> PartialEq<Id<'repo>> for Id<'repo> { fn eq(&self, other: &Id<'repo>) -> bool { self.inner == other.inner } } impl<'repo> PartialEq<ObjectId> for Id<'repo> { fn eq(&self, other: &ObjectId) -> bool { &self.inner == other } } impl<'repo> PartialEq<oid> for Id<'repo> { fn eq(&self, other: &oid) -> bool { self.inner == other } } impl<'repo> PartialEq<Object<'repo>> for Id<'repo> { fn eq(&self, other: &Object<'repo>) -> bool { self.inner == other.id } } impl<'repo> PartialEq<DetachedObject> 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<oid> for Id<'repo> { fn as_ref(&self) -> &oid { &self.inner } } impl<'repo> From<Id<'repo>> 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::<Id<'_>>(), 32, "size of oid shouldn't change without notice" ) }}
//!
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<Object<'repo>, 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<Option<Object<'repo>>, 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<git_hash::Prefix, shorten::Error> {
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<ObjectId>, 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<dyn Iterator<Item = ObjectId>>,
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<Iter<'repo>, 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<dyn Iterator<Item = Result<git_hash::ObjectId, git_traverse::commit::ancestors::Error>> + '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<bool>,
}
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<Id<'repo>, git_traverse::commit::ancestors::Error>;
fn next(&mut self) -> Option<Self::Item> {
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<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state)
}
}
impl<'a> PartialOrd<Id<'a>> for Id<'a> {
fn partial_cmp(&self, other: &Id<'a>) -> Option<Ordering> {
self.inner.partial_cmp(&other.inner)
}
}
impl<'repo> PartialEq<Id<'repo>> for Id<'repo> {
fn eq(&self, other: &Id<'repo>) -> bool {
self.inner == other.inner
}
}
impl<'repo> PartialEq<ObjectId> for Id<'repo> {
fn eq(&self, other: &ObjectId) -> bool {
&self.inner == other
}
}
impl<'repo> PartialEq<oid> for Id<'repo> {
fn eq(&self, other: &oid) -> bool {
self.inner == other
}
}
impl<'repo> PartialEq<Object<'repo>> for Id<'repo> {
fn eq(&self, other: &Object<'repo>) -> bool {
self.inner == other.id
}
}
impl<'repo> PartialEq<DetachedObject> 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<oid> for Id<'repo> {
fn as_ref(&self) -> &oid {
&self.inner
}
}
impl<'repo> From<Id<'repo>> 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::<Id<'_>>(),
32,
"size of oid shouldn't change without notice"
)
}
}