use std::path::PathBuf; use super::Iter;use crate::{ PathspecDetached, Repository, bstr::BString, dirwalk, util::OwnedOrStaticAtomicBool, worktree::IndexPersistedOrInMemory,}; /// An entry of the directory walk as returned by the [iterator](Iter).pub struct Item { /// The directory entry. pub entry: gix_dir::Entry, /// `collapsed_directory_status` is `Some(dir_status)` if this entry was part of a directory with the given /// `dir_status` that wasn't the same as the one of `entry` and if [gix_dir::walk::Options::emit_collapsed] was /// [gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch]. It will also be `Some(dir_status)` if that option /// was [gix_dir::walk::CollapsedEntriesEmissionMode::All]. pub collapsed_directory_status: Option<gix_dir::entry::Status>,} impl Item { fn new(entry: gix_dir::EntryRef<'_>, collapsed_directory_status: Option<gix_dir::entry::Status>) -> Self { Item { entry: entry.to_owned(), collapsed_directory_status, } }} /// The outcome of fully consumed [dirwalk iterator](Iter).pub struct Outcome { /// The index originally passed in to create the iterator. pub index: IndexPersistedOrInMemory, /// The excludes stack used for the dirwalk, for access of `.gitignore` information. pub excludes: gix_worktree::Stack, /// The pathspecs used to guide the operation, pub pathspec: PathspecDetached, /// The root actually being used for the traversal, and useful to transform the paths returned for the user. /// It's always within the [`work-dir`](Repository::workdir). pub traversal_root: PathBuf, /// The actual result of the dirwalk. pub dirwalk: gix_dir::walk::Outcome,} /// The error returned by [Repository::dirwalk_iter()].#[derive(Debug, thiserror::Error)]#[expect(missing_docs)]pub enum Error { #[error("Failed to spawn producer thread")] #[cfg(feature = "parallel")] SpawnThread(#[from] std::io::Error), #[error(transparent)] #[cfg(not(feature = "parallel"))] Dirwalk(#[from] dirwalk::Error), #[error(transparent)] #[cfg(not(feature = "parallel"))] DetachPathSpec(#[from] std::io::Error),} /// Lifecycleimpl Iter { pub(crate) fn new( repo: &Repository, index: IndexPersistedOrInMemory, patterns: Vec<BString>, should_interrupt: OwnedOrStaticAtomicBool, options: dirwalk::Options, ) -> Result<Iter, Error> { #[cfg(feature = "parallel")] { let repo = repo.clone().into_sync(); let (tx, rx) = std::sync::mpsc::channel(); let handle = std::thread::Builder::new() .name("gix::dirwalk::iter::producer".into()) .spawn({ let should_interrupt = should_interrupt.clone(); move || -> Result<Outcome, dirwalk::Error> { let repo: Repository = repo.into(); let mut collect = Collect { tx }; let out = repo.dirwalk(&index, patterns, &should_interrupt, options, &mut collect)?; Ok(Outcome { index, excludes: out.excludes.detach(), pathspec: out.pathspec.detach().map_err(|err| { dirwalk::Error::Walk(gix_dir::walk::Error::ReadDir { path: repo.git_dir().to_owned(), source: err, }) })?, traversal_root: out.traversal_root, dirwalk: out.dirwalk, }) } })?; Ok(Iter { rx_and_join: Some((rx, handle)), should_interrupt, out: None, }) } #[cfg(not(feature = "parallel"))] { let mut collect = Collect { items: Vec::new() }; let out = repo.dirwalk(&index, patterns, &should_interrupt, options, &mut collect)?; let out = Outcome { index, excludes: out.excludes.detach(), pathspec: out.pathspec.detach()?, traversal_root: out.traversal_root, dirwalk: out.dirwalk, }; Ok(Iter { items: collect.items.into_iter(), out: Some(out), }) } }} /// Accessimpl Iter { /// Return the outcome of the iteration, or `None` if the iterator isn't fully consumed. pub fn outcome_mut(&mut self) -> Option<&mut Outcome> { self.out.as_mut() } /// Turn the iterator into the iteration outcome, which is `None` on error or if the iteration /// isn't complete. pub fn into_outcome(mut self) -> Option<Outcome> { self.out.take() }} impl Iterator for Iter { type Item = Result<Item, dirwalk::Error>; fn next(&mut self) -> Option<Self::Item> { #[cfg(feature = "parallel")] { let (rx, _join) = self.rx_and_join.as_ref()?; match rx.recv().ok() { Some(item) => Some(Ok(item)), None => { let (_rx, handle) = self.rx_and_join.take()?; match handle.join().expect("no panic") { Ok(out) => { self.out = Some(out); None } Err(err) => Some(Err(err)), } } } } #[cfg(not(feature = "parallel"))] self.items.next().map(Ok) }} #[cfg(feature = "parallel")]impl Drop for Iter { fn drop(&mut self) { crate::util::parallel_iter_drop( self.rx_and_join .take() .map(|(rx, handle)| (rx, handle, None::<std::thread::JoinHandle<()>>)), &self.should_interrupt, ); }} struct Collect { #[cfg(feature = "parallel")] tx: std::sync::mpsc::Sender<Item>, #[cfg(not(feature = "parallel"))] items: Vec<Item>,} impl gix_dir::walk::Delegate for Collect { fn emit( &mut self, entry: gix_dir::EntryRef<'_>, collapsed_directory_status: Option<gix_dir::entry::Status>, ) -> gix_dir::walk::Action { // NOTE: we assume that the receiver triggers interruption so the operation will stop if the receiver is down. let item = Item::new(entry, collapsed_directory_status); #[cfg(feature = "parallel")] self.tx.send(item).ok(); #[cfg(not(feature = "parallel"))] self.items.push(item); std::ops::ControlFlow::Continue(()) }}
use std::path::PathBuf;
use super::Iter;
use crate::{
PathspecDetached, Repository, bstr::BString, dirwalk, util::OwnedOrStaticAtomicBool,
worktree::IndexPersistedOrInMemory,
};
/// An entry of the directory walk as returned by the [iterator](Iter).
pub struct Item {
/// The directory entry.
pub entry: gix_dir::Entry,
/// `collapsed_directory_status` is `Some(dir_status)` if this entry was part of a directory with the given
/// `dir_status` that wasn't the same as the one of `entry` and if [gix_dir::walk::Options::emit_collapsed] was
/// [gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch]. It will also be `Some(dir_status)` if that option
/// was [gix_dir::walk::CollapsedEntriesEmissionMode::All].
pub collapsed_directory_status: Option<gix_dir::entry::Status>,
}
impl Item {
fn new(entry: gix_dir::EntryRef<'_>, collapsed_directory_status: Option<gix_dir::entry::Status>) -> Self {
Item {
entry: entry.to_owned(),
collapsed_directory_status,
}
}
}
/// The outcome of fully consumed [dirwalk iterator](Iter).
pub struct Outcome {
/// The index originally passed in to create the iterator.
pub index: IndexPersistedOrInMemory,
/// The excludes stack used for the dirwalk, for access of `.gitignore` information.
pub excludes: gix_worktree::Stack,
/// The pathspecs used to guide the operation,
pub pathspec: PathspecDetached,
/// The root actually being used for the traversal, and useful to transform the paths returned for the user.
/// It's always within the [`work-dir`](Repository::workdir).
pub traversal_root: PathBuf,
/// The actual result of the dirwalk.
pub dirwalk: gix_dir::walk::Outcome,
}
/// The error returned by [Repository::dirwalk_iter()].
#[derive(Debug, thiserror::Error)]
#[expect(missing_docs)]
pub enum Error {
#[error("Failed to spawn producer thread")]
#[cfg(feature = "parallel")]
SpawnThread(#[from] std::io::Error),
#[error(transparent)]
#[cfg(not(feature = "parallel"))]
Dirwalk(#[from] dirwalk::Error),
#[error(transparent)]
#[cfg(not(feature = "parallel"))]
DetachPathSpec(#[from] std::io::Error),
}
/// Lifecycle
impl Iter {
pub(crate) fn new(
repo: &Repository,
index: IndexPersistedOrInMemory,
patterns: Vec<BString>,
should_interrupt: OwnedOrStaticAtomicBool,
options: dirwalk::Options,
) -> Result<Iter, Error> {
#[cfg(feature = "parallel")]
{
let repo = repo.clone().into_sync();
let (tx, rx) = std::sync::mpsc::channel();
let handle = std::thread::Builder::new()
.name("gix::dirwalk::iter::producer".into())
.spawn({
let should_interrupt = should_interrupt.clone();
move || -> Result<Outcome, dirwalk::Error> {
let repo: Repository = repo.into();
let mut collect = Collect { tx };
let out = repo.dirwalk(&index, patterns, &should_interrupt, options, &mut collect)?;
Ok(Outcome {
index,
excludes: out.excludes.detach(),
pathspec: out.pathspec.detach().map_err(|err| {
dirwalk::Error::Walk(gix_dir::walk::Error::ReadDir {
path: repo.git_dir().to_owned(),
source: err,
})
})?,
traversal_root: out.traversal_root,
dirwalk: out.dirwalk,
})
}
})?;
Ok(Iter {
rx_and_join: Some((rx, handle)),
should_interrupt,
out: None,
})
}
#[cfg(not(feature = "parallel"))]
{
let mut collect = Collect { items: Vec::new() };
let out = repo.dirwalk(&index, patterns, &should_interrupt, options, &mut collect)?;
let out = Outcome {
index,
excludes: out.excludes.detach(),
pathspec: out.pathspec.detach()?,
traversal_root: out.traversal_root,
dirwalk: out.dirwalk,
};
Ok(Iter {
items: collect.items.into_iter(),
out: Some(out),
})
}
}
}
/// Access
impl Iter {
/// Return the outcome of the iteration, or `None` if the iterator isn't fully consumed.
pub fn outcome_mut(&mut self) -> Option<&mut Outcome> {
self.out.as_mut()
}
/// Turn the iterator into the iteration outcome, which is `None` on error or if the iteration
/// isn't complete.
pub fn into_outcome(mut self) -> Option<Outcome> {
self.out.take()
}
}
impl Iterator for Iter {
type Item = Result<Item, dirwalk::Error>;
fn next(&mut self) -> Option<Self::Item> {
#[cfg(feature = "parallel")]
{
let (rx, _join) = self.rx_and_join.as_ref()?;
match rx.recv().ok() {
Some(item) => Some(Ok(item)),
None => {
let (_rx, handle) = self.rx_and_join.take()?;
match handle.join().expect("no panic") {
Ok(out) => {
self.out = Some(out);
None
}
Err(err) => Some(Err(err)),
}
}
}
}
#[cfg(not(feature = "parallel"))]
self.items.next().map(Ok)
}
}
#[cfg(feature = "parallel")]
impl Drop for Iter {
fn drop(&mut self) {
crate::util::parallel_iter_drop(
self.rx_and_join
.take()
.map(|(rx, handle)| (rx, handle, None::<std::thread::JoinHandle<()>>)),
&self.should_interrupt,
);
}
}
struct Collect {
#[cfg(feature = "parallel")]
tx: std::sync::mpsc::Sender<Item>,
#[cfg(not(feature = "parallel"))]
items: Vec<Item>,
}
impl gix_dir::walk::Delegate for Collect {
fn emit(
&mut self,
entry: gix_dir::EntryRef<'_>,
collapsed_directory_status: Option<gix_dir::entry::Status>,
) -> gix_dir::walk::Action {
// NOTE: we assume that the receiver triggers interruption so the operation will stop if the receiver is down.
let item = Item::new(entry, collapsed_directory_status);
#[cfg(feature = "parallel")]
self.tx.send(item).ok();
#[cfg(not(feature = "parallel"))]
self.items.push(item);
std::ops::ControlFlow::Continue(())
}
}