2020-12-12 08:00:42 +00:00
|
|
|
// Copyright 2020 Google LLC
|
|
|
|
//
|
|
|
|
// 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(dead_code)]
|
|
|
|
|
2021-05-17 04:21:45 +00:00
|
|
|
use std::collections::{BTreeSet, HashMap, HashSet};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
2021-05-19 16:41:25 +00:00
|
|
|
use crate::repo_path::{RepoPath, RepoPathComponent};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2022-02-14 04:05:17 +00:00
|
|
|
pub enum Visit {
|
|
|
|
/// Everything in the directory is *guaranteed* to match, no need to check
|
|
|
|
/// descendants
|
|
|
|
AllRecursively,
|
|
|
|
Specific {
|
|
|
|
dirs: VisitDirs,
|
|
|
|
files: VisitFiles,
|
|
|
|
},
|
2022-05-22 05:31:37 +00:00
|
|
|
/// 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,
|
2021-05-17 04:21:45 +00:00
|
|
|
}
|
|
|
|
|
2022-02-12 17:39:03 +00:00
|
|
|
impl Visit {
|
2022-05-22 05:31:37 +00:00
|
|
|
fn sets(dirs: HashSet<RepoPathComponent>, files: HashSet<RepoPathComponent>) -> Self {
|
|
|
|
if dirs.is_empty() && files.is_empty() {
|
|
|
|
Self::Nothing
|
|
|
|
} else {
|
|
|
|
Self::Specific {
|
|
|
|
dirs: VisitDirs::Set(dirs),
|
|
|
|
files: VisitFiles::Set(files),
|
|
|
|
}
|
2022-02-12 17:39:03 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-17 04:43:35 +00:00
|
|
|
|
|
|
|
pub fn is_nothing(&self) -> bool {
|
2022-05-22 05:31:37 +00:00
|
|
|
*self == Visit::Nothing
|
2022-04-17 04:43:35 +00:00
|
|
|
}
|
2022-02-12 17:39:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2021-05-20 04:27:53 +00:00
|
|
|
pub enum VisitDirs {
|
2020-12-12 08:00:42 +00:00
|
|
|
All,
|
2021-05-20 04:27:53 +00:00
|
|
|
Set(HashSet<RepoPathComponent>),
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2021-05-20 04:27:53 +00:00
|
|
|
pub enum VisitFiles {
|
2020-12-12 08:00:42 +00:00
|
|
|
All,
|
2021-05-20 04:27:53 +00:00
|
|
|
Set(HashSet<RepoPathComponent>),
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Matcher {
|
2021-05-17 04:55:51 +00:00
|
|
|
fn matches(&self, file: &RepoPath) -> bool;
|
2021-05-19 16:41:25 +00:00
|
|
|
fn visit(&self, dir: &RepoPath) -> Visit;
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2022-02-12 17:39:03 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub struct NothingMatcher;
|
|
|
|
|
|
|
|
impl Matcher for NothingMatcher {
|
|
|
|
fn matches(&self, _file: &RepoPath) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit(&self, _dir: &RepoPath) -> Visit {
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::Nothing
|
2022-02-12 17:39:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2021-05-17 04:17:01 +00:00
|
|
|
pub struct EverythingMatcher;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
2021-05-17 04:17:01 +00:00
|
|
|
impl Matcher for EverythingMatcher {
|
2021-05-17 04:55:51 +00:00
|
|
|
fn matches(&self, _file: &RepoPath) -> bool {
|
2020-12-12 08:00:42 +00:00
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2021-05-19 16:41:25 +00:00
|
|
|
fn visit(&self, _dir: &RepoPath) -> Visit {
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub struct FilesMatcher {
|
2021-05-17 04:55:51 +00:00
|
|
|
files: HashSet<RepoPath>,
|
2020-12-12 08:00:42 +00:00
|
|
|
dirs: Dirs,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FilesMatcher {
|
2021-06-05 21:29:40 +00:00
|
|
|
pub fn new(files: HashSet<RepoPath>) -> Self {
|
2020-12-12 08:00:42 +00:00
|
|
|
let mut dirs = Dirs::new();
|
|
|
|
for f in &files {
|
|
|
|
dirs.add_file(f);
|
|
|
|
}
|
|
|
|
FilesMatcher { files, dirs }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Matcher for FilesMatcher {
|
2021-05-17 04:55:51 +00:00
|
|
|
fn matches(&self, file: &RepoPath) -> bool {
|
2020-12-12 08:00:42 +00:00
|
|
|
self.files.contains(file)
|
|
|
|
}
|
|
|
|
|
2021-05-19 16:41:25 +00:00
|
|
|
fn visit(&self, dir: &RepoPath) -> Visit {
|
2020-12-12 08:00:42 +00:00
|
|
|
let dirs = self.dirs.get_dirs(dir);
|
|
|
|
let files = self.dirs.get_files(dir);
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(dirs, files)
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-17 04:21:45 +00:00
|
|
|
pub struct PrefixMatcher {
|
|
|
|
prefixes: BTreeSet<RepoPath>,
|
|
|
|
dirs: Dirs,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PrefixMatcher {
|
|
|
|
pub fn new(prefixes: &[RepoPath]) -> Self {
|
|
|
|
let prefixes = prefixes.iter().cloned().collect();
|
|
|
|
let mut dirs = Dirs::new();
|
|
|
|
for prefix in &prefixes {
|
|
|
|
dirs.add_dir(prefix);
|
|
|
|
if !prefix.is_root() {
|
|
|
|
dirs.add_file(prefix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PrefixMatcher { prefixes, dirs }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Matcher for PrefixMatcher {
|
|
|
|
fn matches(&self, file: &RepoPath) -> bool {
|
|
|
|
let components = file.components();
|
|
|
|
// TODO: Make Dirs a trie instead, so this can just walk that trie.
|
|
|
|
for i in 0..components.len() + 1 {
|
|
|
|
let prefix = RepoPath::from_components(components[0..i].to_vec());
|
|
|
|
if self.prefixes.contains(&prefix) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit(&self, dir: &RepoPath) -> Visit {
|
|
|
|
if self.matches(dir) {
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2021-05-17 04:21:45 +00:00
|
|
|
} else {
|
|
|
|
let dirs = self.dirs.get_dirs(dir);
|
|
|
|
let files = self.dirs.get_files(dir);
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(dirs, files)
|
2021-05-17 04:21:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 05:10:35 +00:00
|
|
|
/// Matches paths that are matched by the first input matcher but not by the
|
|
|
|
/// second.
|
2022-04-17 04:43:35 +00:00
|
|
|
pub struct DifferenceMatcher<'input> {
|
|
|
|
/// The minuend
|
|
|
|
wanted: &'input dyn Matcher,
|
|
|
|
/// The subtrahend
|
|
|
|
unwanted: &'input dyn Matcher,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'input> DifferenceMatcher<'input> {
|
|
|
|
pub fn new(wanted: &'input dyn Matcher, unwanted: &'input dyn Matcher) -> Self {
|
|
|
|
Self { wanted, unwanted }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Matcher for DifferenceMatcher<'_> {
|
|
|
|
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) {
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::AllRecursively => Visit::Nothing,
|
2022-05-22 05:59:43 +00:00
|
|
|
Visit::Nothing => self.wanted.visit(dir),
|
|
|
|
Visit::Specific { .. } => match self.wanted.visit(dir) {
|
|
|
|
Visit::AllRecursively => Visit::Specific {
|
|
|
|
dirs: VisitDirs::All,
|
|
|
|
files: VisitFiles::All,
|
|
|
|
},
|
2022-04-17 04:43:35 +00:00
|
|
|
wanted_visit => wanted_visit,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 05:10:35 +00:00
|
|
|
/// Matches paths that are matched by both input matchers.
|
|
|
|
pub struct IntersectionMatcher<'input> {
|
|
|
|
input1: &'input dyn Matcher,
|
|
|
|
input2: &'input dyn Matcher,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'input> IntersectionMatcher<'input> {
|
|
|
|
pub fn new(input1: &'input dyn Matcher, input2: &'input dyn Matcher) -> Self {
|
|
|
|
Self { input1, input2 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Matcher for IntersectionMatcher<'_> {
|
|
|
|
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 },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
/// Keeps track of which subdirectories and files of each directory need to be
|
|
|
|
/// visited.
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
struct Dirs {
|
2021-05-19 16:41:25 +00:00
|
|
|
dirs: HashMap<RepoPath, HashSet<RepoPathComponent>>,
|
|
|
|
files: HashMap<RepoPath, HashSet<RepoPathComponent>>,
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Dirs {
|
|
|
|
fn new() -> Self {
|
|
|
|
Dirs {
|
|
|
|
dirs: HashMap::new(),
|
|
|
|
files: HashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-17 04:21:45 +00:00
|
|
|
fn add_dir(&mut self, dir: &RepoPath) {
|
|
|
|
let mut dir = dir.clone();
|
2020-12-12 08:00:42 +00:00
|
|
|
let mut maybe_child = None;
|
|
|
|
loop {
|
|
|
|
let was_present = self.dirs.contains_key(&dir);
|
|
|
|
let children = self.dirs.entry(dir.clone()).or_default();
|
|
|
|
if let Some(child) = maybe_child {
|
|
|
|
children.insert(child);
|
|
|
|
}
|
|
|
|
if was_present {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
match dir.split() {
|
|
|
|
None => break,
|
|
|
|
Some((new_dir, new_child)) => {
|
2021-05-19 16:41:25 +00:00
|
|
|
maybe_child = Some(new_child.clone());
|
2020-12-12 08:00:42 +00:00
|
|
|
dir = new_dir;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-17 04:55:51 +00:00
|
|
|
fn add_file(&mut self, file: &RepoPath) {
|
|
|
|
let (dir, basename) = file
|
|
|
|
.split()
|
|
|
|
.unwrap_or_else(|| panic!("got empty filename: {:?}", file));
|
2021-05-17 04:21:45 +00:00
|
|
|
self.add_dir(&dir);
|
2021-05-17 06:40:39 +00:00
|
|
|
self.files.entry(dir).or_default().insert(basename.clone());
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-05-20 04:27:53 +00:00
|
|
|
fn get_dirs(&self, dir: &RepoPath) -> HashSet<RepoPathComponent> {
|
|
|
|
self.dirs.get(dir).cloned().unwrap_or_default()
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-05-20 04:27:53 +00:00
|
|
|
fn get_files(&self, dir: &RepoPath) -> HashSet<RepoPathComponent> {
|
|
|
|
self.files.get(dir).cloned().unwrap_or_default()
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-09-22 04:52:04 +00:00
|
|
|
use maplit::hashset;
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
use super::*;
|
2021-05-19 16:41:25 +00:00
|
|
|
use crate::repo_path::{RepoPath, RepoPathComponent};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
#[test]
|
2021-05-17 04:21:45 +00:00
|
|
|
fn test_dirs_empty() {
|
2020-12-12 08:00:42 +00:00
|
|
|
let dirs = Dirs::new();
|
2021-05-20 04:27:53 +00:00
|
|
|
assert_eq!(dirs.get_dirs(&RepoPath::root()), hashset! {});
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-05-17 04:21:45 +00:00
|
|
|
fn test_dirs_root() {
|
2020-12-12 08:00:42 +00:00
|
|
|
let mut dirs = Dirs::new();
|
2021-05-17 04:21:45 +00:00
|
|
|
dirs.add_dir(&RepoPath::root());
|
2021-05-20 04:27:53 +00:00
|
|
|
assert_eq!(dirs.get_dirs(&RepoPath::root()), hashset! {});
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-05-17 04:21:45 +00:00
|
|
|
fn test_dirs_dir() {
|
2020-12-12 08:00:42 +00:00
|
|
|
let mut dirs = Dirs::new();
|
2021-05-17 04:21:45 +00:00
|
|
|
dirs.add_dir(&RepoPath::from_internal_string("dir"));
|
2021-05-16 17:20:06 +00:00
|
|
|
assert_eq!(
|
2021-05-19 16:41:25 +00:00
|
|
|
dirs.get_dirs(&RepoPath::root()),
|
2021-05-20 04:27:53 +00:00
|
|
|
hashset! {RepoPathComponent::from("dir")}
|
2021-05-16 17:20:06 +00:00
|
|
|
);
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-05-17 04:21:45 +00:00
|
|
|
fn test_dirs_file() {
|
2020-12-12 08:00:42 +00:00
|
|
|
let mut dirs = Dirs::new();
|
2021-05-17 05:47:31 +00:00
|
|
|
dirs.add_file(&RepoPath::from_internal_string("dir/file"));
|
2021-05-16 17:20:06 +00:00
|
|
|
assert_eq!(
|
2021-05-19 16:41:25 +00:00
|
|
|
dirs.get_dirs(&RepoPath::root()),
|
2021-05-20 04:27:53 +00:00
|
|
|
hashset! {RepoPathComponent::from("dir")}
|
2021-05-16 17:20:06 +00:00
|
|
|
);
|
2021-05-20 04:27:53 +00:00
|
|
|
assert_eq!(dirs.get_files(&RepoPath::root()), hashset! {});
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2022-02-12 17:39:03 +00:00
|
|
|
#[test]
|
|
|
|
fn test_nothingmatcher() {
|
|
|
|
let m = NothingMatcher;
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("file")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("dir/file")));
|
2022-05-22 05:31:37 +00:00
|
|
|
assert_eq!(m.visit(&RepoPath::root()), Visit::Nothing);
|
2022-02-12 17:39:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
#[test]
|
2021-05-17 04:21:45 +00:00
|
|
|
fn test_filesmatcher_empty() {
|
2021-05-20 04:27:53 +00:00
|
|
|
let m = FilesMatcher::new(hashset! {});
|
2021-05-17 05:47:31 +00:00
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("file")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("dir/file")));
|
2022-05-22 05:31:37 +00:00
|
|
|
assert_eq!(m.visit(&RepoPath::root()), Visit::Nothing);
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-05-17 04:21:45 +00:00
|
|
|
fn test_filesmatcher_nonempty() {
|
2021-05-16 17:20:06 +00:00
|
|
|
let m = FilesMatcher::new(hashset! {
|
2021-05-17 05:47:31 +00:00
|
|
|
RepoPath::from_internal_string("dir1/subdir1/file1"),
|
|
|
|
RepoPath::from_internal_string("dir1/subdir1/file2"),
|
|
|
|
RepoPath::from_internal_string("dir1/subdir2/file3"),
|
|
|
|
RepoPath::from_internal_string("file4"),
|
2021-05-16 17:20:06 +00:00
|
|
|
});
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
assert_eq!(
|
2021-05-19 16:41:25 +00:00
|
|
|
m.visit(&RepoPath::root()),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("dir1")},
|
|
|
|
hashset! {RepoPathComponent::from("file4")}
|
|
|
|
)
|
2021-05-16 17:20:06 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-19 16:41:25 +00:00
|
|
|
m.visit(&RepoPath::from_internal_string("dir1")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("subdir1"), RepoPathComponent::from("subdir2")},
|
|
|
|
hashset! {}
|
|
|
|
)
|
2021-05-16 17:20:06 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-19 16:41:25 +00:00
|
|
|
m.visit(&RepoPath::from_internal_string("dir1/subdir1")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {},
|
|
|
|
hashset! {RepoPathComponent::from("file1"), RepoPathComponent::from("file2")}
|
|
|
|
)
|
2021-05-16 17:20:06 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-19 16:41:25 +00:00
|
|
|
m.visit(&RepoPath::from_internal_string("dir1/subdir2")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(hashset! {}, hashset! {RepoPathComponent::from("file3")})
|
2020-12-12 08:00:42 +00:00
|
|
|
);
|
|
|
|
}
|
2021-05-17 04:21:45 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_prefixmatcher_empty() {
|
|
|
|
let m = PrefixMatcher::new(&[]);
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("file")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("dir/file")));
|
2022-05-22 05:31:37 +00:00
|
|
|
assert_eq!(m.visit(&RepoPath::root()), Visit::Nothing);
|
2021-05-17 04:21:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_prefixmatcher_root() {
|
|
|
|
let m = PrefixMatcher::new(&[RepoPath::root()]);
|
|
|
|
// Matches all files
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("file")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("dir/file")));
|
|
|
|
// Visits all directories
|
2022-02-14 04:05:17 +00:00
|
|
|
assert_eq!(m.visit(&RepoPath::root()), Visit::AllRecursively);
|
2021-05-17 04:21:45 +00:00
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar")),
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_prefixmatcher_single_prefix() {
|
|
|
|
let m = PrefixMatcher::new(&[RepoPath::from_internal_string("foo/bar")]);
|
|
|
|
|
|
|
|
// Parts of the prefix should not match
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("bar")));
|
|
|
|
// A file matching the prefix exactly should match
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/bar")));
|
|
|
|
// Files in subdirectories should match
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/bar/baz")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/bar/baz/qux")));
|
|
|
|
// Sibling files should not match
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo/foo")));
|
|
|
|
// An unrooted "foo/bar" should not match
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("bar/foo/bar")));
|
|
|
|
|
|
|
|
// The matcher should only visit directory foo/ in the root (file "foo"
|
|
|
|
// shouldn't be visited)
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::root()),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(hashset! {RepoPathComponent::from("foo")}, hashset! {})
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
// Inside parent directory "foo/", both subdirectory "bar" and file "bar" may
|
|
|
|
// match
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("bar")},
|
|
|
|
hashset! {RepoPathComponent::from("bar")}
|
|
|
|
)
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
2022-02-14 04:05:17 +00:00
|
|
|
// Inside a directory that matches the prefix, everything matches recursively
|
2021-05-17 04:21:45 +00:00
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar")),
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
// Same thing in subdirectories of the prefix
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar/baz")),
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
// Nothing in directories that are siblings of the prefix can match, so don't
|
|
|
|
// visit
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::Nothing
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_prefixmatcher_nested_prefixes() {
|
|
|
|
let m = PrefixMatcher::new(&[
|
|
|
|
RepoPath::from_internal_string("foo"),
|
|
|
|
RepoPath::from_internal_string("foo/bar/baz"),
|
|
|
|
]);
|
|
|
|
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/bar")));
|
2022-09-09 17:32:38 +00:00
|
|
|
// Matches because the "foo" pattern matches
|
2021-05-17 04:21:45 +00:00
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/baz/foo")));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::root()),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("foo")},
|
|
|
|
hashset! {RepoPathComponent::from("foo")}
|
|
|
|
)
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
2022-02-14 04:05:17 +00:00
|
|
|
// Inside a directory that matches the prefix, everything matches recursively
|
2021-05-17 04:21:45 +00:00
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo")),
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
// Same thing in subdirectories of the prefix
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar/baz")),
|
2022-02-14 04:05:17 +00:00
|
|
|
Visit::AllRecursively
|
2021-05-17 04:21:45 +00:00
|
|
|
);
|
|
|
|
}
|
2022-04-17 04:43:35 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_differencematcher_remove_subdir() {
|
|
|
|
let m1 = PrefixMatcher::new(&[
|
|
|
|
RepoPath::from_internal_string("foo"),
|
|
|
|
RepoPath::from_internal_string("bar"),
|
|
|
|
]);
|
|
|
|
let m2 = PrefixMatcher::new(&[RepoPath::from_internal_string("foo/bar")]);
|
|
|
|
let m = DifferenceMatcher::new(&m1, &m2);
|
|
|
|
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo/bar")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo/bar/baz")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/baz")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("bar")));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::root()),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("foo"), RepoPathComponent::from("bar")},
|
|
|
|
hashset! {RepoPathComponent::from("foo"), RepoPathComponent::from("bar")}
|
|
|
|
)
|
2022-04-17 04:43:35 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo")),
|
|
|
|
Visit::Specific {
|
|
|
|
dirs: VisitDirs::All,
|
|
|
|
files: VisitFiles::All,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::Nothing
|
2022-04-17 04:43:35 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/baz")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_differencematcher_shared_patterns() {
|
|
|
|
let m1 = PrefixMatcher::new(&[
|
|
|
|
RepoPath::from_internal_string("foo"),
|
|
|
|
RepoPath::from_internal_string("bar"),
|
|
|
|
]);
|
|
|
|
let m2 = PrefixMatcher::new(&[RepoPath::from_internal_string("foo")]);
|
|
|
|
let m = DifferenceMatcher::new(&m1, &m2);
|
|
|
|
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo/bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("bar/foo")));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::root()),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("foo"), RepoPathComponent::from("bar")},
|
|
|
|
hashset! {RepoPathComponent::from("foo"), RepoPathComponent::from("bar")}
|
|
|
|
)
|
2022-04-17 04:43:35 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::Nothing
|
2022-04-17 04:43:35 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar")),
|
2022-05-22 05:31:37 +00:00
|
|
|
Visit::Nothing
|
2022-04-17 04:43:35 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar/foo")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
}
|
2022-05-22 05:10:35 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_intersectionmatcher_intersecting_roots() {
|
|
|
|
let m1 = PrefixMatcher::new(&[
|
|
|
|
RepoPath::from_internal_string("foo"),
|
|
|
|
RepoPath::from_internal_string("bar"),
|
|
|
|
]);
|
|
|
|
let m2 = PrefixMatcher::new(&[
|
|
|
|
RepoPath::from_internal_string("bar"),
|
|
|
|
RepoPath::from_internal_string("baz"),
|
|
|
|
]);
|
|
|
|
let m = IntersectionMatcher::new(&m1, &m2);
|
|
|
|
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo/bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("bar/foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("baz")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("baz/foo")));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::root()),
|
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("bar")},
|
|
|
|
hashset! {RepoPathComponent::from("bar")}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo")),
|
|
|
|
Visit::Nothing
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar")),
|
|
|
|
Visit::Nothing
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar/foo")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("baz")),
|
|
|
|
Visit::Nothing
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("baz/foo")),
|
|
|
|
Visit::Nothing
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_intersectionmatcher_subdir() {
|
|
|
|
let m1 = PrefixMatcher::new(&[RepoPath::from_internal_string("foo")]);
|
|
|
|
let m2 = PrefixMatcher::new(&[RepoPath::from_internal_string("foo/bar")]);
|
|
|
|
let m = IntersectionMatcher::new(&m1, &m2);
|
|
|
|
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/bar")));
|
|
|
|
assert!(m.matches(&RepoPath::from_internal_string("foo/bar/baz")));
|
|
|
|
assert!(!m.matches(&RepoPath::from_internal_string("foo/baz")));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::root()),
|
|
|
|
Visit::sets(hashset! {RepoPathComponent::from("foo")}, hashset! {})
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("bar")),
|
|
|
|
Visit::Nothing
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo")),
|
|
|
|
Visit::sets(
|
|
|
|
hashset! {RepoPathComponent::from("bar")},
|
|
|
|
hashset! {RepoPathComponent::from("bar")}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
m.visit(&RepoPath::from_internal_string("foo/bar")),
|
|
|
|
Visit::AllRecursively
|
|
|
|
);
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|