// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. #![allow(missing_docs)] use std::collections::HashMap;use std::collections::HashSet;use std::fmt;use std::fmt::Debug;use std::iter; use itertools::Itertools as _;use tracing::instrument; use crate::repo_path::RepoPath;use crate::repo_path::RepoPathComponentBuf; #[derive(PartialEq, Eq, Debug)]pub enum Visit { /// Everything in the directory is *guaranteed* to match, no need to check /// descendants AllRecursively, Specific { dirs: VisitDirs, files: VisitFiles, }, /// Nothing in the directory or its subdirectories will match. /// /// This is the same as `Specific` with no directories or files. Use /// `Visit::set()` to get create an instance that's `Specific` or /// `Nothing` depending on the values at runtime. Nothing,} impl Visit { fn sets(dirs: HashSet<RepoPathComponentBuf>, files: HashSet<RepoPathComponentBuf>) -> Self { if dirs.is_empty() && files.is_empty() { Self::Nothing } else { Self::Specific { dirs: VisitDirs::Set(dirs), files: VisitFiles::Set(files), } } } pub fn is_nothing(&self) -> bool { *self == Visit::Nothing }} #[derive(PartialEq, Eq, Debug)]pub enum VisitDirs { All, Set(HashSet<RepoPathComponentBuf>),} #[derive(PartialEq, Eq, Debug)]pub enum VisitFiles { All, Set(HashSet<RepoPathComponentBuf>),} pub trait Matcher: Debug + Sync { fn matches(&self, file: &RepoPath) -> bool; fn visit(&self, dir: &RepoPath) -> Visit;} impl<T: Matcher + ?Sized> Matcher for &T { fn matches(&self, file: &RepoPath) -> bool { <T as Matcher>::matches(self, file) } fn visit(&self, dir: &RepoPath) -> Visit { <T as Matcher>::visit(self, dir) }} impl<T: Matcher + ?Sized> Matcher for Box<T> { fn matches(&self, file: &RepoPath) -> bool { <T as Matcher>::matches(self, file) } fn visit(&self, dir: &RepoPath) -> Visit { <T as Matcher>::visit(self, dir) }} #[derive(PartialEq, Eq, Debug)]pub struct NothingMatcher; impl Matcher for NothingMatcher { fn matches(&self, _file: &RepoPath) -> bool { false } fn visit(&self, _dir: &RepoPath) -> Visit { Visit::Nothing }} #[derive(PartialEq, Eq, Debug)]pub struct EverythingMatcher; impl Matcher for EverythingMatcher { fn matches(&self, _file: &RepoPath) -> bool { true } fn visit(&self, _dir: &RepoPath) -> Visit { Visit::AllRecursively }} #[derive(PartialEq, Eq, Debug)]pub struct FilesMatcher { tree: RepoPathTree<FilesNodeKind>,} impl FilesMatcher { pub fn new(files: impl IntoIterator<Item = impl AsRef<RepoPath>>) -> Self { let mut tree = RepoPathTree::default(); for f in files { tree.add(f.as_ref()).value = FilesNodeKind::File; } FilesMatcher { tree } }} impl Matcher for FilesMatcher { fn matches(&self, file: &RepoPath) -> bool { self.tree .get(file) .is_some_and(|sub| sub.value == FilesNodeKind::File) } fn visit(&self, dir: &RepoPath) -> Visit { self.tree .get(dir) .map_or(Visit::Nothing, files_tree_to_visit_sets) }} #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]enum FilesNodeKind { /// Represents an intermediate directory. #[default] Dir, /// Represents a file (which might also be an intermediate directory.) File,} fn files_tree_to_visit_sets(tree: &RepoPathTree<FilesNodeKind>) -> Visit { let mut dirs = HashSet::new(); let mut files = HashSet::new(); for (name, sub) in &tree.entries { // should visit only intermediate directories if !sub.entries.is_empty() { dirs.insert(name.clone()); } if sub.value == FilesNodeKind::File { files.insert(name.clone()); } } Visit::sets(dirs, files)} #[derive(Debug)]pub struct PrefixMatcher { tree: RepoPathTree<PrefixNodeKind>,} impl PrefixMatcher { #[instrument(skip(prefixes))] pub fn new(prefixes: impl IntoIterator<Item = impl AsRef<RepoPath>>) -> Self { let mut tree = RepoPathTree::default(); for prefix in prefixes { tree.add(prefix.as_ref()).value = PrefixNodeKind::Prefix; } PrefixMatcher { tree } }} impl Matcher for PrefixMatcher { fn matches(&self, file: &RepoPath) -> bool { self.tree .walk_to(file) .any(|(sub, _)| sub.value == PrefixNodeKind::Prefix) } fn visit(&self, dir: &RepoPath) -> Visit { for (sub, tail_path) in self.tree.walk_to(dir) { // ancestor of 'dir' matches prefix paths if sub.value == PrefixNodeKind::Prefix { return Visit::AllRecursively; } // 'dir' found, and is an ancestor of prefix paths if tail_path.is_root() { return prefix_tree_to_visit_sets(sub); } } Visit::Nothing }} #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]enum PrefixNodeKind { /// Represents an intermediate directory. #[default] Dir, /// Represents a file and prefix directory. Prefix,} fn prefix_tree_to_visit_sets(tree: &RepoPathTree<PrefixNodeKind>) -> Visit { let mut dirs = HashSet::new(); let mut files = HashSet::new(); for (name, sub) in &tree.entries { // should visit both intermediate and prefix directories dirs.insert(name.clone()); if sub.value == PrefixNodeKind::Prefix { files.insert(name.clone()); } } Visit::sets(dirs, files)} /// Matches file paths with glob patterns.////// Patterns are provided as `(dir, pattern)` pairs, where `dir` should be the/// longest directory path that contains no glob meta characters, and `pattern`/// will be evaluated relative to `dir`.#[derive(Clone, Debug)]pub struct FileGlobsMatcher { tree: RepoPathTree<Vec<glob::Pattern>>,} impl FileGlobsMatcher { pub fn new<D: AsRef<RepoPath>>( dir_patterns: impl IntoIterator<Item = (D, glob::Pattern)>, ) -> Self { let mut tree: RepoPathTree<Vec<glob::Pattern>> = Default::default(); for (dir, pattern) in dir_patterns { tree.add(dir.as_ref()).value.push(pattern); } FileGlobsMatcher { tree } }} impl Matcher for FileGlobsMatcher { fn matches(&self, file: &RepoPath) -> bool { // TODO: glob::Pattern relies on path::is_separator() internally, but // RepoPath separator should be '/'. One way to address this problem is // to switch to globset::Glob, and use the underlying regex pattern. const OPTIONS: glob::MatchOptions = glob::MatchOptions { case_sensitive: true, require_literal_separator: true, require_literal_leading_dot: false, }; // check if any ancestor (dir, patterns) matches 'file' self.tree .walk_to(file) .take_while(|(_, tail_path)| !tail_path.is_root()) // only dirs .any(|(sub, tail_path)| { let name = tail_path.as_internal_file_string(); sub.value.iter().any(|pat| pat.matches_with(name, OPTIONS)) }) } fn visit(&self, dir: &RepoPath) -> Visit { for (sub, tail_path) in self.tree.walk_to(dir) { // ancestor of 'dir' has patterns, can't narrow visit anymore if !sub.value.is_empty() { return Visit::Specific { dirs: VisitDirs::All, files: VisitFiles::All, }; } // 'dir' found, and is an ancestor of pattern paths if tail_path.is_root() { let sub_dirs = sub.entries.keys().cloned().collect(); return Visit::sets(sub_dirs, HashSet::new()); } } Visit::Nothing }} /// Matches paths that are matched by any of the input matchers.#[derive(Clone, Debug)]pub struct UnionMatcher<M1, M2> { input1: M1, input2: M2,} impl<M1: Matcher, M2: Matcher> UnionMatcher<M1, M2> { pub fn new(input1: M1, input2: M2) -> Self { Self { input1, input2 } }} impl<M1: Matcher, M2: Matcher> Matcher for UnionMatcher<M1, M2> { fn matches(&self, file: &RepoPath) -> bool { self.input1.matches(file) || self.input2.matches(file) } fn visit(&self, dir: &RepoPath) -> Visit { match self.input1.visit(dir) { Visit::AllRecursively => Visit::AllRecursively, Visit::Nothing => self.input2.visit(dir), Visit::Specific { dirs: dirs1, files: files1, } => match self.input2.visit(dir) { Visit::AllRecursively => Visit::AllRecursively, Visit::Nothing => Visit::Specific { dirs: dirs1, files: files1, }, Visit::Specific { dirs: dirs2, files: files2, } => { let dirs = match (dirs1, dirs2) { (VisitDirs::All, _) | (_, VisitDirs::All) => VisitDirs::All, (VisitDirs::Set(dirs1), VisitDirs::Set(dirs2)) => { VisitDirs::Set(dirs1.iter().chain(&dirs2).cloned().collect()) } }; let files = match (files1, files2) { (VisitFiles::All, _) | (_, VisitFiles::All) => VisitFiles::All, (VisitFiles::Set(files1), VisitFiles::Set(files2)) => { VisitFiles::Set(files1.iter().chain(&files2).cloned().collect()) } }; Visit::Specific { dirs, files } } }, } }} /// Matches paths that are matched by the first input matcher but not by the/// second.#[derive(Clone, Debug)]pub struct DifferenceMatcher<M1, M2> { /// The minuend wanted: M1, /// The subtrahend unwanted: M2,} impl<M1: Matcher, M2: Matcher> DifferenceMatcher<M1, M2> { pub fn new(wanted: M1, unwanted: M2) -> Self { Self { wanted, unwanted } }} impl<M1: Matcher, M2: Matcher> Matcher for DifferenceMatcher<M1, M2> { fn matches(&self, file: &RepoPath) -> bool { self.wanted.matches(file) && !self.unwanted.matches(file) } fn visit(&self, dir: &RepoPath) -> Visit { match self.unwanted.visit(dir) { Visit::AllRecursively => Visit::Nothing, Visit::Nothing => self.wanted.visit(dir), Visit::Specific { .. } => match self.wanted.visit(dir) { Visit::AllRecursively => Visit::Specific { dirs: VisitDirs::All, files: VisitFiles::All, }, wanted_visit => wanted_visit, }, } }} /// Matches paths that are matched by both input matchers.#[derive(Clone, Debug)]pub struct IntersectionMatcher<M1, M2> { input1: M1, input2: M2,} impl<M1: Matcher, M2: Matcher> IntersectionMatcher<M1, M2> { pub fn new(input1: M1, input2: M2) -> Self { Self { input1, input2 } }} impl<M1: Matcher, M2: Matcher> Matcher for IntersectionMatcher<M1, M2> { fn matches(&self, file: &RepoPath) -> bool { self.input1.matches(file) && self.input2.matches(file) } fn visit(&self, dir: &RepoPath) -> Visit { match self.input1.visit(dir) { Visit::AllRecursively => self.input2.visit(dir), Visit::Nothing => Visit::Nothing, Visit::Specific { dirs: dirs1, files: files1, } => match self.input2.visit(dir) { Visit::AllRecursively => Visit::Specific { dirs: dirs1, files: files1, }, Visit::Nothing => Visit::Nothing, Visit::Specific { dirs: dirs2, files: files2, } => { let dirs = match (dirs1, dirs2) { (VisitDirs::All, VisitDirs::All) => VisitDirs::All, (dirs1, VisitDirs::All) => dirs1, (VisitDirs::All, dirs2) => dirs2, (VisitDirs::Set(dirs1), VisitDirs::Set(dirs2)) => { VisitDirs::Set(dirs1.intersection(&dirs2).cloned().collect()) } }; let files = match (files1, files2) { (VisitFiles::All, VisitFiles::All) => VisitFiles::All, (files1, VisitFiles::All) => files1, (VisitFiles::All, files2) => files2, (VisitFiles::Set(files1), VisitFiles::Set(files2)) => { VisitFiles::Set(files1.intersection(&files2).cloned().collect()) } }; match (&dirs, &files) { (VisitDirs::Set(dirs), VisitFiles::Set(files)) if dirs.is_empty() && files.is_empty() => { Visit::Nothing } _ => Visit::Specific { dirs, files }, } } }, } }} /// Tree that maps `RepoPath` to value of type `V`.#[derive(Clone, Default, Eq, PartialEq)]struct RepoPathTree<V> { entries: HashMap<RepoPathComponentBuf, RepoPathTree<V>>, value: V,} impl<V> RepoPathTree<V> { fn add(&mut self, dir: &RepoPath) -> &mut Self where V: Default, { dir.components().fold(self, |sub, name| { // Avoid name.clone() if entry already exists. if !sub.entries.contains_key(name) { sub.entries.insert(name.to_owned(), Self::default()); } sub.entries.get_mut(name).unwrap() }) } fn get(&self, dir: &RepoPath) -> Option<&Self> { dir.components() .try_fold(self, |sub, name| sub.entries.get(name)) } /// Walks the tree from the root to the given `dir`, yielding each sub tree /// and remaining path. fn walk_to<'a, 'b>( &'a self, dir: &'b RepoPath, // TODO: V doesn't have to be captured, but "currently, all type // parameters are required to be mentioned in the precise captures list" // as of rustc 1.85.0. ) -> impl Iterator<Item = (&'a Self, &'b RepoPath)> + use<'a, 'b, V> { iter::successors(Some((self, dir)), |(sub, dir)| { let mut components = dir.components(); let name = components.next()?; Some((sub.entries.get(name)?, components.as_path())) }) }} impl<V: Debug> Debug for RepoPathTree<V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.value.fmt(f)?;Lines 1–500 of 1045. Load more
// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(missing_docs)] use std::collections::HashMap; use std::collections::HashSet; use std::fmt; use std::fmt::Debug; use std::iter; use itertools::Itertools as _; use tracing::instrument; use crate::repo_path::RepoPath; use crate::repo_path::RepoPathComponentBuf; #[derive(PartialEq, Eq, Debug)] pub enum Visit { /// Everything in the directory is *guaranteed* to match, no need to check /// descendants AllRecursively, Specific { dirs: VisitDirs, files: VisitFiles, }, /// Nothing in the directory or its subdirectories will match. /// /// This is the same as `Specific` with no directories or files. Use /// `Visit::set()` to get create an instance that's `Specific` or /// `Nothing` depending on the values at runtime. Nothing, } impl Visit { fn sets(dirs: HashSet<RepoPathComponentBuf>, files: HashSet<RepoPathComponentBuf>) -> Self { if dirs.is_empty() && files.is_empty() { Self::Nothing } else { Self::Specific { dirs: VisitDirs::Set(dirs), files: VisitFiles::Set(files), } } } pub fn is_nothing(&self) -> bool { *self == Visit::Nothing } } #[derive(PartialEq, Eq, Debug)] pub enum VisitDirs { All, Set(HashSet<RepoPathComponentBuf>), } #[derive(PartialEq, Eq, Debug)] pub enum VisitFiles { All, Set(HashSet<RepoPathComponentBuf>), } pub trait Matcher: Debug + Sync { fn matches(&self, file: &RepoPath) -> bool; fn visit(&self, dir: &RepoPath) -> Visit; } impl<T: Matcher + ?Sized> Matcher for &T { fn matches(&self, file: &RepoPath) -> bool { <T as Matcher>::matches(self, file) } fn visit(&self, dir: &RepoPath) -> Visit { <T as Matcher>::visit(self, dir) } } impl<T: Matcher + ?Sized> Matcher for Box<T> { fn matches(&self, file: &RepoPath) -> bool { <T as Matcher>::matches(self, file) } fn visit(&self, dir: &RepoPath) -> Visit { <T as Matcher>::visit(self, dir) } } #[derive(PartialEq, Eq, Debug)] pub struct NothingMatcher; impl Matcher for NothingMatcher { fn matches(&self, _file: &RepoPath) -> bool { false } fn visit(&self, _dir: &RepoPath) -> Visit { Visit::Nothing } } #[derive(PartialEq, Eq, Debug)] pub struct EverythingMatcher; impl Matcher for EverythingMatcher { fn matches(&self, _file: &RepoPath) -> bool { true } fn visit(&self, _dir: &RepoPath) -> Visit { Visit::AllRecursively } } #[derive(PartialEq, Eq, Debug)] pub struct FilesMatcher { tree: RepoPathTree<FilesNodeKind>, } impl FilesMatcher { pub fn new(files: impl IntoIterator<Item = impl AsRef<RepoPath>>) -> Self { let mut tree = RepoPathTree::default(); for f in files { tree.add(f.as_ref()).value = FilesNodeKind::File; } FilesMatcher { tree } } } impl Matcher for FilesMatcher { fn matches(&self, file: &RepoPath) -> bool { self.tree .get(file) .is_some_and(|sub| sub.value == FilesNodeKind::File) } fn visit(&self, dir: &RepoPath) -> Visit { self.tree .get(dir) .map_or(Visit::Nothing, files_tree_to_visit_sets) } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] enum FilesNodeKind { /// Represents an intermediate directory. #[default] Dir, /// Represents a file (which might also be an intermediate directory.) File, } fn files_tree_to_visit_sets(tree: &RepoPathTree<FilesNodeKind>) -> Visit { let mut dirs = HashSet::new(); let mut files = HashSet::new(); for (name, sub) in &tree.entries { // should visit only intermediate directories if !sub.entries.is_empty() { dirs.insert(name.clone()); } if sub.value == FilesNodeKind::File { files.insert(name.clone()); } } Visit::sets(dirs, files) } #[derive(Debug)] pub struct PrefixMatcher { tree: RepoPathTree<PrefixNodeKind>, } impl PrefixMatcher { #[instrument(skip(prefixes))] pub fn new(prefixes: impl IntoIterator<Item = impl AsRef<RepoPath>>) -> Self { let mut tree = RepoPathTree::default(); for prefix in prefixes { tree.add(prefix.as_ref()).value = PrefixNodeKind::Prefix; } PrefixMatcher { tree } } } impl Matcher for PrefixMatcher { fn matches(&self, file: &RepoPath) -> bool { self.tree .walk_to(file) .any(|(sub, _)| sub.value == PrefixNodeKind::Prefix) } fn visit(&self, dir: &RepoPath) -> Visit { for (sub, tail_path) in self.tree.walk_to(dir) { // ancestor of 'dir' matches prefix paths if sub.value == PrefixNodeKind::Prefix { return Visit::AllRecursively; } // 'dir' found, and is an ancestor of prefix paths if tail_path.is_root() { return prefix_tree_to_visit_sets(sub); } } Visit::Nothing } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] enum PrefixNodeKind { /// Represents an intermediate directory. #[default] Dir, /// Represents a file and prefix directory. Prefix, } fn prefix_tree_to_visit_sets(tree: &RepoPathTree<PrefixNodeKind>) -> Visit { let mut dirs = HashSet::new(); let mut files = HashSet::new(); for (name, sub) in &tree.entries { // should visit both intermediate and prefix directories dirs.insert(name.clone()); if sub.value == PrefixNodeKind::Prefix { files.insert(name.clone()); } } Visit::sets(dirs, files) } /// Matches file paths with glob patterns. /// /// Patterns are provided as `(dir, pattern)` pairs, where `dir` should be the /// longest directory path that contains no glob meta characters, and `pattern` /// will be evaluated relative to `dir`. #[derive(Clone, Debug)] pub struct FileGlobsMatcher { tree: RepoPathTree<Vec<glob::Pattern>>, } impl FileGlobsMatcher { pub fn new<D: AsRef<RepoPath>>( dir_patterns: impl IntoIterator<Item = (D, glob::Pattern)>, ) -> Self { let mut tree: RepoPathTree<Vec<glob::Pattern>> = Default::default(); for (dir, pattern) in dir_patterns { tree.add(dir.as_ref()).value.push(pattern); } FileGlobsMatcher { tree } } } impl Matcher for FileGlobsMatcher { fn matches(&self, file: &RepoPath) -> bool { // TODO: glob::Pattern relies on path::is_separator() internally, but // RepoPath separator should be '/'. One way to address this problem is // to switch to globset::Glob, and use the underlying regex pattern. const OPTIONS: glob::MatchOptions = glob::MatchOptions { case_sensitive: true, require_literal_separator: true, require_literal_leading_dot: false, }; // check if any ancestor (dir, patterns) matches 'file' self.tree .walk_to(file) .take_while(|(_, tail_path)| !tail_path.is_root()) // only dirs .any(|(sub, tail_path)| { let name = tail_path.as_internal_file_string(); sub.value.iter().any(|pat| pat.matches_with(name, OPTIONS)) }) } fn visit(&self, dir: &RepoPath) -> Visit { for (sub, tail_path) in self.tree.walk_to(dir) { // ancestor of 'dir' has patterns, can't narrow visit anymore if !sub.value.is_empty() { return Visit::Specific { dirs: VisitDirs::All, files: VisitFiles::All, }; } // 'dir' found, and is an ancestor of pattern paths if tail_path.is_root() { let sub_dirs = sub.entries.keys().cloned().collect(); return Visit::sets(sub_dirs, HashSet::new()); } } Visit::Nothing } } /// Matches paths that are matched by any of the input matchers. #[derive(Clone, Debug)] pub struct UnionMatcher<M1, M2> { input1: M1, input2: M2, } impl<M1: Matcher, M2: Matcher> UnionMatcher<M1, M2> { pub fn new(input1: M1, input2: M2) -> Self { Self { input1, input2 } } } impl<M1: Matcher, M2: Matcher> Matcher for UnionMatcher<M1, M2> { fn matches(&self, file: &RepoPath) -> bool { self.input1.matches(file) || self.input2.matches(file) } fn visit(&self, dir: &RepoPath) -> Visit { match self.input1.visit(dir) { Visit::AllRecursively => Visit::AllRecursively, Visit::Nothing => self.input2.visit(dir), Visit::Specific { dirs: dirs1, files: files1, } => match self.input2.visit(dir) { Visit::AllRecursively => Visit::AllRecursively, Visit::Nothing => Visit::Specific { dirs: dirs1, files: files1, }, Visit::Specific { dirs: dirs2, files: files2, } => { let dirs = match (dirs1, dirs2) { (VisitDirs::All, _) | (_, VisitDirs::All) => VisitDirs::All, (VisitDirs::Set(dirs1), VisitDirs::Set(dirs2)) => { VisitDirs::Set(dirs1.iter().chain(&dirs2).cloned().collect()) } }; let files = match (files1, files2) { (VisitFiles::All, _) | (_, VisitFiles::All) => VisitFiles::All, (VisitFiles::Set(files1), VisitFiles::Set(files2)) => { VisitFiles::Set(files1.iter().chain(&files2).cloned().collect()) } }; Visit::Specific { dirs, files } } }, } } } /// Matches paths that are matched by the first input matcher but not by the /// second. #[derive(Clone, Debug)] pub struct DifferenceMatcher<M1, M2> { /// The minuend wanted: M1, /// The subtrahend unwanted: M2, } impl<M1: Matcher, M2: Matcher> DifferenceMatcher<M1, M2> { pub fn new(wanted: M1, unwanted: M2) -> Self { Self { wanted, unwanted } } } impl<M1: Matcher, M2: Matcher> Matcher for DifferenceMatcher<M1, M2> { fn matches(&self, file: &RepoPath) -> bool { self.wanted.matches(file) && !self.unwanted.matches(file) } fn visit(&self, dir: &RepoPath) -> Visit { match self.unwanted.visit(dir) { Visit::AllRecursively => Visit::Nothing, Visit::Nothing => self.wanted.visit(dir), Visit::Specific { .. } => match self.wanted.visit(dir) { Visit::AllRecursively => Visit::Specific { dirs: VisitDirs::All, files: VisitFiles::All, }, wanted_visit => wanted_visit, }, } } } /// Matches paths that are matched by both input matchers. #[derive(Clone, Debug)] pub struct IntersectionMatcher<M1, M2> { input1: M1, input2: M2, } impl<M1: Matcher, M2: Matcher> IntersectionMatcher<M1, M2> { pub fn new(input1: M1, input2: M2) -> Self { Self { input1, input2 } } } impl<M1: Matcher, M2: Matcher> Matcher for IntersectionMatcher<M1, M2> { fn matches(&self, file: &RepoPath) -> bool { self.input1.matches(file) && self.input2.matches(file) } fn visit(&self, dir: &RepoPath) -> Visit { match self.input1.visit(dir) { Visit::AllRecursively => self.input2.visit(dir), Visit::Nothing => Visit::Nothing, Visit::Specific { dirs: dirs1, files: files1, } => match self.input2.visit(dir) { Visit::AllRecursively => Visit::Specific { dirs: dirs1, files: files1, }, Visit::Nothing => Visit::Nothing, Visit::Specific { dirs: dirs2, files: files2, } => { let dirs = match (dirs1, dirs2) { (VisitDirs::All, VisitDirs::All) => VisitDirs::All, (dirs1, VisitDirs::All) => dirs1, (VisitDirs::All, dirs2) => dirs2, (VisitDirs::Set(dirs1), VisitDirs::Set(dirs2)) => { VisitDirs::Set(dirs1.intersection(&dirs2).cloned().collect()) } }; let files = match (files1, files2) { (VisitFiles::All, VisitFiles::All) => VisitFiles::All, (files1, VisitFiles::All) => files1, (VisitFiles::All, files2) => files2, (VisitFiles::Set(files1), VisitFiles::Set(files2)) => { VisitFiles::Set(files1.intersection(&files2).cloned().collect()) } }; match (&dirs, &files) { (VisitDirs::Set(dirs), VisitFiles::Set(files)) if dirs.is_empty() && files.is_empty() => { Visit::Nothing } _ => Visit::Specific { dirs, files }, } } }, } } } /// Tree that maps `RepoPath` to value of type `V`. #[derive(Clone, Default, Eq, PartialEq)] struct RepoPathTree<V> { entries: HashMap<RepoPathComponentBuf, RepoPathTree<V>>, value: V, } impl<V> RepoPathTree<V> { fn add(&mut self, dir: &RepoPath) -> &mut Self where V: Default, { dir.components().fold(self, |sub, name| { // Avoid name.clone() if entry already exists. if !sub.entries.contains_key(name) { sub.entries.insert(name.to_owned(), Self::default()); } sub.entries.get_mut(name).unwrap() }) } fn get(&self, dir: &RepoPath) -> Option<&Self> { dir.components() .try_fold(self, |sub, name| sub.entries.get(name)) } /// Walks the tree from the root to the given `dir`, yielding each sub tree /// and remaining path. fn walk_to<'a, 'b>( &'a self, dir: &'b RepoPath, // TODO: V doesn't have to be captured, but "currently, all type // parameters are required to be mentioned in the precise captures list" // as of rustc 1.85.0. ) -> impl Iterator<Item = (&'a Self, &'b RepoPath)> + use<'a, 'b, V> { iter::successors(Some((self, dir)), |(sub, dir)| { let mut components = dir.components(); let name = components.next()?; Some((sub.entries.get(name)?, components.as_path())) }) } } impl<V: Debug> Debug for RepoPathTree<V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.value.fmt(f)?;