revset: insert StringPattern enum to add support for other kind of matching

This commit is contained in:
Yuya Nishihara 2023-08-16 11:35:43 +09:00
parent 313670d3c2
commit 5b3c73dfc4
3 changed files with 236 additions and 126 deletions

View file

@ -86,17 +86,17 @@ revsets (expressions) as arguments.
* `all()`: All visible commits in the repo. * `all()`: All visible commits in the repo.
* `none()`: No commits. This function is rarely useful; it is provided for * `none()`: No commits. This function is rarely useful; it is provided for
completeness. completeness.
* `branches([needle])`: All local branch targets. If `needle` is specified, * `branches([pattern])`: All local branch targets. If `pattern` is specified,
branches whose name contains the given string are selected. For example, branches whose name contains the given string are selected. For example,
`branches(push)` would match the branches `push-123` and `repushed` but not `branches(push)` would match the branches `push-123` and `repushed` but not
the branch `main`. If a branch is in a conflicted state, all its possible the branch `main`. If a branch is in a conflicted state, all its possible
targets are included. targets are included.
* `remote_branches([branch_needle[, [remote=]remote_needle]])`: All remote * `remote_branches([branch_pattern[, [remote=]remote_pattern]])`: All remote
branch targets across all remotes. If just the `branch_needle` is specified, branch targets across all remotes. If just the `branch_pattern` is specified,
branches whose name contains the given string across all remotes are branches whose name contains the given string across all remotes are
selected. If both `branch_needle` and `remote_needle` are specified, the selected. If both `branch_pattern` and `remote_pattern` are specified, the
selection is further restricted to just the remotes whose name contains selection is further restricted to just the remotes whose name contains
`remote_needle`. For example, `remote_branches(push, ri)` would match the `remote_pattern`. For example, `remote_branches(push, ri)` would match the
branches `push-123@origin` and `repushed@private` but not `push-123@upstream` branches `push-123@origin` and `repushed@private` but not `push-123@upstream`
or `main@origin` or `main@upstream`. If a branch is in a conflicted state, or `main@origin` or `main@upstream`. If a branch is in a conflicted state,
all its possible targets are included. all its possible targets are included.
@ -118,13 +118,13 @@ revsets (expressions) as arguments.
* `latest(x[, count])`: Latest `count` commits in `x`, based on committer * `latest(x[, count])`: Latest `count` commits in `x`, based on committer
timestamp. The default `count` is 1. timestamp. The default `count` is 1.
* `merges()`: Merge commits. * `merges()`: Merge commits.
* `description(needle)`: Commits with the given string in their * `description(pattern)`: Commits with the given string in their
description. description.
* `author(needle)`: Commits with the given string in the author's name or * `author(pattern)`: Commits with the given string in the author's name or
email. email.
* `mine()`: Commits where the author's email matches the email of the current * `mine()`: Commits where the author's email matches the email of the current
user. user.
* `committer(needle)`: Commits with the given string in the committer's * `committer(pattern)`: Commits with the given string in the committer's
name or email. name or email.
* `empty()`: Commits modifying no files. This also includes `merges()` without * `empty()`: Commits modifying no files. This also includes `merges()` without
user modifications and `root`. user modifications and `root`.

View file

@ -831,33 +831,29 @@ fn build_predicate_fn<'index>(
let parent_count_range = parent_count_range.clone(); let parent_count_range = parent_count_range.clone();
pure_predicate_fn(move |entry| parent_count_range.contains(&entry.num_parents())) pure_predicate_fn(move |entry| parent_count_range.contains(&entry.num_parents()))
} }
RevsetFilterPredicate::Description(needle) => { RevsetFilterPredicate::Description(pattern) => {
let needle = needle.clone(); let pattern = pattern.clone();
pure_predicate_fn(move |entry| { pure_predicate_fn(move |entry| {
store let commit = store.get_commit(&entry.commit_id()).unwrap();
.get_commit(&entry.commit_id()) pattern.matches(commit.description())
.unwrap()
.description()
.contains(needle.as_str())
}) })
} }
RevsetFilterPredicate::Author(needle) => { RevsetFilterPredicate::Author(pattern) => {
let needle = needle.clone(); let pattern = pattern.clone();
// TODO: Make these functions that take a needle to search for accept some // TODO: Make these functions that take a needle to search for accept some
// syntax for specifying whether it's a regex and whether it's // syntax for specifying whether it's a regex and whether it's
// case-sensitive. // case-sensitive.
pure_predicate_fn(move |entry| { pure_predicate_fn(move |entry| {
let commit = store.get_commit(&entry.commit_id()).unwrap(); let commit = store.get_commit(&entry.commit_id()).unwrap();
commit.author().name.contains(needle.as_str()) pattern.matches(&commit.author().name) || pattern.matches(&commit.author().email)
|| commit.author().email.contains(needle.as_str())
}) })
} }
RevsetFilterPredicate::Committer(needle) => { RevsetFilterPredicate::Committer(pattern) => {
let needle = needle.clone(); let pattern = pattern.clone();
pure_predicate_fn(move |entry| { pure_predicate_fn(move |entry| {
let commit = store.get_commit(&entry.commit_id()).unwrap(); let commit = store.get_commit(&entry.commit_id()).unwrap();
commit.committer().name.contains(needle.as_str()) pattern.matches(&commit.committer().name)
|| commit.committer().email.contains(needle.as_str()) || pattern.matches(&commit.committer().email)
}) })
} }
RevsetFilterPredicate::File(paths) => { RevsetFilterPredicate::File(paths) => {

View file

@ -208,15 +208,37 @@ impl error::Error for RevsetParseError {
pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX; pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX;
pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0; pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0;
/// Pattern to be tested against string property like commit description or
/// branch name.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum StringPattern {
/// Matches strings that contain `substring`.
Substring(String),
}
impl StringPattern {
/// Pattern that matches any string.
pub fn everything() -> Self {
StringPattern::Substring(String::new())
}
/// Returns true if this pattern matches the `haystack`.
pub fn matches(&self, haystack: &str) -> bool {
match self {
StringPattern::Substring(needle) => haystack.contains(needle),
}
}
}
/// Symbol or function to be resolved to `CommitId`s. /// Symbol or function to be resolved to `CommitId`s.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum RevsetCommitRef { pub enum RevsetCommitRef {
Symbol(String), Symbol(String),
VisibleHeads, VisibleHeads,
Branches(String), Branches(StringPattern),
RemoteBranches { RemoteBranches {
branch_needle: String, branch_pattern: StringPattern,
remote_needle: String, remote_pattern: StringPattern,
}, },
Tags, Tags,
GitRefs, GitRefs,
@ -228,11 +250,11 @@ pub enum RevsetFilterPredicate {
/// Commits with number of parents in the range. /// Commits with number of parents in the range.
ParentCount(Range<u32>), ParentCount(Range<u32>),
/// Commits with description containing the needle. /// Commits with description containing the needle.
Description(String), Description(StringPattern),
/// Commits with author's name or email containing the needle. /// Commits with author's name or email containing the needle.
Author(String), Author(StringPattern),
/// Commits with committer's name or email containing the needle. /// Commits with committer's name or email containing the needle.
Committer(String), Committer(StringPattern),
/// Commits modifying the paths specified by the pattern. /// Commits modifying the paths specified by the pattern.
File(Option<Vec<RepoPath>>), // TODO: embed matcher expression? File(Option<Vec<RepoPath>>), // TODO: embed matcher expression?
/// Commits with conflicts /// Commits with conflicts
@ -309,17 +331,20 @@ impl RevsetExpression {
Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::VisibleHeads)) Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::VisibleHeads))
} }
pub fn branches(needle: String) -> Rc<RevsetExpression> { pub fn branches(pattern: StringPattern) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::Branches( Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::Branches(
needle, pattern,
))) )))
} }
pub fn remote_branches(branch_needle: String, remote_needle: String) -> Rc<RevsetExpression> { pub fn remote_branches(
branch_pattern: StringPattern,
remote_pattern: StringPattern,
) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::CommitRef( Rc::new(RevsetExpression::CommitRef(
RevsetCommitRef::RemoteBranches { RevsetCommitRef::RemoteBranches {
branch_needle, branch_pattern,
remote_needle, remote_pattern,
}, },
)) ))
} }
@ -986,29 +1011,29 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
}); });
map.insert("branches", |name, arguments_pair, state| { map.insert("branches", |name, arguments_pair, state| {
let ([], [opt_arg]) = expect_arguments(name, arguments_pair)?; let ([], [opt_arg]) = expect_arguments(name, arguments_pair)?;
let needle = if let Some(arg) = opt_arg { let pattern = if let Some(arg) = opt_arg {
parse_function_argument_to_string(name, arg, state)? parse_function_argument_to_string_pattern(name, arg, state)?
} else { } else {
"".to_owned() StringPattern::everything()
}; };
Ok(RevsetExpression::branches(needle)) Ok(RevsetExpression::branches(pattern))
}); });
map.insert("remote_branches", |name, arguments_pair, state| { map.insert("remote_branches", |name, arguments_pair, state| {
let ([], [branch_opt_arg, remote_opt_arg]) = let ([], [branch_opt_arg, remote_opt_arg]) =
expect_named_arguments(name, &["", "remote"], arguments_pair)?; expect_named_arguments(name, &["", "remote"], arguments_pair)?;
let branch_needle = if let Some(branch_arg) = branch_opt_arg { let branch_pattern = if let Some(branch_arg) = branch_opt_arg {
parse_function_argument_to_string(name, branch_arg, state)? parse_function_argument_to_string_pattern(name, branch_arg, state)?
} else { } else {
"".to_owned() StringPattern::everything()
}; };
let remote_needle = if let Some(remote_arg) = remote_opt_arg { let remote_pattern = if let Some(remote_arg) = remote_opt_arg {
parse_function_argument_to_string(name, remote_arg, state)? parse_function_argument_to_string_pattern(name, remote_arg, state)?
} else { } else {
"".to_owned() StringPattern::everything()
}; };
Ok(RevsetExpression::remote_branches( Ok(RevsetExpression::remote_branches(
branch_needle, branch_pattern,
remote_needle, remote_pattern,
)) ))
}); });
map.insert("tags", |name, arguments_pair, _state| { map.insert("tags", |name, arguments_pair, _state| {
@ -1041,29 +1066,30 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
}); });
map.insert("description", |name, arguments_pair, state| { map.insert("description", |name, arguments_pair, state| {
let arg = expect_one_argument(name, arguments_pair)?; let arg = expect_one_argument(name, arguments_pair)?;
let needle = parse_function_argument_to_string(name, arg, state)?; let pattern = parse_function_argument_to_string_pattern(name, arg, state)?;
Ok(RevsetExpression::filter( Ok(RevsetExpression::filter(
RevsetFilterPredicate::Description(needle), RevsetFilterPredicate::Description(pattern),
)) ))
}); });
map.insert("author", |name, arguments_pair, state| { map.insert("author", |name, arguments_pair, state| {
let arg = expect_one_argument(name, arguments_pair)?; let arg = expect_one_argument(name, arguments_pair)?;
let needle = parse_function_argument_to_string(name, arg, state)?; let pattern = parse_function_argument_to_string_pattern(name, arg, state)?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::Author( Ok(RevsetExpression::filter(RevsetFilterPredicate::Author(
needle, pattern,
))) )))
}); });
map.insert("mine", |name, arguments_pair, state| { map.insert("mine", |name, arguments_pair, state| {
expect_no_arguments(name, arguments_pair)?; expect_no_arguments(name, arguments_pair)?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::Author( Ok(RevsetExpression::filter(RevsetFilterPredicate::Author(
state.user_email.to_owned(), // TODO: use exact match
StringPattern::Substring(state.user_email.to_owned()),
))) )))
}); });
map.insert("committer", |name, arguments_pair, state| { map.insert("committer", |name, arguments_pair, state| {
let arg = expect_one_argument(name, arguments_pair)?; let arg = expect_one_argument(name, arguments_pair)?;
let needle = parse_function_argument_to_string(name, arg, state)?; let pattern = parse_function_argument_to_string_pattern(name, arg, state)?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::Committer( Ok(RevsetExpression::filter(RevsetFilterPredicate::Committer(
needle, pattern,
))) )))
}); });
map.insert("empty", |name, arguments_pair, _state| { map.insert("empty", |name, arguments_pair, _state| {
@ -1249,6 +1275,15 @@ fn parse_function_argument_to_string(
parse_function_argument_as_literal("string", name, pair, state) parse_function_argument_as_literal("string", name, pair, state)
} }
fn parse_function_argument_to_string_pattern(
name: &str,
pair: Pair<Rule>,
state: ParseState,
) -> Result<StringPattern, RevsetParseError> {
let needle = parse_function_argument_as_literal("string", name, pair, state)?;
Ok(StringPattern::Substring(needle))
}
fn parse_function_argument_as_literal<T: FromStr>( fn parse_function_argument_as_literal<T: FromStr>(
type_name: &str, type_name: &str,
name: &str, name: &str,
@ -1941,10 +1976,10 @@ fn resolve_commit_ref(
match commit_ref { match commit_ref {
RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(symbol), RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(symbol),
RevsetCommitRef::VisibleHeads => Ok(repo.view().heads().iter().cloned().collect_vec()), RevsetCommitRef::VisibleHeads => Ok(repo.view().heads().iter().cloned().collect_vec()),
RevsetCommitRef::Branches(needle) => { RevsetCommitRef::Branches(pattern) => {
let mut commit_ids = vec![]; let mut commit_ids = vec![];
for (branch_name, branch_target) in repo.view().branches() { for (branch_name, branch_target) in repo.view().branches() {
if !branch_name.contains(needle) { if !pattern.matches(branch_name) {
continue; continue;
} }
commit_ids.extend(branch_target.local_target.added_ids().cloned()); commit_ids.extend(branch_target.local_target.added_ids().cloned());
@ -1952,16 +1987,16 @@ fn resolve_commit_ref(
Ok(commit_ids) Ok(commit_ids)
} }
RevsetCommitRef::RemoteBranches { RevsetCommitRef::RemoteBranches {
branch_needle, branch_pattern,
remote_needle, remote_pattern,
} => { } => {
let mut commit_ids = vec![]; let mut commit_ids = vec![];
for (branch_name, branch_target) in repo.view().branches() { for (branch_name, branch_target) in repo.view().branches() {
if !branch_name.contains(branch_needle) { if !branch_pattern.matches(branch_name) {
continue; continue;
} }
for (remote_name, remote_target) in branch_target.remote_targets.iter() { for (remote_name, remote_target) in branch_target.remote_targets.iter() {
if remote_name.contains(remote_needle) { if remote_pattern.matches(remote_name) {
commit_ids.extend(remote_target.added_ids().cloned()); commit_ids.extend(remote_target.added_ids().cloned());
} }
} }
@ -2544,16 +2579,16 @@ mod tests {
// Space is allowed around infix operators and function arguments // Space is allowed around infix operators and function arguments
assert_eq!( assert_eq!(
parse(" description( arg1 ) ~ file( arg1 , arg2 ) ~ visible_heads( ) "), parse(" description( arg1 ) ~ file( arg1 , arg2 ) ~ visible_heads( ) "),
Ok( Ok(RevsetExpression::filter(RevsetFilterPredicate::Description(
RevsetExpression::filter(RevsetFilterPredicate::Description("arg1".to_string())) StringPattern::Substring("arg1".to_string())
.minus(&RevsetExpression::filter(RevsetFilterPredicate::File( ))
Some(vec![ .minus(&RevsetExpression::filter(RevsetFilterPredicate::File(
RepoPath::from_internal_string("arg1"), Some(vec![
RepoPath::from_internal_string("arg2"), RepoPath::from_internal_string("arg1"),
]) RepoPath::from_internal_string("arg2"),
))) ])
.minus(&RevsetExpression::visible_heads()) )))
) .minus(&RevsetExpression::visible_heads()))
); );
// Space is allowed around keyword arguments // Space is allowed around keyword arguments
assert_eq!( assert_eq!(
@ -2695,13 +2730,13 @@ mod tests {
assert_eq!( assert_eq!(
parse(r#"description("")"#), parse(r#"description("")"#),
Ok(RevsetExpression::filter( Ok(RevsetExpression::filter(
RevsetFilterPredicate::Description("".to_string()) RevsetFilterPredicate::Description(StringPattern::Substring("".to_string()))
)) ))
); );
assert_eq!( assert_eq!(
parse("description(foo)"), parse("description(foo)"),
Ok(RevsetExpression::filter( Ok(RevsetExpression::filter(
RevsetFilterPredicate::Description("foo".to_string()) RevsetFilterPredicate::Description(StringPattern::Substring("foo".to_string()))
)) ))
); );
assert_eq!( assert_eq!(
@ -2714,20 +2749,20 @@ mod tests {
assert_eq!( assert_eq!(
parse("description((foo))"), parse("description((foo))"),
Ok(RevsetExpression::filter( Ok(RevsetExpression::filter(
RevsetFilterPredicate::Description("foo".to_string()) RevsetFilterPredicate::Description(StringPattern::Substring("foo".to_string()))
)) ))
); );
assert_eq!( assert_eq!(
parse("description(\"(foo)\")"), parse("description(\"(foo)\")"),
Ok(RevsetExpression::filter( Ok(RevsetExpression::filter(
RevsetFilterPredicate::Description("(foo)".to_string()) RevsetFilterPredicate::Description(StringPattern::Substring("(foo)".to_string()))
)) ))
); );
assert!(parse("mine(foo)").is_err()); assert!(parse("mine(foo)").is_err());
assert_eq!( assert_eq!(
parse("mine()"), parse("mine()"),
Ok(RevsetExpression::filter(RevsetFilterPredicate::Author( Ok(RevsetExpression::filter(RevsetFilterPredicate::Author(
"test.user@example.com".to_string() StringPattern::Substring("test.user@example.com".to_string())
))) )))
); );
assert_eq!( assert_eq!(
@ -2960,42 +2995,44 @@ mod tests {
assert_eq!( assert_eq!(
optimize(parse("parents(branches() & all())").unwrap()), optimize(parse("parents(branches() & all())").unwrap()),
RevsetExpression::branches("".to_owned()).parents() RevsetExpression::branches(StringPattern::everything()).parents()
); );
assert_eq!( assert_eq!(
optimize(parse("children(branches() & all())").unwrap()), optimize(parse("children(branches() & all())").unwrap()),
RevsetExpression::branches("".to_owned()).children() RevsetExpression::branches(StringPattern::everything()).children()
); );
assert_eq!( assert_eq!(
optimize(parse("ancestors(branches() & all())").unwrap()), optimize(parse("ancestors(branches() & all())").unwrap()),
RevsetExpression::branches("".to_owned()).ancestors() RevsetExpression::branches(StringPattern::everything()).ancestors()
); );
assert_eq!( assert_eq!(
optimize(parse("descendants(branches() & all())").unwrap()), optimize(parse("descendants(branches() & all())").unwrap()),
RevsetExpression::branches("".to_owned()).descendants() RevsetExpression::branches(StringPattern::everything()).descendants()
); );
assert_eq!( assert_eq!(
optimize(parse("(branches() & all())..(all() & tags())").unwrap()), optimize(parse("(branches() & all())..(all() & tags())").unwrap()),
RevsetExpression::branches("".to_owned()).range(&RevsetExpression::tags()) RevsetExpression::branches(StringPattern::everything())
.range(&RevsetExpression::tags())
); );
assert_eq!( assert_eq!(
optimize(parse("(branches() & all()):(all() & tags())").unwrap()), optimize(parse("(branches() & all()):(all() & tags())").unwrap()),
RevsetExpression::branches("".to_owned()).dag_range_to(&RevsetExpression::tags()) RevsetExpression::branches(StringPattern::everything())
.dag_range_to(&RevsetExpression::tags())
); );
assert_eq!( assert_eq!(
optimize(parse("heads(branches() & all())").unwrap()), optimize(parse("heads(branches() & all())").unwrap()),
RevsetExpression::branches("".to_owned()).heads() RevsetExpression::branches(StringPattern::everything()).heads()
); );
assert_eq!( assert_eq!(
optimize(parse("roots(branches() & all())").unwrap()), optimize(parse("roots(branches() & all())").unwrap()),
RevsetExpression::branches("".to_owned()).roots() RevsetExpression::branches(StringPattern::everything()).roots()
); );
assert_eq!( assert_eq!(
optimize(parse("latest(branches() & all(), 2)").unwrap()), optimize(parse("latest(branches() & all(), 2)").unwrap()),
RevsetExpression::branches("".to_owned()).latest(2) RevsetExpression::branches(StringPattern::everything()).latest(2)
); );
assert_eq!( assert_eq!(
@ -3008,25 +3045,28 @@ mod tests {
assert_eq!( assert_eq!(
optimize(parse("present(branches() & all())").unwrap()), optimize(parse("present(branches() & all())").unwrap()),
Rc::new(RevsetExpression::Present(RevsetExpression::branches( Rc::new(RevsetExpression::Present(RevsetExpression::branches(
"".to_owned() StringPattern::everything()
))) )))
); );
assert_eq!( assert_eq!(
optimize(parse("~branches() & all()").unwrap()), optimize(parse("~branches() & all()").unwrap()),
RevsetExpression::branches("".to_owned()).negated() RevsetExpression::branches(StringPattern::everything()).negated()
); );
assert_eq!( assert_eq!(
optimize(parse("(branches() & all()) | (all() & tags())").unwrap()), optimize(parse("(branches() & all()) | (all() & tags())").unwrap()),
RevsetExpression::branches("".to_owned()).union(&RevsetExpression::tags()) RevsetExpression::branches(StringPattern::everything())
.union(&RevsetExpression::tags())
); );
assert_eq!( assert_eq!(
optimize(parse("(branches() & all()) & (all() & tags())").unwrap()), optimize(parse("(branches() & all()) & (all() & tags())").unwrap()),
RevsetExpression::branches("".to_owned()).intersection(&RevsetExpression::tags()) RevsetExpression::branches(StringPattern::everything())
.intersection(&RevsetExpression::tags())
); );
assert_eq!( assert_eq!(
optimize(parse("(branches() & all()) ~ (all() & tags())").unwrap()), optimize(parse("(branches() & all()) ~ (all() & tags())").unwrap()),
RevsetExpression::branches("".to_owned()).minus(&RevsetExpression::tags()) RevsetExpression::branches(StringPattern::everything())
.minus(&RevsetExpression::tags())
); );
} }
@ -3059,7 +3099,7 @@ mod tests {
let optimized = optimize(parsed.clone()); let optimized = optimize(parsed.clone());
assert_eq!( assert_eq!(
unwrap_union(&optimized).0.as_ref(), unwrap_union(&optimized).0.as_ref(),
&RevsetExpression::CommitRef(RevsetCommitRef::Branches("".to_owned())) &RevsetExpression::CommitRef(RevsetCommitRef::Branches(StringPattern::everything()))
); );
assert!(Rc::ptr_eq( assert!(Rc::ptr_eq(
unwrap_union(&parsed).1, unwrap_union(&parsed).1,
@ -3336,7 +3376,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
), ),
) )
@ -3355,7 +3397,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"bar", Substring(
"bar",
),
), ),
), ),
) )
@ -3374,7 +3418,9 @@ mod tests {
Union( Union(
Filter( Filter(
Author( Author(
"bar", Substring(
"bar",
),
), ),
), ),
CommitRef( CommitRef(
@ -3400,7 +3446,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
), ),
) )
@ -3412,7 +3460,9 @@ mod tests {
insta::assert_debug_snapshot!(optimize(parse("author(foo)").unwrap()), @r###" insta::assert_debug_snapshot!(optimize(parse("author(foo)").unwrap()), @r###"
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
) )
"###); "###);
@ -3426,7 +3476,9 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"bar", Substring(
"bar",
),
), ),
), ),
) )
@ -3440,7 +3492,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
), ),
) )
@ -3450,12 +3504,16 @@ mod tests {
Intersection( Intersection(
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
), ),
Filter( Filter(
Committer( Committer(
"bar", Substring(
"bar",
),
), ),
), ),
) )
@ -3472,13 +3530,17 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"bar", Substring(
"bar",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
) )
@ -3494,13 +3556,17 @@ mod tests {
), ),
Filter( Filter(
Committer( Committer(
"foo", Substring(
"foo",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
) )
@ -3516,7 +3582,9 @@ mod tests {
), ),
Filter( Filter(
Committer( Committer(
"foo", Substring(
"foo",
),
), ),
), ),
), ),
@ -3537,7 +3605,9 @@ mod tests {
Intersection( Intersection(
Filter( Filter(
Committer( Committer(
"foo", Substring(
"foo",
),
), ),
), ),
Filter( Filter(
@ -3552,7 +3622,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
) )
@ -3601,13 +3673,17 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"bar", Substring(
"bar",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
) )
@ -3625,7 +3701,9 @@ mod tests {
Ancestors { Ancestors {
heads: Filter( heads: Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
generation: 1..2, generation: 1..2,
@ -3640,7 +3718,9 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"bar", Substring(
"bar",
),
), ),
), ),
) )
@ -3663,7 +3743,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
), ),
@ -3673,7 +3755,9 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"bar", Substring(
"bar",
),
), ),
), ),
) )
@ -3706,19 +3790,25 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"A", Substring(
"A",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"B", Substring(
"B",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"C", Substring(
"C",
),
), ),
), ),
) )
@ -3757,19 +3847,25 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"A", Substring(
"A",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"B", Substring(
"B",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"C", Substring(
"C",
),
), ),
), ),
) )
@ -3788,13 +3884,17 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"bar", Substring(
"bar",
),
), ),
), ),
), ),
Filter( Filter(
Author( Author(
"baz", Substring(
"baz",
),
), ),
), ),
) )
@ -3815,7 +3915,9 @@ mod tests {
Union( Union(
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
), ),
CommitRef( CommitRef(
@ -3846,7 +3948,9 @@ mod tests {
), ),
Filter( Filter(
Committer( Committer(
"bar", Substring(
"bar",
),
), ),
), ),
), ),
@ -3854,7 +3958,9 @@ mod tests {
), ),
Filter( Filter(
Description( Description(
"baz", Substring(
"baz",
),
), ),
), ),
) )
@ -3882,7 +3988,9 @@ mod tests {
), ),
Filter( Filter(
Author( Author(
"foo", Substring(
"foo",
),
), ),
), ),
), ),
@ -3931,7 +4039,9 @@ mod tests {
Union( Union(
Filter( Filter(
Author( Author(
"A", Substring(
"A",
),
), ),
), ),
CommitRef( CommitRef(
@ -3946,7 +4056,9 @@ mod tests {
Union( Union(
Filter( Filter(
Author( Author(
"B", Substring(
"B",
),
), ),
), ),
CommitRef( CommitRef(
@ -3961,7 +4073,9 @@ mod tests {
Union( Union(
Filter( Filter(
Author( Author(
"C", Substring(
"C",
),
), ),
), ),
CommitRef( CommitRef(