Sebastian Thiel[git-ref] refactor0c795c55 years agoHistory
238 lines · 8.2 KB · Rust
RawDownload
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<Result<Reference<'a>, 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<MaybeUnsafeState> for State {        type Error = Error;         fn try_from(v: MaybeUnsafeState) -> Result<Self, Self::Error> {            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<PathBuf>,            path_contents: &[u8],        ) -> Result<Self, Error> {            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)        }    }}