152 Commits

Author SHA1 Message Date
Yuya Nishihara
069a8ed9bc revset: reimplement parents() as ancestors() with generation filter
This wouldn't make things any better by itself, but it allows us to merge
nested parents().
2022-12-13 15:55:18 +09:00
Yuya Nishihara
46b1465324 revset: add generation parameter to RevsetExpression::Ancestors/Range
Parents(heads) will be translated to Ancestors(heads, 1..2).
2022-12-13 15:55:18 +09:00
Yuya Nishihara
29a565e3fb revset: add substitution rule for roots..heads
While working on ancestor generation, I noticed Mercurial has this
substitution rule. Since it's easier to deal with Ancestors() than Range {},
'roots..heads' is first decomposed to ':heads & ~:roots'.
2022-12-13 15:55:18 +09:00
Yuya Nishihara
e6229e6d77 revset: turn RevWalkRevset into generic wrapper of cloneable iterator
I failed to solve type puzzle for to_predicate_fn<'a>(&'a self) where
'repo: 'a, so struct RevWalkRevset<'repo, T> is bounded by T to consume
the lifetime parameter.
2022-12-13 15:55:18 +09:00
Yuya Nishihara
a569ac07f6 revset: alias 'empty()' to '~file(*)'
We're more likely to filter out empty commits, so this should be slightly
faster in practice.

The extra Option<> isn't needed, but it should clarify that "prefix([])"
is not "everything".
2022-12-07 23:38:17 +09:00
Yuya Nishihara
1eaa05b6fd revset: remove uninteresting expressions early
Since internalize_filter() should no longer insert redundant 'all() & x'
nodes, it's simpler to clean up uninteresting nodes first.
2022-12-07 23:38:17 +09:00
Yuya Nishihara
75e072b80c revset: remove stale comment about filter intersection with 'all()'
This should have been removed at e17fc89a8da0 "revset: make filter node unary,
move candidates to outer intersection".
2022-12-07 23:38:17 +09:00
Yuya Nishihara
222d9a6527 revset: rewrite 'x ~ y' to 'x & ~y' first to apply filter optimization
This is remainder of 48d10d648c0c "revset: add unary negate (or set
complement) operator '~y'".
2022-12-07 23:38:17 +09:00
Yuya Nishihara
951eb0b61a revset: use filter intersection for tree containing filter
This basically transforms 's1 & (f() | s2)' to
's1.iter().filter(all && f || s2)'. Still the predicate part includes "all",
the filter function doesn't need to load commit data for every entry since
's1.iter().filter(all)' is tested first. To optimize "all" predicate out,
maybe we can add a wrapper that returns '|_: &IndexEntry| true'.

Instead of inserting AsFilter(_) node, I could add a recursive is_filter()
function. That would also work so long as the height of RevsetExpression tree
is limited. I chose node insertion just for ease of snapshot testing.
2022-12-07 11:01:59 +09:00
Yuya Nishihara
f2e7a5ad03 revset: introduce trait that turns evaluated revset into predicate function
This allows us to evaluate 's1 & (f() | s2)' as 's1.iter().filter(f || s2)'
instead of 's1 & (all.iter().filter(f) | s2)'.
2022-12-07 11:01:59 +09:00
Yuya Nishihara
f64f96251f revset: add basic tests for revset combinator
I've made MutableIndex::add_commit_data() crate-public since it's convenient
to build an IndexEntry by that function.
2022-12-07 11:01:59 +09:00
Yuya Nishihara
e17fc89a8d revset: make filter node unary, move candidates to outer intersection
In order to optimize a query like '(author(_) | @) & main..', we'll probably
need a predicate form of an iterable set so that the query can be evaluated
to '(main..).iter().filter(author(_) | @)'. And if a predicate function can
terminate the source iterator early (by returning true/false/false_forever),
complexity of a filtered revset is basically the same as an intersection of
iterator pair. This means we can eventually merge IntersectionRevset with
FilterRevset.

With that in mind, this patch removes the redundant 'candidates' field from
the filter node, which would otherwise appear in the predicate function as
'candidates.contains(entry)'. A filter node with candidates was somewhat
useful while rewriting the tree, but that can be dealt with a view function
like as_filter_intersection() in this patch.

This also simplify the subsequent filter transformation as we no longer need
to test if candidates == All.
2022-12-05 00:32:18 +09:00
Yuya Nishihara
6d977c73e4 revset: add test of filter intersection over non-linear tree
Previously we only have a test for the left recursion. The added test
contains right recursion path, which should have caught the error I made
while working on the next "unary filter node" patch.
2022-12-05 00:32:18 +09:00
Yuya Nishihara
48d426529c revset: update doc of filter transformation, apply minor style change
The doc comment summarizes what I'm going to implement. I'm not sure if
we'll add all of them because revset evaluation isn't the key performance
bottleneck at the moment. Anyway, I don't think any of these ideas would
logically conflict with segmented changelog adaptation unless we decide to
replace the whole revset stack with Eden/Sapling's.
2022-12-05 00:32:18 +09:00
Yuya Nishihara
ec6f2cf393 revset: extract function that builds predicate function from spec 2022-12-05 00:32:18 +09:00
Yuya Nishihara
5cc99b6451 revset: inline ChildrenRevsetIterator by using .filter() 2022-11-30 23:42:51 +09:00
Yuya Nishihara
fae3822422 revset: inline FilterRevsetIterator by using .filter() 2022-11-30 23:42:51 +09:00
Yuya Nishihara
5b13c0b38f revset: inline RevWalkRevsetIterator which is just an identity iterator 2022-11-30 23:42:51 +09:00
Yuya Nishihara
0e99747728 revset: eliminate double negates
Writing double negates is silly, but it might be hidden by revset alias
if we added such feature.

I made fold_redundant_expression() a separate step from fold_difference()
since I'll probably want to apply the cleanup step before rewriting filter
expressions.
2022-11-29 15:46:15 +09:00
Yuya Nishihara
54044ea8d6 revset: transform negative intersection to difference 2022-11-29 15:46:15 +09:00
Yuya Nishihara
48d10d648c revset: add unary negate (or set complement) operator '~y'
Because a unary negation node '~y' is more primitive than the corresponding
difference node 'x~y', '~y' is easier to deal with while rewriting the tree.
That's the main reason to add RevsetExpression::NotIn node.

As we have a NotIn node, it makes sense to add an operator for that. This
patch reuses '~' token, which I feel intuitive since the other set operators
looks like bitwise ops. Another option is '!'.

The unary '~' operator has the highest precedence among the set operators,
but they are lower than the ranges. This might be counter intuitive, but
useful because a prefix range ':x' can be negated without parens.

Maybe we can remove the redundant infix operator 'x ~ y', but it isn't
decided yet.
2022-11-29 15:46:15 +09:00
Yuya Nishihara
7fbd7b48e5 revset: highlight whole function expression on substitution failed
The error may be caused by arguments passed in to the alias function.
2022-11-29 04:17:12 +09:00
Yuya Nishihara
70292f79b7 revset: implement function alias expansion
Function parameters are processed as local symbols while substituting
alias expression. This isn't as efficient as Mercurial which caches
a tree of fully-expanded function template, but that wouldn't matter in
practice.
2022-11-29 04:17:12 +09:00
Martin von Zweigbergk
d8feed9be4 copyright: change from "Google LLC" to "The Jujutsu Authors"
Let's acknowledge everyone's contributions by replacing "Google LLC"
in the copyright header by "The Jujutsu Authors". If I understand
correctly, it won't have any legal effect, but maybe it still helps
reduce concerns from contributors (though I haven't heard any
concerns).

Google employees can read about Google's policy at
go/releasing/contributions#copyright.
2022-11-28 06:05:45 -10:00
Yuya Nishihara
e40c041384 revset: merge AmbiguousChange/CommitIdPrefix error into one
Follows up c5ed3e14776b. Now change/commit ids are resolved at the same
precedence, which means there are at least three types of ambiguity.
I don't think we would need to discriminate these.
2022-11-28 22:49:07 +09:00
Yuya Nishihara
c5ed3e1477 revset: for short hash, look up both commit and change ids to disambiguate
Because the use of the change id is recommended, any operation should abort
if a valid change id happens to match a commit id. We still try the commit
id lookup first as the change id lookup is more costly.

Ambiguous change/commit id is reported as AmbiguousCommitIdPrefix for now.
Maybe we can merge AmbiguousCommit/ChangeIdPrefix errors into one?

Closes #799
2022-11-28 17:30:53 +09:00
Yuya Nishihara
1fa0392a3e revset: wrap internal symbol resolution result with Option
Option<T> or Result<Option<T>, _> is easier to pattern match than testing
RevsetError::NoSuchRevision.
2022-11-28 17:30:53 +09:00
Yuya Nishihara
11ee2f22c4 revset: implement simple symbol alias expansion
Since syntactic information like symbol or function name is lost after
parse(), alias substitution is inserted to the middle of the post-parsing
stage, not after the whole RevsetExpression tree is built. This is the main
difference from Mercurial. Mercurial also caches parsed aliases, but I don't
think that would have a measurable impact.
2022-11-27 20:12:22 +09:00
Yuya Nishihara
5df25cd834 revset: add origin field to RevsetParseError to chain alias errors
This could be embedded in a variant of RevsetParseErrorKind, but I want to
keep the enum comparable.
2022-11-27 20:12:22 +09:00
Yuya Nishihara
7632466cc0 revset: add table of symbol aliases and pass around parse functions
The CLI will load aliases from config, insert them one by one, and warn if
declaration part is invalid. That's why RevsetAliasesMap is a public struct
and needs to be instantiated by the caller.
2022-11-27 20:12:22 +09:00
Yuya Nishihara
f0b1221749 revset: pack parsing state into struct
I'll add aliases map, substitution stack (to detect recursion), and locals
(for function aliases) there. Fortunately, we can avoid shared mutables
so a copyable struct should be good.

parse_function_argument_to_string() doesn't need a workspace_ctx, but there
should be no reason to explicitly nullify it either.
2022-11-27 20:12:22 +09:00
Yuya Nishihara
ce3397c2cb revset: combine operator parsing steps
I'm thinking of adding alias expansion at this stage, and it would be a bit
tedious to pass around mutable context by function parameter. So let's reduce
the number of the intermediate functions.

This also produces a better error message.
2022-11-25 11:01:15 +09:00
Yuya Nishihara
78e34d2f81 revset: migrate operator parsing to PrattParser 2022-11-25 11:01:15 +09:00
Yuya Nishihara
5491b5581f revset: decouple prefix/infix/postfix range operators
This unblocks the use of PrattParser, which builds an operator map keyed
solely by Rule, not by (Rule, prefix/infix/postfix) pair.
2022-11-25 11:01:15 +09:00
Waleed Khan
94815a7cb5 log: warn if the provided path looks like a revset 2022-11-21 16:42:48 -08:00
Yuya Nishihara
a5297c0082 revset: add comment about subtle optimization issue regarding hidden commits
Per the discussion in #764.
2022-11-20 22:01:19 +09:00
Yuya Nishihara
84efed420f revset: allow empty string literal "" 2022-11-20 13:11:28 +09:00
Yuya Nishihara
13bb53e839 revset: give higher precedence to intersection/difference operators
Just like hg revsets and major programming languages.
2022-11-17 01:11:08 +09:00
Yuya Nishihara
a90c9960ba revset: leverage PrattParser to parse infix (or set) expression
Apparently, this is new feature introduced in pest 2.4.0. It allows us to
easily enforce operator precedence. I think the whole expression post-parsing
can be migrated to PrattParser, but let's start small. We might want to
add a weird rule to the range_expression layer in future.

https://github.com/pest-parser/pest/releases/tag/v2.4.0
https://docs.rs/pest/latest/pest/pratt_parser/struct.PrattParser.html#example
2022-11-17 01:11:08 +09:00
Yuya Nishihara
1717690a64 revset: leverage SOI/EOI markers to detect incomplete parser input
The error message is still a bit cryptic, but I don't think it's worse than
the original "incomplete parse" error.

https://pest.rs/book/grammars/syntax.html#start-and-end-of-input
2022-11-17 01:11:08 +09:00
Yuya Nishihara
a81ebeb85e 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)'
2022-11-16 08:50:33 +09:00
Yuya Nishihara
230ac043ff revset: extract helper function that tests diff from parent revision 2022-11-16 08:50:33 +09:00
Yuya Nishihara
c7145a2ed9 revset: unify constructors of RevsetExpression::Filter
Since filter predicates no longer take an optional candidates argument,
these .with_<predicate>() constructors aren't useful anymore.
2022-11-16 08:50:33 +09:00
Yuya Nishihara
19a3fb7d6c revset: flatten match arms of description|author|committer predicates
Since most of the code duplicates has been extracted to helper functions,
nested match statement looks more verbose.
2022-11-16 08:50:33 +09:00
Yuya Nishihara
fa3ad16bf2 revset: add present(set) predicate that suppresses NoSuchRevision error
This is copied from Mercurial. Typical use case I have in mind is
"present(master) | present(main)" in stock revset.
2022-11-07 21:41:54 +09:00
Yuya Nishihara
ed14292aa2 revset: add EagerRevset::empty() constructor for convenience 2022-11-07 21:41:54 +09:00
Yuya Nishihara
1c4888f769 revset: report bad number of arguments with span 2022-11-03 09:41:04 +09:00
Yuya Nishihara
b938b5e907 revset: report invalid string argument with span
Also dropped "found: {}" from the error summary as it's obvious.
2022-11-03 09:41:04 +09:00
Yuya Nishihara
aeee0acd08 revset: report unknown function with span 2022-11-03 09:41:04 +09:00
Yuya Nishihara
fdbd44571d revset: report unparsable file path with span 2022-11-03 09:41:04 +09:00