diff --git a/lib/src/revset.rs b/lib/src/revset.rs index c6f47f6fb..00b3238a7 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -1262,11 +1262,31 @@ fn fold_redundant_expression(expression: &Rc) -> Option) -> Option> { + fn to_difference( + expression: &Rc, + complement: &Rc, + ) -> Rc { + match (expression.as_ref(), complement.as_ref()) { + // :heads & ~(:roots) -> roots..heads + (RevsetExpression::Ancestors(heads), RevsetExpression::Ancestors(roots)) => { + Rc::new(RevsetExpression::Range { + roots: roots.clone(), + heads: heads.clone(), + }) + } + _ => expression.minus(complement), + } + } + transform_expression_bottom_up(expression, |expression| match expression.as_ref() { RevsetExpression::Intersection(expression1, expression2) => { match (expression1.as_ref(), expression2.as_ref()) { - (_, RevsetExpression::NotIn(complement)) => Some(expression1.minus(complement)), - (RevsetExpression::NotIn(complement), _) => Some(expression2.minus(complement)), + (_, RevsetExpression::NotIn(complement)) => { + Some(to_difference(expression1, complement)) + } + (RevsetExpression::NotIn(complement), _) => { + Some(to_difference(expression2, complement)) + } _ => None, } } @@ -1280,6 +1300,10 @@ fn fold_difference(expression: &Rc) -> Option) -> Option> { transform_expression_bottom_up(expression, |expression| match expression.as_ref() { + // roots..heads -> :heads & ~(:roots) + RevsetExpression::Range { roots, heads } => { + Some(heads.ancestors().intersection(&roots.ancestors().negated())) + } RevsetExpression::Difference(expression1, expression2) => { Some(expression1.intersection(&expression2.negated())) } @@ -2639,6 +2663,46 @@ mod tests { ) "###); + // Range expression. + insta::assert_debug_snapshot!(optimize(parse(":foo & ~:bar").unwrap()), @r###" + Range { + roots: Symbol( + "bar", + ), + heads: Symbol( + "foo", + ), + } + "###); + insta::assert_debug_snapshot!(optimize(parse("~:foo & :bar").unwrap()), @r###" + Range { + roots: Symbol( + "foo", + ), + heads: Symbol( + "bar", + ), + } + "###); + insta::assert_debug_snapshot!(optimize(parse("foo..").unwrap()), @r###" + Range { + roots: Symbol( + "foo", + ), + heads: VisibleHeads, + } + "###); + insta::assert_debug_snapshot!(optimize(parse("foo..bar").unwrap()), @r###" + Range { + roots: Symbol( + "foo", + ), + heads: Symbol( + "bar", + ), + } + "###); + // Double/triple negates. insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar").unwrap()), @r###" Intersection(