revset: extend file() predicate to accept more than one paths

'file(a, b)' could be expressed as 'file(a) | file(b)', but the former is
easier to type and can be evaluated efficiently without optimization step.
This commit is contained in:
Yuya Nishihara 2022-11-01 17:11:22 +09:00
parent fba6741c23
commit 62511f7cad
3 changed files with 49 additions and 25 deletions

View file

@ -20,8 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj git push` will search `@-` for branches to push if `@` has none. * `jj git push` will search `@-` for branches to push if `@` has none.
* The new revset function `file(pattern)` finds commits modifying the * The new revset function `file(pattern..)` finds commits modifying the
paths specified by the `pattern`. paths specified by the `pattern..`.
### Fixed bugs ### Fixed bugs

View file

@ -108,7 +108,7 @@ revsets (expressions) as arguments.
email. email.
* `committer(needle)`: Commits with the given string in the committer's * `committer(needle)`: Commits with the given string in the committer's
name or email. name or email.
* `file(pattern)`: Commits modifying the paths specified by the `pattern`. * `file(pattern..)`: Commits modifying the paths specified by the `pattern..`.
## Examples ## Examples

View file

@ -211,7 +211,7 @@ pub enum RevsetFilterPredicate {
Description(String), Description(String),
Author(String), // Matches against both name and email Author(String), // Matches against both name and email
Committer(String), // Matches against both name and email Committer(String), // Matches against both name and email
File(RepoPath), File(Vec<RepoPath>),
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -395,10 +395,10 @@ impl RevsetExpression {
} }
/// Commits in `self` modifying the paths specified by the `pattern`. /// Commits in `self` modifying the paths specified by the `pattern`.
pub fn with_file(self: &Rc<RevsetExpression>, pattern: RepoPath) -> Rc<RevsetExpression> { pub fn with_file(self: &Rc<RevsetExpression>, paths: Vec<RepoPath>) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::Filter { Rc::new(RevsetExpression::Filter {
candidates: self.clone(), candidates: self.clone(),
predicate: RevsetFilterPredicate::File(pattern), predicate: RevsetFilterPredicate::File(paths),
}) })
} }
@ -808,19 +808,21 @@ fn parse_function_expression(
} }
} }
"file" => { "file" => {
if arg_count != 1 { if arg_count < 1 {
return Err(RevsetParseError::InvalidFunctionArguments { return Err(RevsetParseError::InvalidFunctionArguments {
name, name,
message: "Expected 1 argument".to_string(), message: "Expected at least 1 argument".to_string(),
}); });
} }
if let Some(ctx) = workspace_ctx { if let Some(ctx) = workspace_ctx {
let needle = parse_function_argument_to_string( let paths = argument_pairs
&name, .map(|arg| {
argument_pairs.next().unwrap().into_inner(), let needle = parse_function_argument_to_string(&name, arg.into_inner())?;
)?;
let path = RepoPath::parse_fs_path(ctx.cwd, ctx.workspace_root, &needle)?; let path = RepoPath::parse_fs_path(ctx.cwd, ctx.workspace_root, &needle)?;
Ok(RevsetExpression::all().with_file(path)) Ok(path)
})
.collect::<Result<Vec<_>, RevsetParseError>>()?;
Ok(RevsetExpression::all().with_file(paths))
} else { } else {
Err(RevsetParseError::FsPathWithoutWorkspace) Err(RevsetParseError::FsPathWithoutWorkspace)
} }
@ -1581,10 +1583,9 @@ pub fn evaluate_expression<'repo>(
}), }),
})) }))
} }
RevsetFilterPredicate::File(pattern) => { RevsetFilterPredicate::File(paths) => {
// TODO: Add support for globs and other formats // TODO: Add support for globs and other formats
let matcher: Box<dyn Matcher> = let matcher: Box<dyn Matcher> = Box::new(PrefixMatcher::new(paths));
Box::new(PrefixMatcher::new(std::slice::from_ref(pattern)));
Ok(filter_by_diff(repo, matcher, candidates)) Ok(filter_by_diff(repo, matcher, candidates))
} }
} }
@ -1752,10 +1753,12 @@ mod tests {
}) })
); );
assert_eq!( assert_eq!(
foo_symbol.with_file(RepoPath::from_internal_string("pattern")), foo_symbol.with_file(vec![RepoPath::from_internal_string("pattern")]),
Rc::new(RevsetExpression::Filter { Rc::new(RevsetExpression::Filter {
candidates: foo_symbol.clone(), candidates: foo_symbol.clone(),
predicate: RevsetFilterPredicate::File(RepoPath::from_internal_string("pattern")), predicate: RevsetFilterPredicate::File(vec![RepoPath::from_internal_string(
"pattern"
)]),
}) })
); );
assert_eq!( assert_eq!(
@ -1839,12 +1842,14 @@ mod tests {
// Incomplete parse // Incomplete parse
assert_matches!(parse("foo | -"), Err(RevsetParseError::SyntaxError(_))); assert_matches!(parse("foo | -"), Err(RevsetParseError::SyntaxError(_)));
// Space is allowed around infix operators and function arguments // Space is allowed around infix operators and function arguments
// TODO: test two_arg_function( arg1 , arg2 ) if any
assert_eq!( assert_eq!(
parse(" description( arg1 ) ~ parents( arg1 ) ~ heads( ) "), parse(" description( arg1 ) ~ file( arg1 , arg2 ) ~ heads( ) "),
Ok(RevsetExpression::all() Ok(RevsetExpression::all()
.with_description("arg1".to_string()) .with_description("arg1".to_string())
.minus(&RevsetExpression::symbol("arg1".to_string()).parents()) .minus(&RevsetExpression::all().with_file(vec![
RepoPath::from_internal_string("arg1"),
RepoPath::from_internal_string("arg2"),
]))
.minus(&RevsetExpression::visible_heads())) .minus(&RevsetExpression::visible_heads()))
); );
} }
@ -1918,6 +1923,19 @@ mod tests {
parse("description(\"(foo)\")"), parse("description(\"(foo)\")"),
Ok(RevsetExpression::all().with_description("(foo)".to_string())) Ok(RevsetExpression::all().with_description("(foo)".to_string()))
); );
assert!(parse("file()").is_err());
assert_eq!(
parse("file(foo)"),
Ok(RevsetExpression::all().with_file(vec![RepoPath::from_internal_string("foo")]))
);
assert_eq!(
parse("file(foo, bar, baz)"),
Ok(RevsetExpression::all().with_file(vec![
RepoPath::from_internal_string("foo"),
RepoPath::from_internal_string("bar"),
RepoPath::from_internal_string("baz"),
]))
);
} }
#[test] #[test]
@ -2114,7 +2132,9 @@ mod tests {
), ),
}, },
predicate: File( predicate: File(
[
"bar", "bar",
],
), ),
} }
"###); "###);
@ -2129,7 +2149,9 @@ mod tests {
), ),
}, },
predicate: File( predicate: File(
[
"bar", "bar",
],
), ),
}, },
predicate: Author( predicate: Author(
@ -2148,7 +2170,9 @@ mod tests {
), ),
), ),
predicate: File( predicate: File(
[
"bar", "bar",
],
), ),
} }
"###); "###);