--- 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<dyn Matcher + Sync>,
+ excluded: Box<dyn Matcher + Sync>,
+ files: Option<HashSet<HgPathBuf>>,
+}
+
+impl Matcher for DifferenceMatcher {
+ fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
+ 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<dyn Matcher + Sync>,
+ excluded: Box<dyn Matcher + Sync>,
+ ) -> 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
+ );
+ }
}