use std::{ borrow::Cow, cmp::Ordering, io::Read, iter::Peekable, path::{Path, PathBuf},}; use crate::{ file::{loose, loose::iter::SortedLoosePaths, 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> { git_dir: &'s Path, common_dir: Option<&'s Path>, namespace: Option<&'s Namespace>, iter_packed: Option<Peekable<packed::Iter<'p>>>, iter_git_dir: Peekable<SortedLoosePaths>, #[allow(dead_code)] iter_common_dir: Option<Peekable<SortedLoosePaths>>, buf: Vec<u8>,} enum IterKind { Git, GitAndConsumeCommon, Common,} /// 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<file::packed::SharedBufferSnapshot>,} 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 loose_iter(&mut self, kind: IterKind) -> &mut Peekable<SortedLoosePaths> { match kind { IterKind::GitAndConsumeCommon => { drop(self.iter_common_dir.as_mut().map(|iter| iter.next())); &mut self.iter_git_dir } IterKind::Git => &mut self.iter_git_dir, IterKind::Common => self .iter_common_dir .as_mut() .expect("caller knows there is a common iter"), } } 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(|err| Error::ReadFileContents { err, path: refpath.to_owned(), })?; loose::Reference::try_from_path(name, &self.buf) .map_err(|err| { let relative_path = refpath .strip_prefix(&self.git_dir) .ok() .or_else(|| { self.common_dir .and_then(|common_dir| refpath.strip_prefix(common_dir).ok()) }) .expect("one of our bases contains the path"); Error::ReferenceCreation { err, relative_path: relative_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> { fn advance_to_non_private(iter: &mut Peekable<SortedLoosePaths>) { while let Some(Ok((_path, name))) = iter.peek() { if name.category().map_or(true, |cat| cat.is_worktree_private()) { iter.next(); } else { break; } } } fn peek_loose<'a>( git_dir: &'a mut Peekable<SortedLoosePaths>, common_dir: Option<&'a mut Peekable<SortedLoosePaths>>, ) -> Option<(&'a std::io::Result<(PathBuf, FullName)>, IterKind)> { match common_dir { Some(common_dir) => match (git_dir.peek(), { advance_to_non_private(common_dir); common_dir.peek() }) { (None, None) => None, (None, Some(res)) | (Some(_), Some(res @ Err(_))) => Some((res, IterKind::Common)), (Some(res), None) | (Some(res @ Err(_)), Some(_)) => Some((res, IterKind::Git)), (Some(r_gitdir @ Ok((_, git_dir_name))), Some(r_cd @ Ok((_, common_dir_name)))) => { match git_dir_name.cmp(common_dir_name) { Ordering::Less => Some((r_gitdir, IterKind::Git)), Ordering::Equal => Some((r_gitdir, IterKind::GitAndConsumeCommon)), Ordering::Greater => Some((r_cd, IterKind::Common)), } } }, None => git_dir.peek().map(|r| (r, IterKind::Git)), } } match self.iter_packed.as_mut() { Some(packed_iter) => match ( peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()), 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((_, kind)), None) | (Some((Err(_), kind)), Some(_)) => { let res = self.loose_iter(kind).next().expect("prior peek"); Some(self.convert_loose(res)) } (Some((Ok((_, loose_name)), kind)), Some(Ok(packed))) => match loose_name.as_ref().cmp(packed.name) { Ordering::Less => { let res = self.loose_iter(kind).next().expect("prior peek"); Some(self.convert_loose(res)) } Ordering::Equal => { drop(packed_iter.next()); let res = self.loose_iter(kind).next().expect("prior peek"); Some(self.convert_loose(res)) } Ordering::Greater => { let res = packed_iter.next().expect("name retrieval configured"); Some(self.convert_packed(res)) } }, }, None => match peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()) { None => None, Some((_, kind)) => self.loose_iter(kind).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_ref().map(|b| &***b)) } /// 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_ref().map(|b| &***b)) }} 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()?, }) }} #[derive(Debug)]pub(crate) enum IterInfo<'a> { Base { base: &'a Path, }, BaseAndIterRoot { base: &'a Path, iter_root: PathBuf, prefix: Cow<'a, Path>, }, PrefixAndBase { base: &'a Path, prefix: &'a Path, }, ComputedIterationRoot { /// The root to iterate over iter_root: PathBuf, /// The top-level directory as boundary of all references, used to create their short-names after iteration base: &'a Path, /// The original prefix prefix: Cow<'a, Path>, /// The remainder of the prefix that wasn't a valid path remainder: Option<BString>, },} impl<'a> IterInfo<'a> { fn prefix(&self) -> Option<&Path> { match self { IterInfo::Base { .. } => None, IterInfo::PrefixAndBase { prefix, .. } => Some(*prefix), IterInfo::ComputedIterationRoot { prefix, .. } | IterInfo::BaseAndIterRoot { prefix, .. } => { prefix.as_ref().into() } } } fn into_iter(self) -> Peekable<SortedLoosePaths> { match self { IterInfo::Base { base } => SortedLoosePaths::at(base.join("refs"), base, None), IterInfo::BaseAndIterRoot { base, iter_root, prefix: _, } => SortedLoosePaths::at(iter_root, base, None), IterInfo::PrefixAndBase { base, prefix } => SortedLoosePaths::at(base.join(prefix), base, None), IterInfo::ComputedIterationRoot { iter_root, base, prefix: _, remainder, } => SortedLoosePaths::at(iter_root, base, remainder), } .peekable() } fn from_prefix(base: &'a Path, prefix: Cow<'a, Path>) -> std::io::Result<Self> { if prefix.is_absolute() { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "prefix must be a relative path, like 'refs/heads'", )); } use std::path::Component::*; if prefix.components().any(|c| matches!(c, CurDir | ParentDir)) { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "Refusing to handle prefixes with relative path components", )); } let iter_root = base.join(prefix.as_ref()); if iter_root.is_dir() { Ok(IterInfo::BaseAndIterRoot { base, iter_root, prefix, }) } else { let filename_prefix = iter_root .file_name() .map(ToOwned::to_owned) .map(|p| { git_path::try_into_bstr(PathBuf::from(p)) .map(|p| p.into_owned()) .map_err(|_| { std::io::Error::new(std::io::ErrorKind::InvalidInput, "prefix contains ill-formed UTF-8") }) }) .transpose()?; let iter_root = iter_root .parent() .expect("a parent is always there unless empty") .to_owned(); Ok(IterInfo::ComputedIterationRoot { base, prefix, iter_root, remainder: filename_prefix, }) } }} 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_from_info( IterInfo::PrefixAndBase { base: self.git_dir(), prefix: namespace.to_path(), }, self.common_dir().map(|base| IterInfo::PrefixAndBase { base, prefix: namespace.to_path(), }), packed, ), None => self.iter_from_info( IterInfo::Base { base: self.git_dir() }, self.common_dir().map(|base| IterInfo::Base { base }), packed, ), } } /// 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 prefix = prefix.as_ref(); let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.into())?; let common_dir_info = self .common_dir() .map(|base| IterInfo::from_prefix(base, prefix.into())) .transpose()?; self.iter_from_info(git_dir_info, common_dir_info, packed) } Some(namespace) => { let prefix = namespace.to_owned().into_namespaced_prefix(prefix); let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.clone().into())?; let common_dir_info = self .common_dir() .map(|base| IterInfo::from_prefix(base, prefix.into())) .transpose()?; self.iter_from_info(git_dir_info, common_dir_info, packed) } } } fn iter_from_info<'s, 'p>( &'s self, git_dir_info: IterInfo<'_>, common_dir_info: Option<IterInfo<'_>>, packed: Option<&'p packed::Buffer>, ) -> std::io::Result<LooseThenPacked<'p, 's>> { Ok(LooseThenPacked { git_dir: self.git_dir(), common_dir: self.common_dir(), iter_packed: match packed { Some(packed) => Some( match git_dir_info.prefix() { Some(prefix) => packed.iter_prefixed(path_to_name(prefix).into_owned()), None => packed.iter(), } .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? .peekable(), ), None => None, }, iter_git_dir: git_dir_info.into_iter(), iter_common_dir: common_dir_info.map(IterInfo::into_iter), 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, path: PathBuf} { display("The ref file '{}' could not be read in full", path.display()) 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::{
borrow::Cow,
cmp::Ordering,
io::Read,
iter::Peekable,
path::{Path, PathBuf},
};
use crate::{
file::{loose, loose::iter::SortedLoosePaths, 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> {
git_dir: &'s Path,
common_dir: Option<&'s Path>,
namespace: Option<&'s Namespace>,
iter_packed: Option<Peekable<packed::Iter<'p>>>,
iter_git_dir: Peekable<SortedLoosePaths>,
#[allow(dead_code)]
iter_common_dir: Option<Peekable<SortedLoosePaths>>,
buf: Vec<u8>,
}
enum IterKind {
Git,
GitAndConsumeCommon,
Common,
}
/// 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<file::packed::SharedBufferSnapshot>,
}
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 loose_iter(&mut self, kind: IterKind) -> &mut Peekable<SortedLoosePaths> {
match kind {
IterKind::GitAndConsumeCommon => {
drop(self.iter_common_dir.as_mut().map(|iter| iter.next()));
&mut self.iter_git_dir
}
IterKind::Git => &mut self.iter_git_dir,
IterKind::Common => self
.iter_common_dir
.as_mut()
.expect("caller knows there is a common iter"),
}
}
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(|err| Error::ReadFileContents {
err,
path: refpath.to_owned(),
})?;
loose::Reference::try_from_path(name, &self.buf)
.map_err(|err| {
let relative_path = refpath
.strip_prefix(&self.git_dir)
.ok()
.or_else(|| {
self.common_dir
.and_then(|common_dir| refpath.strip_prefix(common_dir).ok())
})
.expect("one of our bases contains the path");
Error::ReferenceCreation {
err,
relative_path: relative_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> {
fn advance_to_non_private(iter: &mut Peekable<SortedLoosePaths>) {
while let Some(Ok((_path, name))) = iter.peek() {
if name.category().map_or(true, |cat| cat.is_worktree_private()) {
iter.next();
} else {
break;
}
}
}
fn peek_loose<'a>(
git_dir: &'a mut Peekable<SortedLoosePaths>,
common_dir: Option<&'a mut Peekable<SortedLoosePaths>>,
) -> Option<(&'a std::io::Result<(PathBuf, FullName)>, IterKind)> {
match common_dir {
Some(common_dir) => match (git_dir.peek(), {
advance_to_non_private(common_dir);
common_dir.peek()
}) {
(None, None) => None,
(None, Some(res)) | (Some(_), Some(res @ Err(_))) => Some((res, IterKind::Common)),
(Some(res), None) | (Some(res @ Err(_)), Some(_)) => Some((res, IterKind::Git)),
(Some(r_gitdir @ Ok((_, git_dir_name))), Some(r_cd @ Ok((_, common_dir_name)))) => {
match git_dir_name.cmp(common_dir_name) {
Ordering::Less => Some((r_gitdir, IterKind::Git)),
Ordering::Equal => Some((r_gitdir, IterKind::GitAndConsumeCommon)),
Ordering::Greater => Some((r_cd, IterKind::Common)),
}
}
},
None => git_dir.peek().map(|r| (r, IterKind::Git)),
}
}
match self.iter_packed.as_mut() {
Some(packed_iter) => match (
peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()),
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((_, kind)), None) | (Some((Err(_), kind)), Some(_)) => {
let res = self.loose_iter(kind).next().expect("prior peek");
Some(self.convert_loose(res))
}
(Some((Ok((_, loose_name)), kind)), Some(Ok(packed))) => match loose_name.as_ref().cmp(packed.name) {
Ordering::Less => {
let res = self.loose_iter(kind).next().expect("prior peek");
Some(self.convert_loose(res))
}
Ordering::Equal => {
drop(packed_iter.next());
let res = self.loose_iter(kind).next().expect("prior peek");
Some(self.convert_loose(res))
}
Ordering::Greater => {
let res = packed_iter.next().expect("name retrieval configured");
Some(self.convert_packed(res))
}
},
},
None => match peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()) {
None => None,
Some((_, kind)) => self.loose_iter(kind).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_ref().map(|b| &***b))
}
/// 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_ref().map(|b| &***b))
}
}
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()?,
})
}
}
#[derive(Debug)]
pub(crate) enum IterInfo<'a> {
Base {
base: &'a Path,
},
BaseAndIterRoot {
base: &'a Path,
iter_root: PathBuf,
prefix: Cow<'a, Path>,
},
PrefixAndBase {
base: &'a Path,
prefix: &'a Path,
},
ComputedIterationRoot {
/// The root to iterate over
iter_root: PathBuf,
/// The top-level directory as boundary of all references, used to create their short-names after iteration
base: &'a Path,
/// The original prefix
prefix: Cow<'a, Path>,
/// The remainder of the prefix that wasn't a valid path
remainder: Option<BString>,
},
}
impl<'a> IterInfo<'a> {
fn prefix(&self) -> Option<&Path> {
match self {
IterInfo::Base { .. } => None,
IterInfo::PrefixAndBase { prefix, .. } => Some(*prefix),
IterInfo::ComputedIterationRoot { prefix, .. } | IterInfo::BaseAndIterRoot { prefix, .. } => {
prefix.as_ref().into()
}
}
}
fn into_iter(self) -> Peekable<SortedLoosePaths> {
match self {
IterInfo::Base { base } => SortedLoosePaths::at(base.join("refs"), base, None),
IterInfo::BaseAndIterRoot {
base,
iter_root,
prefix: _,
} => SortedLoosePaths::at(iter_root, base, None),
IterInfo::PrefixAndBase { base, prefix } => SortedLoosePaths::at(base.join(prefix), base, None),
IterInfo::ComputedIterationRoot {
iter_root,
base,
prefix: _,
remainder,
} => SortedLoosePaths::at(iter_root, base, remainder),
}
.peekable()
}
fn from_prefix(base: &'a Path, prefix: Cow<'a, Path>) -> std::io::Result<Self> {
if prefix.is_absolute() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"prefix must be a relative path, like 'refs/heads'",
));
}
use std::path::Component::*;
if prefix.components().any(|c| matches!(c, CurDir | ParentDir)) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Refusing to handle prefixes with relative path components",
));
}
let iter_root = base.join(prefix.as_ref());
if iter_root.is_dir() {
Ok(IterInfo::BaseAndIterRoot {
base,
iter_root,
prefix,
})
} else {
let filename_prefix = iter_root
.file_name()
.map(ToOwned::to_owned)
.map(|p| {
git_path::try_into_bstr(PathBuf::from(p))
.map(|p| p.into_owned())
.map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "prefix contains ill-formed UTF-8")
})
})
.transpose()?;
let iter_root = iter_root
.parent()
.expect("a parent is always there unless empty")
.to_owned();
Ok(IterInfo::ComputedIterationRoot {
base,
prefix,
iter_root,
remainder: filename_prefix,
})
}
}
}
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_from_info(
IterInfo::PrefixAndBase {
base: self.git_dir(),
prefix: namespace.to_path(),
},
self.common_dir().map(|base| IterInfo::PrefixAndBase {
base,
prefix: namespace.to_path(),
}),
packed,
),
None => self.iter_from_info(
IterInfo::Base { base: self.git_dir() },
self.common_dir().map(|base| IterInfo::Base { base }),
packed,
),
}
}
/// 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 prefix = prefix.as_ref();
let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.into())?;
let common_dir_info = self
.common_dir()
.map(|base| IterInfo::from_prefix(base, prefix.into()))
.transpose()?;
self.iter_from_info(git_dir_info, common_dir_info, packed)
}
Some(namespace) => {
let prefix = namespace.to_owned().into_namespaced_prefix(prefix);
let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.clone().into())?;
let common_dir_info = self
.common_dir()
.map(|base| IterInfo::from_prefix(base, prefix.into()))
.transpose()?;
self.iter_from_info(git_dir_info, common_dir_info, packed)
}
}
}
fn iter_from_info<'s, 'p>(
&'s self,
git_dir_info: IterInfo<'_>,
common_dir_info: Option<IterInfo<'_>>,
packed: Option<&'p packed::Buffer>,
) -> std::io::Result<LooseThenPacked<'p, 's>> {
Ok(LooseThenPacked {
git_dir: self.git_dir(),
common_dir: self.common_dir(),
iter_packed: match packed {
Some(packed) => Some(
match git_dir_info.prefix() {
Some(prefix) => packed.iter_prefixed(path_to_name(prefix).into_owned()),
None => packed.iter(),
}
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?
.peekable(),
),
None => None,
},
iter_git_dir: git_dir_info.into_iter(),
iter_common_dir: common_dir_info.map(IterInfo::into_iter),
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, path: PathBuf} {
display("The ref file '{}' could not be read in full", path.display())
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;