revsets: allow :: as synonym for :

The `--allow-large-revsets` flag we have on `jj rebase` and `jj new`
allows the user to do e.g. `jj rebase --allow-large-revsets -b
main.. -d main` to rebase all commits that are not in main onto
main. The reason we don't allow these revsets to resolve to multiple
commits by default is that we think users might specify multiple
commits by mistake. That's probably not much of a problem with `jj
rebase -b` (maybe we should always allow that to resolve to multiple
commits), but the user might want to know if `jj rebase -d @-`
resolves to multiple commits.

One problem with having a flag to allow multiple commits is that it
needs to be added to every command where we want to allow multiple
commits but default to one. Also, it should probably apply to each
revset argument those commands take. For example, even if the user
meant `-b main..` to resolve to multiple commits, they might not have
meant `-d main` to resolve to multiple commits (which it will in case
of a conflicted branch), so we might want separate
`--allow-large-revsets-in-destination` and
`--allow-large-revsets-in-source`, which gets quite cumbersome. It
seems better to have some syntax in the individual revsets for saying
that multiple commits are allowed.

One proposal I had was to use a `multiple()` revset function which
would have no effect in general but would be used as a marker if used
at the top level (e.g. `jj rebase -d 'multiple(@-)'`). After some
discussion on the PR adding that function (#1911), it seems that the
consensus is to instead use a prefix like `many:` or `all:`. That
avoids the problem with having a function that has no effect unless
it's used at the top level (`jj rebase -d 'multiple(x)|y'` would have
no effect).

Since we already have the `:` operator for DAG ranges, we need to
change it to make room for `many:`/`all:` syntax. This commit starts
that by allowing both `:` and `::`.

I have tried to update the documentation in this commit to either
mention both forms, or just the new and preferred `::` form. However,
it's useless to search for `:` in Rust code, so I'm sure I've missed
many instances. We'll have to address those as we notice them. I'll
let most tests use `:` until we deprecate it or delete it.
This commit is contained in:
Martin von Zweigbergk 2023-07-27 16:27:44 -07:00 committed by Martin von Zweigbergk
parent a22255bd51
commit 48580ed8b1
13 changed files with 195 additions and 90 deletions

View file

@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj log` output is now topologically grouped.
[#242](https://github.com/martinvonz/jj/issues/242)
* The `:` revset operator can now be written as `::` instead. We plan to
delete the `:` form in jj 0.15+.
### Fixed bugs
## [0.8.0] - 2023-07-09

View file

@ -196,7 +196,7 @@ To get shorter prefixes for certain revisions, set `revsets.short-prefixes`:
```toml
# Prioritize the current branch
revsets.short-prefixes = "(main..@):"
revsets.short-prefixes = "(main..@)::"
```
### Relative timestamps
@ -259,7 +259,7 @@ You can define aliases for commands, including their arguments. For example:
```toml
# `jj l` shows commits on the working-copy commit's (anonymous) branch
# compared to the `main` branch
aliases.l = ["log", "-r", "(main..@): | (main..@)-"]
aliases.l = ["log", "-r", "(main..@):: | (main..@)-"]
```
## Editor

View file

@ -148,7 +148,7 @@ remote
Log all remote branches, which you authored or committed to
`jj log -r 'remote_branches() & (committer(your@email.com) | author(your@email.com))'`
Log all descendants of the current working copy, which aren't on a remote
`jj log -r ':@ & ~remote_branches()'`
`jj log -r '::@ & ~remote_branches()'`
## Merge conflicts

View file

@ -58,14 +58,16 @@ only symbols.
* `~x`: Revisions that are not in `x`.
* `x-`: Parents of `x`.
* `x+`: Children of `x`.
* `:x`: Ancestors of `x`, including the commits in `x` itself.
* `x:`: Descendants of `x`, including the commits in `x` itself.
* `x:y`: Descendants of `x` that are also ancestors of `y`. Equivalent to
`x: & :y`. This is what `git log` calls `--ancestry-path x..y`.
* `::x`: Ancestors of `x`, including the commits in `x` itself.
* `x::`: Descendants of `x`, including the commits in `x` itself.
* `x::y`: Descendants of `x` that are also ancestors of `y`. Equivalent
to `x:: & ::y`. This is what `git log` calls `--ancestry-path x..y`.
* `:x`, `x:`, and `x:y`: Deprecated synonyms for `::x`, `x::`, and `x::y`. We
plan to delete them in jj 0.15+.
* `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).
`::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. Equivalent to
`:x` and provided for consistency.
`::x` and provided for consistency.
* `x..`: Revisions that are not ancestors of `x`.
You can use parentheses to control evaluation order, such as `(x & y) | z` or
@ -78,9 +80,9 @@ revsets (expressions) as arguments.
* `parents(x)`: Same as `x-`.
* `children(x)`: Same as `x+`.
* `ancestors(x)`: Same as `:x`.
* `descendants(x)`: Same as `x:`.
* `connected(x)`: Same as `x:x`. Useful when `x` includes several commits.
* `ancestors(x)`: Same as `::x`.
* `descendants(x)`: Same as `x::`.
* `connected(x)`: Same as `x::x`. Useful when `x` includes several commits.
* `all()`: All visible commits in the repo.
* `none()`: No commits. This function is rarely useful; it is provided for
completeness.
@ -169,7 +171,7 @@ jj log -r 'remote_branches(remote=origin)..'
Show all ancestors of the working copy (almost like plain `git log`)
```
jj log -r :@
jj log -r ::@
```
Show the initial commits in the repo (the ones Git calls "root commits"):
@ -187,8 +189,9 @@ jj log -r 'tags() | branches()'
Show local commits leading up to the working copy, as well as descendants of
those commits:
```
jj log -r '(remote_branches()..@):'
jj log -r '(remote_branches()..@)::'
```
Show commits authored by "martinvonz" and containing the word "reset" in the

View file

@ -159,7 +159,7 @@ called the "root commit". It's the root commit of every repo. The `root` symbol
in the revset matches it.
There are also operators for getting the parents (`foo-`), children (`foo+`),
ancestors (`:foo`), descendants (`foo:`), DAG range (`foo:bar`, like
ancestors (`::foo`), descendants (`foo::`), DAG range (`foo::bar`, like
`git log --ancestry-path`), range (`foo..bar`, same as Git's). There are also a
few more functions, such as `heads(<set>)`, which filters out revisions in the
input set if they're ancestors of other revisions in the set.
@ -345,7 +345,7 @@ $ jj new -m ABC; printf 'A\nB\nc\n' > file
Working copy now at: 6f30cd1fb351 ABC
$ jj new -m ABCD; printf 'A\nB\nC\nD\n' > file
Working copy now at: a67491542e10 ABCD
$ jj log -r master:@
$ jj log -r master::@
@ mrxqplykzpkw martinvonz@google.com 2023-02-12 19:38:21.000 -08:00 b98c607bf87f
│ ABCD
◉ kwtuwqnmqyqp martinvonz@google.com 2023-02-12 19:38:12.000 -08:00 30aecc0871ea

View file

@ -2951,7 +2951,7 @@ mod tests {
);
// Merge range with sub-range (1..4 + 2..3 should be 1..4, not 1..3):
// 8,7,6->5:1..4, B5_1->5:2..3
// 8,7,6->5::1..4, B5_1->5::2..3
assert_eq!(
walk_commit_ids(
&[&ids[8], &ids[7], &ids[6], &id_branch5_1].map(Clone::clone),

View file

@ -712,7 +712,7 @@ impl<'index> EvaluationContext<'index> {
}
}
/// Calculates `root_set:head_set`.
/// Calculates `root_set::head_set`.
fn collect_dag_range<'a, 'b, S, T>(
&self,
root_set: &S,

View file

@ -28,15 +28,19 @@ parents_op = { "-" }
children_op = { "+" }
compat_parents_op = { "^" }
dag_range_op = { ":" }
dag_range_pre_op = { ":" }
dag_range_post_op = { ":" }
dag_range_op = { "::" }
dag_range_pre_op = { "::" }
dag_range_post_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_ops = _{ dag_range_op | range_op }
range_pre_ops = _{ dag_range_pre_op | range_pre_op }
range_post_ops = _{ dag_range_post_op | range_post_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 }
negate_op = { "~" }
union_op = { "|" }

View file

@ -246,10 +246,12 @@ pub enum RevsetExpression {
Ancestors {
heads: Rc<RevsetExpression>,
generation: Range<u64>,
is_legacy: bool,
},
Descendants {
roots: Rc<RevsetExpression>,
generation: Range<u64>,
is_legacy: bool,
},
// Commits that are ancestors of "heads" but not ancestors of "roots"
Range {
@ -261,6 +263,7 @@ pub enum RevsetExpression {
DagRange {
roots: Rc<RevsetExpression>,
heads: Rc<RevsetExpression>,
is_legacy: bool,
// TODO: maybe add generation_from_roots/heads?
},
Heads(Rc<RevsetExpression>),
@ -357,6 +360,7 @@ impl RevsetExpression {
Rc::new(RevsetExpression::Ancestors {
heads: self.clone(),
generation: 1..2,
is_legacy: false,
})
}
@ -365,6 +369,14 @@ impl RevsetExpression {
Rc::new(RevsetExpression::Ancestors {
heads: self.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: false,
})
}
fn legacy_ancestors(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::Ancestors {
heads: self.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: true,
})
}
@ -373,6 +385,7 @@ impl RevsetExpression {
Rc::new(RevsetExpression::Descendants {
roots: self.clone(),
generation: 1..2,
is_legacy: false,
})
}
@ -381,6 +394,14 @@ impl RevsetExpression {
Rc::new(RevsetExpression::Descendants {
roots: self.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: false,
})
}
fn legacy_descendants(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::Descendants {
roots: self.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: true,
})
}
@ -393,6 +414,17 @@ impl RevsetExpression {
Rc::new(RevsetExpression::DagRange {
roots: self.clone(),
heads: heads.clone(),
is_legacy: false,
})
}
pub fn legacy_dag_range_to(
self: &Rc<RevsetExpression>,
heads: &Rc<RevsetExpression>,
) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::DagRange {
roots: self.clone(),
heads: heads.clone(),
is_legacy: true,
})
}
@ -737,9 +769,15 @@ fn parse_expression_rule(
| Op::infix(Rule::compat_sub_op, Assoc::Left))
.op(Op::prefix(Rule::negate_op))
// Ranges can't be nested without parentheses. Associativity doesn't matter.
.op(Op::infix(Rule::dag_range_op, Assoc::Left) | Op::infix(Rule::range_op, Assoc::Left))
.op(Op::prefix(Rule::dag_range_pre_op) | Op::prefix(Rule::range_pre_op))
.op(Op::postfix(Rule::dag_range_post_op) | Op::postfix(Rule::range_post_op))
.op(Op::infix(Rule::dag_range_op, Assoc::Left)
| Op::infix(Rule::legacy_dag_range_op, Assoc::Left)
| Op::infix(Rule::range_op, Assoc::Left))
.op(Op::prefix(Rule::dag_range_pre_op)
| Op::prefix(Rule::legacy_dag_range_pre_op)
| Op::prefix(Rule::range_pre_op))
.op(Op::postfix(Rule::dag_range_post_op)
| Op::postfix(Rule::legacy_dag_range_post_op)
| Op::postfix(Rule::range_post_op))
// Neighbors
.op(Op::postfix(Rule::parents_op)
| Op::postfix(Rule::children_op)
@ -750,10 +788,12 @@ fn parse_expression_rule(
.map_prefix(|op, rhs| match op.as_rule() {
Rule::negate_op => Ok(rhs?.negated()),
Rule::dag_range_pre_op | Rule::range_pre_op => Ok(rhs?.ancestors()),
Rule::legacy_dag_range_pre_op => Ok(rhs?.legacy_ancestors()),
r => panic!("unexpected prefix operator rule {r:?}"),
})
.map_postfix(|lhs, op| match op.as_rule() {
Rule::dag_range_post_op => Ok(lhs?.descendants()),
Rule::legacy_dag_range_post_op => Ok(lhs?.legacy_descendants()),
Rule::range_post_op => Ok(lhs?.range(&RevsetExpression::visible_heads())),
Rule::parents_op => Ok(lhs?.parents()),
Rule::children_op => Ok(lhs?.children()),
@ -767,6 +807,7 @@ fn parse_expression_rule(
Rule::difference_op => Ok(lhs?.minus(&rhs?)),
Rule::compat_sub_op => Err(not_infix_op(&op, "~", "difference")),
Rule::dag_range_op => Ok(lhs?.dag_range_to(&rhs?)),
Rule::legacy_dag_range_op => Ok(lhs?.legacy_dag_range_to(&rhs?)),
Rule::range_op => Ok(lhs?.range(&rhs?)),
r => panic!("unexpected infix operator rule {r:?}"),
})
@ -1276,15 +1317,19 @@ fn try_transform_expression<E>(
RevsetExpression::All => None,
RevsetExpression::Commits(_) => None,
RevsetExpression::CommitRef(_) => None,
RevsetExpression::Ancestors { heads, generation } => transform_rec(heads, pre, post)?
.map(|heads| RevsetExpression::Ancestors {
RevsetExpression::Ancestors {
heads, generation, ..
} => transform_rec(heads, pre, post)?.map(|heads| RevsetExpression::Ancestors {
heads,
generation: generation.clone(),
is_legacy: false,
}),
RevsetExpression::Descendants { roots, generation } => transform_rec(roots, pre, post)?
.map(|roots| RevsetExpression::Descendants {
RevsetExpression::Descendants {
roots, generation, ..
} => transform_rec(roots, pre, post)?.map(|roots| RevsetExpression::Descendants {
roots,
generation: generation.clone(),
is_legacy: false,
}),
RevsetExpression::Range {
roots,
@ -1297,9 +1342,14 @@ fn try_transform_expression<E>(
generation: generation.clone(),
}
}),
RevsetExpression::DagRange { roots, heads } => {
transform_rec_pair((roots, heads), pre, post)?
.map(|(roots, heads)| RevsetExpression::DagRange { roots, heads })
RevsetExpression::DagRange { roots, heads, .. } => {
transform_rec_pair((roots, heads), pre, post)?.map(|(roots, heads)| {
RevsetExpression::DagRange {
roots,
heads,
is_legacy: false,
}
})
}
RevsetExpression::Heads(candidates) => {
transform_rec(candidates, pre, post)?.map(RevsetExpression::Heads)
@ -1493,10 +1543,13 @@ fn fold_difference(expression: &Rc<RevsetExpression>) -> TransformedExpression {
match (expression.as_ref(), complement.as_ref()) {
// :heads & ~(:roots) -> roots..heads
(
RevsetExpression::Ancestors { heads, generation },
RevsetExpression::Ancestors {
heads, generation, ..
},
RevsetExpression::Ancestors {
heads: roots,
generation: GENERATION_RANGE_FULL,
..
},
) => Rc::new(RevsetExpression::Range {
roots: roots.clone(),
@ -1540,6 +1593,7 @@ fn unfold_difference(expression: &Rc<RevsetExpression>) -> TransformedExpression
let heads_ancestors = Rc::new(RevsetExpression::Ancestors {
heads: heads.clone(),
generation: generation.clone(),
is_legacy: false,
});
Some(heads_ancestors.intersection(&roots.ancestors().negated()))
}
@ -1568,6 +1622,7 @@ fn fold_generation(expression: &Rc<RevsetExpression>) -> TransformedExpression {
RevsetExpression::Ancestors {
heads,
generation: generation1,
..
} => {
match heads.as_ref() {
// (h-)- -> ancestors(ancestors(h, 1), 1) -> ancestors(h, 2)
@ -1576,9 +1631,11 @@ fn fold_generation(expression: &Rc<RevsetExpression>) -> TransformedExpression {
RevsetExpression::Ancestors {
heads,
generation: generation2,
..
} => Some(Rc::new(RevsetExpression::Ancestors {
heads: heads.clone(),
generation: add_generation(generation1, generation2),
is_legacy: false,
})),
_ => None,
}
@ -1586,6 +1643,7 @@ fn fold_generation(expression: &Rc<RevsetExpression>) -> TransformedExpression {
RevsetExpression::Descendants {
roots,
generation: generation1,
..
} => {
match roots.as_ref() {
// (r+)+ -> descendants(descendants(r, 1), 1) -> descendants(r, 2)
@ -1594,9 +1652,11 @@ fn fold_generation(expression: &Rc<RevsetExpression>) -> TransformedExpression {
RevsetExpression::Descendants {
roots,
generation: generation2,
..
} => Some(Rc::new(RevsetExpression::Descendants {
roots: roots.clone(),
generation: add_generation(generation1, generation2),
is_legacy: false,
})),
_ => None,
}
@ -1986,11 +2046,15 @@ impl VisibilityResolutionContext<'_> {
RevsetExpression::CommitRef(_) => {
panic!("Expression '{expression:?}' should have been resolved by caller");
}
RevsetExpression::Ancestors { heads, generation } => ResolvedExpression::Ancestors {
RevsetExpression::Ancestors {
heads, generation, ..
} => ResolvedExpression::Ancestors {
heads: self.resolve(heads).into(),
generation: generation.clone(),
},
RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
RevsetExpression::Descendants {
roots, generation, ..
} => ResolvedExpression::DagRange {
roots: self.resolve(roots).into(),
heads: self.resolve_visible_heads().into(),
generation_from_roots: generation.clone(),
@ -2004,7 +2068,7 @@ impl VisibilityResolutionContext<'_> {
heads: self.resolve(heads).into(),
generation: generation.clone(),
},
RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
RevsetExpression::DagRange { roots, heads, .. } => ResolvedExpression::DagRange {
roots: self.resolve(roots).into(),
heads: self.resolve(heads).into(),
generation_from_roots: GENERATION_RANGE_FULL,
@ -2266,6 +2330,7 @@ mod tests {
Rc::new(RevsetExpression::Ancestors {
heads: wc_symbol.clone(),
generation: 1..2,
is_legacy: false,
})
);
assert_eq!(
@ -2273,13 +2338,15 @@ mod tests {
Rc::new(RevsetExpression::Ancestors {
heads: wc_symbol.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: false,
})
);
assert_eq!(
foo_symbol.children(),
Rc::new(RevsetExpression::Descendants {
roots: foo_symbol.clone(),
generation: 1..2
generation: 1..2,
is_legacy: false,
}),
);
assert_eq!(
@ -2287,6 +2354,7 @@ mod tests {
Rc::new(RevsetExpression::Descendants {
roots: foo_symbol.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: false,
})
);
assert_eq!(
@ -2294,6 +2362,7 @@ mod tests {
Rc::new(RevsetExpression::DagRange {
roots: foo_symbol.clone(),
heads: wc_symbol.clone(),
is_legacy: false,
})
);
assert_eq!(
@ -2301,6 +2370,7 @@ mod tests {
Rc::new(RevsetExpression::DagRange {
roots: foo_symbol.clone(),
heads: foo_symbol.clone(),
is_legacy: false,
})
);
assert_eq!(
@ -2420,11 +2490,11 @@ mod tests {
// Parse the "children" operator
assert_eq!(parse("@+"), Ok(wc_symbol.children()));
// Parse the "ancestors" operator
assert_eq!(parse(":@"), Ok(wc_symbol.ancestors()));
assert_eq!(parse("::@"), Ok(wc_symbol.ancestors()));
// Parse the "descendants" operator
assert_eq!(parse("@:"), Ok(wc_symbol.descendants()));
assert_eq!(parse("@::"), Ok(wc_symbol.descendants()));
// Parse the "dag range" operator
assert_eq!(parse("foo:bar"), Ok(foo_symbol.dag_range_to(&bar_symbol)));
assert_eq!(parse("foo::bar"), Ok(foo_symbol.dag_range_to(&bar_symbol)));
// Parse the "range" prefix operator
assert_eq!(parse("..@"), Ok(wc_symbol.ancestors()));
assert_eq!(
@ -2447,10 +2517,10 @@ mod tests {
// Parentheses are allowed before suffix operators
assert_eq!(parse("(@)-"), Ok(wc_symbol.parents()));
// Space is allowed around expressions
assert_eq!(parse(" :@ "), Ok(wc_symbol.ancestors()));
assert_eq!(parse("( :@ )"), Ok(wc_symbol.ancestors()));
assert_eq!(parse(" ::@ "), Ok(wc_symbol.ancestors()));
assert_eq!(parse("( ::@ )"), Ok(wc_symbol.ancestors()));
// Space is not allowed around prefix operators
assert_eq!(parse(" : @ "), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse(" :: @ "), Err(RevsetParseErrorKind::SyntaxError));
// Incomplete parse
assert_eq!(parse("foo | -"), Err(RevsetParseErrorKind::SyntaxError));
// Space is allowed around infix operators and function arguments
@ -2565,12 +2635,15 @@ 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());
// Parse repeated "ancestors"/"descendants"/"dag range"/"range" operators
assert_eq!(parse(":foo:"), 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("::foo::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse(":::foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::::foo"), 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("foo::bar::"), 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));
@ -2579,8 +2652,8 @@ mod tests {
// 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("foo-::"), Ok(foo_symbol.parents().descendants()));
assert_eq!(parse("::foo+"), Ok(foo_symbol.children().ancestors()));
}
#[test]
@ -3531,6 +3604,7 @@ mod tests {
),
),
generation: 1..2,
is_legacy: false,
},
),
CommitRef(
@ -3569,6 +3643,7 @@ mod tests {
),
),
generation: 1..2,
is_legacy: false,
},
),
Filter(
@ -3886,6 +3961,7 @@ mod tests {
),
),
generation: 2..3,
is_legacy: false,
}
"###);
insta::assert_debug_snapshot!(optimize(parse(":(foo---)").unwrap()), @r###"
@ -3896,6 +3972,7 @@ mod tests {
),
),
generation: 3..18446744073709551615,
is_legacy: false,
}
"###);
insta::assert_debug_snapshot!(optimize(parse("(:foo)---").unwrap()), @r###"
@ -3906,6 +3983,7 @@ mod tests {
),
),
generation: 3..18446744073709551615,
is_legacy: false,
}
"###);
@ -3919,8 +3997,10 @@ mod tests {
),
),
generation: 3..4,
is_legacy: false,
},
generation: 1..2,
is_legacy: false,
}
"###);
@ -3951,6 +4031,7 @@ mod tests {
),
),
generation: 3..18446744073709551615,
is_legacy: false,
},
Ancestors {
heads: CommitRef(
@ -3959,6 +4040,7 @@ mod tests {
),
),
generation: 2..18446744073709551615,
is_legacy: false,
},
)
"###);
@ -3981,6 +4063,7 @@ mod tests {
generation: 0..18446744073709551615,
},
generation: 2..3,
is_legacy: false,
}
"###);
insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)").unwrap()), @r###"
@ -4014,6 +4097,7 @@ mod tests {
Rc::new(RevsetExpression::Ancestors {
heads,
generation: GENERATION_RANGE_EMPTY,
is_legacy: false,
})
};
insta::assert_debug_snapshot!(
@ -4028,6 +4112,7 @@ mod tests {
),
),
generation: 0..0,
is_legacy: false,
}
"###
);
@ -4043,6 +4128,7 @@ mod tests {
),
),
generation: 0..0,
is_legacy: false,
}
"###
);
@ -4059,6 +4145,7 @@ mod tests {
),
),
generation: 2..3,
is_legacy: false,
}
"###);
insta::assert_debug_snapshot!(optimize(parse("(foo+++):").unwrap()), @r###"
@ -4069,6 +4156,7 @@ mod tests {
),
),
generation: 3..18446744073709551615,
is_legacy: false,
}
"###);
insta::assert_debug_snapshot!(optimize(parse("(foo:)+++").unwrap()), @r###"
@ -4079,6 +4167,7 @@ mod tests {
),
),
generation: 3..18446744073709551615,
is_legacy: false,
}
"###);
@ -4092,8 +4181,10 @@ mod tests {
),
),
generation: 3..4,
is_legacy: false,
},
generation: 1..2,
is_legacy: false,
}
"###);
@ -4109,12 +4200,14 @@ mod tests {
),
),
generation: 2..3,
is_legacy: false,
},
heads: CommitRef(
Symbol(
"bar",
),
),
is_legacy: false,
}
"###);
}

View file

@ -678,7 +678,7 @@ mod tests {
A
"###);
// A:F is picked at A, and A will be unblocked. Then, C:D at C, ...
// A::F is picked at A, and A will be unblocked. Then, C::D at C, ...
insta::assert_snapshot!(format_graph(topo_grouped(graph.iter().cloned())), @r###"
G direct(A)
@ -730,7 +730,7 @@ mod tests {
A
"###);
// A:K is picked at A, and A will be unblocked. Then, H:I at H, ...
// A::K is picked at A, and A will be unblocked. Then, H::I at H, ...
insta::assert_snapshot!(format_graph(topo_grouped(graph.iter().cloned())), @r###"
L direct(A)
@ -793,7 +793,7 @@ mod tests {
A
"###);
// A:K is picked at A, and A will be unblocked. Then, E:G at E, ...
// A::K is picked at A, and A will be unblocked. Then, E::G at E, ...
insta::assert_snapshot!(format_graph(topo_grouped(graph.iter().cloned())), @r###"
L direct(A)
@ -855,7 +855,8 @@ mod tests {
~
"###);
// K-E,J is resolved without queuing new heads. Then, G:H, F:I, B:C, and A:D.
// K-E,J is resolved without queuing new heads. Then, G::H, F::I, B::C, and
// A::D.
insta::assert_snapshot!(format_graph(topo_grouped(graph.iter().cloned())), @r###"
K direct(E), direct(J)
@ -919,7 +920,8 @@ mod tests {
~
"###);
// K-I,J is resolved without queuing new heads. Then, D:F, B:H, C:E, and A:G.
// K-I,J is resolved without queuing new heads. Then, D::F, B::H, C::E, and
// A::G.
insta::assert_snapshot!(format_graph(topo_grouped(graph.iter().cloned())), @r###"
K direct(I), direct(J)

View file

@ -782,7 +782,7 @@ struct SplitArgs {
/// * all descendants of those commits
///
/// In other words, `jj rebase -b X -d Y` rebases commits in the revset
/// `(Y..X):` (which is equivalent to `jj rebase -s 'roots(Y..X)' -d Y` for a
/// `(Y..X)::` (which is equivalent to `jj rebase -s 'roots(Y..X)' -d Y` for a
/// single root). For example, either `jj rebase -b L -d O` or `jj rebase -b M
/// -d O` would transform your history like this (because `L` and `M` are on the
/// same "branch", relative to the destination):

View file

@ -4,21 +4,21 @@
v1.0.0
v2.40.0
# Old history
:v1.0.0
::v1.0.0
..v1.0.0
# More history
:v2.40.0
::v2.40.0
..v2.40.0
# Only recent history
v2.39.0..v2.40.0
:v2.40.0 ~ :v2.39.0
v2.39.0:v2.40.0
::v2.40.0 ~ ::v2.39.0
v2.39.0::v2.40.0
# Tags and branches
tags()
branches()
# Intersection of range with a small subset
tags() & :v2.40.0
v2.39.0 & :v2.40.0
tags() & ::v2.40.0
v2.39.0 & ::v2.40.0
# Author and committer
author(peff)
committer(gitster)
@ -26,8 +26,8 @@ committer(gitster)
author(peff) & committer(gitster)
author(peff) | committer(gitster)
# Intersection of filter with a small subset
:v1.0.0 & (author(peff) & committer(gitster))
:v1.0.0 & (author(peff) | committer(gitster))
::v1.0.0 & (author(peff) & committer(gitster))
::v1.0.0 & (author(peff) | committer(gitster))
# Roots and heads of small subsets
roots(tags())
heads(tags())
@ -35,32 +35,32 @@ heads(tags())
roots(author(peff))
heads(author(peff))
# Roots and heads of range
roots(:v2.40.0)
heads(:v2.40.0)
roots(::v2.40.0)
heads(::v2.40.0)
# Parents and ancestors of old commit
v1.0.0-
v1.0.0---
:v1.0.0---
::v1.0.0---
# Children and descendants of old commit
v1.0.0+
v1.0.0+++
v1.0.0+++:
v1.0.0+++::
# Parents and ancestors of recent commit
v2.40.0-
v2.40.0---
:v2.40.0---
::v2.40.0---
# Children and descendants of recent commit
v2.40.0+
v2.40.0+++
v2.40.0+++:
v2.40.0+++::
# Parents and ancestors of small subset
tags()-
tags()---
:tags()---
::tags()---
# Children and descendants of small subset
tags()+
tags()+++
tags()+++:
tags()+++::
# Filter that doesn't read commit object
merges()
~merges()

View file

@ -29,7 +29,7 @@ fn test_syntax_error() {
1 | x &
| ^---
|
= expected dag_range_pre_op, range_pre_op, negate_op, or primary
= expected dag_range_pre_op, legacy_dag_range_pre_op, range_pre_op, negate_op, or primary
"###);
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "x - y"]);
@ -61,12 +61,12 @@ fn test_bad_function_call() {
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "all(or:nothing)"]);
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "all(or::nothing)"]);
insta::assert_snapshot!(stderr, @r###"
Error: Failed to parse revset: --> 1:5
|
1 | all(or:nothing)
| ^--------^
1 | all(or::nothing)
| ^---------^
|
= Invalid arguments to revset function "all": Expected 0 arguments
"###);
@ -141,11 +141,11 @@ fn test_bad_function_call() {
= Invalid file pattern: Path "../out" is not in the repo
"###);
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "root:whatever()"]);
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "root::whatever()"]);
insta::assert_snapshot!(stderr, @r###"
Error: Failed to parse revset: --> 1:6
Error: Failed to parse revset: --> 1:7
|
1 | root:whatever()
1 | root::whatever()
| ^------^
|
= Revset function "whatever" doesn't exist
@ -279,7 +279,7 @@ fn test_alias() {
1 | whatever &
| ^---
|
= expected dag_range_pre_op, range_pre_op, negate_op, or primary
= expected dag_range_pre_op, legacy_dag_range_pre_op, range_pre_op, negate_op, or primary
"###);
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r", "identity()"]);