diff options
author | ptzz <ponca707@gmail.com> | 2018-01-29 22:06:05 +0100 |
---|---|---|
committer | Andrew Gallant <jamslam@gmail.com> | 2018-01-29 16:06:05 -0500 |
commit | 3cb4d1337e9846c0518e3e2dfc4f7b007b16a288 (patch) | |
tree | e5626201bd0dbedd0c4dd03f2847982032bf359a | |
parent | 8514d4fbb45265c263c982d5107216eefb2017dc (diff) |
ignore: support custom file names
This commit adds support for ignore files with custom names. This
allows for application specific ignorefile names, e.g. using
`.fdignore` for `fd`.
See also: https://github.com/BurntSushi/ripgrep/issues/673
See also: https://github.com/sharkdp/fd/issues/156
-rw-r--r-- | ignore/src/dir.rs | 112 | ||||
-rw-r--r-- | ignore/src/walk.rs | 32 | ||||
-rw-r--r-- | src/args.rs | 3 |
3 files changed, 134 insertions, 13 deletions
diff --git a/ignore/src/dir.rs b/ignore/src/dir.rs index 6de66191..923d574e 100644 --- a/ignore/src/dir.rs +++ b/ignore/src/dir.rs @@ -14,7 +14,7 @@ // well. use std::collections::HashMap; -use std::ffi::OsString; +use std::ffi::{OsString, OsStr}; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; @@ -109,8 +109,12 @@ struct IgnoreInner { /// The absolute base path of this matcher. Populated only if parent /// directories are added. absolute_base: Option<Arc<PathBuf>>, - /// Explicit ignore matchers specified by the caller. + /// Explicit global ignore matchers specified by the caller. explicit_ignores: Arc<Vec<Gitignore>>, + /// Ignore files used in addition to `.ignore` + custom_ignore_filenames: Arc<Vec<OsString>>, + /// The matcher for custom ignore files + custom_ignore_matcher: Gitignore, /// The matcher for .ignore files. ignore_matcher: Gitignore, /// A global gitignore matcher, usually from $XDG_CONFIG_HOME/git/ignore. @@ -210,14 +214,19 @@ impl Ignore { /// Like add_child, but takes a full path and returns an IgnoreInner. fn add_child_path(&self, dir: &Path) -> (IgnoreInner, Option<Error>) { - static IG_NAMES: &'static [&'static str] = &[".rgignore", ".ignore"]; - let mut errs = PartialErrorBuilder::default(); + let custom_ig_matcher = + { + let (m, err) = + create_gitignore(&dir, &self.0.custom_ignore_filenames); + errs.maybe_push(err); + m + }; let ig_matcher = if !self.0.opts.ignore { Gitignore::empty() } else { - let (m, err) = create_gitignore(&dir, IG_NAMES); + let (m, err) = create_gitignore(&dir, &[".ignore"]); errs.maybe_push(err); m }; @@ -246,6 +255,8 @@ impl Ignore { is_absolute_parent: false, absolute_base: self.0.absolute_base.clone(), explicit_ignores: self.0.explicit_ignores.clone(), + custom_ignore_filenames: self.0.custom_ignore_filenames.clone(), + custom_ignore_matcher: custom_ig_matcher, ignore_matcher: ig_matcher, git_global_matcher: self.0.git_global_matcher.clone(), git_ignore_matcher: gi_matcher, @@ -314,10 +325,15 @@ impl Ignore { path: &Path, is_dir: bool, ) -> Match<IgnoreMatch<'a>> { - let (mut m_ignore, mut m_gi, mut m_gi_exclude, mut m_explicit) = - (Match::None, Match::None, Match::None, Match::None); + let (mut m_custom_ignore, mut m_ignore, mut m_gi, mut m_gi_exclude, mut m_explicit) = + (Match::None, Match::None, Match::None, Match::None, Match::None); let mut saw_git = false; for ig in self.parents().take_while(|ig| !ig.0.is_absolute_parent) { + if m_custom_ignore.is_none() { + m_custom_ignore = + ig.0.custom_ignore_matcher.matched(path, is_dir) + .map(IgnoreMatch::gitignore); + } if m_ignore.is_none() { m_ignore = ig.0.ignore_matcher.matched(path, is_dir) @@ -338,6 +354,11 @@ impl Ignore { if let Some(abs_parent_path) = self.absolute_base() { let path = abs_parent_path.join(path); for ig in self.parents().skip_while(|ig|!ig.0.is_absolute_parent) { + if m_custom_ignore.is_none() { + m_custom_ignore = + ig.0.custom_ignore_matcher.matched(&path, is_dir) + .map(IgnoreMatch::gitignore); + } if m_ignore.is_none() { m_ignore = ig.0.ignore_matcher.matched(&path, is_dir) @@ -365,7 +386,7 @@ impl Ignore { let m_global = self.0.git_global_matcher.matched(&path, is_dir) .map(IgnoreMatch::gitignore); - m_ignore.or(m_gi).or(m_gi_exclude).or(m_global).or(m_explicit) + m_custom_ignore.or(m_ignore).or(m_gi).or(m_gi_exclude).or(m_global).or(m_explicit) } /// Returns an iterator over parent ignore matchers, including this one. @@ -408,8 +429,10 @@ pub struct IgnoreBuilder { overrides: Arc<Override>, /// A type matcher (default is empty). types: Arc<Types>, - /// Explicit ignore matchers. + /// Explicit global ignore matchers. explicit_ignores: Vec<Gitignore>, + /// Ignore files in addition to .ignore. + custom_ignore_filenames: Vec<OsString>, /// Ignore config. opts: IgnoreOptions, } @@ -425,6 +448,7 @@ impl IgnoreBuilder { overrides: Arc::new(Override::empty()), types: Arc::new(Types::empty()), explicit_ignores: vec![], + custom_ignore_filenames: vec![], opts: IgnoreOptions { hidden: true, ignore: true, @@ -450,6 +474,7 @@ impl IgnoreBuilder { } gi }; + Ignore(Arc::new(IgnoreInner { compiled: Arc::new(RwLock::new(HashMap::new())), dir: self.dir.clone(), @@ -459,6 +484,8 @@ impl IgnoreBuilder { is_absolute_parent: true, absolute_base: None, explicit_ignores: Arc::new(self.explicit_ignores.clone()), + custom_ignore_filenames: Arc::new(self.custom_ignore_filenames.clone()), + custom_ignore_matcher: Gitignore::empty(), ignore_matcher: Gitignore::empty(), git_global_matcher: Arc::new(git_global_matcher), git_ignore_matcher: Gitignore::empty(), @@ -494,6 +521,20 @@ impl IgnoreBuilder { self } + /// Add a custom ignore file name + /// + /// These ignore files have higher precedence than all other ignore files. + /// + /// When specifying multiple names, earlier names have lower precedence than + /// later names. + pub fn add_custom_ignore_filename<S: AsRef<OsStr>>( + &mut self, + file_name: S + ) -> &mut IgnoreBuilder { + self.custom_ignore_filenames.push(file_name.as_ref().to_os_string()); + self + } + /// Enables ignoring hidden files. /// /// This is enabled by default. @@ -555,14 +596,14 @@ impl IgnoreBuilder { /// order given (earlier names have lower precedence than later names). /// /// I/O errors are ignored. -pub fn create_gitignore( +pub fn create_gitignore<T: AsRef<OsStr>>( dir: &Path, - names: &[&str], + names: &[T], ) -> (Gitignore, Option<Error>) { let mut builder = GitignoreBuilder::new(dir); let mut errs = PartialErrorBuilder::default(); for name in names { - let gipath = dir.join(name); + let gipath = dir.join(name.as_ref()); errs.maybe_push_ignore_io(builder.add(gipath)); } let gi = match builder.build() { @@ -655,6 +696,53 @@ mod tests { assert!(ig.matched("baz", false).is_none()); } + #[test] + fn custom_ignore() { + let td = TempDir::new("ignore-test-").unwrap(); + let custom_ignore = ".customignore"; + wfile(td.path().join(custom_ignore), "foo\n!bar"); + + let (ig, err) = IgnoreBuilder::new() + .add_custom_ignore_filename(custom_ignore) + .build().add_child(td.path()); + assert!(err.is_none()); + assert!(ig.matched("foo", false).is_ignore()); + assert!(ig.matched("bar", false).is_whitelist()); + assert!(ig.matched("baz", false).is_none()); + } + + // Tests that a custom ignore file will override an .ignore. + #[test] + fn custom_ignore_over_ignore() { + let td = TempDir::new("ignore-test-").unwrap(); + let custom_ignore = ".customignore"; + wfile(td.path().join(".ignore"), "foo"); + wfile(td.path().join(custom_ignore), "!foo"); + + let (ig, err) = IgnoreBuilder::new() + .add_custom_ignore_filename(custom_ignore) + .build().add_child(td.path()); + assert!(err.is_none()); + assert!(ig.matched("foo", false).is_whitelist()); + } + + // Tests that earlier custom ignore files have lower precedence than later. + #[test] + fn custom_ignore_precedence() { + let td = TempDir::new("ignore-test-").unwrap(); + let custom_ignore1 = ".customignore1"; + let custom_ignore2 = ".customignore2"; + wfile(td.path().join(custom_ignore1), "foo"); + wfile(td.path().join(custom_ignore2), "!foo"); + + let (ig, err) = IgnoreBuilder::new() + .add_custom_ignore_filename(custom_ignore1) + .add_custom_ignore_filename(custom_ignore2) + .build().add_child(td.path()); + assert!(err.is_none()); + assert!(ig.matched("foo", false).is_whitelist()); + } + // Tests that an .ignore will override a .gitignore. #[test] fn ignore_over_gitignore() { diff --git a/ignore/src/walk.rs b/ignore/src/walk.rs index 296bef98..cde4f750 100644 --- a/ignore/src/walk.rs +++ b/ignore/src/walk.rs @@ -538,7 +538,7 @@ impl WalkBuilder { self } - /// Add an ignore file to the matcher. + /// Add a global ignore file to the matcher. /// /// This has lower precedence than all other sources of ignore rules. /// @@ -557,6 +557,20 @@ impl WalkBuilder { errs.into_error_option() } + /// Add a custom ignore file name + /// + /// These ignore files have higher precedence than all other ignore files. + /// + /// When specifying multiple names, earlier names have lower precedence than + /// later names. + pub fn add_custom_ignore_filename<S: AsRef<OsStr>>( + &mut self, + file_name: S + ) -> &mut WalkBuilder { + self.ig_builder.add_custom_ignore_filename(file_name); + self + } + /// Add an override matcher. /// /// By default, no override matcher is used. @@ -1477,6 +1491,22 @@ mod tests { } #[test] + fn custom_ignore() { + let td = TempDir::new("walk-test-").unwrap(); + let custom_ignore = ".customignore"; + mkdirp(td.path().join("a")); + wfile(td.path().join(custom_ignore), "foo"); + wfile(td.path().join("foo"), ""); + wfile(td.path().join("a/foo"), ""); + wfile(td.path().join("bar"), ""); + wfile(td.path().join("a/bar"), ""); + + let mut builder = WalkBuilder::new(td.path()); + builder.add_custom_ignore_filename(&custom_ignore); + assert_paths(td.path(), &builder, &["bar", "a", "a/bar"]); + } + + #[test] fn gitignore() { let td = TempDir::new("walk-test-").unwrap(); mkdirp(td.path().join("a")); diff --git a/src/args.rs b/src/args.rs index d2d0232b..56dacc97 100644 --- a/src/args.rs +++ b/src/args.rs @@ -288,6 +288,9 @@ impl Args { wd.git_ignore(!self.no_ignore && !self.no_ignore_vcs); wd.git_exclude(!self.no_ignore && !self.no_ignore_vcs); wd.ignore(!self.no_ignore); + if !self.no_ignore { + wd.add_custom_ignore_filename(".rgignore"); + } wd.parents(!self.no_ignore_parent); wd.threads(self.threads()); if self.sort_files { |