diff --git a/rust/hg-core/src/filepatterns.rs b/rust/hg-core/src/filepatterns.rs --- a/rust/hg-core/src/filepatterns.rs +++ b/rust/hg-core/src/filepatterns.rs @@ -214,10 +214,33 @@ lazy_static! { static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap(); } +/// Extra path components to match at the end of the pattern +#[derive(Clone, Copy)] +pub enum GlobSuffix { + /// `Empty` means the pattern only matches files, not directories, + /// so the path needs to match exactly. + Empty, + /// `MoreComponents` means the pattern matches directories as well, + /// so any path that has the pattern as a prefix, should match. + MoreComponents, +} + +impl GlobSuffix { + fn to_re(self) -> &'static [u8] { + match self { + Self::Empty => b"$", + Self::MoreComponents => b"(?:/|$)", + } + } +} + /// Builds the regex that corresponds to the given pattern. /// If within a `syntax: regexp` context, returns the pattern, /// otherwise, returns the corresponding regex. -fn _build_single_regex(entry: &IgnorePattern, glob_suffix: &[u8]) -> Vec { +fn _build_single_regex( + entry: &IgnorePattern, + glob_suffix: GlobSuffix, +) -> Vec { let IgnorePattern { syntax, pattern, .. } = entry; @@ -264,7 +287,11 @@ fn _build_single_regex(entry: &IgnorePat if pattern == b"." { return vec![]; } - [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat() + [ + escape_pattern(pattern).as_slice(), + GlobSuffix::MoreComponents.to_re(), + ] + .concat() } PatternSyntax::RootFilesIn => { let mut res = if pattern == b"." { @@ -281,13 +308,13 @@ fn _build_single_regex(entry: &IgnorePat PatternSyntax::RelGlob => { let glob_re = glob_to_re(pattern); if let Some(rest) = glob_re.drop_prefix(b"[^/]*") { - [b".*", rest, glob_suffix].concat() + [b".*", rest, glob_suffix.to_re()].concat() } else { - [b"(?:.*/)?", glob_re.as_slice(), glob_suffix].concat() + [b"(?:.*/)?", glob_re.as_slice(), glob_suffix.to_re()].concat() } } PatternSyntax::Glob | PatternSyntax::RootGlob => { - [glob_to_re(pattern).as_slice(), glob_suffix].concat() + [glob_to_re(pattern).as_slice(), glob_suffix.to_re()].concat() } PatternSyntax::Include | PatternSyntax::SubInclude @@ -345,7 +372,7 @@ pub fn normalize_path_bytes(bytes: &[u8] /// that don't need to be transformed into a regex. pub fn build_single_regex( entry: &IgnorePattern, - glob_suffix: &[u8], + glob_suffix: GlobSuffix, ) -> Result>, PatternError> { let IgnorePattern { pattern, syntax, .. @@ -800,7 +827,7 @@ mod tests { b"rust/target/", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()), @@ -812,7 +839,7 @@ mod tests { br"rust/target/\d+", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(br"rust/target/\d+".to_vec()), @@ -828,7 +855,7 @@ mod tests { b"", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), None, @@ -840,7 +867,7 @@ mod tests { b"whatever", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), None, @@ -852,7 +879,7 @@ mod tests { b"*.o", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(br"[^/]*\.o(?:/|$)".to_vec()), @@ -868,7 +895,7 @@ mod tests { b"^ba{2}r", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(b"^ba{2}r".to_vec()), @@ -880,7 +907,7 @@ mod tests { b"ba{2}r", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(b".*ba{2}r".to_vec()), @@ -892,7 +919,7 @@ mod tests { b"(?ia)ba{2}r", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(b"(?ia:.*ba{2}r)".to_vec()), @@ -904,7 +931,7 @@ mod tests { b"(?ia)^ba{2}r", Path::new("") ), - b"(?:/|$)" + GlobSuffix::MoreComponents ) .unwrap(), Some(b"(?ia:^ba{2}r)".to_vec()), diff --git a/rust/hg-core/src/matchers.rs b/rust/hg-core/src/matchers.rs --- a/rust/hg-core/src/matchers.rs +++ b/rust/hg-core/src/matchers.rs @@ -14,8 +14,8 @@ use crate::{ dirstate::dirs_multiset::{DirsChildrenMultiset, DirsMultiset}, filepatterns::{ build_single_regex, filter_subincludes, get_patterns_from_file, - IgnorePattern, PatternError, PatternFileWarning, PatternResult, - PatternSyntax, + GlobSuffix, IgnorePattern, PatternError, PatternFileWarning, + PatternResult, PatternSyntax, }, utils::{ files::{dir_ancestors, find_dirs}, @@ -328,7 +328,8 @@ impl<'a> PatternMatcher<'a> { let prefix = ignore_patterns.iter().all(|k| { matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath) }); - let (patterns, match_fn) = build_match(ignore_patterns, b"$")?; + let (patterns, match_fn) = + build_match(ignore_patterns, GlobSuffix::Empty)?; Ok(Self { patterns, @@ -807,7 +808,7 @@ fn re_matcher(pattern: &[u8]) -> Pattern /// said regex formed by the given ignore patterns. fn build_regex_match<'a>( ignore_patterns: &[IgnorePattern], - glob_suffix: &[u8], + glob_suffix: GlobSuffix, ) -> PatternResult<(Vec, IgnoreFnType<'a>)> { let mut regexps = vec![]; let mut exact_set = HashSet::new(); @@ -927,7 +928,7 @@ fn roots_dirs_and_parents( /// should be matched. fn build_match<'a>( ignore_patterns: Vec, - glob_suffix: &[u8], + glob_suffix: GlobSuffix, ) -> PatternResult<(Vec, IgnoreFnType<'a>)> { let mut match_funcs: Vec> = vec![]; // For debugging and printing @@ -1067,7 +1068,8 @@ impl<'a> IncludeMatcher<'a> { let prefix = ignore_patterns.iter().all(|k| { matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath) }); - let (patterns, match_fn) = build_match(ignore_patterns, b"(?:/|$)")?; + let (patterns, match_fn) = + build_match(ignore_patterns, GlobSuffix::MoreComponents)?; Ok(Self { patterns,