Skip to content

Commit

Permalink
revset: add 0-ary "::" and ".." operators as short for "all()" and "~…
Browse files Browse the repository at this point in the history
…root()"

Suppose "x::y" is the operator that defaults to "root()::visible_heads()"
respectively, "::" is identical to "all()". Since we've just changed the
behavior of "..y", ".." is now "root()..visible_heads()" meaning "~root()".
  • Loading branch information
yuja committed Sep 5, 2023
1 parent 6b2ad23 commit b0c8e9e
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Revsets gained a new function `mine()` that aliases `author(exact:"your_email")`.

* Added support for `::` and `..` revset operators with both left and right
operands omitted. These expressions are equivalent to `all()` and `~root()`
respectively.

* `jj log` timestamp format now accepts `.utc()` to convert a timestamp to UTC.

* templates now support additional string methods `.starts_with(x)`, `.ends_with(x)`
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/test_revset_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn test_syntax_error() {
1 | x &
| ^---
|
= expected dag_range_pre_op, legacy_dag_range_pre_op, range_pre_op, negate_op, or primary
= expected dag_range_pre_op, dag_range_all_op, legacy_dag_range_pre_op, range_pre_op, range_all_op, negate_op, or primary
"###);

let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "x - y"]);
Expand Down Expand Up @@ -287,7 +287,7 @@ fn test_alias() {
1 | whatever &
| ^---
|
= expected dag_range_pre_op, legacy_dag_range_pre_op, range_pre_op, negate_op, or primary
= expected dag_range_pre_op, dag_range_all_op, legacy_dag_range_pre_op, range_pre_op, range_all_op, negate_op, or primary
"###);

let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "identity()"]);
Expand Down
3 changes: 3 additions & 0 deletions docs/revsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ only symbols.
to `x: & :y`. This is what `git log` calls `--ancestry-path x..y`.
* `::x`, `x::`, and `x::y`: New versions of for `:x`, `x:`, and `x:y` to be
released in jj 0.9.0. We plan to delete the latter in jj 0.15+.
* `::`: All visible commits in the repo. Equivalent to `all()`.
* `x..y`: Ancestors of `y` that are not also ancestors of `x`. Equivalent to
`:y ~ :x`. This is what `git log` calls `x..y` (i.e. the same as we call it).
* `..x`: Ancestors of `x`, including the commits in `x` itself, but excluding
the root commit. Equivalent to `:x ~ root()`.
* `x..`: Revisions that are not ancestors of `x`.
* `..`: All visible commits in the repo, but excluding the root commit.
Equivalent to `~root()`.

You can use parentheses to control evaluation order, such as `(x & y) | z` or
`x & (y | z)`.
Expand Down
4 changes: 4 additions & 0 deletions lib/src/revset.pest
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,19 @@ compat_parents_op = { "^" }
dag_range_op = { "::" }
dag_range_pre_op = { "::" }
dag_range_post_op = { "::" }
dag_range_all_op = { "::" }
// TODO: Drop support for these in 0.15+
legacy_dag_range_op = { ":" }
legacy_dag_range_pre_op = { ":" }
legacy_dag_range_post_op = { ":" }
range_op = { ".." }
range_pre_op = { ".." }
range_post_op = { ".." }
range_all_op = { ".." }
range_ops = _{ dag_range_op | legacy_dag_range_op | range_op }
range_pre_ops = _{ dag_range_pre_op | legacy_dag_range_pre_op | range_pre_op }
range_post_ops = _{ dag_range_post_op | legacy_dag_range_post_op | range_post_op }
range_all_ops = _{ dag_range_all_op | range_all_op }

negate_op = { "~" }
union_op = { "|" }
Expand Down Expand Up @@ -81,6 +84,7 @@ range_expression = _{
| neighbors_expression ~ range_post_ops
| range_pre_ops ~ neighbors_expression
| neighbors_expression
| range_all_ops
}

expression = {
Expand Down
22 changes: 21 additions & 1 deletion lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,14 @@ fn parse_expression_rule(
| Op::postfix(Rule::compat_parents_op))
});
PRATT
.map_primary(|primary| parse_primary_rule(primary, state))
.map_primary(|primary| match primary.as_rule() {
Rule::primary => parse_primary_rule(primary, state),
Rule::dag_range_all_op => Ok(RevsetExpression::all()),
Rule::range_all_op => {
Ok(RevsetExpression::root().range(&RevsetExpression::visible_heads()))
}
r => panic!("unexpected primary rule {r:?}"),
})
.map_prefix(|op, rhs| match op.as_rule() {
Rule::negate_op => Ok(rhs?.negated()),
Rule::dag_range_pre_op => Ok(rhs?.ancestors()),
Expand Down Expand Up @@ -2767,6 +2774,8 @@ mod tests {
assert_eq!(parse("foo::"), Ok(foo_symbol.descendants()));
// Parse the "dag range" operator
assert_eq!(parse("foo::bar"), Ok(foo_symbol.dag_range_to(&bar_symbol)));
// Parse the nullary "dag range" operator
assert_eq!(parse("::"), Ok(RevsetExpression::all()));
// Parse the "range" prefix operator
assert_eq!(
parse("..foo"),
Expand All @@ -2777,6 +2786,11 @@ mod tests {
Ok(foo_symbol.range(&RevsetExpression::visible_heads()))
);
assert_eq!(parse("foo..bar"), Ok(foo_symbol.range(&bar_symbol)));
// Parse the nullary "range" operator
assert_eq!(
parse(".."),
Ok(RevsetExpression::root().range(&RevsetExpression::visible_heads()))
);
// Parse the "negate" operator
assert_eq!(parse("~ foo"), Ok(foo_symbol.negated()));
assert_eq!(
Expand Down Expand Up @@ -2967,6 +2981,7 @@ mod tests {
assert_eq!(parse("x&y|z").unwrap(), parse("(x&y)|z").unwrap());
assert_eq!(parse("x|y&z").unwrap(), parse("x|(y&z)").unwrap());
assert_eq!(parse("x|y~z").unwrap(), parse("x|(y~z)").unwrap());
assert_eq!(parse("::&..").unwrap(), parse("(::)&(..)").unwrap());
// Parse repeated "ancestors"/"descendants"/"dag range"/"range" operators
assert_eq!(parse("::foo::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse(":::foo"), Err(RevsetParseErrorKind::SyntaxError));
Expand All @@ -2977,16 +2992,21 @@ mod tests {
assert_eq!(parse("foo::::bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::foo::bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo::bar::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("....foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo...."), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo.....bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("..foo..bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo..bar.."), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("...."), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::.."), Err(RevsetParseErrorKind::SyntaxError));
// Parse combinations of "parents"/"children" operators and the range operators.
// The former bind more strongly.
assert_eq!(parse("foo-+"), Ok(foo_symbol.parents().children()));
assert_eq!(parse("foo-::"), Ok(foo_symbol.parents().descendants()));
assert_eq!(parse("::foo+"), Ok(foo_symbol.children().ancestors()));
assert_eq!(parse("::-"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("..+"), Err(RevsetParseErrorKind::SyntaxError));
}

#[test]
Expand Down
29 changes: 28 additions & 1 deletion lib/tests/test_revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,16 @@ fn test_evaluate_expression_range(use_git: bool) {
resolve_commit_ids(mut_repo, &format!("{}..", commit2.id().hex())),
vec![commit4.id().clone(), commit3.id().clone()]
);

assert_eq!(
resolve_commit_ids(mut_repo, ".."),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
}

#[test_case(false ; "local backend")]
Expand Down Expand Up @@ -1281,7 +1291,11 @@ fn test_evaluate_expression_dag_range(use_git: bool) {
mut_repo,
&format!("{}:{}", root_commit_id.hex(), commit2.id().hex())
),
vec![commit2.id().clone(), commit1.id().clone(), root_commit_id]
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit_id.clone(),
]
);

// Empty range
Expand Down Expand Up @@ -1344,6 +1358,19 @@ fn test_evaluate_expression_dag_range(use_git: bool) {
commit2.id().clone(),
]
);

// Full range meaning all()
assert_eq!(
resolve_commit_ids(mut_repo, "::"),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit_id.clone(),
]
);
}

#[test_case(false ; "local backend")]
Expand Down

0 comments on commit b0c8e9e

Please sign in to comment.