//!use std::{convert::TryInto, 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_int("core.abbrev", 7); let hex_len = hex_len.try_into().map_err(|_| shorten::Error::ConfigValue { actual: hex_len, max_range: self.inner.kind().len_in_hex(), err: None, })?; let prefix = git_odb::find::PotentialPrefix::new(self.inner, hex_len).map_err(|err| shorten::Error::ConfigValue { actual: hex_len as i64, max_range: self.inner.kind().len_in_hex(), err: Some(err), })?; Ok(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 })?) }} ///pub mod shorten { /// Returned by [`Id::prefix()`][super::Id::shorten()]. #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] FindExisting(#[from] crate::object::find::existing::OdbError), #[error("core.abbrev length was {}, but needs to be between 4 and {}", .actual, .max_range)] ConfigValue { #[source] err: Option<git_hash::prefix::Error>, actual: i64, max_range: usize, }, }} 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) -> Iter<'repo> { 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; 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::{convert::TryInto, 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_int("core.abbrev", 7);
let hex_len = hex_len.try_into().map_err(|_| shorten::Error::ConfigValue {
actual: hex_len,
max_range: self.inner.kind().len_in_hex(),
err: None,
})?;
let prefix =
git_odb::find::PotentialPrefix::new(self.inner, hex_len).map_err(|err| shorten::Error::ConfigValue {
actual: hex_len as i64,
max_range: self.inner.kind().len_in_hex(),
err: Some(err),
})?;
Ok(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 })?)
}
}
///
pub mod shorten {
/// Returned by [`Id::prefix()`][super::Id::shorten()].
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
FindExisting(#[from] crate::object::find::existing::OdbError),
#[error("core.abbrev length was {}, but needs to be between 4 and {}", .actual, .max_range)]
ConfigValue {
#[source]
err: Option<git_hash::prefix::Error>,
actual: i64,
max_range: usize,
},
}
}
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) -> Iter<'repo> {
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;
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"
)
}
}