forked from mirrors/jj
revset: add empty() predicate to find commits with no file change
The expression 'x ~ empty()' is identical to 'x & file(".")', but more intuitive. Note that 'x ~ empty()' is slower than 'x & file(".")' since the negative intersection isn't optimized right now. I think that can be handled as follows: 'x ~ filter(f)' -> 'x & filter(!f)' -> 'filter(!f, x)'
This commit is contained in:
parent
230ac043ff
commit
a81ebeb85e
4 changed files with 34 additions and 1 deletions
|
@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
* The new revset function `file(pattern..)` finds commits modifying the
|
||||
paths specified by the `pattern..`.
|
||||
|
||||
* The new revset function `empty()` finds commits modifying no files.
|
||||
|
||||
* It is now possible to specify configuration options on the command line
|
||||
with the new `--config-toml` global option.
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@ revsets (expressions) as arguments.
|
|||
email.
|
||||
* `committer(needle)`: Commits with the given string in the committer's
|
||||
name or email.
|
||||
* `empty()`: Commits modifying no files. This also includes `merges()` without
|
||||
user modifications and `root`.
|
||||
* `file(pattern..)`: Commits modifying the paths specified by the `pattern..`.
|
||||
* `present(x)`: Same as `x`, but evaluated to `none()` if any of the commits
|
||||
in `x` doesn't exist (e.g. is an unknown branch name.)
|
||||
|
|
|
@ -31,7 +31,7 @@ use thiserror::Error;
|
|||
use crate::backend::{BackendError, BackendResult, CommitId};
|
||||
use crate::commit::Commit;
|
||||
use crate::index::{HexPrefix, IndexEntry, IndexPosition, PrefixResolution, RevWalk};
|
||||
use crate::matchers::{Matcher, PrefixMatcher};
|
||||
use crate::matchers::{EverythingMatcher, Matcher, PrefixMatcher};
|
||||
use crate::op_store::WorkspaceId;
|
||||
use crate::repo::RepoRef;
|
||||
use crate::repo_path::{FsPathParseError, RepoPath};
|
||||
|
@ -280,6 +280,8 @@ pub enum RevsetFilterPredicate {
|
|||
Author(String),
|
||||
/// Commits with committer's name or email containing the needle.
|
||||
Committer(String),
|
||||
/// Commits modifying no files. Equivalent to `Not(File(["."]))`.
|
||||
Empty,
|
||||
/// Commits modifying the paths specified by the pattern.
|
||||
File(Vec<RepoPath>),
|
||||
}
|
||||
|
@ -727,6 +729,10 @@ fn parse_function_expression(
|
|||
needle,
|
||||
)))
|
||||
}
|
||||
"empty" => {
|
||||
expect_no_arguments(name, arguments_pair)?;
|
||||
Ok(RevsetExpression::filter(RevsetFilterPredicate::Empty))
|
||||
}
|
||||
"file" => {
|
||||
if let Some(ctx) = workspace_ctx {
|
||||
let arguments_span = arguments_pair.as_span();
|
||||
|
@ -1589,6 +1595,12 @@ pub fn evaluate_expression<'repo>(
|
|||
}),
|
||||
}))
|
||||
}
|
||||
RevsetFilterPredicate::Empty => Ok(Box::new(FilterRevset {
|
||||
candidates,
|
||||
predicate: Box::new(move |entry| {
|
||||
!has_diff_from_parent(repo, entry, &EverythingMatcher)
|
||||
}),
|
||||
})),
|
||||
RevsetFilterPredicate::File(paths) => {
|
||||
// TODO: Add support for globs and other formats
|
||||
let matcher: Box<dyn Matcher> = Box::new(PrefixMatcher::new(paths));
|
||||
|
@ -1911,6 +1923,11 @@ mod tests {
|
|||
RevsetFilterPredicate::Description("(foo)".to_string())
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("empty()"),
|
||||
Ok(RevsetExpression::filter(RevsetFilterPredicate::Empty))
|
||||
);
|
||||
assert!(parse("empty(foo)").is_err());
|
||||
assert!(parse("file()").is_err());
|
||||
assert_eq!(
|
||||
parse("file(foo)"),
|
||||
|
|
|
@ -1816,6 +1816,9 @@ fn test_filter_by_diff(use_git: bool) {
|
|||
let commit3 =
|
||||
CommitBuilder::for_new_commit(&settings, vec![commit2.id().clone()], tree3.id().clone())
|
||||
.write_to_repo(mut_repo);
|
||||
let commit4 =
|
||||
CommitBuilder::for_new_commit(&settings, vec![commit3.id().clone()], tree3.id().clone())
|
||||
.write_to_repo(mut_repo);
|
||||
|
||||
// matcher API:
|
||||
let resolve = |file_path: &RepoPath| -> Vec<CommitId> {
|
||||
|
@ -1862,4 +1865,13 @@ fn test_filter_by_diff(use_git: bool) {
|
|||
),
|
||||
vec![commit2.id().clone()]
|
||||
);
|
||||
|
||||
// empty() revset, which is identical to ~file(".")
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("{}: & empty()", commit1.id().hex())
|
||||
),
|
||||
vec![commit4.id().clone()]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue