1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2025-08-04 21:52:54 +02:00

cli: make rg -vf file behave sensibly

Previously, when `file` is empty (literally empty, as in, zero byte),
`rg -f file` and `rg -vf file` would behave identically. This is odd
and also doesn't match how GNU grep behaves. It's also not logically
correct. An empty file means _zero_ patterns which is an empty set. An
empty set matches nothing. Inverting the empty set should result in
matching everything.

This was because of an errant optimization that lets ripgrep quit early
if it can statically detect that no matches are possible.

Moreover, there was *also* a bug in how we constructed the PCRE2 pattern
when there are zero patterns. PCRE2 doesn't have a concept of sets of
patterns (unlike the `regex` crate), so we need to fake it with an empty
character class.

Fixes #1332, Fixes #3001, Closes #3041
This commit is contained in:
Andrew Gallant
2025-07-27 15:08:09 -04:00
parent b9f2da2782
commit afc820c9e9
4 changed files with 47 additions and 2 deletions

View File

@ -16,6 +16,9 @@ Bug fixes:
[BUG #2836](https://github.com/BurntSushi/ripgrep/issues/2836),
[BUG #2933](https://github.com/BurntSushi/ripgrep/pull/2933):
Fix bug related to gitignores from parent directories.
* [BUG #1332](https://github.com/BurntSushi/ripgrep/issues/1332),
[BUG #3001](https://github.com/BurntSushi/ripgrep/issues/3001):
Make `rg -vf file` where `file` is empty match everything.
* [BUG #2177](https://github.com/BurntSushi/ripgrep/issues/2177):
Ignore a UTF-8 BOM marker at the start of `.gitignore` (and similar files).

View File

@ -517,7 +517,7 @@ impl HiArgs {
/// When this returns false, it is impossible for ripgrep to ever report
/// a match.
pub(crate) fn matches_possible(&self) -> bool {
if self.patterns.patterns.is_empty() {
if self.patterns.patterns.is_empty() && !self.invert_match {
return false;
}
if self.max_count == Some(0) {

View File

@ -55,7 +55,12 @@ impl RegexMatcherBuilder {
format!("(?:{})", p.as_ref())
});
}
let mut singlepat = pats.join("|");
let mut singlepat = if patterns.is_empty() {
// A way to spell a pattern that can never match anything.
r"[^\S\s]".to_string()
} else {
pats.join("|")
};
if self.case_smart && !has_uppercase_literal(&singlepat) {
builder.caseless(true);
}

View File

@ -955,6 +955,43 @@ rgtest!(r1319, |dir: Dir, mut cmd: TestCommand| {
);
});
// See: https://github.com/BurntSushi/ripgrep/issues/1332
rgtest!(r1334_invert_empty_patterns, |dir: Dir, _cmd: TestCommand| {
dir.create("zero-patterns", "");
dir.create("one-pattern", "\n");
dir.create("haystack", "one\ntwo\nthree\n");
// zero patterns matches nothing
{
let mut cmd = dir.command();
cmd.arg("-f").arg("zero-patterns").arg("haystack").assert_err();
}
// one pattern that matches empty string matches everything
{
let mut cmd = dir.command();
eqnice!(
"one\ntwo\nthree\n",
cmd.arg("-f").arg("one-pattern").arg("haystack").stdout()
);
}
// inverting zero patterns matches everything
// (This is the regression. ripgrep used to match nothing because of an
// incorrect optimization.)
{
let mut cmd = dir.command();
eqnice!(
"one\ntwo\nthree\n",
cmd.arg("-vf").arg("zero-patterns").arg("haystack").stdout()
);
}
// inverting one pattern that matches empty string matches nothing
{
let mut cmd = dir.command();
cmd.arg("-vf").arg("one-pattern").arg("haystack").assert_err();
}
});
// See: https://github.com/BurntSushi/ripgrep/issues/1334
rgtest!(r1334_crazy_literals, |dir: Dir, mut cmd: TestCommand| {
dir.create("patterns", &"1.208.0.0/12\n".repeat(40));