mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-31 00:12:06 +00:00
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:
parent
fba6741c23
commit
62511f7cad
3 changed files with 49 additions and 25 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
Loading…
Reference in a new issue