Skip to content

Commit 5eabc18

Browse files
committed
matchers: add IntersectionMatcher
I plan to use this matcher for some future `jj add` command (for #323). The idea is that we'll do a path-restricted walk of the working copy based on the intersection of the sparse patterns and any patterns specified by the user. However, I think it will be useful before that, for @arxanas's fsmonitor feature (#362).
1 parent 932ce9b commit 5eabc18

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

lib/src/matchers.rs

+151
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ impl Matcher for PrefixMatcher {
166166
}
167167
}
168168

169+
/// Matches paths that are matched by the first input matcher but not by the
170+
/// second.
169171
pub struct DifferenceMatcher<'input> {
170172
/// The minuend
171173
wanted: &'input dyn Matcher,
@@ -199,6 +201,70 @@ impl Matcher for DifferenceMatcher<'_> {
199201
}
200202
}
201203

204+
/// Matches paths that are matched by both input matchers.
205+
pub struct IntersectionMatcher<'input> {
206+
input1: &'input dyn Matcher,
207+
input2: &'input dyn Matcher,
208+
}
209+
210+
impl<'input> IntersectionMatcher<'input> {
211+
pub fn new(input1: &'input dyn Matcher, input2: &'input dyn Matcher) -> Self {
212+
Self { input1, input2 }
213+
}
214+
}
215+
216+
impl Matcher for IntersectionMatcher<'_> {
217+
fn matches(&self, file: &RepoPath) -> bool {
218+
self.input1.matches(file) && self.input2.matches(file)
219+
}
220+
221+
fn visit(&self, dir: &RepoPath) -> Visit {
222+
match self.input1.visit(dir) {
223+
Visit::AllRecursively => self.input2.visit(dir),
224+
Visit::Nothing => Visit::Nothing,
225+
Visit::Specific {
226+
dirs: dirs1,
227+
files: files1,
228+
} => match self.input2.visit(dir) {
229+
Visit::AllRecursively => Visit::Specific {
230+
dirs: dirs1,
231+
files: files1,
232+
},
233+
Visit::Nothing => Visit::Nothing,
234+
Visit::Specific {
235+
dirs: dirs2,
236+
files: files2,
237+
} => {
238+
let dirs = match (dirs1, dirs2) {
239+
(VisitDirs::All, VisitDirs::All) => VisitDirs::All,
240+
(dirs1, VisitDirs::All) => dirs1,
241+
(VisitDirs::All, dirs2) => dirs2,
242+
(VisitDirs::Set(dirs1), VisitDirs::Set(dirs2)) => {
243+
VisitDirs::Set(dirs1.intersection(&dirs2).cloned().collect())
244+
}
245+
};
246+
let files = match (files1, files2) {
247+
(VisitFiles::All, VisitFiles::All) => VisitFiles::All,
248+
(files1, VisitFiles::All) => files1,
249+
(VisitFiles::All, files2) => files2,
250+
(VisitFiles::Set(files1), VisitFiles::Set(files2)) => {
251+
VisitFiles::Set(files1.intersection(&files2).cloned().collect())
252+
}
253+
};
254+
match (&dirs, &files) {
255+
(VisitDirs::Set(dirs), VisitFiles::Set(files))
256+
if dirs.is_empty() && files.is_empty() =>
257+
{
258+
Visit::Nothing
259+
}
260+
_ => Visit::Specific { dirs, files },
261+
}
262+
}
263+
},
264+
}
265+
}
266+
}
267+
202268
/// Keeps track of which subdirectories and files of each directory need to be
203269
/// visited.
204270
#[derive(PartialEq, Eq, Debug)]
@@ -530,4 +596,89 @@ mod tests {
530596
Visit::AllRecursively
531597
);
532598
}
599+
600+
#[test]
601+
fn test_intersectionmatcher_intersecting_roots() {
602+
let m1 = PrefixMatcher::new(&[
603+
RepoPath::from_internal_string("foo"),
604+
RepoPath::from_internal_string("bar"),
605+
]);
606+
let m2 = PrefixMatcher::new(&[
607+
RepoPath::from_internal_string("bar"),
608+
RepoPath::from_internal_string("baz"),
609+
]);
610+
let m = IntersectionMatcher::new(&m1, &m2);
611+
612+
assert!(!m.matches(&RepoPath::from_internal_string("foo")));
613+
assert!(!m.matches(&RepoPath::from_internal_string("foo/bar")));
614+
assert!(m.matches(&RepoPath::from_internal_string("bar")));
615+
assert!(m.matches(&RepoPath::from_internal_string("bar/foo")));
616+
assert!(!m.matches(&RepoPath::from_internal_string("baz")));
617+
assert!(!m.matches(&RepoPath::from_internal_string("baz/foo")));
618+
619+
assert_eq!(
620+
m.visit(&RepoPath::root()),
621+
Visit::sets(
622+
hashset! {RepoPathComponent::from("bar")},
623+
hashset! {RepoPathComponent::from("bar")}
624+
)
625+
);
626+
assert_eq!(
627+
m.visit(&RepoPath::from_internal_string("foo")),
628+
Visit::Nothing
629+
);
630+
assert_eq!(
631+
m.visit(&RepoPath::from_internal_string("foo/bar")),
632+
Visit::Nothing
633+
);
634+
assert_eq!(
635+
m.visit(&RepoPath::from_internal_string("bar")),
636+
Visit::AllRecursively
637+
);
638+
assert_eq!(
639+
m.visit(&RepoPath::from_internal_string("bar/foo")),
640+
Visit::AllRecursively
641+
);
642+
assert_eq!(
643+
m.visit(&RepoPath::from_internal_string("baz")),
644+
Visit::Nothing
645+
);
646+
assert_eq!(
647+
m.visit(&RepoPath::from_internal_string("baz/foo")),
648+
Visit::Nothing
649+
);
650+
}
651+
652+
#[test]
653+
fn test_intersectionmatcher_subdir() {
654+
let m1 = PrefixMatcher::new(&[RepoPath::from_internal_string("foo")]);
655+
let m2 = PrefixMatcher::new(&[RepoPath::from_internal_string("foo/bar")]);
656+
let m = IntersectionMatcher::new(&m1, &m2);
657+
658+
assert!(!m.matches(&RepoPath::from_internal_string("foo")));
659+
assert!(!m.matches(&RepoPath::from_internal_string("bar")));
660+
assert!(m.matches(&RepoPath::from_internal_string("foo/bar")));
661+
assert!(m.matches(&RepoPath::from_internal_string("foo/bar/baz")));
662+
assert!(!m.matches(&RepoPath::from_internal_string("foo/baz")));
663+
664+
assert_eq!(
665+
m.visit(&RepoPath::root()),
666+
Visit::sets(hashset! {RepoPathComponent::from("foo")}, hashset! {})
667+
);
668+
assert_eq!(
669+
m.visit(&RepoPath::from_internal_string("bar")),
670+
Visit::Nothing
671+
);
672+
assert_eq!(
673+
m.visit(&RepoPath::from_internal_string("foo")),
674+
Visit::sets(
675+
hashset! {RepoPathComponent::from("bar")},
676+
hashset! {RepoPathComponent::from("bar")}
677+
)
678+
);
679+
assert_eq!(
680+
m.visit(&RepoPath::from_internal_string("foo/bar")),
681+
Visit::AllRecursively
682+
);
683+
}
533684
}

0 commit comments

Comments
 (0)