472 }; |
472 }; |
473 Self { m1, m2, files } |
473 Self { m1, m2, files } |
474 } |
474 } |
475 } |
475 } |
476 |
476 |
|
477 pub struct DifferenceMatcher { |
|
478 base: Box<dyn Matcher + Sync>, |
|
479 excluded: Box<dyn Matcher + Sync>, |
|
480 files: Option<HashSet<HgPathBuf>>, |
|
481 } |
|
482 |
|
483 impl Matcher for DifferenceMatcher { |
|
484 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> { |
|
485 self.files.as_ref() |
|
486 } |
|
487 |
|
488 fn exact_match(&self, filename: &HgPath) -> bool { |
|
489 self.files.as_ref().map_or(false, |f| f.contains(filename)) |
|
490 } |
|
491 |
|
492 fn matches(&self, filename: &HgPath) -> bool { |
|
493 self.base.matches(filename) && !self.excluded.matches(filename) |
|
494 } |
|
495 |
|
496 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet { |
|
497 let excluded_set = self.excluded.visit_children_set(directory); |
|
498 if excluded_set == VisitChildrenSet::Recursive { |
|
499 return VisitChildrenSet::Empty; |
|
500 } |
|
501 let base_set = self.base.visit_children_set(directory); |
|
502 // Possible values for base: 'recursive', 'this', set(...), set() |
|
503 // Possible values for excluded: 'this', set(...), set() |
|
504 // If excluded has nothing under here that we care about, return base, |
|
505 // even if it's 'recursive'. |
|
506 if excluded_set == VisitChildrenSet::Empty { |
|
507 return base_set; |
|
508 } |
|
509 match base_set { |
|
510 VisitChildrenSet::This | VisitChildrenSet::Recursive => { |
|
511 // Never return 'recursive' here if excluded_set is any kind of |
|
512 // non-empty (either 'this' or set(foo)), since excluded might |
|
513 // return set() for a subdirectory. |
|
514 VisitChildrenSet::This |
|
515 } |
|
516 set => { |
|
517 // Possible values for base: set(...), set() |
|
518 // Possible values for excluded: 'this', set(...) |
|
519 // We ignore excluded set results. They're possibly incorrect: |
|
520 // base = path:dir/subdir |
|
521 // excluded=rootfilesin:dir, |
|
522 // visit_children_set(''): |
|
523 // base returns {'dir'}, excluded returns {'dir'}, if we |
|
524 // subtracted we'd return set(), which is *not* correct, we |
|
525 // still need to visit 'dir'! |
|
526 set |
|
527 } |
|
528 } |
|
529 } |
|
530 |
|
531 fn matches_everything(&self) -> bool { |
|
532 false |
|
533 } |
|
534 |
|
535 fn is_exact(&self) -> bool { |
|
536 self.base.is_exact() |
|
537 } |
|
538 } |
|
539 |
|
540 impl DifferenceMatcher { |
|
541 pub fn new( |
|
542 base: Box<dyn Matcher + Sync>, |
|
543 excluded: Box<dyn Matcher + Sync>, |
|
544 ) -> Self { |
|
545 let base_is_exact = base.is_exact(); |
|
546 let base_files = base.file_set().map(ToOwned::to_owned); |
|
547 let mut new = Self { |
|
548 base, |
|
549 excluded, |
|
550 files: None, |
|
551 }; |
|
552 if base_is_exact { |
|
553 new.files = base_files.map(|files| { |
|
554 files.iter().cloned().filter(|f| new.matches(f)).collect() |
|
555 }); |
|
556 } |
|
557 new |
|
558 } |
|
559 } |
|
560 |
477 /// Returns a function that matches an `HgPath` against the given regex |
561 /// Returns a function that matches an `HgPath` against the given regex |
478 /// pattern. |
562 /// pattern. |
479 /// |
563 /// |
480 /// This can fail when the pattern is invalid or not supported by the |
564 /// This can fail when the pattern is invalid or not supported by the |
481 /// underlying engine (the `regex` crate), for instance anything with |
565 /// underlying engine (the `regex` crate), for instance anything with |
1487 assert_eq!( |
1571 assert_eq!( |
1488 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), |
1572 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), |
1489 VisitChildrenSet::Empty |
1573 VisitChildrenSet::Empty |
1490 ); |
1574 ); |
1491 } |
1575 } |
1492 } |
1576 |
|
1577 #[test] |
|
1578 fn test_differencematcher() { |
|
1579 // Two alwaysmatchers should function like a nevermatcher |
|
1580 let m1 = AlwaysMatcher; |
|
1581 let m2 = AlwaysMatcher; |
|
1582 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2)); |
|
1583 |
|
1584 for case in &[ |
|
1585 &b""[..], |
|
1586 b"dir", |
|
1587 b"dir/subdir", |
|
1588 b"dir/subdir/z", |
|
1589 b"dir/foo", |
|
1590 b"dir/subdir/x", |
|
1591 b"folder", |
|
1592 ] { |
|
1593 assert_eq!( |
|
1594 matcher.visit_children_set(HgPath::new(case)), |
|
1595 VisitChildrenSet::Empty |
|
1596 ); |
|
1597 } |
|
1598 |
|
1599 // One always and one never should behave the same as an always |
|
1600 let m1 = AlwaysMatcher; |
|
1601 let m2 = NeverMatcher; |
|
1602 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2)); |
|
1603 |
|
1604 for case in &[ |
|
1605 &b""[..], |
|
1606 b"dir", |
|
1607 b"dir/subdir", |
|
1608 b"dir/subdir/z", |
|
1609 b"dir/foo", |
|
1610 b"dir/subdir/x", |
|
1611 b"folder", |
|
1612 ] { |
|
1613 assert_eq!( |
|
1614 matcher.visit_children_set(HgPath::new(case)), |
|
1615 VisitChildrenSet::Recursive |
|
1616 ); |
|
1617 } |
|
1618 |
|
1619 // Two include matchers |
|
1620 let m1 = Box::new( |
|
1621 IncludeMatcher::new(vec![IgnorePattern::new( |
|
1622 PatternSyntax::RelPath, |
|
1623 b"dir/subdir", |
|
1624 Path::new("/repo"), |
|
1625 )]) |
|
1626 .unwrap(), |
|
1627 ); |
|
1628 let m2 = Box::new( |
|
1629 IncludeMatcher::new(vec![IgnorePattern::new( |
|
1630 PatternSyntax::RootFiles, |
|
1631 b"dir", |
|
1632 Path::new("/repo"), |
|
1633 )]) |
|
1634 .unwrap(), |
|
1635 ); |
|
1636 |
|
1637 let matcher = DifferenceMatcher::new(m1, m2); |
|
1638 |
|
1639 let mut set = HashSet::new(); |
|
1640 set.insert(HgPathBuf::from_bytes(b"dir")); |
|
1641 assert_eq!( |
|
1642 matcher.visit_children_set(HgPath::new(b"")), |
|
1643 VisitChildrenSet::Set(set) |
|
1644 ); |
|
1645 |
|
1646 let mut set = HashSet::new(); |
|
1647 set.insert(HgPathBuf::from_bytes(b"subdir")); |
|
1648 assert_eq!( |
|
1649 matcher.visit_children_set(HgPath::new(b"dir")), |
|
1650 VisitChildrenSet::Set(set) |
|
1651 ); |
|
1652 assert_eq!( |
|
1653 matcher.visit_children_set(HgPath::new(b"dir/subdir")), |
|
1654 VisitChildrenSet::Recursive |
|
1655 ); |
|
1656 assert_eq!( |
|
1657 matcher.visit_children_set(HgPath::new(b"dir/foo")), |
|
1658 VisitChildrenSet::Empty |
|
1659 ); |
|
1660 assert_eq!( |
|
1661 matcher.visit_children_set(HgPath::new(b"folder")), |
|
1662 VisitChildrenSet::Empty |
|
1663 ); |
|
1664 assert_eq!( |
|
1665 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")), |
|
1666 VisitChildrenSet::This |
|
1667 ); |
|
1668 assert_eq!( |
|
1669 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), |
|
1670 VisitChildrenSet::This |
|
1671 ); |
|
1672 } |
|
1673 } |