mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-07-26 09:42:00 -07:00
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
This commit is contained in:
committed by
Andrew Gallant
parent
7cbc535d70
commit
718a00f6f2
10
CHANGELOG.md
10
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)
|
0.10.0 (2018-09-07)
|
||||||
===================
|
===================
|
||||||
This is a new minor version release of ripgrep that contains some major new
|
This is a new minor version release of ripgrep that contains some major new
|
||||||
|
5
GUIDE.md
5
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
|
will be processed with respect to the directory it resides in, just like
|
||||||
`.gitignore`.
|
`.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
|
For a more in depth description of how glob patterns in a `.gitignore` file
|
||||||
are interpreted, please see `man gitignore`.
|
are interpreted, please see `man gitignore`.
|
||||||
|
|
||||||
|
@@ -115,6 +115,10 @@ _rg() {
|
|||||||
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs)--no-ignore[don't respect ignore files]"
|
"(--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]'
|
$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
|
+ '(ignore-global)' # Global ignore-file options
|
||||||
"--no-ignore-global[don't respect global ignore files]"
|
"--no-ignore-global[don't respect global ignore files]"
|
||||||
$no'--ignore-global[respect global ignore files]'
|
$no'--ignore-global[respect global ignore files]'
|
||||||
|
@@ -73,6 +73,8 @@ struct IgnoreOptions {
|
|||||||
git_ignore: bool,
|
git_ignore: bool,
|
||||||
/// Whether to read .git/info/exclude files.
|
/// Whether to read .git/info/exclude files.
|
||||||
git_exclude: bool,
|
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.
|
/// Ignore is a matcher useful for recursively walking one or more directories.
|
||||||
@@ -225,7 +227,11 @@ impl Ignore {
|
|||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
let (m, err) =
|
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);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -233,7 +239,12 @@ impl Ignore {
|
|||||||
if !self.0.opts.ignore {
|
if !self.0.opts.ignore {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} 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);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -241,7 +252,12 @@ impl Ignore {
|
|||||||
if !self.0.opts.git_ignore {
|
if !self.0.opts.git_ignore {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} 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);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -249,7 +265,12 @@ impl Ignore {
|
|||||||
if !self.0.opts.git_exclude {
|
if !self.0.opts.git_exclude {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} 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);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -483,6 +504,7 @@ impl IgnoreBuilder {
|
|||||||
git_global: true,
|
git_global: true,
|
||||||
git_ignore: true,
|
git_ignore: true,
|
||||||
git_exclude: true,
|
git_exclude: true,
|
||||||
|
ignore_case_insensitive: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,7 +518,11 @@ impl IgnoreBuilder {
|
|||||||
if !self.opts.git_global {
|
if !self.opts.git_global {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} 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 {
|
if let Some(err) = err {
|
||||||
debug!("{}", err);
|
debug!("{}", err);
|
||||||
}
|
}
|
||||||
@@ -627,6 +653,17 @@ impl IgnoreBuilder {
|
|||||||
self.opts.git_exclude = yes;
|
self.opts.git_exclude = yes;
|
||||||
self
|
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.
|
/// Creates a new gitignore matcher for the directory given.
|
||||||
@@ -638,9 +675,11 @@ impl IgnoreBuilder {
|
|||||||
pub fn create_gitignore<T: AsRef<OsStr>>(
|
pub fn create_gitignore<T: AsRef<OsStr>>(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
names: &[T],
|
names: &[T],
|
||||||
|
case_insensitive: bool,
|
||||||
) -> (Gitignore, Option<Error>) {
|
) -> (Gitignore, Option<Error>) {
|
||||||
let mut builder = GitignoreBuilder::new(dir);
|
let mut builder = GitignoreBuilder::new(dir);
|
||||||
let mut errs = PartialErrorBuilder::default();
|
let mut errs = PartialErrorBuilder::default();
|
||||||
|
builder.case_insensitive(case_insensitive).unwrap();
|
||||||
for name in names {
|
for name in names {
|
||||||
let gipath = dir.join(name.as_ref());
|
let gipath = dir.join(name.as_ref());
|
||||||
errs.maybe_push_ignore_io(builder.add(gipath));
|
errs.maybe_push_ignore_io(builder.add(gipath));
|
||||||
|
@@ -127,16 +127,7 @@ impl Gitignore {
|
|||||||
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
|
/// `$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.
|
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
|
||||||
pub fn global() -> (Gitignore, Option<Error>) {
|
pub fn global() -> (Gitignore, Option<Error>) {
|
||||||
match gitconfig_excludes_path() {
|
GitignoreBuilder::new("").build_global()
|
||||||
None => (Gitignore::empty(), None),
|
|
||||||
Some(path) => {
|
|
||||||
if !path.is_file() {
|
|
||||||
(Gitignore::empty(), None)
|
|
||||||
} else {
|
|
||||||
Gitignore::new(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new empty gitignore matcher that never matches anything.
|
/// 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.
|
/// Add each glob from the file path given.
|
||||||
///
|
///
|
||||||
/// The file given should be formatted as a `gitignore` file.
|
/// 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.
|
/// 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.
|
/// This is disabled by default.
|
||||||
pub fn case_insensitive(
|
pub fn case_insensitive(
|
||||||
&mut self, yes: bool
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
) -> Result<&mut GitignoreBuilder, Error> {
|
) -> Result<&mut GitignoreBuilder, Error> {
|
||||||
|
// TODO: This should not return a `Result`. Fix this in the next semver
|
||||||
|
// release.
|
||||||
self.case_insensitive = yes;
|
self.case_insensitive = yes;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
@@ -144,8 +144,11 @@ impl OverrideBuilder {
|
|||||||
///
|
///
|
||||||
/// This is disabled by default.
|
/// This is disabled by default.
|
||||||
pub fn case_insensitive(
|
pub fn case_insensitive(
|
||||||
&mut self, yes: bool
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
) -> Result<&mut OverrideBuilder, Error> {
|
) -> Result<&mut OverrideBuilder, Error> {
|
||||||
|
// TODO: This should not return a `Result`. Fix this in the next semver
|
||||||
|
// release.
|
||||||
self.builder.case_insensitive(yes)?;
|
self.builder.case_insensitive(yes)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
@@ -764,6 +764,14 @@ impl WalkBuilder {
|
|||||||
self
|
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.
|
/// Set a function for sorting directory entries by their path.
|
||||||
///
|
///
|
||||||
/// If a compare function is set, the resulting iterator will return all
|
/// If a compare function is set, the resulting iterator will return all
|
||||||
|
22
src/app.rs
22
src/app.rs
@@ -571,6 +571,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_iglob(&mut args);
|
flag_iglob(&mut args);
|
||||||
flag_ignore_case(&mut args);
|
flag_ignore_case(&mut args);
|
||||||
flag_ignore_file(&mut args);
|
flag_ignore_file(&mut args);
|
||||||
|
flag_ignore_file_case_insensitive(&mut args);
|
||||||
flag_invert_match(&mut args);
|
flag_invert_match(&mut args);
|
||||||
flag_json(&mut args);
|
flag_json(&mut args);
|
||||||
flag_line_buffered(&mut args);
|
flag_line_buffered(&mut args);
|
||||||
@@ -1209,6 +1210,27 @@ directly on the command line, then used -g instead.
|
|||||||
args.push(arg);
|
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>) {
|
fn flag_invert_match(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Invert matching.";
|
const SHORT: &str = "Invert matching.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
|
10
src/args.rs
10
src/args.rs
@@ -797,7 +797,8 @@ impl ArgMatches {
|
|||||||
&& !self.no_ignore_vcs()
|
&& !self.no_ignore_vcs()
|
||||||
&& !self.no_ignore_global())
|
&& !self.no_ignore_global())
|
||||||
.git_ignore(!self.no_ignore() && !self.no_ignore_vcs())
|
.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() {
|
if !self.no_ignore() {
|
||||||
builder.add_custom_ignore_filename(".rgignore");
|
builder.add_custom_ignore_filename(".rgignore");
|
||||||
}
|
}
|
||||||
@@ -1003,6 +1004,11 @@ impl ArgMatches {
|
|||||||
self.is_present("hidden") || self.unrestricted_count() >= 2
|
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.
|
/// Return all of the ignore file paths given on the command line.
|
||||||
fn ignore_paths(&self) -> Vec<PathBuf> {
|
fn ignore_paths(&self) -> Vec<PathBuf> {
|
||||||
let paths = match self.values_of_os("ignore-file") {
|
let paths = match self.values_of_os("ignore-file") {
|
||||||
@@ -1143,7 +1149,7 @@ impl ArgMatches {
|
|||||||
builder.add(&glob)?;
|
builder.add(&glob)?;
|
||||||
}
|
}
|
||||||
// This only enables case insensitivity for subsequent globs.
|
// 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") {
|
for glob in self.values_of_lossy_vec("iglob") {
|
||||||
builder.add(&glob)?;
|
builder.add(&glob)?;
|
||||||
}
|
}
|
||||||
|
@@ -568,3 +568,16 @@ rgtest!(r1064, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
dir.create("input", "abc");
|
dir.create("input", "abc");
|
||||||
eqnice!("input:abc\n", cmd.arg("a(.*c)").stdout());
|
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()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user