summaryrefslogtreecommitdiffstats
path: root/ignore/src/dir.rs
diff options
context:
space:
mode:
authorptzz <ponca707@gmail.com>2018-01-29 22:06:05 +0100
committerAndrew Gallant <jamslam@gmail.com>2018-01-29 16:06:05 -0500
commit3cb4d1337e9846c0518e3e2dfc4f7b007b16a288 (patch)
treee5626201bd0dbedd0c4dd03f2847982032bf359a /ignore/src/dir.rs
parent8514d4fbb45265c263c982d5107216eefb2017dc (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
Diffstat (limited to 'ignore/src/dir.rs')
-rw-r--r--ignore/src/dir.rs112
1 files changed, 100 insertions, 12 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() {