264 lines · 9.9 KB · Rust
RawDownload
use std::{    cmp::Ordering,    io::Read,    iter::Peekable,    path::{Path, PathBuf},}; use crate::{    file::{loose, path_to_name},    store_impl::{file, packed},    BString, FullName, Namespace, Reference,}; /// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise/// equivalent packed references.////// All errors will be returned verbatim, while packed errors are depleted first if loose refs also error.pub struct LooseThenPacked<'p, 's> {    base: &'s Path,    namespace: Option<&'s Namespace>,    packed: Option<Peekable<packed::Iter<'p>>>,    loose: Peekable<loose::iter::SortedLoosePaths>,    buf: Vec<u8>,} /// An intermediate structure to hold shared state alive long enough for iteration to happen.#[must_use = "Iterators should be obtained from this platform"]pub struct Platform<'s> {    store: &'s file::Store,    packed: Option<OwnShared<packed::Buffer>>,} impl<'p, 's> LooseThenPacked<'p, 's> {    fn strip_namespace(&self, mut r: Reference) -> Reference {        if let Some(namespace) = &self.namespace {            r.strip_namespace(namespace);        }        r    }     fn convert_packed(        &mut self,        packed: Result<packed::Reference<'p>, packed::iter::Error>,    ) -> Result<Reference, Error> {        packed            .map(Into::into)            .map(|r| self.strip_namespace(r))            .map_err(|err| match err {                packed::iter::Error::Reference {                    invalid_line,                    line_number,                } => Error::PackedReference {                    invalid_line,                    line_number,                },                packed::iter::Error::Header { .. } => unreachable!("this one only happens on iteration creation"),            })    }     fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result<Reference, Error> {        let (refpath, name) = res.map_err(Error::Traversal)?;        std::fs::File::open(&refpath)            .and_then(|mut f| {                self.buf.clear();                f.read_to_end(&mut self.buf)            })            .map_err(Error::ReadFileContents)?;        loose::Reference::try_from_path(name, &self.buf)            .map_err(|err| Error::ReferenceCreation {                err,                relative_path: refpath.strip_prefix(&self.base).expect("base contains path").into(),            })            .map(Into::into)            .map(|r| self.strip_namespace(r))    }} impl<'p, 's> Iterator for LooseThenPacked<'p, 's> {    type Item = Result<Reference, Error>;     fn next(&mut self) -> Option<Self::Item> {        match self.packed.as_mut() {            Some(packed_iter) => match (self.loose.peek(), packed_iter.peek()) {                (None, None) => None,                (None, Some(_)) | (Some(_), Some(Err(_))) => {                    let res = packed_iter.next().expect("peeked value exists");                    Some(self.convert_packed(res))                }                (Some(_), None) | (Some(Err(_)), Some(_)) => {                    let res = self.loose.next().expect("peeked value exists");                    Some(self.convert_loose(res))                }                (Some(Ok(loose)), Some(Ok(packed))) => {                    let loose_name = loose.1.as_bstr();                    match loose_name.cmp(packed.name.as_bstr()) {                        Ordering::Less => {                            let res = self.loose.next().expect("name retrieval configured");                            Some(self.convert_loose(res))                        }                        Ordering::Equal => {                            drop(packed_iter.next());                            let res = self.loose.next().expect("peeked value exists");                            Some(self.convert_loose(res))                        }                        Ordering::Greater => {                            let res = packed_iter.next().expect("name retrieval configured");                            Some(self.convert_packed(res))                        }                    }                }            },            None => self.loose.next().map(|res| self.convert_loose(res)),        }    }} impl<'s> Platform<'s> {    /// Return an iterator over all references, loose or `packed`, sorted by their name.    ///    /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themeselves.    pub fn all(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {        self.store.iter_packed(self.packed.as_deref())    }     /// As [`iter(…)`][file::Store::iter()], but filters by `prefix`, i.e. "refs/heads".    ///    /// Please note that "refs/heads` or "refs\\heads" is equivalent to "refs/heads/"    pub fn prefixed(&self, prefix: impl AsRef<Path>) -> std::io::Result<LooseThenPacked<'_, '_>> {        self.store.iter_prefixed_packed(prefix, self.packed.as_deref())    }} impl file::Store {    /// Return a platform to obtain iterator over all references, or prefixed ones, loose or packed, sorted by their name.    ///    /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.    pub fn iter(&self) -> Result<Platform<'_>, packed::buffer::open::Error> {        Ok(Platform {            store: self,            packed: self.assure_packed_refs_uptodate()?,        })    }} impl file::Store {    /// Return an iterator over all references, loose or `packed`, sorted by their name.    ///    /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themeselves.    pub fn iter_packed<'s, 'p>(        &'s self,        packed: Option<&'p packed::Buffer>,    ) -> std::io::Result<LooseThenPacked<'p, 's>> {        match self.namespace.as_ref() {            Some(namespace) => self.iter_prefixed_unvalidated(namespace.to_path(), (None, None), packed),            None => Ok(LooseThenPacked {                base: &self.base,                packed: match packed {                    Some(packed) => Some(                        packed                            .iter()                            .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?                            .peekable(),                    ),                    None => None,                },                loose: loose::iter::SortedLoosePaths::at_root_with_filename_prefix(                    self.refs_dir(),                    self.base.to_owned(),                    None,                )                .peekable(),                buf: Vec::new(),                namespace: None,            }),        }    }     /// As [`iter(…)`][file::Store::iter()], but filters by `prefix`, i.e. "refs/heads".    ///    /// Please note that "refs/heads` or "refs\\heads" is equivalent to "refs/heads/"    pub fn iter_prefixed_packed<'s, 'p>(        &'s self,        prefix: impl AsRef<Path>,        packed: Option<&'p packed::Buffer>,    ) -> std::io::Result<LooseThenPacked<'p, 's>> {        match self.namespace.as_ref() {            None => {                let (root, remainder) = self.validate_prefix(&self.base, prefix.as_ref())?;                self.iter_prefixed_unvalidated(prefix, (root.into(), remainder), packed)            }            Some(namespace) => {                let prefix = namespace.to_owned().into_namespaced_prefix(prefix);                let (root, remainder) = self.validate_prefix(&self.base, &prefix)?;                self.iter_prefixed_unvalidated(prefix, (root.into(), remainder), packed)            }        }    }     fn iter_prefixed_unvalidated<'s, 'p>(        &'s self,        prefix: impl AsRef<Path>,        loose_root_and_filename_prefix: (Option<PathBuf>, Option<BString>),        packed: Option<&'p packed::Buffer>,    ) -> std::io::Result<LooseThenPacked<'p, 's>> {        let packed_prefix = path_to_name(prefix.as_ref());        Ok(LooseThenPacked {            base: &self.base,            packed: match packed {                Some(packed) => Some(                    packed                        .iter_prefixed(packed_prefix.into_owned())                        .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?                        .peekable(),                ),                None => None,            },            loose: loose::iter::SortedLoosePaths::at_root_with_filename_prefix(                loose_root_and_filename_prefix                    .0                    .unwrap_or_else(|| self.base.join(prefix)),                self.base.to_owned(),                loose_root_and_filename_prefix.1,            )            .peekable(),            buf: Vec::new(),            namespace: self.namespace.as_ref(),        })    }} mod error {    use std::{io, path::PathBuf};     use git_object::bstr::BString;    use quick_error::quick_error;     use crate::store_impl::file;     quick_error! {        /// The error returned by the [`LooseThenPacked`][super::LooseThenPacked] iterator.        #[derive(Debug)]        #[allow(missing_docs)]        pub enum Error {            Traversal(err: io::Error) {                display("The file system could not be traversed")                source(err)            }            ReadFileContents(err: io::Error) {                display("The ref file could not be read in full")                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)            }            PackedReference { invalid_line: BString, line_number: usize } {                display("Invalid reference in line {}: '{}'", line_number, invalid_line)            }        }    }} pub use error::Error;use git_features::threading::OwnShared;