use std::path::PathBuf;
use super::{
BackendEntry, DirectoryBackend, WalkEntry, Walker, gitignore::IgnoreScope, glob_path_bytes,
has_hidden_component, should_skip_git_directory,
};
pub(crate) enum EntryAction {
Skip,
Descend(DirectoryTask),
Emit(WalkEntry),
DescendAndEmit(WalkEntry, DirectoryTask),
Failed {
failure: EntryFailure,
descend: Option<DirectoryTask>,
},
}
#[derive(Debug)]
pub(crate) struct DirectoryTask {
pub(crate) path: PathBuf,
pub(crate) ignores: IgnoreScope,
}
pub(crate) struct EntryFailure {
pub(crate) operation: &'static str,
pub(crate) path: PathBuf,
pub(crate) source: std::io::Error,
}
fn should_emit(walker: &Walker, entry: &BackendEntry, bytes: &[u8], git_ignored: bool) -> bool {
if git_ignored {
return false;
}
if walker.options.directories_only && !entry.is_dir {
return false;
}
if walker.options.files_only && entry.is_dir {
return false;
}
walker.includes.is_empty()
|| walker
.includes
.iter()
.any(|pattern| pattern.matches(bytes, entry.is_dir))
}
pub(crate) fn classify_entry<B: DirectoryBackend + ?Sized>(
walker: &Walker,
backend: &B,
mut entry: BackendEntry,
ignores: &IgnoreScope,
) -> EntryAction {
let relative = entry
.path
.strip_prefix(&walker.root)
.unwrap_or(entry.path.as_path());
let depth = relative.components().count();
if walker
.options
.max_depth
.is_some_and(|max_depth| depth > max_depth)
{
return EntryAction::Skip;
}
let bytes = glob_path_bytes(relative);
if walker.options.skip_hidden && has_hidden_component(bytes.as_ref()) {
return EntryAction::Skip;
}
if should_skip_git_directory(walker, &entry.path) {
return EntryAction::Skip;
}
if walker
.excludes
.iter()
.any(|pattern| pattern.matches(bytes.as_ref(), entry.is_dir))
{
return EntryAction::Skip;
}
let git_ignored = ignores.is_ignored(&entry.path, entry.is_dir);
if git_ignored && !entry.is_dir {
return EntryAction::Skip;
}
if entry.is_symlink && walker.options.follow_symlinks {
match backend.metadata(&entry.path) {
Ok(metadata) => entry.is_dir = metadata.is_dir(),
Err(source) => {
return EntryAction::Failed {
failure: EntryFailure {
operation: "metadata",
path: entry.path,
source,
},
descend: None,
};
}
}
}
if !entry.is_dir && !walker.may_include_file(bytes.as_ref()) {
return EntryAction::Skip;
}
let descend = entry.is_dir
&& !git_ignored
&& !walker
.excludes
.iter()
.any(|pattern| pattern.covers_subtree(bytes.as_ref()))
&& walker.may_descend_at(depth, bytes.as_ref());
let emit = should_emit(walker, &entry, bytes.as_ref(), git_ignored);
let task = |path| DirectoryTask {
path,
ignores: ignores.clone(),
};
if !emit {
if descend {
return EntryAction::Descend(task(entry.path));
}
return EntryAction::Skip;
}
let metadata = if walker.options.metadata {
match backend.symlink_metadata(&entry.path) {
Ok(metadata) => Some(metadata),
Err(source) => {
return EntryAction::Failed {
descend: descend.then(|| task(entry.path.clone())),
failure: EntryFailure {
operation: "symlink_metadata",
path: entry.path,
source,
},
};
}
}
} else {
None
};
let walk_entry = WalkEntry {
path: entry.path,
is_dir: entry.is_dir,
is_symlink: entry.is_symlink,
depth,
metadata,
};
if descend {
let task = task(walk_entry.path.clone());
EntryAction::DescendAndEmit(walk_entry, task)
} else {
EntryAction::Emit(walk_entry)
}
}