# HG changeset patch # User Raphaël Gomès # Date 1654704564 -7200 # Node ID 5e53ecbc308f48361cb36f38307142a93bf4f2da # Parent 0043c7aa3250af867b40d0d2f515f07672fbacb7 rust: add IntersectionMatcher This will be used in the upcoming support for sparse checkouts in Rust-augmented status and later in rhg support for sparse checkouts. diff -r 0043c7aa3250 -r 5e53ecbc308f rust/hg-core/src/matchers.rs --- a/rust/hg-core/src/matchers.rs Wed Jun 08 15:39:14 2022 +0200 +++ b/rust/hg-core/src/matchers.rs Wed Jun 08 18:09:24 2022 +0200 @@ -368,6 +368,87 @@ } } +pub struct IntersectionMatcher { + m1: Box, + m2: Box, + files: Option>, +} + +impl Matcher for IntersectionMatcher { + 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.m1.matches(filename) && self.m2.matches(filename) + } + + fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet { + let m1_set = self.m1.visit_children_set(directory); + if m1_set == VisitChildrenSet::Empty { + return VisitChildrenSet::Empty; + } + let m2_set = self.m2.visit_children_set(directory); + if m2_set == VisitChildrenSet::Empty { + return VisitChildrenSet::Empty; + } + + if m1_set == VisitChildrenSet::Recursive { + return m2_set; + } else if m2_set == VisitChildrenSet::Recursive { + return m1_set; + } + + match (&m1_set, &m2_set) { + (VisitChildrenSet::Recursive, _) => m2_set, + (_, VisitChildrenSet::Recursive) => m1_set, + (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => { + VisitChildrenSet::This + } + (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => { + let set: HashSet<_> = m1.intersection(&m2).cloned().collect(); + if set.is_empty() { + VisitChildrenSet::Empty + } else { + VisitChildrenSet::Set(set) + } + } + _ => unreachable!(), + } + } + + fn matches_everything(&self) -> bool { + self.m1.matches_everything() && self.m2.matches_everything() + } + + fn is_exact(&self) -> bool { + self.m1.is_exact() || self.m2.is_exact() + } +} + +impl IntersectionMatcher { + pub fn new( + mut m1: Box, + mut m2: Box, + ) -> Self { + let files = if m1.is_exact() || m2.is_exact() { + if !m1.is_exact() { + std::mem::swap(&mut m1, &mut m2); + } + m1.file_set().map(|m1_files| { + m1_files.iter().cloned().filter(|f| m2.matches(f)).collect() + }) + } else { + None + }; + Self { m1, m2, files } + } +} + /// Returns a function that matches an `HgPath` against the given regex /// pattern. /// @@ -1147,4 +1228,215 @@ VisitChildrenSet::This ); } + + #[test] + fn test_intersectionmatcher() { + // Include path + Include rootfiles + let m1 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir", + Path::new(""), + )]) + .unwrap(), + ); + let m2 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RootFiles, + b"dir", + Path::new(""), + )]) + .unwrap(), + ); + let matcher = IntersectionMatcher::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) + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir")), + VisitChildrenSet::This + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir")), + VisitChildrenSet::Empty + ); + 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::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), + VisitChildrenSet::Empty + ); + + // Non intersecting paths + let m1 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir", + Path::new(""), + )]) + .unwrap(), + ); + let m2 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"folder", + Path::new(""), + )]) + .unwrap(), + ); + let matcher = IntersectionMatcher::new(m1, m2); + + assert_eq!( + matcher.visit_children_set(HgPath::new(b"")), + VisitChildrenSet::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir")), + VisitChildrenSet::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir")), + VisitChildrenSet::Empty + ); + 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::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), + VisitChildrenSet::Empty + ); + + // Nested paths + let m1 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir/x", + Path::new(""), + )]) + .unwrap(), + ); + let m2 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir", + Path::new(""), + )]) + .unwrap(), + ); + let matcher = IntersectionMatcher::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) + ); + let mut set = HashSet::new(); + set.insert(HgPathBuf::from_bytes(b"x")); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir")), + VisitChildrenSet::Set(set) + ); + 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::Empty + ); + // OPT: this should probably be 'all' not 'this'. + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), + VisitChildrenSet::This + ); + + // Diverging paths + let m1 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir/x", + Path::new(""), + )]) + .unwrap(), + ); + let m2 = Box::new( + IncludeMatcher::new(vec![IgnorePattern::new( + PatternSyntax::RelPath, + b"dir/subdir/z", + Path::new(""), + )]) + .unwrap(), + ); + let matcher = IntersectionMatcher::new(m1, m2); + + // OPT: these next two could probably be Empty as well. + let mut set = HashSet::new(); + set.insert(HgPathBuf::from_bytes(b"dir")); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"")), + VisitChildrenSet::Set(set) + ); + // OPT: these next two could probably be Empty as well. + 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::Empty + ); + 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::Empty + ); + assert_eq!( + matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), + VisitChildrenSet::Empty + ); + } }