use std::collections::BTreeSet; use git_hash::ObjectId; use crate::{ packed, peel, raw::Reference, store_impl::{file, file::log}, Target,}; pub trait Sealed {}impl Sealed for crate::Reference {} /// A trait to extend [Reference][crate::Reference] with functionality requiring a [file::Store].pub trait ReferenceExt: Sealed { /// A step towards obtaining forward or reverse iterators on reference logs. fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's>; /// For details, see [Reference::log_exists()]. fn log_exists(&self, store: &file::Store) -> bool; /// For details, see [Reference::peel_to_id_in_place()]. fn peel_to_id_in_place<E: std::error::Error + Send + Sync + 'static>( &mut self, store: &file::Store, find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>, ) -> Result<ObjectId, peel::to_id::Error>; /// For details, see [Reference::peel_to_id_in_place()], with support for a known stable packed buffer. fn peel_to_id_in_place_packed<E: std::error::Error + Send + Sync + 'static>( &mut self, store: &file::Store, find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>, packed: Option<&packed::Buffer>, ) -> Result<ObjectId, peel::to_id::Error>; /// Follow this symbolic reference one level and return the ref it refers to. /// /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>>; /// Follow this symbolic reference one level and return the ref it refers to, /// possibly providing access to `packed` references for lookup if it contains the referent. /// /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. fn follow_packed( &self, store: &file::Store, packed: Option<&packed::Buffer>, ) -> Option<Result<Reference, file::find::existing::Error>>;} impl ReferenceExt for Reference { fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's> { log::iter::Platform { store, name: self.name.as_ref(), buf: Vec::new(), } } fn log_exists(&self, store: &file::Store) -> bool { store .reflog_exists(self.name.as_ref()) .expect("infallible name conversion") } fn peel_to_id_in_place<E: std::error::Error + Send + Sync + 'static>( &mut self, store: &file::Store, find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>, ) -> Result<ObjectId, peel::to_id::Error> { let packed = store.assure_packed_refs_uptodate().map_err(|err| { peel::to_id::Error::Follow(file::find::existing::Error::Find(file::find::Error::PackedOpen(err))) })?; self.peel_to_id_in_place_packed(store, find, packed.as_ref().map(|b| &***b)) } fn peel_to_id_in_place_packed<E: std::error::Error + Send + Sync + 'static>( &mut self, store: &file::Store, mut find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>, packed: Option<&packed::Buffer>, ) -> Result<ObjectId, peel::to_id::Error> { match self.peeled { Some(peeled) => { self.target = Target::Peeled(peeled.to_owned()); Ok(peeled) } None => { if self.target.kind() == crate::Kind::Symbolic { let mut seen = BTreeSet::new(); let cursor = &mut *self; while let Some(next) = cursor.follow_packed(store, packed) { let next = next?; if seen.contains(&next.name) { return Err(peel::to_id::Error::Cycle(store.reference_path(cursor.name.as_ref()))); } *cursor = next; seen.insert(cursor.name.clone()); const MAX_REF_DEPTH: usize = 5; if seen.len() == MAX_REF_DEPTH { return Err(peel::to_id::Error::DepthLimitExceeded { max_depth: MAX_REF_DEPTH, }); } } }; let mut buf = Vec::new(); let mut oid = self.target.try_id().expect("peeled ref").to_owned(); let peeled_id = loop { let (kind, data) = find(oid, &mut buf) .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)? .ok_or_else(|| peel::to_id::Error::NotFound { oid, name: self.name.0.clone(), })?; match kind { git_object::Kind::Tag => { oid = git_object::TagRefIter::from_bytes(data).target_id().map_err(|_err| { peel::to_id::Error::NotFound { oid, name: self.name.0.clone(), } })?; } _ => break oid, }; }; self.peeled = Some(peeled_id); self.target = Target::Peeled(peeled_id); Ok(peeled_id) } } } fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>> { let packed = match store .assure_packed_refs_uptodate() .map_err(|err| file::find::existing::Error::Find(file::find::Error::PackedOpen(err))) { Ok(packed) => packed, Err(err) => return Some(Err(err)), }; self.follow_packed(store, packed.as_ref().map(|b| &***b)) } fn follow_packed( &self, store: &file::Store, packed: Option<&packed::Buffer>, ) -> Option<Result<Reference, file::find::existing::Error>> { match self.peeled { Some(peeled) => Some(Ok(Reference { name: self.name.clone(), target: Target::Peeled(peeled), peeled: None, })), None => match &self.target { Target::Peeled(_) => None, Target::Symbolic(full_name) => match store.try_find_packed(full_name.as_ref(), packed) { Ok(Some(next)) => Some(Ok(next)), Ok(None) => Some(Err(file::find::existing::Error::NotFound( full_name.to_path().to_owned(), ))), Err(err) => Some(Err(file::find::existing::Error::Find(err))), }, }, } }}
use std::collections::BTreeSet;
use git_hash::ObjectId;
use crate::{
packed, peel,
raw::Reference,
store_impl::{file, file::log},
Target,
};
pub trait Sealed {}
impl Sealed for crate::Reference {}
/// A trait to extend [Reference][crate::Reference] with functionality requiring a [file::Store].
pub trait ReferenceExt: Sealed {
/// A step towards obtaining forward or reverse iterators on reference logs.
fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's>;
/// For details, see [Reference::log_exists()].
fn log_exists(&self, store: &file::Store) -> bool;
/// For details, see [Reference::peel_to_id_in_place()].
fn peel_to_id_in_place<E: std::error::Error + Send + Sync + 'static>(
&mut self,
store: &file::Store,
find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>,
) -> Result<ObjectId, peel::to_id::Error>;
/// For details, see [Reference::peel_to_id_in_place()], with support for a known stable packed buffer.
fn peel_to_id_in_place_packed<E: std::error::Error + Send + Sync + 'static>(
&mut self,
store: &file::Store,
find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>,
packed: Option<&packed::Buffer>,
) -> Result<ObjectId, peel::to_id::Error>;
/// Follow this symbolic reference one level and return the ref it refers to.
///
/// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>>;
/// Follow this symbolic reference one level and return the ref it refers to,
/// possibly providing access to `packed` references for lookup if it contains the referent.
///
/// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
fn follow_packed(
&self,
store: &file::Store,
packed: Option<&packed::Buffer>,
) -> Option<Result<Reference, file::find::existing::Error>>;
}
impl ReferenceExt for Reference {
fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's> {
log::iter::Platform {
store,
name: self.name.as_ref(),
buf: Vec::new(),
}
}
fn log_exists(&self, store: &file::Store) -> bool {
store
.reflog_exists(self.name.as_ref())
.expect("infallible name conversion")
}
fn peel_to_id_in_place<E: std::error::Error + Send + Sync + 'static>(
&mut self,
store: &file::Store,
find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>,
) -> Result<ObjectId, peel::to_id::Error> {
let packed = store.assure_packed_refs_uptodate().map_err(|err| {
peel::to_id::Error::Follow(file::find::existing::Error::Find(file::find::Error::PackedOpen(err)))
})?;
self.peel_to_id_in_place_packed(store, find, packed.as_ref().map(|b| &***b))
}
fn peel_to_id_in_place_packed<E: std::error::Error + Send + Sync + 'static>(
&mut self,
store: &file::Store,
mut find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>,
packed: Option<&packed::Buffer>,
) -> Result<ObjectId, peel::to_id::Error> {
match self.peeled {
Some(peeled) => {
self.target = Target::Peeled(peeled.to_owned());
Ok(peeled)
}
None => {
if self.target.kind() == crate::Kind::Symbolic {
let mut seen = BTreeSet::new();
let cursor = &mut *self;
while let Some(next) = cursor.follow_packed(store, packed) {
let next = next?;
if seen.contains(&next.name) {
return Err(peel::to_id::Error::Cycle(store.reference_path(cursor.name.as_ref())));
}
*cursor = next;
seen.insert(cursor.name.clone());
const MAX_REF_DEPTH: usize = 5;
if seen.len() == MAX_REF_DEPTH {
return Err(peel::to_id::Error::DepthLimitExceeded {
max_depth: MAX_REF_DEPTH,
});
}
}
};
let mut buf = Vec::new();
let mut oid = self.target.try_id().expect("peeled ref").to_owned();
let peeled_id = loop {
let (kind, data) = find(oid, &mut buf)
.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)?
.ok_or_else(|| peel::to_id::Error::NotFound {
oid,
name: self.name.0.clone(),
})?;
match kind {
git_object::Kind::Tag => {
oid = git_object::TagRefIter::from_bytes(data).target_id().map_err(|_err| {
peel::to_id::Error::NotFound {
oid,
name: self.name.0.clone(),
}
})?;
}
_ => break oid,
};
};
self.peeled = Some(peeled_id);
self.target = Target::Peeled(peeled_id);
Ok(peeled_id)
}
}
}
fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>> {
let packed = match store
.assure_packed_refs_uptodate()
.map_err(|err| file::find::existing::Error::Find(file::find::Error::PackedOpen(err)))
{
Ok(packed) => packed,
Err(err) => return Some(Err(err)),
};
self.follow_packed(store, packed.as_ref().map(|b| &***b))
}
fn follow_packed(
&self,
store: &file::Store,
packed: Option<&packed::Buffer>,
) -> Option<Result<Reference, file::find::existing::Error>> {
match self.peeled {
Some(peeled) => Some(Ok(Reference {
name: self.name.clone(),
target: Target::Peeled(peeled),
peeled: None,
})),
None => match &self.target {
Target::Peeled(_) => None,
Target::Symbolic(full_name) => match store.try_find_packed(full_name.as_ref(), packed) {
Ok(Some(next)) => Some(Ok(next)),
Ok(None) => Some(Err(file::find::existing::Error::NotFound(
full_name.to_path().to_owned(),
))),
Err(err) => Some(Err(file::find::existing::Error::Find(err))),
},
},
}
}
}