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, } impl Item { fn new(entry: gix_dir::EntryRef<'_>, collapsed_directory_status: Option) -> 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, should_interrupt: OwnedOrStaticAtomicBool, options: dirwalk::Options, ) -> Result { #[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 { 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 { self.out.take() } } impl Iterator for Iter { type Item = Result; fn next(&mut self) -> Option { #[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::>)), &self.should_interrupt, ); } } struct Collect { #[cfg(feature = "parallel")] tx: std::sync::mpsc::Sender, #[cfg(not(feature = "parallel"))] items: Vec, } impl gix_dir::walk::Delegate for Collect { fn emit( &mut self, entry: gix_dir::EntryRef<'_>, collapsed_directory_status: Option, ) -> 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(()) } }