rebase ignore crate from 0.4.24 to 0.4.33
This commit is contained in:
parent
8ac18c7e72
commit
a4d3f804ce
13 changed files with 2699 additions and 657 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -40,7 +40,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-automata 0.4.18",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -241,14 +241,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.17"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
|
||||
checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-automata 0.4.18",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ dependencies = [
|
|||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-automata 0.4.18",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
|
|
@ -281,7 +281,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.24"
|
||||
version = "0.4.33"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"crossbeam-channel",
|
||||
|
|
@ -290,7 +290,7 @@ dependencies = [
|
|||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-automata 0.4.18",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
|
|
@ -517,7 +517,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
|||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-automata 0.4.18",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
|
|
@ -532,9 +532,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -646,7 +646,7 @@ dependencies = [
|
|||
"dunce",
|
||||
"fast-glob",
|
||||
"globwalk",
|
||||
"ignore 0.4.24",
|
||||
"ignore 0.4.33",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"rayon",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ignore"
|
||||
version = "0.4.24" #:version
|
||||
version = "0.4.33" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
A fast library for efficiently matching ignore files such as `.gitignore`
|
||||
|
|
@ -12,7 +12,10 @@ repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore"
|
|||
readme = "README.md"
|
||||
keywords = ["glob", "ignore", "gitignore", "pattern", "file"]
|
||||
license = "Unlicense OR MIT"
|
||||
# CHANGED: Use an explicit edition instead of `edition.workspace = true` since this crate is
|
||||
# vendored into the Tailwind CSS workspace.
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
|
||||
[lib]
|
||||
name = "ignore"
|
||||
|
|
@ -20,15 +23,17 @@ bench = false
|
|||
|
||||
[dependencies]
|
||||
crossbeam-deque = "0.8.3"
|
||||
globset = "0.4.17"
|
||||
# CHANGED: Use the published globset crate instead of a path dependency.
|
||||
globset = "0.4.20"
|
||||
log = "0.4.20"
|
||||
memchr = "2.6.3"
|
||||
same-file = "1.0.6"
|
||||
walkdir = "2.4.0"
|
||||
# CHANGED: Added `dunce` to canonicalize paths without UNC prefixes on Windows.
|
||||
dunce = "1.0.5"
|
||||
|
||||
[dependencies.regex-automata]
|
||||
version = "0.4.0"
|
||||
version = "0.4.18"
|
||||
default-features = false
|
||||
features = ["std", "perf", "syntax", "meta", "nfa", "hybrid", "dfa-onepass"]
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ fn main() {
|
|||
let stdout_thread = std::thread::spawn(move || {
|
||||
let mut stdout = std::io::BufWriter::new(std::io::stdout());
|
||||
for dent in rx {
|
||||
stdout
|
||||
.write_all(&Vec::from_path_lossy(dent.path()))
|
||||
.unwrap();
|
||||
stdout.write_all(&Vec::from_path_lossy(dent.path())).unwrap();
|
||||
stdout.write_all(b"\n").unwrap();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
|||
(&["cml"], &["*.cml"]),
|
||||
(&["coffeescript"], &["*.coffee"]),
|
||||
(&["config"], &["*.cfg", "*.conf", "*.config", "*.ini"]),
|
||||
(&["container"], &["*Containerfile*", "*Dockerfile*"]),
|
||||
(&["coq"], &["*.v"]),
|
||||
(&["cpp"], &[
|
||||
"*.[ChH]", "*.cc", "*.[ch]pp", "*.[ch]xx", "*.hh", "*.inl",
|
||||
|
|
@ -109,6 +110,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
|||
(&["hbs"], &["*.hbs"]),
|
||||
(&["hs"], &["*.hs", "*.lhs"]),
|
||||
(&["html"], &["*.htm", "*.html", "*.ejs"]),
|
||||
(&["hurl"], &["*.hurl"]),
|
||||
(&["hy"], &["*.hy"]),
|
||||
(&["idris"], &["*.idr", "*.lidr"]),
|
||||
(&["janet"], &["*.janet"]),
|
||||
|
|
@ -185,6 +187,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
|||
(&["mint"], &["*.mint"]),
|
||||
(&["mk"], &["mkfile"]),
|
||||
(&["ml"], &["*.ml"]),
|
||||
(&["mojo"], &["*.mojo"]),
|
||||
(&["motoko"], &["*.mo"]),
|
||||
(&["msbuild"], &[
|
||||
"*.csproj", "*.fsproj", "*.vcxproj", "*.proj", "*.props", "*.targets",
|
||||
|
|
@ -206,11 +209,12 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
|||
"*.php", "*.php3", "*.php4", "*.php5", "*.php7", "*.php8",
|
||||
"*.pht", "*.phtml"
|
||||
]),
|
||||
(&["pkgbuild"], &["PKGBUILD"]),
|
||||
(&["po"], &["*.po"]),
|
||||
(&["pod"], &["*.pod"]),
|
||||
(&["postscript"], &["*.eps", "*.ps"]),
|
||||
(&["prolog"], &["*.pl", "*.pro", "*.prolog", "*.P"]),
|
||||
(&["protobuf"], &["*.proto"]),
|
||||
(&["proto", "protobuf"], &["*.proto"]),
|
||||
(&["ps"], &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
||||
(&["puppet"], &["*.epp", "*.erb", "*.pp", "*.rb"]),
|
||||
(&["purs"], &["*.purs"]),
|
||||
|
|
@ -231,6 +235,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
|||
(&["red"], &["*.r", "*.red", "*.reds"]),
|
||||
(&["rescript"], &["*.res", "*.resi"]),
|
||||
(&["robot"], &["*.robot"]),
|
||||
(&["rocq"], &["*.v"]),
|
||||
(&["rst"], &["*.rst"]),
|
||||
(&["ruby"], &[
|
||||
// Idiomatic files
|
||||
|
|
@ -274,6 +279,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
|||
(&["spark"], &["*.spark"]),
|
||||
(&["spec"], &["*.spec"]),
|
||||
(&["sql"], &["*.sql", "*.psql"]),
|
||||
(&["ssa"], &["*.ssa"]),
|
||||
(&["stylus"], &["*.styl"]),
|
||||
(&["sv"], &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||
(&["svelte"], &["*.svelte", "*.svelte.ts"]),
|
||||
|
|
@ -359,4 +365,14 @@ mod tests {
|
|||
previous_name = name;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_types_aliases_are_sorted() {
|
||||
for (aliases, _) in DEFAULT_TYPES.iter() {
|
||||
assert!(
|
||||
aliases.is_sorted(),
|
||||
"this alias list is not sorted: {aliases:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -102,7 +102,9 @@ impl Gitignore {
|
|||
///
|
||||
/// Note that I/O errors are ignored. For more granular control over
|
||||
/// errors, use `GitignoreBuilder`.
|
||||
pub fn new<P: AsRef<Path>>(gitignore_path: P) -> (Gitignore, Option<Error>) {
|
||||
pub fn new<P: AsRef<Path>>(
|
||||
gitignore_path: P,
|
||||
) -> (Gitignore, Option<Error>) {
|
||||
let path = gitignore_path.as_ref();
|
||||
let parent = path.parent().unwrap_or(Path::new("/"));
|
||||
let mut builder = GitignoreBuilder::new(parent);
|
||||
|
|
@ -123,6 +125,17 @@ impl Gitignore {
|
|||
/// The global config file path is specified by git's `core.excludesFile`
|
||||
/// config option.
|
||||
///
|
||||
/// # Behavior
|
||||
///
|
||||
/// This routine does its best to discover any global git exclude files.
|
||||
/// This will try to parse out the `excludesFile` config option in your
|
||||
/// global git configuration, if necessary.
|
||||
///
|
||||
/// The specific things this routine tries (which are subject to change
|
||||
/// based on how git behaves) are:
|
||||
///
|
||||
///
|
||||
///
|
||||
/// Git's config file location is `$HOME/.gitconfig`. If `$HOME/.gitconfig`
|
||||
/// does not exist or does not specify `core.excludesFile`, then
|
||||
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
|
||||
|
|
@ -145,7 +158,8 @@ impl Gitignore {
|
|||
num_ignores: 0,
|
||||
num_whitelists: 0,
|
||||
matches: None,
|
||||
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
|
||||
// CHANGED: Add a flag to have Gitignore rules that apply only to
|
||||
// files.
|
||||
only_on_files: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +204,11 @@ impl Gitignore {
|
|||
/// determined by a common suffix of the directory containing this
|
||||
/// gitignore) is stripped. If there is no common suffix/prefix overlap,
|
||||
/// then `path` is assumed to be relative to this matcher.
|
||||
pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match<&Glob> {
|
||||
pub fn matched<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<&Glob> {
|
||||
if self.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
|
|
@ -243,11 +261,16 @@ impl Gitignore {
|
|||
}
|
||||
|
||||
/// Like matched, but takes a path that has already been stripped.
|
||||
fn matched_stripped<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match<&Glob> {
|
||||
fn matched_stripped<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<&Glob> {
|
||||
if self.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
// CHANGED: Rules marked as only_on_files can not match against directories.
|
||||
// CHANGED: Rules marked as only_on_files can not match against
|
||||
// directories.
|
||||
if self.only_on_files && is_dir {
|
||||
return Match::None;
|
||||
}
|
||||
|
|
@ -270,7 +293,10 @@ impl Gitignore {
|
|||
|
||||
/// Strips the given path such that it's suitable for matching with this
|
||||
/// gitignore matcher.
|
||||
fn strip<'a, P: 'a + AsRef<Path> + ?Sized>(&'a self, path: &'a P) -> &'a Path {
|
||||
fn strip<'a, P: 'a + AsRef<Path> + ?Sized>(
|
||||
&'a self,
|
||||
path: &'a P,
|
||||
) -> &'a Path {
|
||||
let mut path = path.as_ref();
|
||||
// A leading ./ is completely superfluous. We also strip it from
|
||||
// our gitignore root path, so we need to strip it from our candidate
|
||||
|
|
@ -326,7 +352,8 @@ impl GitignoreBuilder {
|
|||
globs: vec![],
|
||||
case_insensitive: false,
|
||||
allow_unclosed_class: true,
|
||||
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
|
||||
// CHANGED: Add a flag to have Gitignore rules that apply only to
|
||||
// files.
|
||||
only_on_files: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -337,18 +364,21 @@ impl GitignoreBuilder {
|
|||
pub fn build(&self) -> Result<Gitignore, Error> {
|
||||
let nignore = self.globs.iter().filter(|g| !g.is_whitelist()).count();
|
||||
let nwhite = self.globs.iter().filter(|g| g.is_whitelist()).count();
|
||||
let set = self.builder.build().map_err(|err| Error::Glob {
|
||||
glob: None,
|
||||
err: err.to_string(),
|
||||
})?;
|
||||
let set = self
|
||||
.builder
|
||||
.build()
|
||||
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
|
||||
Ok(Gitignore {
|
||||
set,
|
||||
root: self.root.clone(),
|
||||
globs: self.globs.clone(),
|
||||
num_ignores: nignore as u64,
|
||||
num_whitelists: nwhite as u64,
|
||||
matches: Some(Arc::new(Pool::new(|| vec![]))),
|
||||
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
|
||||
matches: Some(Arc::new(
|
||||
Pool::with_available_parallelism_capacity(|| vec![]),
|
||||
)),
|
||||
// CHANGED: Add a flag to have Gitignore rules that apply only to
|
||||
// files.
|
||||
only_on_files: self.only_on_files,
|
||||
})
|
||||
}
|
||||
|
|
@ -411,11 +441,8 @@ impl GitignoreBuilder {
|
|||
|
||||
// Match Git's handling of .gitignore files that begin with the Unicode BOM
|
||||
const UTF8_BOM: &str = "\u{feff}";
|
||||
let line = if i == 0 {
|
||||
line.trim_start_matches(UTF8_BOM)
|
||||
} else {
|
||||
&line
|
||||
};
|
||||
let line =
|
||||
if i == 0 { line.trim_start_matches(UTF8_BOM) } else { &line };
|
||||
|
||||
if let Err(err) = self.add_line(Some(path.to_path_buf()), &line) {
|
||||
errs.push(err.tagged(path, lineno));
|
||||
|
|
@ -537,7 +564,10 @@ impl GitignoreBuilder {
|
|||
/// affected.
|
||||
///
|
||||
/// This is disabled by default.
|
||||
pub fn case_insensitive(&mut self, yes: bool) -> Result<&mut GitignoreBuilder, Error> {
|
||||
pub fn case_insensitive(
|
||||
&mut self,
|
||||
yes: bool,
|
||||
) -> Result<&mut GitignoreBuilder, Error> {
|
||||
// TODO: This should not return a `Result`. Fix this in the next semver
|
||||
// release.
|
||||
self.case_insensitive = yes;
|
||||
|
|
@ -556,7 +586,10 @@ impl GitignoreBuilder {
|
|||
/// modes since the glob parser becomes more permissive. You might want to
|
||||
/// enable this when compatibility (e.g., with POSIX glob implementations)
|
||||
/// is more important than good error messages.
|
||||
pub fn allow_unclosed_class(&mut self, yes: bool) -> &mut GitignoreBuilder {
|
||||
pub fn allow_unclosed_class(
|
||||
&mut self,
|
||||
yes: bool,
|
||||
) -> &mut GitignoreBuilder {
|
||||
self.allow_unclosed_class = yes;
|
||||
self
|
||||
}
|
||||
|
|
@ -576,32 +609,56 @@ impl GitignoreBuilder {
|
|||
///
|
||||
/// Note that the file path returned may not exist.
|
||||
pub fn gitconfig_excludes_path() -> Option<PathBuf> {
|
||||
// git supports $HOME/.gitconfig and $XDG_CONFIG_HOME/git/config. Notably,
|
||||
// both can be active at the same time, where $HOME/.gitconfig takes
|
||||
// precedent. So if $HOME/.gitconfig defines a `core.excludesFile`, then
|
||||
// we're done.
|
||||
match gitconfig_home_contents().and_then(|x| parse_excludes_file(&x)) {
|
||||
Some(path) => return Some(path),
|
||||
None => {}
|
||||
// When GIT_CONFIG_GLOBAL is set, it replaces both $HOME/.gitconfig and
|
||||
// $XDG_CONFIG_HOME/git/config (per git 2.32+). Otherwise, git supports
|
||||
// $HOME/.gitconfig and $XDG_CONFIG_HOME/git/config simultaneously, where
|
||||
// $HOME/.gitconfig takes precedent.
|
||||
gitconfig_global_env_contents()
|
||||
.and_then(|x| parse_excludes_file(&x))
|
||||
.or_else(|| {
|
||||
gitconfig_home_contents().and_then(|x| parse_excludes_file(&x))
|
||||
})
|
||||
.or_else(|| {
|
||||
gitconfig_xdg_contents().and_then(|x| parse_excludes_file(&x))
|
||||
})
|
||||
// System-level config has the lowest priority for core.excludesFile.
|
||||
// GIT_CONFIG_SYSTEM overrides the default /etc/gitconfig path.
|
||||
.or_else(|| {
|
||||
gitconfig_system_contents().and_then(|x| parse_excludes_file(&x))
|
||||
})
|
||||
.or_else(excludes_file_default)
|
||||
}
|
||||
|
||||
/// Returns the file contents of git's global config file from the path
|
||||
/// specified by the `GIT_CONFIG_GLOBAL` environment variable.
|
||||
fn gitconfig_global_env_contents() -> Option<Vec<u8>> {
|
||||
let path = std::env::var_os("GIT_CONFIG_GLOBAL").map(PathBuf::from)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
return None;
|
||||
}
|
||||
match gitconfig_xdg_contents().and_then(|x| parse_excludes_file(&x)) {
|
||||
Some(path) => return Some(path),
|
||||
None => {}
|
||||
}
|
||||
excludes_file_default()
|
||||
let mut file = BufReader::new(File::open(path).ok()?);
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
||||
/// Returns the file contents of git's system-level config file.
|
||||
///
|
||||
/// Checks `GIT_CONFIG_SYSTEM` first, then falls back to `/etc/gitconfig`.
|
||||
fn gitconfig_system_contents() -> Option<Vec<u8>> {
|
||||
let path = std::env::var_os("GIT_CONFIG_SYSTEM")
|
||||
.map(PathBuf::from)
|
||||
.filter(|x| !x.as_os_str().is_empty())
|
||||
.unwrap_or_else(|| PathBuf::from("/etc/gitconfig"));
|
||||
let mut file = BufReader::new(File::open(path).ok()?);
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
||||
/// Returns the file contents of git's global config file, if one exists, in
|
||||
/// the user's home directory.
|
||||
fn gitconfig_home_contents() -> Option<Vec<u8>> {
|
||||
let home = match home_dir() {
|
||||
None => return None,
|
||||
Some(home) => home,
|
||||
};
|
||||
let mut file = match File::open(home.join(".gitconfig")) {
|
||||
Err(_) => return None,
|
||||
Ok(file) => BufReader::new(file),
|
||||
};
|
||||
let home = home_dir()?;
|
||||
let mut file = BufReader::new(File::open(home.join(".gitconfig")).ok()?);
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
|
@ -610,19 +667,11 @@ fn gitconfig_home_contents() -> Option<Vec<u8>> {
|
|||
/// the user's XDG_CONFIG_HOME directory.
|
||||
fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
|
||||
let path = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.and_then(|x| {
|
||||
if x.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(PathBuf::from(x))
|
||||
}
|
||||
})
|
||||
.map(PathBuf::from)
|
||||
.filter(|x| !x.as_os_str().is_empty())
|
||||
.or_else(|| home_dir().map(|p| p.join(".config")))
|
||||
.map(|x| x.join("git/config"));
|
||||
let mut file = match path.and_then(|p| File::open(p).ok()) {
|
||||
None => return None,
|
||||
Some(file) => BufReader::new(file),
|
||||
};
|
||||
.map(|x| x.join("git/config"))?;
|
||||
let mut file = BufReader::new(File::open(path).ok()?);
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
|
@ -632,13 +681,8 @@ fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
|
|||
/// Specifically, this respects XDG_CONFIG_HOME.
|
||||
fn excludes_file_default() -> Option<PathBuf> {
|
||||
std::env::var_os("XDG_CONFIG_HOME")
|
||||
.and_then(|x| {
|
||||
if x.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(PathBuf::from(x))
|
||||
}
|
||||
})
|
||||
.map(PathBuf::from)
|
||||
.filter(|x| !x.as_os_str().is_empty())
|
||||
.or_else(|| home_dir().map(|p| p.join(".config")))
|
||||
.map(|x| x.join("git/ignore"))
|
||||
}
|
||||
|
|
@ -667,9 +711,7 @@ fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
|||
re.captures(data, &mut caps);
|
||||
let span = caps.get_group(1)?;
|
||||
let candidate = &data[span];
|
||||
std::str::from_utf8(candidate)
|
||||
.ok()
|
||||
.map(|s| PathBuf::from(expand_tilde(s)))
|
||||
std::str::from_utf8(candidate).ok().map(|s| PathBuf::from(expand_tilde(s)))
|
||||
}
|
||||
|
||||
/// Expands ~ in file paths to the value of $HOME.
|
||||
|
|
@ -831,7 +873,10 @@ mod tests {
|
|||
fn parse_excludes_file4() {
|
||||
let data = bytes("[core]\nexcludesFile = \"~/foo/bar\"");
|
||||
let got = super::parse_excludes_file(&data);
|
||||
assert_eq!(path_string(got.unwrap()), super::expand_tilde("~/foo/bar"));
|
||||
assert_eq!(
|
||||
path_string(got.unwrap()),
|
||||
super::expand_tilde("~/foo/bar")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
1286
crates/ignore/src/incremental.rs
Normal file
1286
crates/ignore/src/incremental.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -48,13 +48,16 @@ See the documentation for `WalkBuilder` for many other options.
|
|||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub use crate::incremental::{IncrementalIgnore, IncrementalMatch};
|
||||
pub use crate::walk::{
|
||||
DirEntry, ParallelVisitor, ParallelVisitorBuilder, Walk, WalkBuilder, WalkParallel, WalkState,
|
||||
DirEntry, ParallelVisitor, ParallelVisitorBuilder, Walk, WalkBuilder,
|
||||
WalkParallel, WalkState,
|
||||
};
|
||||
|
||||
mod default_types;
|
||||
mod dir;
|
||||
pub mod gitignore;
|
||||
mod incremental;
|
||||
pub mod overrides;
|
||||
mod pathutil;
|
||||
pub mod types;
|
||||
|
|
@ -120,34 +123,31 @@ impl Clone for Error {
|
|||
fn clone(&self) -> Error {
|
||||
match *self {
|
||||
Error::Partial(ref errs) => Error::Partial(errs.clone()),
|
||||
Error::WithLineNumber { line, ref err } => Error::WithLineNumber {
|
||||
line,
|
||||
err: err.clone(),
|
||||
},
|
||||
Error::WithPath { ref path, ref err } => Error::WithPath {
|
||||
path: path.clone(),
|
||||
err: err.clone(),
|
||||
},
|
||||
Error::WithDepth { depth, ref err } => Error::WithDepth {
|
||||
depth,
|
||||
err: err.clone(),
|
||||
},
|
||||
Error::Loop {
|
||||
ref ancestor,
|
||||
ref child,
|
||||
} => Error::Loop {
|
||||
Error::WithLineNumber { line, ref err } => {
|
||||
Error::WithLineNumber { line, err: err.clone() }
|
||||
}
|
||||
Error::WithPath { ref path, ref err } => {
|
||||
Error::WithPath { path: path.clone(), err: err.clone() }
|
||||
}
|
||||
Error::WithDepth { depth, ref err } => {
|
||||
Error::WithDepth { depth, err: err.clone() }
|
||||
}
|
||||
Error::Loop { ref ancestor, ref child } => Error::Loop {
|
||||
ancestor: ancestor.clone(),
|
||||
child: child.clone(),
|
||||
},
|
||||
Error::Io(ref err) => match err.raw_os_error() {
|
||||
Some(e) => Error::Io(std::io::Error::from_raw_os_error(e)),
|
||||
None => Error::Io(std::io::Error::new(err.kind(), err.to_string())),
|
||||
None => {
|
||||
Error::Io(std::io::Error::new(err.kind(), err.to_string()))
|
||||
}
|
||||
},
|
||||
Error::Glob { ref glob, ref err } => Error::Glob {
|
||||
glob: glob.clone(),
|
||||
err: err.clone(),
|
||||
},
|
||||
Error::UnrecognizedFileType(ref err) => Error::UnrecognizedFileType(err.clone()),
|
||||
Error::Glob { ref glob, ref err } => {
|
||||
Error::Glob { glob: glob.clone(), err: err.clone() }
|
||||
}
|
||||
Error::UnrecognizedFileType(ref err) => {
|
||||
Error::UnrecognizedFileType(err.clone())
|
||||
}
|
||||
Error::InvalidDefinition => Error::InvalidDefinition,
|
||||
}
|
||||
}
|
||||
|
|
@ -269,19 +269,14 @@ impl Error {
|
|||
|
||||
/// Turn an error into a tagged error with the given depth.
|
||||
fn with_depth(self, depth: usize) -> Error {
|
||||
Error::WithDepth {
|
||||
depth,
|
||||
err: Box::new(self),
|
||||
}
|
||||
Error::WithDepth { depth, err: Box::new(self) }
|
||||
}
|
||||
|
||||
/// Turn an error into a tagged error with the given file path and line
|
||||
/// number. If path is empty, then it is omitted from the error.
|
||||
fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error {
|
||||
let errline = Error::WithLineNumber {
|
||||
line: lineno,
|
||||
err: Box::new(self),
|
||||
};
|
||||
let errline =
|
||||
Error::WithLineNumber { line: lineno, err: Box::new(self) };
|
||||
if path.as_ref().as_os_str().is_empty() {
|
||||
return errline;
|
||||
}
|
||||
|
|
@ -301,12 +296,12 @@ impl Error {
|
|||
};
|
||||
}
|
||||
let path = err.path().map(|p| p.to_path_buf());
|
||||
let mut ig_err = Error::Io(std::io::Error::from(err));
|
||||
let mut ig_err = Error::WithDepth {
|
||||
depth,
|
||||
err: Box::new(Error::Io(std::io::Error::from(err))),
|
||||
};
|
||||
if let Some(path) = path {
|
||||
ig_err = Error::WithPath {
|
||||
path,
|
||||
err: Box::new(ig_err),
|
||||
};
|
||||
ig_err = Error::WithPath { path, err: Box::new(ig_err) };
|
||||
}
|
||||
ig_err
|
||||
}
|
||||
|
|
@ -333,7 +328,8 @@ impl std::fmt::Display for Error {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Error::Partial(ref errs) => {
|
||||
let msgs: Vec<String> = errs.iter().map(|err| err.to_string()).collect();
|
||||
let msgs: Vec<String> =
|
||||
errs.iter().map(|err| err.to_string()).collect();
|
||||
write!(f, "{}", msgs.join("\n"))
|
||||
}
|
||||
Error::WithLineNumber { line, ref err } => {
|
||||
|
|
@ -343,10 +339,7 @@ impl std::fmt::Display for Error {
|
|||
write!(f, "{}: {}", path.display(), err)
|
||||
}
|
||||
Error::WithDepth { ref err, .. } => err.fmt(f),
|
||||
Error::Loop {
|
||||
ref ancestor,
|
||||
ref child,
|
||||
} => write!(
|
||||
Error::Loop { ref ancestor, ref child } => write!(
|
||||
f,
|
||||
"File system loop found: \
|
||||
{} points to an ancestor {}",
|
||||
|
|
@ -354,14 +347,8 @@ impl std::fmt::Display for Error {
|
|||
ancestor.display()
|
||||
),
|
||||
Error::Io(ref err) => err.fmt(f),
|
||||
Error::Glob {
|
||||
glob: None,
|
||||
ref err,
|
||||
} => write!(f, "{}", err),
|
||||
Error::Glob {
|
||||
glob: Some(ref glob),
|
||||
ref err,
|
||||
} => {
|
||||
Error::Glob { glob: None, ref err } => write!(f, "{}", err),
|
||||
Error::Glob { glob: Some(ref glob), ref err } => {
|
||||
write!(f, "error parsing glob '{}': {}", glob, err)
|
||||
}
|
||||
Error::UnrecognizedFileType(ref ty) => {
|
||||
|
|
@ -507,7 +494,8 @@ mod tests {
|
|||
};
|
||||
|
||||
/// A convenient result type alias.
|
||||
pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
pub(crate) type Result<T> =
|
||||
std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
macro_rules! err {
|
||||
($($tt:tt)*) => {
|
||||
|
|
@ -545,8 +533,9 @@ mod tests {
|
|||
if path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
fs::create_dir_all(&path)
|
||||
.map_err(|e| err!("failed to create {}: {}", path.display(), e))?;
|
||||
fs::create_dir_all(&path).map_err(|e| {
|
||||
err!("failed to create {}: {}", path.display(), e)
|
||||
})?;
|
||||
return Ok(TempDir(path));
|
||||
}
|
||||
Err(err!("failed to create temp dir after {} tries", TRIES))
|
||||
|
|
|
|||
|
|
@ -94,7 +94,11 @@ impl Override {
|
|||
/// given) is stripped. If there is no common suffix/prefix overlap, then
|
||||
/// `path` is assumed to reside in the same directory as the root path for
|
||||
/// this set of overrides.
|
||||
pub fn matched<'a, P: AsRef<Path>>(&'a self, path: P, is_dir: bool) -> Match<Glob<'a>> {
|
||||
pub fn matched<'a, P: AsRef<Path>>(
|
||||
&'a self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<Glob<'a>> {
|
||||
if self.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
|
|
@ -146,7 +150,10 @@ impl OverrideBuilder {
|
|||
/// affected.
|
||||
///
|
||||
/// This is disabled by default.
|
||||
pub fn case_insensitive(&mut self, yes: bool) -> Result<&mut OverrideBuilder, Error> {
|
||||
pub fn case_insensitive(
|
||||
&mut self,
|
||||
yes: bool,
|
||||
) -> Result<&mut OverrideBuilder, Error> {
|
||||
// TODO: This should not return a `Result`. Fix this in the next semver
|
||||
// release.
|
||||
self.builder.case_insensitive(yes)?;
|
||||
|
|
@ -276,11 +283,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn default_case_sensitive() {
|
||||
let ov = OverrideBuilder::new(ROOT)
|
||||
.add("*.html")
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
let ov =
|
||||
OverrideBuilder::new(ROOT).add("*.html").unwrap().build().unwrap();
|
||||
assert!(ov.matched("foo.html", false).is_whitelist());
|
||||
assert!(ov.matched("foo.HTML", false).is_ignore());
|
||||
assert!(ov.matched("foo.htm", false).is_ignore());
|
||||
|
|
|
|||
|
|
@ -2,55 +2,89 @@ use std::{ffi::OsStr, path::Path};
|
|||
|
||||
use crate::walk::DirEntry;
|
||||
|
||||
/// Returns true if and only if this entry is considered to be hidden.
|
||||
/// Returns true if and only if this path is considered to be hidden.
|
||||
///
|
||||
/// This only returns true if the base name of the path starts with a `.`.
|
||||
/// # Platform behavior
|
||||
///
|
||||
/// On Unix, this implements a more optimized check.
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
if let Some(name) = file_name(dent.path()) {
|
||||
name.as_bytes().get(0) == Some(&b'.')
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if this entry is considered to be hidden.
|
||||
/// ## Windows
|
||||
///
|
||||
/// On Windows, this returns true if one of the following is true:
|
||||
/// This returns true if one of the following is true:
|
||||
///
|
||||
/// * The base name of the path starts with a `.`.
|
||||
/// * The file attributes have the `HIDDEN` property set.
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use winapi_util::file;
|
||||
|
||||
// This looks like we're doing an extra stat call, but on Windows, the
|
||||
// directory traverser reuses the metadata retrieved from each directory
|
||||
// entry and stores it on the DirEntry itself. So this is "free."
|
||||
if let Ok(md) = dent.metadata() {
|
||||
if file::is_hidden(md.file_attributes() as u64) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(name) = file_name(dent.path()) {
|
||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if this entry is considered to be hidden.
|
||||
///
|
||||
/// ## All other platforms
|
||||
///
|
||||
/// This only returns true if the base name of the path starts with a `.`.
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
||||
if let Some(name) = file_name(dent.path()) {
|
||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||
pub(crate) fn is_hidden_path(dent: &Path) -> bool {
|
||||
#[cfg(not(windows))]
|
||||
fn imp(path: &Path) -> bool {
|
||||
is_hidden_path_only(path)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn imp(path: &Path) -> bool {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use winapi_util::file;
|
||||
|
||||
if let Ok(md) = path.metadata() {
|
||||
if file::is_hidden(md.file_attributes() as u64) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
is_hidden_path_only(path)
|
||||
}
|
||||
|
||||
imp(dent)
|
||||
}
|
||||
|
||||
/// Returns true if and only if this directory entry is considered to be
|
||||
/// hidden.
|
||||
///
|
||||
/// # Platform behavior
|
||||
///
|
||||
/// ## Windows
|
||||
///
|
||||
/// This returns true if one of the following is true:
|
||||
///
|
||||
/// * The base name of the path starts with a `.`.
|
||||
/// * The file attributes have the `HIDDEN` property set.
|
||||
///
|
||||
/// ## All other platforms
|
||||
///
|
||||
/// This only returns true if the base name of the path starts with a `.`.
|
||||
pub(crate) fn is_hidden_entry(dent: &DirEntry) -> bool {
|
||||
#[cfg(not(windows))]
|
||||
fn imp(dent: &DirEntry) -> bool {
|
||||
is_hidden_path_only(dent.path())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn imp(dent: &DirEntry) -> bool {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use winapi_util::file;
|
||||
|
||||
// This looks like we're doing an extra stat call, but on Windows, the
|
||||
// directory traverser reuses the metadata retrieved from each directory
|
||||
// entry and stores it on the DirEntry itself. So this is "free."
|
||||
if let Ok(md) = dent.metadata() {
|
||||
if file::is_hidden(md.file_attributes() as u64) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
is_hidden_path_only(dent.path())
|
||||
}
|
||||
|
||||
imp(dent)
|
||||
}
|
||||
|
||||
/// Returns true if and only if this path is considered to be hidden from only
|
||||
/// the path itself.
|
||||
///
|
||||
/// This has the same behavior on all platforms.
|
||||
fn is_hidden_path_only(path: &Path) -> bool {
|
||||
if let Some(name) = file_name(path) {
|
||||
name.as_encoded_bytes().starts_with(b".")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -59,83 +93,79 @@ pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
|||
/// Strip `prefix` from the `path` and return the remainder.
|
||||
///
|
||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> Option<&'a Path> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(unix)]
|
||||
fn imp<'a>(prefix: &'a Path, path: &'a Path) -> Option<&'a Path> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let prefix = prefix.as_ref().as_os_str().as_bytes();
|
||||
let path = path.as_os_str().as_bytes();
|
||||
if prefix.len() > path.len() || prefix != &path[0..prefix.len()] {
|
||||
None
|
||||
} else {
|
||||
Some(&Path::new(OsStr::from_bytes(&path[prefix.len()..])))
|
||||
let prefix = prefix.as_os_str().as_bytes();
|
||||
let path = path.as_os_str().as_bytes();
|
||||
if prefix.len() > path.len() || prefix != &path[0..prefix.len()] {
|
||||
None
|
||||
} else {
|
||||
Some(&Path::new(OsStr::from_bytes(&path[prefix.len()..])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip `prefix` from the `path` and return the remainder.
|
||||
///
|
||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||
#[cfg(not(unix))]
|
||||
pub(crate) fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> Option<&'a Path> {
|
||||
path.strip_prefix(prefix).ok()
|
||||
#[cfg(not(unix))]
|
||||
fn imp<'a>(prefix: &'a Path, path: &'a Path) -> Option<&'a Path> {
|
||||
path.strip_prefix(prefix).ok()
|
||||
}
|
||||
|
||||
imp(prefix.as_ref(), path)
|
||||
}
|
||||
|
||||
/// Returns true if this file path is just a file name. i.e., Its parent is
|
||||
/// the empty string.
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
use memchr::memchr;
|
||||
|
||||
let path = path.as_ref().as_os_str().as_bytes();
|
||||
memchr(b'/', path).is_none()
|
||||
}
|
||||
|
||||
/// Returns true if this file path is just a file name. i.e., Its parent is
|
||||
/// the empty string.
|
||||
#[cfg(not(unix))]
|
||||
pub(crate) fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
||||
path.as_ref()
|
||||
.parent()
|
||||
.map(|p| p.as_os_str().is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||
/// file_name will return None.
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(path: &'a P) -> Option<&'a OsStr> {
|
||||
use memchr::memrchr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let path = path.as_ref().as_os_str().as_bytes();
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
} else if path.len() == 1 && path[0] == b'.' {
|
||||
return None;
|
||||
} else if path.last() == Some(&b'.') {
|
||||
return None;
|
||||
} else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
|
||||
return None;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
memchr::memchr(b'/', path.as_ref().as_os_str().as_encoded_bytes())
|
||||
.is_none()
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
path.as_ref()
|
||||
.parent()
|
||||
.map(|p| p.as_os_str().is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
|
||||
Some(OsStr::from_bytes(&path[last_slash..]))
|
||||
}
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||
/// file_name will return None.
|
||||
#[cfg(not(unix))]
|
||||
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(path: &'a P) -> Option<&'a OsStr> {
|
||||
path.as_ref().file_name()
|
||||
/// If the path terminates in `.`, `..`, or consists solely of a root of
|
||||
/// prefix, this will return `None`.
|
||||
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||
path: &'a P,
|
||||
) -> Option<&'a OsStr> {
|
||||
#[cfg(unix)]
|
||||
fn imp(path: &Path) -> Option<&OsStr> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
use memchr::memrchr;
|
||||
|
||||
let path = path.as_os_str().as_bytes();
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
} else if path.len() == 1 && path[0] == b'.' {
|
||||
return None;
|
||||
} else if path.last() == Some(&b'.') {
|
||||
return None;
|
||||
} else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
|
||||
return None;
|
||||
}
|
||||
let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
|
||||
Some(OsStr::from_bytes(&path[last_slash..]))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn imp(path: &Path) -> Option<&OsStr> {
|
||||
path.file_name()
|
||||
}
|
||||
|
||||
imp(path.as_ref())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,8 +204,12 @@ impl<T> Selection<T> {
|
|||
|
||||
fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Selection<U> {
|
||||
match self {
|
||||
Selection::Select(name, inner) => Selection::Select(name, f(inner)),
|
||||
Selection::Negate(name, inner) => Selection::Negate(name, f(inner)),
|
||||
Selection::Select(name, inner) => {
|
||||
Selection::Select(name, f(inner))
|
||||
}
|
||||
Selection::Negate(name, inner) => {
|
||||
Selection::Negate(name, f(inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,7 +231,9 @@ impl Types {
|
|||
has_selected: false,
|
||||
glob_to_selection: vec![],
|
||||
set: GlobSetBuilder::new().build().unwrap(),
|
||||
matches: Arc::new(Pool::new(|| vec![])),
|
||||
matches: Arc::new(Pool::with_available_parallelism_capacity(
|
||||
|| vec![],
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +260,11 @@ impl Types {
|
|||
/// The path is considered ignored if it matches a negated file type.
|
||||
/// If at least one file type is selected and `path` doesn't match, then
|
||||
/// the path is also considered ignored.
|
||||
pub fn matched<'a, P: AsRef<Path>>(&'a self, path: P, is_dir: bool) -> Match<Glob<'a>> {
|
||||
pub fn matched<'a, P: AsRef<Path>>(
|
||||
&'a self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<Glob<'a>> {
|
||||
// File types don't apply to directories, and we can't do anything
|
||||
// if our glob set is empty.
|
||||
if is_dir || self.set.is_empty() {
|
||||
|
|
@ -306,10 +316,7 @@ impl TypesBuilder {
|
|||
/// of default type definitions can be added with `add_defaults`, and
|
||||
/// additional type definitions can be added with `select` and `negate`.
|
||||
pub fn new() -> TypesBuilder {
|
||||
TypesBuilder {
|
||||
types: HashMap::new(),
|
||||
selections: vec![],
|
||||
}
|
||||
TypesBuilder { types: HashMap::new(), selections: vec![] }
|
||||
}
|
||||
|
||||
/// Build the current set of file type definitions *and* selections into
|
||||
|
|
@ -343,17 +350,18 @@ impl TypesBuilder {
|
|||
}
|
||||
selections.push(selection.clone().map(move |_| def));
|
||||
}
|
||||
let set = build_set.build().map_err(|err| Error::Glob {
|
||||
glob: None,
|
||||
err: err.to_string(),
|
||||
})?;
|
||||
let set = build_set
|
||||
.build()
|
||||
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
|
||||
Ok(Types {
|
||||
defs,
|
||||
selections,
|
||||
has_selected,
|
||||
glob_to_selection,
|
||||
set,
|
||||
matches: Arc::new(Pool::new(|| vec![])),
|
||||
matches: Arc::new(Pool::with_available_parallelism_capacity(
|
||||
|| vec![],
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -377,12 +385,10 @@ impl TypesBuilder {
|
|||
pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
if name == "all" {
|
||||
for name in self.types.keys() {
|
||||
self.selections
|
||||
.push(Selection::Select(name.to_string(), ()));
|
||||
self.selections.push(Selection::Select(name.to_string(), ()));
|
||||
}
|
||||
} else {
|
||||
self.selections
|
||||
.push(Selection::Select(name.to_string(), ()));
|
||||
self.selections.push(Selection::Select(name.to_string(), ()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -393,12 +399,10 @@ impl TypesBuilder {
|
|||
pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
if name == "all" {
|
||||
for name in self.types.keys() {
|
||||
self.selections
|
||||
.push(Selection::Negate(name.to_string(), ()));
|
||||
self.selections.push(Selection::Negate(name.to_string(), ()));
|
||||
}
|
||||
} else {
|
||||
self.selections
|
||||
.push(Selection::Negate(name.to_string(), ()));
|
||||
self.selections.push(Selection::Negate(name.to_string(), ()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -453,7 +457,10 @@ impl TypesBuilder {
|
|||
3 => {
|
||||
let name = parts[0];
|
||||
let types_string = parts[2];
|
||||
if name.is_empty() || parts[1] != "include" || types_string.is_empty() {
|
||||
if name.is_empty()
|
||||
|| parts[1] != "include"
|
||||
|| types_string.is_empty()
|
||||
{
|
||||
return Err(Error::InvalidDefinition);
|
||||
}
|
||||
let types = types_string.split(',');
|
||||
|
|
@ -463,7 +470,8 @@ impl TypesBuilder {
|
|||
return Err(Error::InvalidDefinition);
|
||||
}
|
||||
for type_name in types {
|
||||
let globs = self.types.get(type_name).unwrap().globs.clone();
|
||||
let globs =
|
||||
self.types.get(type_name).unwrap().globs.clone();
|
||||
for glob in globs {
|
||||
self.add(name, &glob)?;
|
||||
}
|
||||
|
|
@ -549,30 +557,9 @@ mod tests {
|
|||
|
||||
matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
|
||||
matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
|
||||
matched!(
|
||||
not,
|
||||
matchnot3,
|
||||
types(),
|
||||
vec!["foo"],
|
||||
vec!["rust"],
|
||||
"main.rs"
|
||||
);
|
||||
matched!(
|
||||
not,
|
||||
matchnot4,
|
||||
types(),
|
||||
vec!["rust"],
|
||||
vec!["foo"],
|
||||
"main.rs"
|
||||
);
|
||||
matched!(
|
||||
not,
|
||||
matchnot5,
|
||||
types(),
|
||||
vec!["rust"],
|
||||
vec!["foo"],
|
||||
"main.foo"
|
||||
);
|
||||
matched!(not, matchnot3, types(), vec!["foo"], vec!["rust"], "main.rs");
|
||||
matched!(not, matchnot4, types(), vec!["rust"], vec!["foo"], "main.rs");
|
||||
matched!(not, matchnot5, types(), vec!["rust"], vec!["foo"], "main.foo");
|
||||
matched!(not, matchnot6, types(), vec!["combo"], vec![], "leftpad.js");
|
||||
matched!(not, matchnot7, types(), vec!["py"], vec![], "index.html");
|
||||
matched!(not, matchnot8, types(), vec!["python"], vec![], "doc.md");
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ use {
|
|||
use crate::{
|
||||
Error, PartialErrorBuilder,
|
||||
dir::{Ignore, IgnoreBuilder},
|
||||
// CHANGED: Also import `Gitignore` for `WalkBuilder::add_gitignore`.
|
||||
gitignore::{Gitignore, GitignoreBuilder},
|
||||
incremental::{IncrementalIgnore, IncrementalIgnoreOptions},
|
||||
overrides::Override,
|
||||
types::Types,
|
||||
};
|
||||
|
|
@ -104,24 +106,15 @@ impl DirEntry {
|
|||
}
|
||||
|
||||
fn new_stdin() -> DirEntry {
|
||||
DirEntry {
|
||||
dent: DirEntryInner::Stdin,
|
||||
err: None,
|
||||
}
|
||||
DirEntry { dent: DirEntryInner::Stdin, err: None }
|
||||
}
|
||||
|
||||
fn new_walkdir(dent: walkdir::DirEntry, err: Option<Error>) -> DirEntry {
|
||||
DirEntry {
|
||||
dent: DirEntryInner::Walkdir(dent),
|
||||
err,
|
||||
}
|
||||
DirEntry { dent: DirEntryInner::Walkdir(dent), err }
|
||||
}
|
||||
|
||||
fn new_raw(dent: DirEntryRaw, err: Option<Error>) -> DirEntry {
|
||||
DirEntry {
|
||||
dent: DirEntryInner::Raw(dent),
|
||||
err,
|
||||
}
|
||||
DirEntry { dent: DirEntryInner::Raw(dent), err }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,9 +180,11 @@ impl DirEntryInner {
|
|||
));
|
||||
Err(err.with_path("<stdin>"))
|
||||
}
|
||||
Walkdir(ref x) => x
|
||||
.metadata()
|
||||
.map_err(|err| Error::Io(io::Error::from(err)).with_path(x.path())),
|
||||
Walkdir(ref x) => x.metadata().map_err(|err| {
|
||||
Error::Io(io::Error::from(err))
|
||||
.with_depth(x.depth())
|
||||
.with_path(x.path())
|
||||
}),
|
||||
Raw(ref x) => x.metadata(),
|
||||
}
|
||||
}
|
||||
|
|
@ -308,7 +303,9 @@ impl DirEntryRaw {
|
|||
} else {
|
||||
fs::symlink_metadata(&self.path)
|
||||
}
|
||||
.map_err(|err| Error::Io(io::Error::from(err)).with_path(&self.path))
|
||||
.map_err(|err| {
|
||||
Error::Io(err).with_depth(self.depth).with_path(&self.path)
|
||||
})
|
||||
}
|
||||
|
||||
fn file_type(&self) -> FileType {
|
||||
|
|
@ -316,9 +313,7 @@ impl DirEntryRaw {
|
|||
}
|
||||
|
||||
fn file_name(&self) -> &OsStr {
|
||||
self.path
|
||||
.file_name()
|
||||
.unwrap_or_else(|| self.path.as_os_str())
|
||||
self.path.file_name().unwrap_or_else(|| self.path.as_os_str())
|
||||
}
|
||||
|
||||
fn depth(&self) -> usize {
|
||||
|
|
@ -330,13 +325,13 @@ impl DirEntryRaw {
|
|||
self.ino
|
||||
}
|
||||
|
||||
fn from_entry(depth: usize, ent: &fs::DirEntry) -> Result<DirEntryRaw, Error> {
|
||||
fn from_entry(
|
||||
depth: usize,
|
||||
ent: &fs::DirEntry,
|
||||
) -> Result<DirEntryRaw, Error> {
|
||||
let ty = ent.file_type().map_err(|err| {
|
||||
let err = Error::Io(io::Error::from(err)).with_path(ent.path());
|
||||
Error::WithDepth {
|
||||
depth,
|
||||
err: Box::new(err),
|
||||
}
|
||||
let err = Error::Io(err).with_depth(depth).with_path(ent.path());
|
||||
Error::WithDepth { depth, err: Box::new(err) }
|
||||
})?;
|
||||
DirEntryRaw::from_entry_os(depth, ent, ty)
|
||||
}
|
||||
|
|
@ -348,11 +343,8 @@ impl DirEntryRaw {
|
|||
ty: fs::FileType,
|
||||
) -> Result<DirEntryRaw, Error> {
|
||||
let md = ent.metadata().map_err(|err| {
|
||||
let err = Error::Io(io::Error::from(err)).with_path(ent.path());
|
||||
Error::WithDepth {
|
||||
depth,
|
||||
err: Box::new(err),
|
||||
}
|
||||
let err = Error::Io(err).with_depth(depth).with_path(ent.path());
|
||||
Error::WithDepth { depth, err: Box::new(err) }
|
||||
})?;
|
||||
Ok(DirEntryRaw {
|
||||
path: ent.path(),
|
||||
|
|
@ -395,8 +387,13 @@ impl DirEntryRaw {
|
|||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn from_path(depth: usize, pb: PathBuf, link: bool) -> Result<DirEntryRaw, Error> {
|
||||
let md = fs::metadata(&pb).map_err(|err| Error::Io(err).with_path(&pb))?;
|
||||
fn from_path(
|
||||
depth: usize,
|
||||
pb: PathBuf,
|
||||
link: bool,
|
||||
) -> Result<DirEntryRaw, Error> {
|
||||
let md = fs::metadata(&pb)
|
||||
.map_err(|err| Error::Io(err).with_depth(depth).with_path(&pb))?;
|
||||
Ok(DirEntryRaw {
|
||||
path: pb,
|
||||
ty: md.file_type(),
|
||||
|
|
@ -407,10 +404,15 @@ impl DirEntryRaw {
|
|||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn from_path(depth: usize, pb: PathBuf, link: bool) -> Result<DirEntryRaw, Error> {
|
||||
fn from_path(
|
||||
depth: usize,
|
||||
pb: PathBuf,
|
||||
link: bool,
|
||||
) -> Result<DirEntryRaw, Error> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let md = fs::metadata(&pb).map_err(|err| Error::Io(err).with_path(&pb))?;
|
||||
let md = fs::metadata(&pb)
|
||||
.map_err(|err| Error::Io(err).with_depth(depth).with_path(&pb))?;
|
||||
Ok(DirEntryRaw {
|
||||
path: pb,
|
||||
ty: md.file_type(),
|
||||
|
|
@ -423,7 +425,11 @@ impl DirEntryRaw {
|
|||
// Placeholder implementation to allow compiling on non-standard platforms
|
||||
// (e.g. wasm32).
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
fn from_path(depth: usize, pb: PathBuf, link: bool) -> Result<DirEntryRaw, Error> {
|
||||
fn from_path(
|
||||
depth: usize,
|
||||
pb: PathBuf,
|
||||
link: bool,
|
||||
) -> Result<DirEntryRaw, Error> {
|
||||
Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"unsupported platform",
|
||||
|
|
@ -502,7 +508,8 @@ pub struct WalkBuilder {
|
|||
///
|
||||
/// When `None`, the CWD is fetched from `std::env::current_dir()`. If
|
||||
/// that fails, then global gitignores are ignored (an error is logged).
|
||||
global_gitignores_relative_to: OnceLock<Result<PathBuf, Arc<std::io::Error>>>,
|
||||
global_gitignores_relative_to:
|
||||
OnceLock<Result<PathBuf, Arc<std::io::Error>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -544,8 +551,16 @@ impl WalkBuilder {
|
|||
/// is better to call `add` on this builder than to create multiple
|
||||
/// `Walk` values.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> WalkBuilder {
|
||||
WalkBuilder::from_iter([path])
|
||||
}
|
||||
|
||||
/// Create an empty builder to which paths can be added.
|
||||
///
|
||||
/// Note that if you call `build` on this instance before calling `add`
|
||||
/// on it, it will return exactly zero items during iteration.
|
||||
pub fn empty() -> WalkBuilder {
|
||||
WalkBuilder {
|
||||
paths: vec![path.as_ref().to_path_buf()],
|
||||
paths: vec![],
|
||||
ig_builder: IgnoreBuilder::new(),
|
||||
max_depth: None,
|
||||
min_depth: None,
|
||||
|
|
@ -560,6 +575,21 @@ impl WalkBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new builder for a recursive directory iterator from the
|
||||
/// sequence of paths.
|
||||
///
|
||||
/// Note that if the iterator is empty, this is the same as
|
||||
/// `WalkBuilder::empty`.
|
||||
pub fn from_iter<P: AsRef<Path>, I: IntoIterator<Item = P>>(
|
||||
paths: I,
|
||||
) -> WalkBuilder {
|
||||
let mut builder = WalkBuilder::empty();
|
||||
for path in paths.into_iter() {
|
||||
builder.add(path);
|
||||
}
|
||||
builder
|
||||
}
|
||||
|
||||
/// Build a new `Walk` iterator.
|
||||
pub fn build(&self) -> Walk {
|
||||
let follow_links = self.follow_links;
|
||||
|
|
@ -585,10 +615,14 @@ impl WalkBuilder {
|
|||
if let Some(ref sorter) = sorter {
|
||||
match sorter.clone() {
|
||||
Sorter::ByName(cmp) => {
|
||||
wd = wd.sort_by(move |a, b| cmp(a.file_name(), b.file_name()));
|
||||
wd = wd.sort_by(move |a, b| {
|
||||
cmp(a.file_name(), b.file_name())
|
||||
});
|
||||
}
|
||||
Sorter::ByPath(cmp) => {
|
||||
wd = wd.sort_by(move |a, b| cmp(a.path(), b.path()));
|
||||
wd = wd.sort_by(move |a, b| {
|
||||
cmp(a.path(), b.path())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -597,31 +631,76 @@ impl WalkBuilder {
|
|||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter();
|
||||
let ig_root = self
|
||||
.get_or_set_current_dir()
|
||||
.map(|cwd| self.ig_builder.build_with_cwd(Some(cwd.to_path_buf())))
|
||||
.unwrap_or_else(|| self.ig_builder.build());
|
||||
let ig_root = self.build_ignore();
|
||||
Walk {
|
||||
its,
|
||||
it: None,
|
||||
ig_root: ig_root.clone(),
|
||||
ig: ig_root.clone(),
|
||||
max_depth: self.max_depth,
|
||||
max_filesize: self.max_filesize,
|
||||
skip: self.skip.clone(),
|
||||
filter: self.filter.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build matchers for checking paths against ignore files without
|
||||
/// recursively walking the configured roots.
|
||||
///
|
||||
/// The returned matchers use the path-based filtering configuration
|
||||
/// on this builder, including glob overrides, file type selections,
|
||||
/// parent ignore files, `.ignore`, `.gitignore`, global Git
|
||||
/// ignore files, explicitly added ignore files and custom ignore
|
||||
/// file names. For example, ripgrep configures `.rgignore` via
|
||||
/// [`WalkBuilder::add_custom_ignore_filename`]. Minimum and maximum depth
|
||||
/// limits, maximum file size and hidden-file filtering are also applied.
|
||||
/// Other options that only control traversal or require a directory entry,
|
||||
/// such as custom entry predicates, are not applied.
|
||||
///
|
||||
/// One matcher is returned for each configured path, in the same order as
|
||||
/// the paths were added to this builder. Each matcher accepts paths
|
||||
/// relative to its own [`IncrementalIgnore::root`]. The matcher for the
|
||||
/// special `-` path representing standard input always returns a non-match
|
||||
/// for all inputs.
|
||||
///
|
||||
/// Ignore matchers are loaded lazily and cached by directory.
|
||||
/// Thus, the first query may read ignore files from the root and
|
||||
/// its parents, while later queries reuse the compiled matchers.
|
||||
/// Errors encountered while loading ignore files are returned by
|
||||
/// [`IncrementalIgnore::matched_with_errors`]. Once an ignore file has
|
||||
/// been loaded, changes to it are not observed. Build new matchers to
|
||||
/// reload changed ignore files.
|
||||
///
|
||||
/// Matchers built together share the builder's base ignore configuration
|
||||
/// and compiled parent matchers.
|
||||
pub fn build_matchers(&self) -> Vec<IncrementalIgnore> {
|
||||
let ignore = self.build_ignore();
|
||||
let options = IncrementalIgnoreOptions {
|
||||
min_depth: self.min_depth,
|
||||
max_depth: self.max_depth,
|
||||
max_filesize: self.max_filesize,
|
||||
hidden: self.ig_builder.is_hidden(),
|
||||
follow_links: self.follow_links,
|
||||
};
|
||||
self.paths
|
||||
.iter()
|
||||
.map(move |path| {
|
||||
IncrementalIgnore::new(
|
||||
path.clone(),
|
||||
ignore.clone(),
|
||||
options.clone(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Build a new `WalkParallel` iterator.
|
||||
///
|
||||
/// Note that this *doesn't* return something that implements `Iterator`.
|
||||
/// Instead, the returned value must be run with a closure. e.g.,
|
||||
/// `builder.build_parallel().run(|| |path| { println!("{path:?}"); WalkState::Continue })`.
|
||||
pub fn build_parallel(&self) -> WalkParallel {
|
||||
let ig_root = self
|
||||
.get_or_set_current_dir()
|
||||
.map(|cwd| self.ig_builder.build_with_cwd(Some(cwd.to_path_buf())))
|
||||
.unwrap_or_else(|| self.ig_builder.build());
|
||||
let ig_root = self.build_ignore();
|
||||
WalkParallel {
|
||||
paths: self.paths.clone().into_iter(),
|
||||
ig_root,
|
||||
|
|
@ -651,7 +730,10 @@ impl WalkBuilder {
|
|||
/// The default, `None`, imposes no depth restriction.
|
||||
pub fn max_depth(&mut self, depth: Option<usize>) -> &mut WalkBuilder {
|
||||
self.max_depth = depth;
|
||||
if self.min_depth.is_some() && self.max_depth.is_some() && self.max_depth < self.min_depth {
|
||||
if self.min_depth.is_some()
|
||||
&& self.max_depth.is_some()
|
||||
&& self.max_depth < self.min_depth
|
||||
{
|
||||
self.max_depth = self.min_depth;
|
||||
}
|
||||
self
|
||||
|
|
@ -662,7 +744,10 @@ impl WalkBuilder {
|
|||
/// The default, `None`, imposes no minimum depth restriction.
|
||||
pub fn min_depth(&mut self, depth: Option<usize>) -> &mut WalkBuilder {
|
||||
self.min_depth = depth;
|
||||
if self.max_depth.is_some() && self.min_depth.is_some() && self.min_depth > self.max_depth {
|
||||
if self.max_depth.is_some()
|
||||
&& self.min_depth.is_some()
|
||||
&& self.min_depth > self.max_depth
|
||||
{
|
||||
self.min_depth = self.max_depth;
|
||||
}
|
||||
self
|
||||
|
|
@ -705,7 +790,12 @@ impl WalkBuilder {
|
|||
/// An error will also occur if this walker could not get the current
|
||||
/// working directory (and `WalkBuilder::current_dir` isn't set).
|
||||
pub fn add_ignore<P: AsRef<Path>>(&mut self, path: P) -> Option<Error> {
|
||||
// CHANGED: Dropped this code
|
||||
// CHANGED: Root the ignore file at `""` instead of the current working
|
||||
// directory. Explicit ignores are scoped to the directory of the
|
||||
// ignore file (see `matched_ignore`), and a root of `""` makes the
|
||||
// rules apply to every walked path regardless of the walk root. This
|
||||
// also avoids depending on the current working directory entirely.
|
||||
//
|
||||
// let path = path.as_ref();
|
||||
// let Some(cwd) = self.get_or_set_current_dir() else {
|
||||
// let err = std::io::Error::other(format!(
|
||||
|
|
@ -729,7 +819,11 @@ impl WalkBuilder {
|
|||
errs.into_error_option()
|
||||
}
|
||||
|
||||
/// CHANGED: Add a Gitignore to the builder.
|
||||
/// CHANGED: Add a prebuilt Gitignore to the builder.
|
||||
///
|
||||
/// Like the ignore file added via `add_ignore`, these rules are matched
|
||||
/// against the full path of each walked entry, scoped to the `Gitignore`'s
|
||||
/// root path.
|
||||
pub fn add_gitignore(&mut self, gi: Gitignore) {
|
||||
self.ig_builder.add_ignore(gi);
|
||||
}
|
||||
|
|
@ -982,7 +1076,10 @@ impl WalkBuilder {
|
|||
///
|
||||
/// Global gitignore files come from things like a user's git configuration
|
||||
/// or from gitignore files added via [`WalkBuilder::add_ignore`].
|
||||
pub fn current_dir(&mut self, cwd: impl Into<PathBuf>) -> &mut WalkBuilder {
|
||||
pub fn current_dir(
|
||||
&mut self,
|
||||
cwd: impl Into<PathBuf>,
|
||||
) -> &mut WalkBuilder {
|
||||
let cwd = cwd.into();
|
||||
self.ig_builder.current_dir(cwd.clone());
|
||||
if let Err(cwd) = self.global_gitignores_relative_to.set(Ok(cwd)) {
|
||||
|
|
@ -1002,7 +1099,10 @@ impl WalkBuilder {
|
|||
let result = std::env::current_dir().map_err(Arc::new);
|
||||
match result {
|
||||
Ok(ref path) => {
|
||||
log::trace!("automatically discovered CWD: {}", path.display());
|
||||
log::trace!(
|
||||
"automatically discovered CWD: {}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
Err(ref err) => {
|
||||
log::debug!(
|
||||
|
|
@ -1016,6 +1116,13 @@ impl WalkBuilder {
|
|||
});
|
||||
result.as_ref().ok().map(|path| &**path)
|
||||
}
|
||||
|
||||
/// Build the root ignore matcher shared by all consumers of this builder.
|
||||
fn build_ignore(&self) -> Ignore {
|
||||
self.get_or_set_current_dir()
|
||||
.map(|cwd| self.ig_builder.build_with_cwd(Some(cwd.to_path_buf())))
|
||||
.unwrap_or_else(|| self.ig_builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk is a recursive directory iterator over file paths in one or more
|
||||
|
|
@ -1029,6 +1136,7 @@ pub struct Walk {
|
|||
it: Option<WalkEventIter>,
|
||||
ig_root: Ignore,
|
||||
ig: Ignore,
|
||||
max_depth: Option<usize>,
|
||||
max_filesize: Option<u64>,
|
||||
skip: Option<Arc<Handle>>,
|
||||
filter: Option<Filter>,
|
||||
|
|
@ -1044,6 +1152,17 @@ impl Walk {
|
|||
WalkBuilder::new(path).build()
|
||||
}
|
||||
|
||||
/// Create a new recursive directory iterator from the sequence of paths
|
||||
/// given.
|
||||
///
|
||||
/// Note that if the provided iterator is empty, then `Walk` is guaranteed
|
||||
/// to yield zero entries.
|
||||
pub fn from_iter<P: AsRef<Path>, I: IntoIterator<Item = P>>(
|
||||
paths: I,
|
||||
) -> Walk {
|
||||
WalkBuilder::from_iter(paths).build()
|
||||
}
|
||||
|
||||
fn skip_entry(&self, ent: &DirEntry) -> Result<bool, Error> {
|
||||
if ent.depth() == 0 {
|
||||
return Ok(false);
|
||||
|
|
@ -1128,12 +1247,17 @@ impl Iterator for Walk {
|
|||
self.it.as_mut().unwrap().it.skip_current_dir();
|
||||
// Still need to push this on the stack because
|
||||
// we'll get a WalkEvent::Exit event for this dir.
|
||||
// We don't care if it errors though.
|
||||
let (igtmp, _) = self.ig.add_child(ent.path());
|
||||
// Its ignore files cannot apply to any visited entry.
|
||||
let (igtmp, _) =
|
||||
self.ig.add_child_with_entries(ent.path(), &[]);
|
||||
self.ig = igtmp;
|
||||
continue;
|
||||
}
|
||||
let (igtmp, err) = self.ig.add_child(ent.path());
|
||||
let (igtmp, err) = if self.max_depth == Some(ent.depth()) {
|
||||
self.ig.add_child_with_entries(ent.path(), &[])
|
||||
} else {
|
||||
self.ig.add_child(ent.path())
|
||||
};
|
||||
self.ig = igtmp;
|
||||
ent.err = err;
|
||||
return Some(Ok(ent));
|
||||
|
|
@ -1175,11 +1299,7 @@ enum WalkEvent {
|
|||
|
||||
impl From<WalkDir> for WalkEventIter {
|
||||
fn from(it: WalkDir) -> WalkEventIter {
|
||||
WalkEventIter {
|
||||
depth: 0,
|
||||
it: it.into_iter(),
|
||||
next: None,
|
||||
}
|
||||
WalkEventIter { depth: 0, it: it.into_iter(), next: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1252,7 +1372,9 @@ pub trait ParallelVisitorBuilder<'s> {
|
|||
fn build(&mut self) -> Box<dyn ParallelVisitor + 's>;
|
||||
}
|
||||
|
||||
impl<'a, 's, P: ParallelVisitorBuilder<'s>> ParallelVisitorBuilder<'s> for &'a mut P {
|
||||
impl<'a, 's, P: ParallelVisitorBuilder<'s>> ParallelVisitorBuilder<'s>
|
||||
for &'a mut P
|
||||
{
|
||||
fn build(&mut self) -> Box<dyn ParallelVisitor + 's> {
|
||||
(**self).build()
|
||||
}
|
||||
|
|
@ -1273,14 +1395,17 @@ struct FnBuilder<F> {
|
|||
builder: F,
|
||||
}
|
||||
|
||||
impl<'s, F: FnMut() -> FnVisitor<'s>> ParallelVisitorBuilder<'s> for FnBuilder<F> {
|
||||
impl<'s, F: FnMut() -> FnVisitor<'s>> ParallelVisitorBuilder<'s>
|
||||
for FnBuilder<F>
|
||||
{
|
||||
fn build(&mut self) -> Box<dyn ParallelVisitor + 's> {
|
||||
let visitor = (self.builder)();
|
||||
Box::new(FnVisitorImp { visitor })
|
||||
}
|
||||
}
|
||||
|
||||
type FnVisitor<'s> = Box<dyn FnMut(Result<DirEntry, Error>) -> WalkState + Send + 's>;
|
||||
type FnVisitor<'s> =
|
||||
Box<dyn FnMut(Result<DirEntry, Error>) -> WalkState + Send + 's>;
|
||||
|
||||
struct FnVisitorImp<'s> {
|
||||
visitor: FnVisitor<'s>,
|
||||
|
|
@ -1370,7 +1495,9 @@ impl WalkParallel {
|
|||
}
|
||||
};
|
||||
match DirEntryRaw::from_path(0, path, false) {
|
||||
Ok(dent) => (DirEntry::new_raw(dent, None), root_device),
|
||||
Ok(dent) => {
|
||||
(DirEntry::new_raw(dent, None), root_device)
|
||||
}
|
||||
Err(err) => {
|
||||
if visitor.visit(Err(err)).is_quit() {
|
||||
return;
|
||||
|
|
@ -1394,21 +1521,28 @@ impl WalkParallel {
|
|||
let quit_now = Arc::new(AtomicBool::new(false));
|
||||
let active_workers = Arc::new(AtomicUsize::new(threads));
|
||||
let stacks = Stack::new_for_each_thread(threads, stack);
|
||||
// Collect all of the workers first. In the case that
|
||||
// `builder.build()` panics, we want that to happen and
|
||||
// propagate before we actually start to run any of the
|
||||
// workers.
|
||||
let workers: Vec<_> = stacks
|
||||
.into_iter()
|
||||
.map(|stack| Worker {
|
||||
visitor: builder.build(),
|
||||
stack,
|
||||
quit_now: quit_now.clone(),
|
||||
active_workers: active_workers.clone(),
|
||||
max_depth: self.max_depth,
|
||||
min_depth: self.min_depth,
|
||||
max_filesize: self.max_filesize,
|
||||
follow_links: self.follow_links,
|
||||
skip: self.skip.clone(),
|
||||
filter: self.filter.clone(),
|
||||
})
|
||||
.collect();
|
||||
std::thread::scope(|s| {
|
||||
let handles: Vec<_> = stacks
|
||||
let handles: Vec<_> = workers
|
||||
.into_iter()
|
||||
.map(|stack| Worker {
|
||||
visitor: builder.build(),
|
||||
stack,
|
||||
quit_now: quit_now.clone(),
|
||||
active_workers: active_workers.clone(),
|
||||
max_depth: self.max_depth,
|
||||
min_depth: self.min_depth,
|
||||
max_filesize: self.max_filesize,
|
||||
follow_links: self.follow_links,
|
||||
skip: self.skip.clone(),
|
||||
filter: self.filter.clone(),
|
||||
})
|
||||
.map(|worker| s.spawn(|| worker.run()))
|
||||
.collect();
|
||||
for handle in handles {
|
||||
|
|
@ -1419,9 +1553,7 @@ impl WalkParallel {
|
|||
|
||||
fn threads(&self) -> usize {
|
||||
if self.threads == 0 {
|
||||
std::thread::available_parallelism()
|
||||
.map_or(1, |n| n.get())
|
||||
.min(12)
|
||||
std::thread::available_parallelism().map_or(1, |n| n.get()).min(12)
|
||||
} else {
|
||||
self.threads
|
||||
}
|
||||
|
|
@ -1452,6 +1584,12 @@ struct Work {
|
|||
root_device: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ReadDirResult {
|
||||
entries: Vec<fs::DirEntry>,
|
||||
errors: Vec<Error>,
|
||||
}
|
||||
|
||||
impl Work {
|
||||
/// Returns true if and only if this work item is a directory.
|
||||
fn is_dir(&self) -> bool {
|
||||
|
|
@ -1478,6 +1616,13 @@ impl Work {
|
|||
err
|
||||
}
|
||||
|
||||
/// Adds ignore rules for this directory without reading its contents.
|
||||
fn add_ignore(&mut self) {
|
||||
let (ig, err) = self.ignore.add_child(self.dent.path());
|
||||
self.ignore = ig;
|
||||
self.dent.err = err;
|
||||
}
|
||||
|
||||
/// Reads the directory contents of this work item and adds ignore
|
||||
/// rules for this directory.
|
||||
///
|
||||
|
|
@ -1485,7 +1630,7 @@ impl Work {
|
|||
/// an error is returned. If there was a problem reading the ignore
|
||||
/// rules for this directory, then the error is attached to this
|
||||
/// work item's directory entry.
|
||||
fn read_dir(&mut self) -> Result<fs::ReadDir, Error> {
|
||||
fn read_dir(&mut self) -> Result<ReadDirResult, Error> {
|
||||
let readdir = match fs::read_dir(self.dent.path()) {
|
||||
Ok(readdir) => readdir,
|
||||
Err(err) => {
|
||||
|
|
@ -1495,10 +1640,24 @@ impl Work {
|
|||
return Err(err);
|
||||
}
|
||||
};
|
||||
let (ig, err) = self.ignore.add_child(self.dent.path());
|
||||
// Actually descend into the directory and read its contents
|
||||
let mut result = ReadDirResult::default();
|
||||
for entry in readdir {
|
||||
match entry {
|
||||
Ok(entry) => result.entries.push(entry),
|
||||
Err(err) => result.errors.push(
|
||||
Error::from(err)
|
||||
.with_path(self.dent.path())
|
||||
.with_depth(self.dent.depth() + 1),
|
||||
),
|
||||
}
|
||||
}
|
||||
let (ig, err) = self
|
||||
.ignore
|
||||
.add_child_with_entries(self.dent.path(), &result.entries);
|
||||
self.ignore = ig;
|
||||
self.dent.err = err;
|
||||
Ok(readdir)
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1522,11 +1681,11 @@ impl Stack {
|
|||
// breadth-first. We do depth-first because a breadth first traversal
|
||||
// on wide directories with a lot of gitignores is disastrous (for
|
||||
// example, searching a directory tree containing all of crates.io).
|
||||
let deques: Vec<Deque<Message>> = std::iter::repeat_with(Deque::new_lifo)
|
||||
.take(threads)
|
||||
.collect();
|
||||
let stealers =
|
||||
Arc::<[Stealer<Message>]>::from(deques.iter().map(Deque::stealer).collect::<Vec<_>>());
|
||||
let deques: Vec<Deque<Message>> =
|
||||
std::iter::repeat_with(Deque::new_lifo).take(threads).collect();
|
||||
let stealers = Arc::<[Stealer<Message>]>::from(
|
||||
deques.iter().map(Deque::stealer).collect::<Vec<_>>(),
|
||||
);
|
||||
let stacks: Vec<Stack> = deques
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
|
@ -1668,8 +1827,13 @@ impl<'s> Worker<'s> {
|
|||
// have sufficient read permissions to list the directory.
|
||||
// In that case we still want to provide the closure with a valid
|
||||
// entry before passing the error value.
|
||||
let readdir = work.read_dir();
|
||||
let depth = work.dent.depth();
|
||||
let readdir = if descend && self.max_depth.is_none_or(|m| depth < m) {
|
||||
Some(work.read_dir())
|
||||
} else {
|
||||
work.add_ignore();
|
||||
None
|
||||
};
|
||||
if should_visit {
|
||||
let state = self.visitor.visit(Ok(work.dent));
|
||||
if !state.is_continue() {
|
||||
|
|
@ -1680,6 +1844,10 @@ impl<'s> Worker<'s> {
|
|||
return WalkState::Skip;
|
||||
}
|
||||
|
||||
let readdir = match readdir {
|
||||
Some(readdir) => readdir,
|
||||
None => return WalkState::Skip,
|
||||
};
|
||||
let readdir = match readdir {
|
||||
Ok(readdir) => readdir,
|
||||
Err(err) => {
|
||||
|
|
@ -1687,11 +1855,19 @@ impl<'s> Worker<'s> {
|
|||
}
|
||||
};
|
||||
|
||||
if self.max_depth.map_or(false, |max| depth >= max) {
|
||||
return WalkState::Skip;
|
||||
for result in readdir.entries {
|
||||
let state = self.generate_work(
|
||||
&work.ignore,
|
||||
depth + 1,
|
||||
work.root_device,
|
||||
result,
|
||||
);
|
||||
if state.is_quit() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
for result in readdir {
|
||||
let state = self.generate_work(&work.ignore, depth + 1, work.root_device, result);
|
||||
for err in readdir.errors {
|
||||
let state = self.visitor.visit(Err(err));
|
||||
if state.is_quit() {
|
||||
return state;
|
||||
}
|
||||
|
|
@ -1717,14 +1893,8 @@ impl<'s> Worker<'s> {
|
|||
ig: &Ignore,
|
||||
depth: usize,
|
||||
root_device: Option<u64>,
|
||||
result: Result<fs::DirEntry, io::Error>,
|
||||
fs_dent: fs::DirEntry,
|
||||
) -> WalkState {
|
||||
let fs_dent = match result {
|
||||
Ok(fs_dent) => fs_dent,
|
||||
Err(err) => {
|
||||
return self.visitor.visit(Err(Error::from(err).with_depth(depth)));
|
||||
}
|
||||
};
|
||||
let mut dent = match DirEntryRaw::from_entry(depth, &fs_dent) {
|
||||
Ok(dent) => DirEntry::new_raw(dent, None),
|
||||
Err(err) => {
|
||||
|
|
@ -1760,26 +1930,24 @@ impl<'s> Worker<'s> {
|
|||
return WalkState::Continue;
|
||||
}
|
||||
}
|
||||
let should_skip_filesize = if self.max_filesize.is_some() && !dent.is_dir() {
|
||||
skip_filesize(
|
||||
self.max_filesize.unwrap(),
|
||||
dent.path(),
|
||||
&dent.metadata().ok(),
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let should_skip_filtered = if let Some(Filter(predicate)) = &self.filter {
|
||||
!predicate(&dent)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let should_skip_filesize =
|
||||
if self.max_filesize.is_some() && !dent.is_dir() {
|
||||
skip_filesize(
|
||||
self.max_filesize.unwrap(),
|
||||
dent.path(),
|
||||
&dent.metadata().ok(),
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let should_skip_filtered =
|
||||
if let Some(Filter(predicate)) = &self.filter {
|
||||
!predicate(&dent)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !should_skip_filesize && !should_skip_filtered {
|
||||
self.send(Work {
|
||||
dent,
|
||||
ignore: ig.clone(),
|
||||
root_device,
|
||||
});
|
||||
self.send(Work { dent, ignore: ig.clone(), root_device });
|
||||
}
|
||||
WalkState::Continue
|
||||
}
|
||||
|
|
@ -1820,6 +1988,9 @@ impl<'s> Worker<'s> {
|
|||
}
|
||||
// Wait for next `Work` or `Quit` message.
|
||||
loop {
|
||||
if self.is_quit_now() {
|
||||
return None;
|
||||
}
|
||||
if let Some(v) = self.recv() {
|
||||
self.activate_worker();
|
||||
value = Some(v);
|
||||
|
|
@ -1873,24 +2044,25 @@ impl<'s> Worker<'s> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'s> Drop for Worker<'s> {
|
||||
fn drop(&mut self) {
|
||||
if std::thread::panicking() {
|
||||
self.quit_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_symlink_loop(
|
||||
ig_parent: &Ignore,
|
||||
child_path: &Path,
|
||||
child_depth: usize,
|
||||
) -> Result<(), Error> {
|
||||
let hchild = Handle::from_path(child_path).map_err(|err| {
|
||||
Error::from(err)
|
||||
.with_path(child_path)
|
||||
.with_depth(child_depth)
|
||||
Error::from(err).with_path(child_path).with_depth(child_depth)
|
||||
})?;
|
||||
for ig in ig_parent
|
||||
.parents()
|
||||
.take_while(|ig| !ig.is_absolute_parent())
|
||||
{
|
||||
for ig in ig_parent.parents().take_while(|ig| !ig.is_absolute_parent()) {
|
||||
let h = Handle::from_path(ig.path()).map_err(|err| {
|
||||
Error::from(err)
|
||||
.with_path(child_path)
|
||||
.with_depth(child_depth)
|
||||
Error::from(err).with_path(child_path).with_depth(child_depth)
|
||||
})?;
|
||||
if hchild == h {
|
||||
return Err(Error::Loop {
|
||||
|
|
@ -1905,7 +2077,11 @@ fn check_symlink_loop(
|
|||
|
||||
// Before calling this function, make sure that you ensure that is really
|
||||
// necessary as the arguments imply a file stat.
|
||||
fn skip_filesize(max_filesize: u64, path: &Path, ent: &Option<Metadata>) -> bool {
|
||||
fn skip_filesize(
|
||||
max_filesize: u64,
|
||||
path: &Path,
|
||||
ent: &Option<Metadata>,
|
||||
) -> bool {
|
||||
let filesize = match *ent {
|
||||
Some(ref md) => Some(md.len()),
|
||||
None => None,
|
||||
|
|
@ -1977,9 +2153,9 @@ fn path_equals(dent: &DirEntry, handle: &Handle) -> Result<bool, Error> {
|
|||
if dent.is_stdin() || never_equal(dent, handle) {
|
||||
return Ok(false);
|
||||
}
|
||||
Handle::from_path(dent.path())
|
||||
.map(|h| &h == handle)
|
||||
.map_err(|err| Error::Io(err).with_path(dent.path()))
|
||||
Handle::from_path(dent.path()).map(|h| &h == handle).map_err(|err| {
|
||||
Error::Io(err).with_depth(dent.depth()).with_path(dent.path())
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the given walkdir entry corresponds to a directory.
|
||||
|
|
@ -1997,16 +2173,14 @@ fn walkdir_is_dir(dent: &walkdir::DirEntry) -> bool {
|
|||
if !dent.file_type().is_symlink() || dent.depth() > 0 {
|
||||
return false;
|
||||
}
|
||||
dent.path()
|
||||
.metadata()
|
||||
.ok()
|
||||
.map_or(false, |md| md.file_type().is_dir())
|
||||
dent.path().metadata().ok().map_or(false, |md| md.file_type().is_dir())
|
||||
}
|
||||
|
||||
/// Returns true if and only if the given path is on the same device as the
|
||||
/// given root device.
|
||||
fn is_same_file_system(root_device: u64, path: &Path) -> Result<bool, Error> {
|
||||
let dent_device = device_num(path).map_err(|err| Error::Io(err).with_path(path))?;
|
||||
let dent_device =
|
||||
device_num(path).map_err(|err| Error::Io(err).with_path(path))?;
|
||||
Ok(root_device == dent_device)
|
||||
}
|
||||
|
||||
|
|
@ -2065,11 +2239,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn normal_path(unix: &str) -> String {
|
||||
if cfg!(windows) {
|
||||
unix.replace("\\", "/")
|
||||
} else {
|
||||
unix.to_string()
|
||||
}
|
||||
if cfg!(windows) { unix.replace("\\", "/") } else { unix.to_string() }
|
||||
}
|
||||
|
||||
fn walk_collect(prefix: &Path, builder: &WalkBuilder) -> Vec<String> {
|
||||
|
|
@ -2089,7 +2259,10 @@ mod tests {
|
|||
paths
|
||||
}
|
||||
|
||||
fn walk_collect_parallel(prefix: &Path, builder: &WalkBuilder) -> Vec<String> {
|
||||
fn walk_collect_parallel(
|
||||
prefix: &Path,
|
||||
builder: &WalkBuilder,
|
||||
) -> Vec<String> {
|
||||
let mut paths = vec![];
|
||||
for dent in walk_collect_entries_parallel(builder) {
|
||||
let path = dent.path().strip_prefix(prefix).unwrap();
|
||||
|
|
@ -2278,6 +2451,27 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_depth_does_not_load_unreachable_ignore_files() {
|
||||
let td = tmpdir();
|
||||
let leaf = td.path().join("leaf");
|
||||
mkdirp(&leaf);
|
||||
wfile(leaf.join(".ignore"), "{invalid\n");
|
||||
|
||||
let mut builder = WalkBuilder::new(td.path());
|
||||
builder.max_depth(Some(1));
|
||||
let entry = builder
|
||||
.build()
|
||||
.find_map(|result| {
|
||||
let entry = result.unwrap();
|
||||
(entry.path() == leaf).then_some(entry)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(entry.error().is_none());
|
||||
assert_paths(td.path(), &builder, &["leaf"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_depth() {
|
||||
let td = tmpdir();
|
||||
|
|
@ -2388,7 +2582,9 @@ mod tests {
|
|||
assert_eq!(1, dents.len());
|
||||
assert!(!dents[0].path_is_symlink());
|
||||
|
||||
let dents = walk_collect_entries_parallel(&WalkBuilder::new(td.path().join("foo")));
|
||||
let dents = walk_collect_entries_parallel(&WalkBuilder::new(
|
||||
td.path().join("foo"),
|
||||
));
|
||||
assert_eq!(1, dents.len());
|
||||
assert!(!dents[0].path_is_symlink());
|
||||
}
|
||||
|
|
@ -2474,8 +2670,88 @@ mod tests {
|
|||
|
||||
assert_paths(
|
||||
td.path(),
|
||||
&WalkBuilder::new(td.path()).filter_entry(|entry| entry.file_name() != OsStr::new("a")),
|
||||
&WalkBuilder::new(td.path())
|
||||
.filter_entry(|entry| entry.file_name() != OsStr::new("a")),
|
||||
&["x", "x/y", "x/y/foo"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let td = tmpdir();
|
||||
assert_paths(td.path(), &WalkBuilder::empty(), &[]);
|
||||
|
||||
let empty_paths: Vec<&OsStr> = Vec::new();
|
||||
assert_paths(td.path(), &WalkBuilder::from_iter(empty_paths), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_iter() {
|
||||
let td = tmpdir();
|
||||
mkdirp(td.path().join("a/b/c"));
|
||||
mkdirp(td.path().join("d/e/f"));
|
||||
mkdirp(td.path().join("x/y"));
|
||||
wfile(td.path().join("a/b/foo"), "");
|
||||
wfile(td.path().join("d/e/f/foo"), "");
|
||||
wfile(td.path().join("x/y/foo"), "");
|
||||
|
||||
let paths = vec![
|
||||
td.path().join("a"),
|
||||
td.path().join("d"),
|
||||
td.path().join("x"),
|
||||
];
|
||||
|
||||
assert_paths(
|
||||
td.path(),
|
||||
&WalkBuilder::from_iter(paths),
|
||||
&[
|
||||
"x",
|
||||
"x/y",
|
||||
"x/y/foo",
|
||||
"d",
|
||||
"d/e",
|
||||
"d/e/f",
|
||||
"d/e/f/foo",
|
||||
"a",
|
||||
"a/b",
|
||||
"a/b/foo",
|
||||
"a/b/c",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// This should always panic and never hang.
|
||||
//
|
||||
// Ref: https://github.com/BurntSushi/ripgrep/issues/3009
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panic_in_parallel() {
|
||||
let td = tmpdir();
|
||||
wfile(td.path().join("foo.txt"), "");
|
||||
|
||||
WalkBuilder::new(td.path())
|
||||
.threads(40)
|
||||
.build_parallel()
|
||||
.run(|| Box::new(|_| panic!("oops!")));
|
||||
}
|
||||
|
||||
// This should always panic and never hang. The first call to the visitor
|
||||
// builder is used while processing the root paths. Previously, a panic on
|
||||
// the third call occurred after the first worker had already been spawned,
|
||||
// leaving it waiting indefinitely for workers that were never created.
|
||||
#[test]
|
||||
#[should_panic(expected = "builder panic")]
|
||||
fn panic_in_parallel_builder() {
|
||||
let td = tmpdir();
|
||||
wfile(td.path().join("foo.txt"), "");
|
||||
|
||||
let mut builds = 0;
|
||||
WalkBuilder::new(td.path()).threads(2).build_parallel().run(|| {
|
||||
builds += 1;
|
||||
if builds == 3 {
|
||||
panic!("builder panic");
|
||||
}
|
||||
Box::new(|_| WalkState::Continue)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use std::path::Path;
|
|||
|
||||
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||
|
||||
const IGNORE_FILE: &'static str = "tests/gitignore_matched_path_or_any_parents_tests.gitignore";
|
||||
const IGNORE_FILE: &'static str =
|
||||
"tests/gitignore_matched_path_or_any_parents_tests.gitignore";
|
||||
|
||||
fn get_gitignore() -> Gitignore {
|
||||
let mut builder = GitignoreBuilder::new("ROOT");
|
||||
|
|
@ -23,7 +24,9 @@ fn test_path_should_be_under_root() {
|
|||
#[test]
|
||||
fn test_files_in_root() {
|
||||
let gitignore = get_gitignore();
|
||||
let m = |path: &str| gitignore.matched_path_or_any_parents(Path::new(path), false);
|
||||
let m = |path: &str| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), false)
|
||||
};
|
||||
|
||||
// 0x
|
||||
assert!(m("ROOT/file_root_00").is_ignore());
|
||||
|
|
@ -53,7 +56,9 @@ fn test_files_in_root() {
|
|||
#[test]
|
||||
fn test_files_in_deep() {
|
||||
let gitignore = get_gitignore();
|
||||
let m = |path: &str| gitignore.matched_path_or_any_parents(Path::new(path), false);
|
||||
let m = |path: &str| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), false)
|
||||
};
|
||||
|
||||
// 0x
|
||||
assert!(m("ROOT/parent_dir/file_deep_00").is_ignore());
|
||||
|
|
@ -83,8 +88,9 @@ fn test_files_in_deep() {
|
|||
#[test]
|
||||
fn test_dirs_in_root() {
|
||||
let gitignore = get_gitignore();
|
||||
let m =
|
||||
|path: &str, is_dir: bool| gitignore.matched_path_or_any_parents(Path::new(path), is_dir);
|
||||
let m = |path: &str, is_dir: bool| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), is_dir)
|
||||
};
|
||||
|
||||
// 00
|
||||
assert!(m("ROOT/dir_root_00", true).is_ignore());
|
||||
|
|
@ -186,20 +192,25 @@ fn test_dirs_in_root() {
|
|||
#[test]
|
||||
fn test_dirs_in_deep() {
|
||||
let gitignore = get_gitignore();
|
||||
let m =
|
||||
|path: &str, is_dir: bool| gitignore.matched_path_or_any_parents(Path::new(path), is_dir);
|
||||
let m = |path: &str, is_dir: bool| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), is_dir)
|
||||
};
|
||||
|
||||
// 00
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_00/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 01
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_01/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 02
|
||||
assert!(m("ROOT/parent_dir/dir_deep_02", true).is_none());
|
||||
|
|
@ -241,51 +252,67 @@ fn test_dirs_in_deep() {
|
|||
assert!(m("ROOT/parent_dir/dir_deep_20", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_20/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 21
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_21/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 22
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_22/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 23
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_23/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 30
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_30/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 31
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_31/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 32
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_32/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 33
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_33/child_dir/file", false).is_ignore()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue