revset: substitute '~(::x)' to 'x..'

Suppose we have an alias 'immutable()' = '::immutable_heads()', user can
express (visible) mutable set as '~immutable()'. 'immutable_heads()..' can
terminate early, but a generic difference 'all() & ~immutable()' can't.
This commit is contained in:
Yuya Nishihara 2024-03-12 18:51:02 +09:00
parent 9207314173
commit 50363419fb
2 changed files with 98 additions and 1 deletions

View file

@ -15,6 +15,7 @@ v2.39.0..v2.40.0
v2.39.0::v2.40.0
# Mostly recent history
v2.40.0-..
~(::v2.40.0)
# Tags and branches
tags()
branches()

View file

@ -1865,6 +1865,23 @@ fn fold_difference(expression: &Rc<RevsetExpression>) -> TransformedExpression {
})
}
/// Transforms remaining negated ancestors `~(::h)` to range `h..`.
///
/// Since this rule inserts redundant `visible_heads()`, negative intersections
/// should have been transformed.
fn fold_not_in_ancestors(expression: &Rc<RevsetExpression>) -> TransformedExpression {
transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
RevsetExpression::NotIn(complement)
if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
{
// ~(::heads) -> heads..
// ~(::heads-) -> ~ancestors(heads, 1..) -> heads-..
to_difference_range(&RevsetExpression::visible_heads().ancestors(), complement)
}
_ => None,
})
}
/// Transforms binary difference to more primitive negative intersection.
///
/// For example, `all() ~ e` will become `all() & ~e`, which can be simplified
@ -1953,7 +1970,8 @@ pub fn optimize(expression: Rc<RevsetExpression>) -> Rc<RevsetExpression> {
let expression = fold_redundant_expression(&expression).unwrap_or(expression);
let expression = fold_generation(&expression).unwrap_or(expression);
let expression = internalize_filter(&expression).unwrap_or(expression);
fold_difference(&expression).unwrap_or(expression)
let expression = fold_difference(&expression).unwrap_or(expression);
fold_not_in_ancestors(&expression).unwrap_or(expression)
}
// TODO: find better place to host this function (or add compile-time revset
@ -3777,6 +3795,84 @@ mod tests {
"###);
}
#[test]
fn test_optimize_not_in_ancestors() {
// '~(::foo)' is equivalent to 'foo..'.
insta::assert_debug_snapshot!(optimize(parse("~(::foo)").unwrap()), @r###"
Range {
roots: CommitRef(
Symbol(
"foo",
),
),
heads: CommitRef(
VisibleHeads,
),
generation: 0..18446744073709551615,
}
"###);
// '~(::foo-)' is equivalent to 'foo-..'.
insta::assert_debug_snapshot!(optimize(parse("~(::foo-)").unwrap()), @r###"
Range {
roots: Ancestors {
heads: CommitRef(
Symbol(
"foo",
),
),
generation: 1..2,
},
heads: CommitRef(
VisibleHeads,
),
generation: 0..18446744073709551615,
}
"###);
insta::assert_debug_snapshot!(optimize(parse("~(::foo--)").unwrap()), @r###"
Range {
roots: Ancestors {
heads: CommitRef(
Symbol(
"foo",
),
),
generation: 2..3,
},
heads: CommitRef(
VisibleHeads,
),
generation: 0..18446744073709551615,
}
"###);
// Bounded ancestors shouldn't be substituted.
insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)").unwrap()), @r###"
NotIn(
Ancestors {
heads: CommitRef(
Symbol(
"foo",
),
),
generation: 0..1,
},
)
"###);
insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)").unwrap()), @r###"
NotIn(
Ancestors {
heads: CommitRef(
Symbol(
"foo",
),
),
generation: 1..2,
},
)
"###);
}
#[test]
fn test_optimize_filter_difference() {
// '~empty()' -> '~~file(*)' -> 'file(*)'