# HG changeset patch # User Raphaël Gomès # Date 1657100660 -7200 # Node ID d8ce883ff1f4509c10ce5505313b070e68acb206 # Parent f3cd2d6eeef917ebd57f0b59f3c7089d3b9071e4 rust-matchers: implement DifferenceMatcher This can be generated by the sparse matcher. diff -r f3cd2d6eeef9 -r d8ce883ff1f4 rust/hg-core/src/matchers.rs --- a/rust/hg-core/src/matchers.rs Mon Jul 11 17:27:39 2022 +0200 +++ b/rust/hg-core/src/matchers.rs Wed Jul 06 11:44:20 2022 +0200 @@ -474,6 +474,90 @@ } } +pub struct DifferenceMatcher { + base: Box, + excluded: Box, + files: Option>, +} + +impl Matcher for DifferenceMatcher { + fn file_set(&self) -> Option<&HashSet> { + self.files.as_ref() + } + + fn exact_match(&self, filename: &HgPath) -> bool { + self.files.as_ref().map_or(false, |f| f.contains(filename)) + } + + fn matches(&self, filename: &HgPath) -> bool { + self.base.matches(filename) && !self.excluded.matches(filename) + } + + fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet { + let excluded_set = self.excluded.visit_children_set(directory); + if excluded_set == VisitChildrenSet::Recursive { + return VisitChildrenSet::Empty; + } + let base_set = self.base.visit_children_set(directory); + // Possible values for base: 'recursive', 'this', set(...), set() + // Possible values for excluded: 'this', set(...), set() + // If excluded has nothing under here that we care about, return base, + // even if it's 'recursive'. + if excluded_set == VisitChildrenSet::Empty { + return base_set; + } + match base_set { + VisitChildrenSet::This | VisitChildrenSet::Recursive => { + // Never return 'recursive' here if excluded_set is any kind of + // non-empty (either 'this' or set(foo)), since excluded might + // return set() for a subdirectory. + VisitChildrenSet::This + } + set => { + // Possible values for base: set(...), set() + // Possible values for excluded: 'this', set(...) + // We ignore excluded set results. They're possibly incorrect: + // base = path:dir/subdir + // excluded=rootfilesin:dir, + // visit_children_set(''): + // base returns {'dir'}, excluded returns {'dir'}, if we + // subtracted we'd return set(), which is *not* correct, we + // still need to visit 'dir'! + set + } + } + } + + fn matches_everything(&self) -> bool { + false + } + + fn is_exact(&self) -> bool { + self.base.is_exact() + } +} + +impl DifferenceMatcher { + pub fn new( + base: Box, + excluded: Box, + ) -> Self { + let base_is_exact = base.is_exact(); + let base_files = base.file_set().map(ToOwned::to_owned); + let mut new = Self { + base, + excluded, + files: None, + }; + if base_is_exact { + new.files = base_files.map(|files| { + files.iter().cloned().filter(|f| new.matches(f)).collect() + }); + } + new + } +} + /// Returns a function that matches an `HgPath` against the given regex /// pattern. /// @@ -1489,4 +1573,101 @@ VisitChildrenSet::Empty ); } + + #[test] + fn test_differencematcher() { + // Two alwaysmatchers should function like a nevermatcher + let m1 = AlwaysMatcher; + let m2 = AlwaysMatcher; + let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2)); + + for case in &[ + &b""[..], + b"dir", + b"dir/subdir", + b"dir/subdir/z", + b"dir/foo", + b"dir/subdir/x", + b"folder", + ] { + assert_eq!( + matcher.visit_children_set(HgPath::new(case)), + VisitChildrenSet::Empty + ); + } + + // One always and one never should behave the same as an always + let m1 = AlwaysMatcher; + let m2 = NeverMatcher; + let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2)); + + for case in &[ + &b""[..], + b"dir", + b"dir/subdir", + b"dir/subdir/z", + b"dir/foo", + b"dir/subdir/x", + b"folder", + ] { + assert_eq!( + matcher.visit_children_set(HgPath::new(case)), + VisitChildrenSet::Recursive + ); + } + + // Two include matchers + let m1 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir", + Path::new("/repo"), + )]) + .unwrap(), + ); + let m2 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RootFiles, + b"dir", + Path::new("/repo"), + )]) + .unwrap(), + ); + + let matcher = DifferenceMatcher::new(m1, m2); + + let mut set = HashSet::new(); + set.insert(HgPathBuf::from_bytes(b"dir")); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"")), + VisitChildrenSet::Set(set) + ); + + let mut set = HashSet::new(); + set.insert(HgPathBuf::from_bytes(b"subdir")); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir")), + VisitChildrenSet::Set(set) + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir")), + VisitChildrenSet::Recursive + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/foo")), + VisitChildrenSet::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"folder")), + VisitChildrenSet::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir/z")), + VisitChildrenSet::This + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), + VisitChildrenSet::This + ); + } }