332 lines · 11.5 KB · Rust
RawDownload
use std::{    convert::TryInto,    io::{self, Read},    path::{Path, PathBuf},}; pub use error::Error; use crate::{    file,    store_impl::{        file::{loose, path_to_name},        packed,    },    FullName, PartialNameRef, Reference,}; enum Transform {    EnforceRefsPrefix,    None,} impl file::Store {    /// Find a single reference by the given `path` which is required to be a valid reference name.    ///    /// Returns `Ok(None)` if no such ref exists.    ///    /// ### Note    ///    /// * The lookup algorithm follows the one in [the git documentation][git-lookup-docs].    /// * The packed buffer is checked for modifications each time the method is called. See [`file::Store::try_find_packed()`]    ///   for a version with more control.    ///    /// [git-lookup-docs]: https://github.com/git/git/blob/5d5b1473453400224ebb126bf3947e0a3276bdf5/Documentation/revisions.txt#L34-L46    pub fn try_find<'a, Name, E>(&self, partial: Name) -> Result<Option<Reference>, Error>    where        Name: TryInto<PartialNameRef<'a>, Error = E>,        Error: From<E>,    {        let path = partial.try_into()?;        let packed = self.assure_packed_refs_uptodate()?;        self.find_one_with_verified_input(path.to_partial_path().as_ref(), packed.as_deref())    }     /// Similar to [`file::Store::find()`] but a non-existing ref is treated as error.    ///    /// Find only loose references, that is references that aren't in the packed-refs buffer.    /// All symbolic references are loose references.    /// `HEAD` is always a loose reference.    pub fn try_find_loose<'a, Name, E>(&self, partial: Name) -> Result<Option<loose::Reference>, Error>    where        Name: TryInto<PartialNameRef<'a>, Error = E>,        Error: From<E>,    {        let path = partial.try_into()?;        self.find_one_with_verified_input(path.to_partial_path().as_ref(), None)            .map(|r| r.map(|r| r.try_into().expect("only loose refs are found without pack")))    }     /// Similar to [`file::Store::find()`], but allows to pass a snapshotted packed buffer instead.    pub fn try_find_packed<'a, Name, E>(        &self,        partial: Name,        packed: Option<&packed::Buffer>,    ) -> Result<Option<Reference>, Error>    where        Name: TryInto<PartialNameRef<'a>, Error = E>,        Error: From<E>,    {        let path = partial.try_into()?;        self.find_one_with_verified_input(path.to_partial_path().as_ref(), packed)    }     pub(crate) fn find_one_with_verified_input(        &self,        relative_path: &Path,        packed: Option<&packed::Buffer>,    ) -> Result<Option<Reference>, Error> {        let is_all_uppercase = relative_path            .to_string_lossy()            .as_ref()            .chars()            .all(|c| c.is_ascii_uppercase());        if relative_path.components().count() == 1 && is_all_uppercase {            if let Some(r) = self.find_inner("", relative_path, None, Transform::None)? {                return Ok(Some(r));            }        }         for inbetween in &["", "tags", "heads", "remotes"] {            match self.find_inner(*inbetween, relative_path, packed, Transform::EnforceRefsPrefix) {                Ok(Some(r)) => return Ok(Some(r)),                Ok(None) => {                    continue;                }                Err(err) => return Err(err),            }        }        self.find_inner(            "remotes",            &relative_path.join("HEAD"),            None,            Transform::EnforceRefsPrefix,        )    }     fn find_inner(        &self,        inbetween: &str,        relative_path: &Path,        packed: Option<&packed::Buffer>,        transform: Transform,    ) -> Result<Option<Reference>, Error> {        let (base, is_definitely_absolute) = match transform {            Transform::EnforceRefsPrefix => (                if relative_path.starts_with("refs") {                    PathBuf::new()                } else {                    PathBuf::from("refs")                },                true,            ),            Transform::None => (PathBuf::new(), false),        };        let relative_path = base.join(inbetween).join(relative_path);         let contents = match self.ref_contents(&relative_path)? {            None => {                if is_definitely_absolute {                    if let Some(packed) = packed {                        let full_name = path_to_name(match &self.namespace {                            None => relative_path,                            Some(namespace) => namespace.to_owned().into_namespaced_prefix(relative_path),                        });                        let full_name = PartialNameRef(full_name.into_owned().into());                        if let Some(packed_ref) = packed.try_find(full_name)? {                            let mut res: Reference = packed_ref.into();                            if let Some(namespace) = &self.namespace {                                res.strip_namespace(namespace);                            }                            return Ok(Some(res));                        };                    }                }                return Ok(None);            }            Some(c) => c,        };        Ok(Some({            let full_name = path_to_name(&relative_path);            loose::Reference::try_from_path(FullName(full_name.into_owned()), &contents)                .map(Into::into)                .map(|mut r: Reference| {                    if let Some(namespace) = &self.namespace {                        r.strip_namespace(namespace);                    }                    r                })                .map_err(|err| Error::ReferenceCreation { err, relative_path })?        }))    }} impl file::Store {    /// Implements the logic required to transform a fully qualified refname into a filesystem path    pub(crate) fn reference_path(&self, name: &Path) -> PathBuf {        match &self.namespace {            None => self.base.join(name),            Some(namespace) => self.base.join(namespace.to_path()).join(name),        }    }     /// Read the file contents with a verified full reference path and return it in the given vector if possible.    pub(crate) fn ref_contents(&self, relative_path: &Path) -> std::io::Result<Option<Vec<u8>>> {        let mut buf = Vec::new();        let ref_path = self.reference_path(relative_path);         match std::fs::File::open(&ref_path) {            Ok(mut file) => {                if let Err(err) = file.read_to_end(&mut buf) {                    return if ref_path.is_dir() { Ok(None) } else { Err(err) };                }                Ok(Some(buf))            }            Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),            #[cfg(target_os = "windows")]            Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => Ok(None),            Err(err) => Err(err),        }    }} ///pub mod existing {    use std::convert::TryInto;     pub use error::Error;     use crate::{        file::{self},        store_impl::{            file::{find, loose},            packed,        },        PartialNameRef, Reference,    };     impl file::Store {        /// Similar to [`file::Store::try_find()`] but a non-existing ref is treated as error.        pub fn find<'a, Name, E>(&self, partial: Name) -> Result<Reference, Error>        where            Name: TryInto<PartialNameRef<'a>, Error = E>,            crate::name::Error: From<E>,        {            let packed = self.assure_packed_refs_uptodate().map_err(find::Error::PackedOpen)?;            self.find_existing_inner(partial, packed.as_deref())        }         /// Similar to [`file::Store::find()`], but supports a stable packed buffer.        pub fn find_packed<'a, Name, E>(            &self,            partial: Name,            packed: Option<&packed::Buffer>,        ) -> Result<Reference, Error>        where            Name: TryInto<PartialNameRef<'a>, Error = E>,            crate::name::Error: From<E>,        {            self.find_existing_inner(partial, packed)        }         /// Similar to [`file::Store::find()`] won't handle packed-refs.        pub fn find_loose<'a, Name, E>(&self, partial: Name) -> Result<loose::Reference, Error>        where            Name: TryInto<PartialNameRef<'a>, Error = E>,            crate::name::Error: From<E>,        {            self.find_existing_inner(partial, None)                .map(|r| r.try_into().expect("always loose without packed"))        }         /// Similar to [`file::Store::find()`] but a non-existing ref is treated as error.        pub(crate) fn find_existing_inner<'a, Name, E>(            &self,            partial: Name,            packed: Option<&packed::Buffer>,        ) -> Result<Reference, Error>        where            Name: TryInto<PartialNameRef<'a>, Error = E>,            crate::name::Error: From<E>,        {            let path = partial                .try_into()                .map_err(|err| Error::Find(find::Error::RefnameValidation(err.into())))?;            match self.find_one_with_verified_input(path.to_partial_path().as_ref(), packed) {                Ok(Some(r)) => Ok(r),                Ok(None) => Err(Error::NotFound(path.to_partial_path().to_owned())),                Err(err) => Err(err.into()),            }        }    }     mod error {        use std::path::PathBuf;         use quick_error::quick_error;         use crate::store_impl::file::find;         quick_error! {            /// The error returned by [file::Store::find_existing()][crate::file::Store::find_existing()].            #[derive(Debug)]            #[allow(missing_docs)]            pub enum Error {                Find(err: find::Error) {                    display("An error occured while trying to find a reference")                    from()                    source(err)                }                NotFound(name: PathBuf) {                    display("The ref partially named '{}' could not be found", name.display())                }            }        }    }} mod error {    use std::{convert::Infallible, io, path::PathBuf};     use quick_error::quick_error;     use crate::{file, store_impl::packed};     quick_error! {        /// The error returned by [file::Store::find()].        #[derive(Debug)]        #[allow(missing_docs)]        pub enum Error {            RefnameValidation(err: crate::name::Error) {                display("The ref name or path is not a valid ref name")                from()                source(err)            }            ReadFileContents(err: io::Error) {                display("The ref file could not be read in full")                from()                source(err)            }            ReferenceCreation{ err: file::loose::reference::decode::Error, relative_path: PathBuf } {                display("The reference at '{}' could not be instantiated", relative_path.display())                source(err)            }            PackedRef(err: packed::find::Error) {                display("A packed ref lookup failed")                from()                source(err)            }            PackedOpen(err: packed::buffer::open::Error) {                display("Could not open the packed refs buffer when trying to find references.")                from()                source(err)            }        }    }     impl From<Infallible> for Error {        fn from(_: Infallible) -> Self {            unreachable!("this impl is needed to allow passing a known valid partial path as parameter")        }    }}