Fix /**/ special case for beginning-of-text matching

Git actually skipped the next slash in the pattern. Since we
don't have a Component::Slash currently we'll just add a
flag to `do_match` instead.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-07-09 14:29:28 +02:00
parent f9fbdc3b94
commit 17b7bd9f3c

View File

@ -396,14 +396,19 @@ impl Pattern {
/// Check whether this pattern matches a text.
pub fn matches<T: AsRef<[u8]>>(&self, text: T) -> bool {
match self.do_matches(0, text.as_ref()) {
match self.do_matches(0, text.as_ref(), false) {
MatchResult::Match => true,
_ => false,
}
}
// The algorithm is ported from git's wildmatch.c.
fn do_matches(&self, mut ci: usize, mut text: &[u8]) -> MatchResult {
fn do_matches(
&self,
mut ci: usize,
mut text: &[u8],
mut skip_slash_in_literal: bool,
) -> MatchResult {
let components = &self.components[..];
if self.flags.intersects(PatternFlag::PATH_NAME) {
@ -411,6 +416,7 @@ impl Pattern {
}
while ci != components.len() {
let skip_slash_in_literal = mem::replace(&mut skip_slash_in_literal, false);
//eprintln!("Matching: {:?} at text: {:?}", components[ci], unsafe {
// std::str::from_utf8_unchecked(text)
//},);
@ -423,6 +429,12 @@ impl Pattern {
return MatchResult::AbortAll;
}
let literal = if skip_slash_in_literal {
&literal[1..]
} else {
literal
};
if !starts_with(text, &literal, self.flags) {
return MatchResult::NoMatch;
}
@ -474,7 +486,7 @@ impl Pattern {
// FIXME: Optimization: Add the "try to advance faster" optimization from
// git here.
match self.do_matches(ci + 1, text) {
match self.do_matches(ci + 1, text, false) {
MatchResult::NoMatch => {
if text[0] == b'/' {
return MatchResult::AbortToStarStar;
@ -498,15 +510,8 @@ impl Pattern {
{
// Assuming we matched `foo/` and are at `/` `**` `/`, see if we can let
// it match nothing, so that `foo/` `**` `/bar` can match `foo/bar`.
//
// Under the condition that the previous component ended with a slash
// (`components[ci - 1].ends_with_slash()`) we can safely move back by
// a byte in `text`.
let text = unsafe {
std::slice::from_raw_parts(text.as_ptr().offset(-1), text.len() + 1)
};
#[allow(clippy::single_match)]
match self.do_matches(ci + 1, text) {
match self.do_matches(ci + 1, text, true) {
MatchResult::Match => return MatchResult::Match,
_ => (), // or just continue regularly
}
@ -519,7 +524,7 @@ impl Pattern {
return MatchResult::AbortAll;
}
match self.do_matches(ci + 1, text) {
match self.do_matches(ci + 1, text, false) {
MatchResult::NoMatch => (),
MatchResult::AbortToStarStar => (), // continue from here
other => return other,
@ -713,4 +718,15 @@ fn test() {
let pattern = Pattern::new("a/b**/c", PatternFlag::PATH_NAME).unwrap();
assert!(pattern.matches("a/bxx/c"));
assert!(!pattern.matches("a/bxx/yy/c"));
let pattern = Pattern::new("**/lost+found", PatternFlag::PATH_NAME).unwrap();
assert!(pattern.matches("/foo/lost+found"));
assert!(pattern.matches("foo/lost+found"));
assert!(pattern.matches("/lost+found"));
assert!(pattern.matches("///lost+found"));
assert!(pattern.matches("lost+found"));
assert!(!pattern.matches("lost+found2"));
assert!(!pattern.matches("lost+found/"));
assert!(!pattern.matches("xlost+found"));
assert!(!pattern.matches("xlost+found/"));
}