globset: fix bug where trailing . in file name was incorrectly handled

I'm not sure why I did this, but I think I was trying to imitate the
contract of [`std::path::Path::file_name`]:

> Returns None if the path terminates in `..`.

But the status quo clearly did not implement this. And as a result, if
you have a glob that ends in a `.`, it was instead treated as the empty
string (which only matches the empty string).

We fix this by implementing the semantic from the standard library
correctly.

Fixes #2990

[`std::path::Path::file_name`]: https://doc.rust-lang.org/std/path/struct.Path.html#method.file_name
This commit is contained in:
Andrew Gallant
2025-08-17 17:18:27 -04:00
parent f6d46006d1
commit 8c2014fe5c
3 changed files with 26 additions and 5 deletions

View File

@@ -23,6 +23,8 @@ Bug fixes:
Ignore a UTF-8 BOM marker at the start of `.gitignore` (and similar files).
* [BUG #2944](https://github.com/BurntSushi/ripgrep/pull/2944):
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 `.`.
Feature enhancements:

View File

@@ -4,21 +4,25 @@ use bstr::{ByteSlice, ByteVec};
/// The final component of the path, if it is a normal file.
///
/// If the path terminates in `.`, `..`, or consists solely of a root of
/// prefix, file_name will return None.
/// If the path terminates in `..`, or consists solely of a root of prefix,
/// file_name will return `None`.
pub(crate) fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
if path.last_byte().map_or(true, |b| b == b'.') {
if path.is_empty() {
return None;
}
let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
Some(match *path {
let got = match *path {
Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]),
Cow::Owned(ref path) => {
let mut path = path.clone();
path.drain_bytes(..last_slash);
Cow::Owned(path)
}
})
};
if got == &b".."[..] {
return None;
}
Some(got)
}
/// Return a file extension given a path's file name.

View File

@@ -1461,3 +1461,18 @@ rgtest!(r2944_incorrect_bytes_searched, |dir: Dir, mut cmd: TestCommand| {
let got = cmd.args(&["--stats", "-m2", "foo", "."]).stdout();
assert!(got.contains("10 bytes searched\n"));
});
// See: https://github.com/BurntSushi/ripgrep/issues/2990
rgtest!(r2990_trip_over_trailing_dot, |dir: Dir, _cmd: TestCommand| {
dir.create_dir("asdf");
dir.create_dir("asdf.");
dir.create("asdf/foo", "");
dir.create("asdf./foo", "");
let got = dir.command().args(&["--files", "-g", "!asdf/"]).stdout();
eqnice!("asdf./foo\n", got);
// This used to ignore the glob given and included `asdf./foo` in output.
let got = dir.command().args(&["--files", "-g", "!asdf./"]).stdout();
eqnice!("asdf/foo\n", got);
});