use std::collections::BTreeSet;
use git_hash::ObjectId;
use crate::{
peel,
raw::Reference,
store::{
file,
file::{log, loose::reference::logiter::must_be_io_err},
packed,
},
Target,
};
pub trait Sealed {}
impl Sealed for crate::Reference {}
pub trait ReferenceExt: Sealed {
fn log_iter_rev<'b>(
&self,
store: &file::Store,
buf: &'b mut [u8],
) -> std::io::Result<Option<log::iter::Reverse<'b, std::fs::File>>>;
fn log_iter<'a, 'b: 'a>(
&'a self,
store: &file::Store,
buf: &'b mut Vec<u8>,
) -> std::io::Result<Option<log::iter::Forward<'b>>>;
fn log_exists(&self, store: &file::Store) -> bool;
fn peel_to_id_in_place<E: std::error::Error + Send + Sync + 'static>(
&mut self,
store: &file::Store,
packed: Option<&packed::Buffer>,
find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>,
) -> Result<ObjectId, peel::to_id::Error>;
fn follow(
&self,
store: &file::Store,
packed: Option<&packed::Buffer>,
) -> Option<Result<Reference, file::find::existing::Error>>;
}
impl ReferenceExt for Reference {
fn log_iter_rev<'b>(
&self,
store: &file::Store,
buf: &'b mut [u8],
) -> std::io::Result<Option<log::iter::Reverse<'b, std::fs::File>>> {
store.reflog_iter_rev(self.name.to_ref(), buf).map_err(must_be_io_err)
}
fn log_iter<'a, 'b: 'a>(
&'a self,
store: &file::Store,
buf: &'b mut Vec<u8>,
) -> std::io::Result<Option<log::iter::Forward<'b>>> {
store.reflog_iter(self.name.to_ref(), buf).map_err(must_be_io_err)
}
fn log_exists(&self, store: &file::Store) -> bool {
store
.reflog_exists(self.name.to_ref())
.expect("infallible name conversion")
}
fn peel_to_id_in_place<E: std::error::Error + Send + Sync + 'static>(
&mut self,
store: &file::Store,
packed: Option<&packed::Buffer>,
mut find: impl FnMut(git_hash::ObjectId, &mut Vec<u8>) -> Result<Option<(git_object::Kind, &[u8])>, E>,
) -> 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(store, packed) {
let next = next?;
if seen.contains(&next.name) {
return Err(peel::to_id::Error::Cycle(store.base.join(cursor.name.to_path())));
}
*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.as_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().ok_or_else(|| {
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(self.target.as_id().expect("to be peeled").to_owned())
}
}
}
fn follow(
&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) => {
let path = full_name.to_path();
match store.find_one_with_verified_input(path.as_ref(), packed) {
Ok(Some(next)) => Some(Ok(next)),
Ok(None) => Some(Err(file::find::existing::Error::NotFound(path.into_owned()))),
Err(err) => Some(Err(file::find::existing::Error::Find(err))),
}
}
},
}
}
}