use quick_error::quick_error;
use crate::{
file,
mutable::Target,
store::{
file::{find, loose},
packed,
},
};
quick_error! {
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Error {
FindExisting(err: find::existing::Error) {
display("Could not resolve symbolic reference name that is expected to exist")
source(err)
}
Decode(err: loose::reference::decode::Error) {
display("The reference could not be decoded.")
source(err)
}
}
}
pub fn none(
_id: git_hash::ObjectId,
_buf: &mut Vec<u8>,
) -> Result<Option<(git_object::Kind, &[u8])>, std::convert::Infallible> {
Ok(Some((git_object::Kind::Commit, &[])))
}
impl loose::Reference {
pub fn follow_symbolic<'p>(
&self,
store: &file::Store,
packed: Option<&'p packed::Buffer>,
) -> Option<Result<file::Reference<'p>, Error>> {
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(Error::FindExisting(find::existing::Error::NotFound(
path.into_owned(),
)))),
Err(err) => Some(Err(Error::FindExisting(find::existing::Error::Find(err)))),
}
}
}
}
}
pub mod to_id {
use std::{collections::BTreeSet, path::PathBuf};
use bstr::BString;
use git_hash::oid;
use quick_error::quick_error;
use crate::{
mutable::{FullName, Target},
store::{file, file::loose, packed},
};
quick_error! {
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Error {
Follow(err: loose::reference::peel::Error) {
display("Could not follow a single level of a symbolic reference")
from()
source(err)
}
Cycle(start_absolute: PathBuf){
display("Aborting due to reference cycle with first seen path being '{}'", start_absolute.display())
}
DepthLimitExceeded{ max_depth: usize } {
display("Refusing to follow more than {} levels of indirection", max_depth)
}
Find(err: Box<dyn std::error::Error + Send + Sync + 'static>) {
display("An error occurred when trying to resolve an object a refererence points to")
from()
source(&**err)
}
NotFound{oid: git_hash::ObjectId, name: BString} {
display("Object {} as referred to by '{}' could not be found", oid, name)
}
}
}
impl loose::Reference {
pub 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<&oid, Error> {
let mut seen = BTreeSet::new();
let mut storage;
let mut cursor = &mut *self;
while let Some(next) = cursor.follow_symbolic(store, packed) {
let next_ref = next?;
if let crate::Kind::Peeled = next_ref.kind() {
match next_ref {
file::Reference::Loose(r) => {
*self = r;
break;
}
file::Reference::Packed(p) => {
self.target = Target::Peeled(p.object());
self.name = FullName(p.name.0.to_owned());
return Ok(self.target.as_id().expect("we just set a peeled id"));
}
};
}
storage = next_ref;
cursor = match &mut storage {
file::Reference::Loose(r) => r,
file::Reference::Packed(_) => unreachable!("handled above - we are done"),
};
if seen.contains(&cursor.name) {
return Err(Error::Cycle(store.base.join(cursor.name.to_path())));
}
seen.insert(cursor.name.clone());
const MAX_REF_DEPTH: usize = 5;
if seen.len() == MAX_REF_DEPTH {
return Err(Error::DepthLimitExceeded {
max_depth: MAX_REF_DEPTH,
});
}
}
let mut buf = Vec::new();
let mut oid = self.target.as_id().expect("peeled ref").to_owned();
self.target = Target::Peeled(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(|| Error::NotFound {
oid,
name: self.name.0.clone(),
})?;
match kind {
git_object::Kind::Tag => {
oid = git_object::immutable::TagIter::from_bytes(data)
.target_id()
.ok_or_else(|| Error::NotFound {
oid,
name: self.name.0.clone(),
})?;
}
_ => break oid,
};
});
Ok(self.target.as_id().expect("to be peeled"))
}
}
}