use std::{
cmp::Ordering,
io::Read,
iter::Peekable,
path::{Path, PathBuf},
};
use crate::{
file::{loose, path_to_name},
store::{file, packed},
FullName, Namespace, Reference,
};
pub struct LooseThenPacked<'p, 's> {
base: &'s Path,
packed: Option<Peekable<packed::Iter<'p>>>,
loose: Peekable<loose::iter::SortedLoosePaths>,
buf: Vec<u8>,
namespace: Option<&'s Namespace>,
}
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 file::Store {
pub fn iter<'p, 's>(&'s self, packed: Option<&'p packed::Buffer>) -> std::io::Result<LooseThenPacked<'p, 's>> {
match &self.namespace {
Some(namespace) => self.iter_prefixed_unvalidated(packed, namespace.to_path()),
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_names(self.refs_dir(), self.base.to_owned())
.peekable(),
buf: Vec::new(),
namespace: None,
}),
}
}
pub fn iter_prefixed<'p, 's>(
&'s self,
packed: Option<&'p packed::Buffer>,
prefix: impl AsRef<Path>,
) -> std::io::Result<LooseThenPacked<'p, 's>> {
self.validate_prefix(prefix.as_ref())?;
match &self.namespace {
None => self.iter_prefixed_unvalidated(packed, prefix),
Some(namespace) => {
self.iter_prefixed_unvalidated(packed, namespace.to_owned().into_namespaced_prefix(prefix))
}
}
}
fn iter_prefixed_unvalidated<'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(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(),
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::file;
quick_error! {
#[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;