use crate::{file::Reference, Kind, Target}; use bstr::BString; use git_hash::{oid, ObjectId}; #[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Hash, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub(in crate::file) enum State { Id(ObjectId), ValidatedPath(BString), } impl State { fn as_id(&self) -> Option<&oid> { match self { State::Id(id) => Some(id), State::ValidatedPath(_) => None, } } } impl<'a> Reference<'a> { /// Return the kind of ref. pub fn kind(&self) -> Kind { match self.state { State::ValidatedPath(_) => Kind::Symbolic, State::Id(_) => Kind::Peeled, } } /// Return the target to which this instance is pointing. pub fn target(&'a self) -> Target<'a> { match self.state { State::ValidatedPath(ref path) => Target::Symbolic(path.as_ref()), State::Id(ref oid) => Target::Peeled(oid.as_ref()), } } } /// pub mod peel { use crate::file::{self, find_one, reference::State, Reference}; use bstr::ByteSlice; use quick_error::quick_error; quick_error! { /// The error returned by [`Reference::peel_one_level()`]. #[derive(Debug)] #[allow(missing_docs)] pub enum Error { FindExisting(err: find_one::existing::Error) { display("Could not resolve symbolic reference name that is expected to exist") source(err) } Decode(err: file::reference::decode::Error) { display("The reference could not be decoded.") source(err) } } } impl<'a> Reference<'a> { /// 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. pub fn peel_one_level(&self) -> Option, Error>> { match &self.state { State::Id(_) => None, State::ValidatedPath(relative_path) => { let path = relative_path.to_path_lossy(); match self.parent.find_one_with_verified_input(path.as_ref()) { Ok(Some(next)) => Some(Ok(next)), Ok(None) => Some(Err(Error::FindExisting(find_one::existing::Error::NotFound( path.into_owned(), )))), Err(err) => Some(Err(Error::FindExisting(find_one::existing::Error::Find(err)))), } } } } } /// pub mod to_id { use crate::file::{reference, Reference}; use git_hash::oid; use quick_error::quick_error; use std::{collections::BTreeSet, path::PathBuf}; quick_error! { /// The error returned by [`Reference::peel_to_id_in_place()`]. #[derive(Debug)] #[allow(missing_docs)] pub enum Error { PeelOne(err: reference::peel::Error) { display("Could not peel a single level of a 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) } } } impl<'a> Reference<'a> { /// Peel this symbolic reference until the end of the chain is reached and an object ID is available. /// /// If an error occurs this reference remains unchanged. pub fn peel_to_id_in_place(&mut self) -> Result<&oid, Error> { let mut count = 0; let mut seen = BTreeSet::new(); let mut storage; let mut cursor = &mut *self; while let Some(next) = cursor.peel_one_level() { let next_ref = next?; if let crate::Kind::Peeled = next_ref.kind() { *self = next_ref; return Ok(self.state.as_id().expect("it to be present")); } storage = next_ref; cursor = &mut storage; if seen.contains(&cursor.relative_path) { return Err(Error::Cycle(cursor.parent.base.join(&cursor.relative_path))); } seen.insert(cursor.relative_path.clone()); count += 1; const MAX_REF_DEPTH: usize = 5; if count == MAX_REF_DEPTH { return Err(Error::DepthLimitExceeded { max_depth: MAX_REF_DEPTH, }); } } Ok(self.state.as_id().expect("to be peeled")) } } } } /// pub mod decode { use crate::file::{reference::State, Reference, Store}; use bstr::BString; use git_hash::ObjectId; use nom::{ branch::alt, bytes::complete::take_while, bytes::complete::{tag, take_while_m_n}, combinator::{map, opt}, sequence::terminated, IResult, }; use quick_error::quick_error; use std::convert::TryInto; use std::{convert::TryFrom, path::PathBuf}; enum MaybeUnsafeState { Id(ObjectId), UnvalidatedPath(BString), } quick_error! { /// The error returned by [`Reference::try_from_path()`]. #[derive(Debug)] #[allow(missing_docs)] pub enum Error { Parse(content: BString) { display("{:?} could not be parsed", content) } RefnameValidation{err: git_validate::reference::name::Error, path: BString} { display("The path to a symbolic reference within a ref file is invalid") source(err) } } } impl TryFrom for State { type Error = Error; fn try_from(v: MaybeUnsafeState) -> Result { Ok(match v { MaybeUnsafeState::Id(id) => State::Id(id), MaybeUnsafeState::UnvalidatedPath(path) => { State::ValidatedPath(match git_validate::refname(path.as_ref()) { Err(err) => return Err(Error::RefnameValidation { err, path }), Ok(_) => path, }) } }) } } impl<'a> Reference<'a> { /// Create a new reference of the given `parent` store with `relative_path` service as unique identifier /// at which the `path_contents` was read to obtain the refs value. pub fn try_from_path( parent: &'a Store, relative_path: impl Into, path_contents: &[u8], ) -> Result { Ok(Reference { parent, relative_path: relative_path.into(), state: parse(path_contents) .map_err(|_| Error::Parse(path_contents.into()))? .1 .try_into()?, }) } } fn is_hex_digit_lc(b: u8) -> bool { matches!(b, b'0'..=b'9' | b'a'..=b'f') } fn hex_sha1(i: &[u8]) -> IResult<&[u8], &[u8]> { take_while_m_n(40usize, 40, is_hex_digit_lc)(i) } fn newline(i: &[u8]) -> IResult<&[u8], &[u8]> { alt((tag(b"\r\n"), tag(b"\n")))(i) } fn parse(bytes: &[u8]) -> IResult<&[u8], MaybeUnsafeState> { let is_space = |b: u8| b == b' '; if let (path, Some(_ref_prefix)) = opt(terminated(tag("ref: "), take_while(is_space)))(bytes)? { map(terminated(take_while(|b| b != b'\r' && b != b'\n'), newline), |path| { MaybeUnsafeState::UnvalidatedPath(path.into()) })(path) } else { map(terminated(hex_sha1, newline), |hex| { MaybeUnsafeState::Id(ObjectId::from_hex(hex).expect("prior validation")) })(bytes) } } }