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