summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Torosyan <davidtorosyan.git@gmail.com>2019-01-20 17:32:34 -0800
committerAndrew Gallant <jamslam@gmail.com>2019-01-22 20:03:59 -0500
commit718a00f6f2f88238546f7d33c1ea52217002495e (patch)
tree6dc4db1c1d1af1f5d87957754341b4f74ff41984
parent7cbc535d70a53c81dfa3e58552c01f21c2e38d28 (diff)
ripgrep: add --ignore-file-case-insensitive
The --ignore-file-case-insensitive flag causes all .gitignore/.rgignore/.ignore files to have their globs matched without regard for case. Because this introduces a potentially significant performance regression, this is always disabled by default. Users that need case insensitive matching can enable it on a case by case basis. Closes #1164, Closes #1170
-rw-r--r--CHANGELOG.md10
-rw-r--r--GUIDE.md5
-rw-r--r--complete/_rg4
-rw-r--r--ignore/src/dir.rs49
-rw-r--r--ignore/src/gitignore.rs49
-rw-r--r--ignore/src/overrides.rs7
-rw-r--r--ignore/src/walk.rs8
-rw-r--r--src/app.rs22
-rw-r--r--src/args.rs10
-rw-r--r--tests/regression.rs13
10 files changed, 156 insertions, 21 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53c21cc2..e7792a3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+0.11.0 (TBD)
+============
+TODO.
+
+Feature enhancements:
+
+* [FEATURE #1170](https://github.com/BurntSushi/ripgrep/pull/1170):
+ Add `--ignore-file-case-insensitive` for case insensitive .ignore globs.
+
+
0.10.0 (2018-09-07)
===================
This is a new minor version release of ripgrep that contains some major new
diff --git a/GUIDE.md b/GUIDE.md
index ffcbff9f..8523b6a5 100644
--- a/GUIDE.md
+++ b/GUIDE.md
@@ -235,6 +235,11 @@ Like `.gitignore`, a `.ignore` file can be placed in any directory. Its rules
will be processed with respect to the directory it resides in, just like
`.gitignore`.
+To process `.gitignore` and `.ignore` files case insensitively, use the flag
+`--ignore-file-case-insensitive`. This is especially useful on case insensitive
+file systems like those on Windows and macOS. Note though that this can come
+with a significant performance penalty, and is therefore disabled by default.
+
For a more in depth description of how glob patterns in a `.gitignore` file
are interpreted, please see `man gitignore`.
diff --git a/complete/_rg b/complete/_rg
index 6f7b0ef8..f48c0998 100644
--- a/complete/_rg
+++ b/complete/_rg
@@ -115,6 +115,10 @@ _rg() {
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs)--no-ignore[don't respect ignore files]"
$no'(--ignore-global --ignore-parent --ignore-vcs)--ignore[respect ignore files]'
+ + '(ignore-file-case-insensitive)' # Ignore-file case sensitivity options
+ '--ignore-file-case-insensitive[process ignore files case insensitively]'
+ $no'--no-ignore-file-case-insensitive[process ignore files case sensitively]'
+
+ '(ignore-global)' # Global ignore-file options
"--no-ignore-global[don't respect global ignore files]"
$no'--ignore-global[respect global ignore files]'
diff --git a/ignore/src/dir.rs b/ignore/src/dir.rs
index 66b18635..30f4cb87 100644
--- a/ignore/src/dir.rs
+++ b/ignore/src/dir.rs
@@ -73,6 +73,8 @@ struct IgnoreOptions {
git_ignore: bool,
/// Whether to read .git/info/exclude files.
git_exclude: bool,
+ /// Whether to ignore files case insensitively
+ ignore_case_insensitive: bool,
}
/// Ignore is a matcher useful for recursively walking one or more directories.
@@ -225,7 +227,11 @@ impl Ignore {
Gitignore::empty()
} else {
let (m, err) =
- create_gitignore(&dir, &self.0.custom_ignore_filenames);
+ create_gitignore(
+ &dir,
+ &self.0.custom_ignore_filenames,
+ self.0.opts.ignore_case_insensitive,
+ );
errs.maybe_push(err);
m
};
@@ -233,7 +239,12 @@ impl Ignore {
if !self.0.opts.ignore {
Gitignore::empty()
} else {
- let (m, err) = create_gitignore(&dir, &[".ignore"]);
+ let (m, err) =
+ create_gitignore(
+ &dir,
+ &[".ignore"],
+ self.0.opts.ignore_case_insensitive,
+ );
errs.maybe_push(err);
m
};
@@ -241,7 +252,12 @@ impl Ignore {
if !self.0.opts.git_ignore {
Gitignore::empty()
} else {
- let (m, err) = create_gitignore(&dir, &[".gitignore"]);
+ let (m, err) =
+ create_gitignore(
+ &dir,
+ &[".gitignore"],
+ self.0.opts.ignore_case_insensitive,
+ );
errs.maybe_push(err);
m
};
@@ -249,7 +265,12 @@ impl Ignore {
if !self.0.opts.git_exclude {
Gitignore::empty()
} else {
- let (m, err) = create_gitignore(&dir, &[".git/info/exclude"]);
+ let (m, err) =
+ create_gitignore(
+ &dir,
+ &[".git/info/exclude"],
+ self.0.opts.ignore_case_insensitive,
+ );
errs.maybe_push(err);
m
};
@@ -483,6 +504,7 @@ impl IgnoreBuilder {
git_global: true,
git_ignore: true,
git_exclude: true,
+ ignore_case_insensitive: false,
},
}
}
@@ -496,7 +518,11 @@ impl IgnoreBuilder {
if !self.opts.git_global {
Gitignore::empty()
} else {
- let (gi, err) = Gitignore::global();
+ let mut builder = GitignoreBuilder::new("");
+ builder
+ .case_insensitive(self.opts.ignore_case_insensitive)
+ .unwrap();
+ let (gi, err) = builder.build_global();
if let Some(err) = err {
debug!("{}", err);
}
@@ -627,6 +653,17 @@ impl IgnoreBuilder {
self.opts.git_exclude = yes;
self
}
+
+ /// Process ignore files case insensitively
+ ///
+ /// This is disabled by default.
+ pub fn ignore_case_insensitive(
+ &mut self,
+ yes: bool,
+ ) -> &mut IgnoreBuilder {
+ self.opts.ignore_case_insensitive = yes;
+ self
+ }
}
/// Creates a new gitignore matcher for the directory given.
@@ -638,9 +675,11 @@ impl IgnoreBuilder {
pub fn create_gitignore<T: AsRef<OsStr>>(
dir: &Path,
names: &[T],
+ case_insensitive: bool,
) -> (Gitignore, Option<Error>) {
let mut builder = GitignoreBuilder::new(dir);
let mut errs = PartialErrorBuilder::default();
+ builder.case_insensitive(case_insensitive).unwrap();
for name in names {
let gipath = dir.join(name.as_ref());
errs.maybe_push_ignore_io(builder.add(gipath));
diff --git a/ignore/src/gitignore.rs b/ignore/src/gitignore.rs
index a151e2de..66f98dfe 100644
--- a/ignore/src/gitignore.rs
+++ b/ignore/src/gitignore.rs
@@ -127,16 +127,7 @@ impl Gitignore {
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
pub fn global() -> (Gitignore, Option<Error>) {
- match gitconfig_excludes_path() {
- None => (Gitignore::empty(), None),
- Some(path) => {
- if !path.is_file() {
- (Gitignore::empty(), None)
- } else {
- Gitignore::new(path)
- }
- }
- }
+ GitignoreBuilder::new("").build_global()
}
/// Creates a new empty gitignore matcher that never matches anything.
@@ -359,6 +350,36 @@ impl GitignoreBuilder {
})
}
+ /// Build a global gitignore matcher using the configuration in this
+ /// builder.
+ ///
+ /// This consumes ownership of the builder unlike `build` because it
+ /// must mutate the builder to add the global gitignore globs.
+ ///
+ /// Note that this ignores the path given to this builder's constructor
+ /// and instead derives the path automatically from git's global
+ /// configuration.
+ pub fn build_global(mut self) -> (Gitignore, Option<Error>) {
+ match gitconfig_excludes_path() {
+ None => (Gitignore::empty(), None),
+ Some(path) => {
+ if !path.is_file() {
+ (Gitignore::empty(), None)
+ } else {
+ let mut errs = PartialErrorBuilder::default();
+ errs.maybe_push_ignore_io(self.add(path));
+ match self.build() {
+ Ok(gi) => (gi, errs.into_error_option()),
+ Err(err) => {
+ errs.push(err);
+ (Gitignore::empty(), errs.into_error_option())
+ }
+ }
+ }
+ }
+ }
+ }
+
/// Add each glob from the file path given.
///
/// The file given should be formatted as a `gitignore` file.
@@ -505,12 +526,16 @@ impl GitignoreBuilder {
/// Toggle whether the globs should be matched case insensitively or not.
///
- /// When this option is changed, only globs added after the change will be affected.
+ /// When this option is changed, only globs added after the change will be
+ /// affected.
///
/// This is disabled by default.
pub fn case_insensitive(
- &mut self, yes: bool
+ &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;
Ok(self)
}
diff --git a/ignore/src/overrides.rs b/ignore/src/overrides.rs
index c63532af..08dbdac2 100644
--- a/ignore/src/overrides.rs
+++ b/ignore/src/overrides.rs
@@ -139,13 +139,16 @@ impl OverrideBuilder {
}
/// Toggle whether the globs should be matched case insensitively or not.
- ///
+ ///
/// When this option is changed, only globs added after the change will be affected.
///
/// This is disabled by default.
pub fn case_insensitive(
- &mut self, yes: bool
+ &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)?;
Ok(self)
}
diff --git a/ignore/src/walk.rs b/ignore/src/walk.rs
index aee7a881..ae1f58ba 100644
--- a/ignore/src/walk.rs
+++ b/ignore/src/walk.rs
@@ -764,6 +764,14 @@ impl WalkBuilder {
self
}
+ /// Process ignore files case insensitively
+ ///
+ /// This is disabled by default.
+ pub fn ignore_case_insensitive(&mut self, yes: bool) -> &mut WalkBuilder {
+ self.ig_builder.ignore_case_insensitive(yes);
+ self
+ }
+
/// Set a function for sorting directory entries by their path.
///
/// If a compare function is set, the resulting iterator will return all
diff --git a/src/app.rs b/src/app.rs
index 5b25b72f..037feec3 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -571,6 +571,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
flag_iglob(&mut args);
flag_ignore_case(&mut args);
flag_ignore_file(&mut args);
+ flag_ignore_file_case_insensitive(&mut args);
flag_invert_match(&mut args);
flag_json(&mut args);
flag_line_buffered(&mut args);
@@ -1209,6 +1210,27 @@ directly on the command line, then used -g instead.
args.push(arg);
}
+fn flag_ignore_file_case_insensitive(args: &mut Vec<RGArg>) {
+ const SHORT: &str =
+ "Process ignore files (.gitignore, .ignore, etc.) case insensitively.";
+ const LONG: &str = long!("\
+Process ignore files (.gitignore, .ignore, etc.) case insensitively. Note that
+this comes with a performance penalty and is most useful on case insensitive
+file systems (such as Windows).
+
+This flag can be disabled with the --no-ignore-file-case-insensitive flag.
+");
+ let arg = RGArg::switch("ignore-file-case-insensitive")
+ .help(SHORT).long_help(LONG)
+ .overrides("no-ignore-file-case-insensitive");
+ args.push(arg);
+
+ let arg = RGArg::switch("no-ignore-file-case-insensitive")
+ .hidden()
+ .overrides("ignore-file-case-insensitive");
+ args.push(arg);
+}
+
fn flag_invert_match(args: &mut Vec<RGArg>) {
const SHORT: &str = "Invert matching.";
const LONG: &str = long!("\
diff --git a/src/args.rs b/src/args.rs
index 70af9df1..df55df25 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -797,7 +797,8 @@ impl ArgMatches {
&& !self.no_ignore_vcs()
&& !self.no_ignore_global())
.git_ignore(!self.no_ignore() && !self.no_ignore_vcs())
- .git_exclude(!self.no_ignore() && !self.no_ignore_vcs());
+ .git_exclude(!self.no_ignore() && !self.no_ignore_vcs())
+ .ignore_case_insensitive(self.ignore_file_case_insensitive());
if !self.no_ignore() {
builder.add_custom_ignore_filename(".rgignore");
}
@@ -1003,6 +1004,11 @@ impl ArgMatches {
self.is_present("hidden") || self.unrestricted_count() >= 2
}
+ /// Returns true if ignore files should be processed case insensitively.
+ fn ignore_file_case_insensitive(&self) -> bool {
+ self.is_present("ignore-file-case-insensitive")
+ }
+
/// Return all of the ignore file paths given on the command line.
fn ignore_paths(&self) -> Vec<PathBuf> {
let paths = match self.values_of_os("ignore-file") {
@@ -1143,7 +1149,7 @@ impl ArgMatches {
builder.add(&glob)?;
}
// This only enables case insensitivity for subsequent globs.
- builder.case_insensitive(true)?;
+ builder.case_insensitive(true).unwrap();
for glob in self.values_of_lossy_vec("iglob") {
builder.add(&glob)?;
}
diff --git a/tests/regression.rs b/tests/regression.rs
index 4ee3ab53..90760ec9 100644
--- a/tests/regression.rs
+++ b/tests/regression.rs
@@ -568,3 +568,16 @@ rgtest!(r1064, |dir: Dir, mut cmd: TestCommand| {
dir.create("input", "abc");
eqnice!("input:abc\n", cmd.arg("a(.*c)").stdout());
});
+
+// See: https://github.com/BurntSushi/ripgrep/issues/1164
+rgtest!(r1164, |dir: Dir, mut cmd: TestCommand| {
+ dir.create_dir(".git");
+ dir.create(".gitignore", "myfile");
+ dir.create("MYFILE", "test");
+
+ cmd.arg("--ignore-file-case-insensitive").arg("test").assert_err();
+ eqnice!(
+ "MYFILE:test\n",
+ cmd.arg("--no-ignore-file-case-insensitive").stdout()
+ );
+});