use std::{ cmp::Ordering, io::Read, iter::Peekable, path::{Path, PathBuf},}; use crate::{ file::{loose, path_to_name, Reference}, mutable::FullName, store::{file, packed},}; /// 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, packed: Option<Peekable<packed::Iter<'p>>>, loose: Peekable<loose::iter::SortedLoosePaths>, buf: Vec<u8>,} impl<'p, 's> LooseThenPacked<'p, 's> { fn convert_packed( &mut self, packed: Result<packed::Reference<'p>, packed::iter::Error>, ) -> Result<Reference<'p>, Error> { packed.map(Reference::Packed).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<'p>, 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(Reference::Loose) }} impl<'p, 's> Iterator for LooseThenPacked<'p, 's> { type Item = Result<Reference<'p>, 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 file::Store { /// Return an iterator over all references, loose or `packed`, sorted by their name. /// /// Note that the caller is responsible for the freshness of the `packed` references buffer. /// If a reference cannot be parsed or read, the error will be visible to the caller and the iteration /// continues. /// /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themeselves. pub fn iter<'p, 's>(&'s self, packed: Option<&'p packed::Buffer>) -> std::io::Result<LooseThenPacked<'p, 's>> { 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_names(self.refs_dir(), self.base.to_owned()).peekable(), buf: Vec::new(), }) } /// 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<'p, 's>( &'s self, packed: Option<&'p packed::Buffer>, prefix: impl AsRef<Path>, ) -> std::io::Result<LooseThenPacked<'p, 's>> { let packed_prefix = path_to_name(self.validate_prefix(prefix.as_ref())?); Ok(LooseThenPacked { base: &self.base, packed: match packed { Some(packed) => Some( packed .iter_prefixed(packed_prefix) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? .peekable(), ), None => None, }, loose: loose::iter::SortedLoosePaths::at_root_with_names(self.base.join(prefix), self.base.to_owned()) .peekable(), buf: Vec::new(), }) }} mod error { use std::{io, path::PathBuf}; use bstr::BString; use quick_error::quick_error; use crate::store::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 std::{
cmp::Ordering,
io::Read,
iter::Peekable,
path::{Path, PathBuf},
};
use crate::{
file::{loose, path_to_name, Reference},
mutable::FullName,
store::{file, packed},
};
/// 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,
packed: Option<Peekable<packed::Iter<'p>>>,
loose: Peekable<loose::iter::SortedLoosePaths>,
buf: Vec<u8>,
}
impl<'p, 's> LooseThenPacked<'p, 's> {
fn convert_packed(
&mut self,
packed: Result<packed::Reference<'p>, packed::iter::Error>,
) -> Result<Reference<'p>, Error> {
packed.map(Reference::Packed).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<'p>, 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(Reference::Loose)
}
}
impl<'p, 's> Iterator for LooseThenPacked<'p, 's> {
type Item = Result<Reference<'p>, 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 file::Store {
/// Return an iterator over all references, loose or `packed`, sorted by their name.
///
/// Note that the caller is responsible for the freshness of the `packed` references buffer.
/// If a reference cannot be parsed or read, the error will be visible to the caller and the iteration
/// continues.
///
/// Errors are returned similarly to what would happen when loose and packed refs where iterated by themeselves.
pub fn iter<'p, 's>(&'s self, packed: Option<&'p packed::Buffer>) -> std::io::Result<LooseThenPacked<'p, 's>> {
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_names(self.refs_dir(), self.base.to_owned()).peekable(),
buf: Vec::new(),
})
}
/// 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<'p, 's>(
&'s self,
packed: Option<&'p packed::Buffer>,
prefix: impl AsRef<Path>,
) -> std::io::Result<LooseThenPacked<'p, 's>> {
let packed_prefix = path_to_name(self.validate_prefix(prefix.as_ref())?);
Ok(LooseThenPacked {
base: &self.base,
packed: match packed {
Some(packed) => Some(
packed
.iter_prefixed(packed_prefix)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?
.peekable(),
),
None => None,
},
loose: loose::iter::SortedLoosePaths::at_root_with_names(self.base.join(prefix), self.base.to_owned())
.peekable(),
buf: Vec::new(),
})
}
}
mod error {
use std::{io, path::PathBuf};
use bstr::BString;
use quick_error::quick_error;
use crate::store::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;