printer: fix handling of has_match for summary printer

Previously, `Quiet` mode in the summary printer always acted like
"print matching paths," except without the printing. This happened even
if we wanted to "print non-matching paths." Since this only afflicted
quiet mode, this had the effect of flipping the exit status when
`--files-without-match --quiet` was used.

Fixes #3108, Ref #3118
This commit is contained in:
Andrew Gallant
2025-08-19 20:51:43 -04:00
parent d969ebc1e5
commit 3fc1f5f15f
4 changed files with 81 additions and 18 deletions

View File

@@ -25,6 +25,8 @@ Bug fixes:
Fix a bug where the "bytes searched" in `--stats` output could be incorrect.
* [BUG #2990](https://github.com/BurntSushi/ripgrep/issues/2990):
Fix a bug where ripgrep would mishandle globs that ended with a `.`.
* [BUG #3108](https://github.com/BurntSushi/ripgrep/issues/3108):
Fix a bug where `-q --files-without-match` inverted the exit code.
Feature enhancements:

View File

@@ -562,7 +562,16 @@ impl HiArgs {
wtr: W,
) -> Printer<W> {
let summary_kind = if self.quiet {
SummaryKind::Quiet
match search_mode {
SearchMode::FilesWithMatches
| SearchMode::Count
| SearchMode::CountMatches
| SearchMode::JSON
| SearchMode::Standard => SummaryKind::QuietWithMatch,
SearchMode::FilesWithoutMatch => {
SummaryKind::QuietWithoutMatch
}
}
} else {
match search_mode {
SearchMode::FilesWithMatches => SummaryKind::PathWithMatch,

View File

@@ -87,7 +87,13 @@ pub enum SummaryKind {
///
/// Note that if `stats` is enabled, then searching continues in order to
/// compute statistics.
Quiet,
QuietWithMatch,
/// Don't show any output and the stop the search once a non-matching file
/// is found.
///
/// Note that if `stats` is enabled, then searching continues in order to
/// compute statistics.
QuietWithoutMatch,
}
impl SummaryKind {
@@ -101,7 +107,7 @@ impl SummaryKind {
match *self {
PathWithMatch | PathWithoutMatch => true,
Count | CountMatches | Quiet => false,
Count | CountMatches | QuietWithMatch | QuietWithoutMatch => false,
}
}
@@ -112,7 +118,8 @@ impl SummaryKind {
match *self {
CountMatches => true,
Count | PathWithMatch | PathWithoutMatch | Quiet => false,
Count | PathWithMatch | PathWithoutMatch | QuietWithMatch
| QuietWithoutMatch => false,
}
}
@@ -122,8 +129,10 @@ impl SummaryKind {
use self::SummaryKind::*;
match *self {
PathWithMatch | Quiet => true,
Count | CountMatches | PathWithoutMatch => false,
PathWithMatch | QuietWithMatch => true,
Count | CountMatches | PathWithoutMatch | QuietWithoutMatch => {
false
}
}
}
}
@@ -246,9 +255,9 @@ impl SummaryBuilder {
///
/// When this is enabled, this printer may need to do extra work in order
/// to compute certain statistics, which could cause the search to take
/// longer. For example, in `Quiet` mode, a search can quit after finding
/// the first match, but if `stats` is enabled, then the search will
/// continue after the first match in order to compute statistics.
/// longer. For example, in `QuietWithMatch` mode, a search can quit after
/// finding the first match, but if `stats` is enabled, then the search
/// will continue after the first match in order to compute statistics.
///
/// For a complete description of available statistics, see [`Stats`].
///
@@ -505,7 +514,9 @@ impl<'p, 's, M: Matcher, W: WriteColor> SummarySink<'p, 's, M, W> {
/// search.
pub fn has_match(&self) -> bool {
match self.summary.config.kind {
SummaryKind::PathWithoutMatch => self.match_count == 0,
SummaryKind::PathWithoutMatch | SummaryKind::QuietWithoutMatch => {
self.match_count == 0
}
_ => self.match_count > 0,
}
}
@@ -749,14 +760,14 @@ impl<'p, 's, M: Matcher, W: WriteColor> Sink for SummarySink<'p, 's, M, W> {
// don't quit and therefore search the entire contents of the file.
//
// There is an unfortunate inconsistency here. Namely, when using
// Quiet or PathWithMatch, then the printer can quit after the first
// match seen, which could be long before seeing binary data. This
// means that using PathWithMatch can print a path where as using
// QuietWithMatch or PathWithMatch, then the printer can quit after the
// first match seen, which could be long before seeing binary data.
// This means that using PathWithMatch can print a path where as using
// Count might not print it at all because of binary data.
//
// It's not possible to fix this without also potentially significantly
// impacting the performance of Quiet or PathWithMatch, so we accept
// the bug.
// impacting the performance of QuietWithMatch or PathWithMatch, so we
// accept the bug.
if self.binary_byte_offset.is_some()
&& searcher.binary_detection().quit_byte().is_some()
{
@@ -798,7 +809,7 @@ impl<'p, 's, M: Matcher, W: WriteColor> Sink for SummarySink<'p, 's, M, W> {
self.write_path_line(searcher)?;
}
}
SummaryKind::Quiet => {}
SummaryKind::QuietWithMatch | SummaryKind::QuietWithoutMatch => {}
}
Ok(())
}
@@ -1122,7 +1133,7 @@ and exhibited clearly, with a label attached.
fn quiet() {
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Quiet)
.kind(SummaryKind::QuietWithMatch)
.build_no_color(vec![]);
let match_count = {
let mut sink = printer.sink_with_path(&matcher, "sherlock");
@@ -1144,7 +1155,7 @@ and exhibited clearly, with a label attached.
fn quiet_with_stats() {
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
let mut printer = SummaryBuilder::new()
.kind(SummaryKind::Quiet)
.kind(SummaryKind::QuietWithMatch)
.stats(true)
.build_no_color(vec![]);
let match_count = {

View File

@@ -1476,3 +1476,44 @@ rgtest!(r2990_trip_over_trailing_dot, |dir: Dir, _cmd: TestCommand| {
let got = dir.command().args(&["--files", "-g", "!asdf./"]).stdout();
eqnice!("asdf/foo\n", got);
});
// See: https://github.com/BurntSushi/ripgrep/issues/3108
rgtest!(r3108_files_without_match_quiet_exit, |dir: Dir, _: TestCommand| {
dir.create("yes-match", "abc");
dir.create("non-match", "xyz");
dir.command().args(&["-q", "abc", "non-match"]).assert_exit_code(1);
dir.command().args(&["-q", "abc", "yes-match"]).assert_exit_code(0);
dir.command()
.args(&["--files-with-matches", "-q", "abc", "non-match"])
.assert_exit_code(1);
dir.command()
.args(&["--files-with-matches", "-q", "abc", "yes-match"])
.assert_exit_code(0);
dir.command()
.args(&["--files-without-match", "abc", "non-match"])
.assert_exit_code(0);
dir.command()
.args(&["--files-without-match", "abc", "yes-match"])
.assert_exit_code(1);
let got = dir
.command()
.args(&["--files-without-match", "abc", "non-match"])
.stdout();
eqnice!("non-match\n", got);
dir.command()
.args(&["--files-without-match", "-q", "abc", "non-match"])
.assert_exit_code(0);
dir.command()
.args(&["--files-without-match", "-q", "abc", "yes-match"])
.assert_exit_code(1);
let got = dir
.command()
.args(&["--files-without-match", "-q", "abc", "non-match"])
.stdout();
eqnice!("", got);
});