Commit Graph

782 Commits

Author SHA1 Message Date
Martijn Pieters
6cc53d84c9 revset: add new topographical sort
Sort revisions in reverse revision order but grouped by topographical branches.
Visualised as a graph, instead of:

  o  4
  |
  | o  3
  | |
  | o  2
  | |
  o |  1
  |/
  o  0

revisions on a 'main' branch are emitted before 'side' branches:

  o  4
  |
  o  1
  |
  | o  3
  | |
  | o  2
  |/
  o  0

where what constitutes a 'main' branch is configurable, so the sort could also
result in:

  o  3
  |
  o  2
  |
  | o  4
  | |
  | o  1
  |/
  o  0

This sort was already available as an experimental option in the graphmod
module, from which it is now removed.

This sort is best used with hg log -G:

  $ hg log -G "sort(all(), topo)"
2016-06-13 18:20:00 +01:00
Martijn Pieters
57bf8caf56 revset: move groupbranchiter over from graphmod
This move is to prepare the adaptation of this function into a toposort
predicate.
2016-06-13 18:20:00 +01:00
Martijn Pieters
ffccd3fc81 revset: record if a set is in topographical order
A later revision adds actual topographical sorting. Recording if a set is in
this order allows hg log -G to avoid re-sorting the revset.
2016-06-14 11:05:36 +01:00
Kostia Balytskyi
5501c91461 revset: make filteredset.__nonzero__ respect the order of the filteredset
This fix allows __nonzero__ to respect the direction of iteration of the
whole filteredset. Here's the case when it matters. Imagine that we have a
very large repository and we want to execute a command like:

    $ hg log --rev '(tip:0) and user(ikostia)' --limit 1

(we want to get the latest commit by me).

Mercurial will evaluate a filteredset lazy data structure, an
instance of the filteredset class, which will know that it has to iterate
in a descending order (isdescending() will return True if called). This
means that when some code iterates over the instance of this filteredset,
the 'and user(ikostia)' condition will be first checked on the latest
revision, then on the second latest and so on, allowing Mercurial to
print matches as it founds them. However, cmdutil.getgraphlogrevs
contains the following code:

    revs = _logrevs(repo, opts)
    if not revs:
        return revset.baseset(), None, None

The "not revs" expression is evaluated by calling filteredset.__nonzero__,
which in its current implementation will try to iterate the filteredset
in ascending order until it finds a revision that matches the 'and user(..'
condition. If the condition is only true on late revisions, a lot of
useless iterations will be done. These iterations could be avoided if
__nonzero__ followed the order of the filteredset, which in my opinion
is a sensible thing to do here.

The problem gets even worse when instead of 'user(ikostia)' some more
expensive check is performed, like grepping the commit diff.


I tested this fix on a very large repo where tip is my commit and my very
first commit comes fairly late in the revision history. Results of timing
of the above command on that very large repo.

-with my fix:
real    0m1.795s
user    0m1.657s
sys     0m0.135s

-without my fix:
real    1m29.245s
user    1m28.223s
sys     0m0.929s

I understand that this is a very specific kind of problem that presents
itself very rarely, only on very big repositories and with expensive
checks and so on. But I don't see any disadvantages to this kind of fix
either.
2016-06-02 22:39:01 +01:00
Yuya Nishihara
e2af538ed7 revset: define table of sort() key functions
This should be more readable than big "if" branch.
2016-05-14 19:52:00 +09:00
Yuya Nishihara
9079f0d3ff revset: factor out reverse flag of sort() key
Prepares for making a table of sort keys. This assumes 'k' has at least one
character, which should be guaranteed by keys.split().
2016-05-14 19:46:18 +09:00
Martijn Pieters
4501d84df3 revset: use getargsdict for sort()
This makes it possible to use keyword arguments to specify per-sort options.
For example, a hypothetical 'first' option for the user sort could sort certain
users first with:

    sort(all(), user, user.first=mpm@selenic.com)
2016-05-23 14:09:50 -07:00
timeless
a1cb3173a2 py3: convert to next() function
next(..) was introduced in py2.6 and .next() is not available in py3

https://docs.python.org/2/library/functions.html#next
2016-05-16 21:30:53 +00:00
timeless
db06992202 revset: rename variable to avoid shadowing with builtin next() function
https://docs.python.org/2/library/functions.html#next
2016-05-16 21:30:32 +00:00
Pierre-Yves David
efaf172347 devel: fix a typo in a deprecation warning
Credit goes to Sean Farley for spotting it.
2016-05-11 09:34:59 +02:00
Pierre-Yves David
4bd2f39b1d devel: officially deprecate old style revset
When we introduce the develwarning, we did not had an official deprecation API
and infrastructure. We can now officially deprecate the old way with a version
deadline.
2016-05-11 09:31:47 +02:00
Yuya Nishihara
2644883398 revset: make dagrange preserve order of input set
Unlike range, dagrange has no inverted range (such as '10:0'). So there should
be no practical reason to keep dagrange as a function that forces its own
ordering.

No performance regression is spotted in contrib/base-revsets.txt.
2016-05-03 12:36:44 +09:00
Yuya Nishihara
d1b0332b16 revset: factor out public optimize() function from recursion
New optimize() hides internal arguments and return values. This makes it easy
to add more parameters and return values to _optimize().
2016-05-02 12:09:00 +09:00
Yuya Nishihara
f18ad29e0a revset: introduce temporary variables in optimize() where they look better 2016-05-02 12:47:09 +09:00
Yuya Nishihara
3a2b799928 revset: construct arguments of only() against matched tree
Since _isonly() knows the structure of 'revs' and 'bases', it should be
slightly easier to understand than destructuring 'ta' and 'tb'.
2016-05-02 11:50:48 +09:00
Yuya Nishihara
dff8edf1fe revset: unnest isonly() closure from optimize()
There were no variables to be captured.
2016-05-02 11:27:26 +09:00
Pierre-Yves David
5d9e7dcea2 devel: use the new 'config' argument for the revset develwarn 2016-05-08 10:43:41 +02:00
Yuya Nishihara
96d6f9fd5f revset: define _parsealias() in _aliasrules class
It's short. It doesn't make sense to define _parsealias() outside of the
class.
2016-04-17 13:06:44 +09:00
Yuya Nishihara
74d711b196 revset: factor out common parsing function 2016-04-17 13:03:23 +09:00
Yuya Nishihara
2838ac3dce revset: inline _tokenizealias() into _parsealias()
This helps factoring out common part between _parsealias() and parse().
2016-04-17 12:57:27 +09:00
Yuya Nishihara
5ed7576a38 revset: unindent "if True" block in sort()
It was there to make the previous patch readable.
2016-04-23 16:11:05 +09:00
Yuya Nishihara
37d980f070 revset: make sort() do dumb multi-pass sorting for multiple keys (issue5218)
Our invert() function was too clever to not take length into account. I could
fix the problem by appending '\xff' as a terminator (opposite to '\0'), but
it turned out to be slower than simple multi-pass sorting.

New implementation is pretty straightforward, which just calls sort() from the
last key. We can do that since Python sort() is guaranteed to be stable. It
doesn't sound nice to call sort() multiple times, but actually it is faster.
That's probably because we have fewer Python codes in hot loop, and can avoid
heavy string and list manipulation.

  revset #0: sort(0:10000, 'branch')
  0) 0.412753
  1) 0.393254

  revset #1: sort(0:10000, '-branch')
  0) 0.455377
  1) 0.389191  85%

  revset #2: sort(0:10000, 'date')
  0) 0.408082
  1) 0.376332  92%

  revset #3: sort(0:10000, '-date')
  0) 0.406910
  1) 0.380498  93%

  revset #4: sort(0:10000, 'desc branch user date rev')
  0) 0.542996
  1) 0.486397  89%

  revset #5: sort(0:10000, '-desc -branch -user -date -rev')
  0) 0.965032
  1) 0.518426  53%
2016-04-23 16:09:30 +09:00
Yuya Nishihara
85d1a67355 parser: factor out _trygetfunc() that extracts function name and arguments
This provides a customization point for templater. In templater, there are
two ways to call a unary function: func(x) and x|func. They are processed
differently in templater due to historical reasons, but they should be
handled in the same way while expanding aliases. In short, x|func should be
processed as syntactic sugar for func(x).

_funcnode and _getlist() are replaced by _trygetfunc().
2016-03-29 17:27:34 +09:00
Yuya Nishihara
4d9561138b revset: rename findaliases() to expandaliases()
This function returns a full tree of alias expansion applied, which sounds
different from what "findaliases" would do.
2016-02-29 22:58:15 +09:00
Yuya Nishihara
45e9c21bc0 parser: move functions that process alias expansion to rule-set class
They will be commonly used by revset and templater. It isn't easy to understand
how _expand() works, so I'll add comments by a follow-up patch.

The local variable 'alias' is renamed to 'a' to avoid shadowing the global
'alias' class.
2016-02-29 22:15:44 +09:00
Yuya Nishihara
5da75dff02 revset: unindent codes in _getalias() function
We generally do return early if tree isn't a tuple.
2016-02-29 22:10:48 +09:00
Yuya Nishihara
a98783fa06 parser: extract helper that creates a dict of aliases
This will be common between revset and templater.

The local variable 'alias' is renamed to 'a' to avoid shadowing the global
'alias' class.
2016-02-29 19:24:15 +09:00
Yuya Nishihara
53c5cbd213 parser: construct alias object by rule-set class
It was odd that the revsetalias did the whole parsing stuff in __init__().
Instead, this patch adds a factory function to the aliasrules class, and
makes the alias (= revsetalias) class a plain-old value object.
2016-02-29 18:33:30 +09:00
Yuya Nishihara
2e16bca7bf parser: unify parser function of alias declaration and definition
We no longer have to keep them separately.
2016-03-29 00:08:25 +09:00
Yuya Nishihara
06651e61a2 revset: unify function that parses alias declaration and definition
We no longer need separate parsers. Only difference between _parsealiasdecl()
and _parsealiasdefn() is whether or not to flatten 'or' tree. Since alias
declaration should have no 'or' operator, there was no practical difference.
2016-03-29 00:05:14 +09:00
Yuya Nishihara
5392dbd7d3 parser: move alias definition parser to common rule-set class
The original _parsealiasdefn() function is split into common _builddefn()
and revset-specific _parsealiasdefn(). revset._relabelaliasargs() is removed
as it is no longer used.

The doctests are ported by using the dummy parse().
2016-02-29 18:10:07 +09:00
Yuya Nishihara
188dee35c2 parser: move _relabelaliasargs() to common rule-set class
This has no doctest because it will be covered by _builddefn() introduced
by the next patch.

revset._relabelaliasargs() will be removed soon.
2016-02-29 18:00:51 +09:00
Yuya Nishihara
6b447db164 parser: move alias declaration parser to common rule-set class
The original _parsealiasdecl() function is split into common _builddecl()
and revset-specific _parsealiasdecl(). And the original _parsealiasdecl()
call is temporarily replaced by rules._builddecl(), which should be eliminated
later.

The doctests are mostly ported by using the dummy parse(), but the test for
'foo bar' is kept in _parsealiasdecl() as it checks if "pos != len(decl)" is
working. Also, 'foo($1)' test is added to make sure the alias tokenizer can
handle '$1' symbol, which is the only reason why we need _parsealiasdecl().
2016-02-29 17:54:03 +09:00
Yuya Nishihara
c51267a286 parser: add stub class that will host alias parsing and expansion
This class will keep syntax rules that are necessary to parse and expand
aliases. The implementations will be extracted from the revset module. In
order to make the porting easier, this class keeps parsedecl and parsedefn
separately, which will be unified later. Also, getlist and funcnode will
be refactored by future patches for better handling of the template aliases.

The following public functions will be added:

  aliasrules.build(decl, defn) -> aliasobj
    parse decl and defn into an object that keeps alias name, arguments
    and replacement tree.
  aliasrules.buildmap(aliasitems) -> aliasdict
    helper to build() a dict of alias objects from a list of (decl, defn)
  aliasrules.expand(aliasdict, tree) -> tree
    expand aliases in tree recursively

Because these functions aren't introduced by this series, there would remain
a few wrapper functions in the revset module. These ugly wrappers should be
eliminated by the next series.

This class is considered an inheritable namespace, which will host only
class/static methods. That's because it won't have no object-scope variables.
I'm not a big fan of using class as a syntax sugar, but I admit it can improve
code readability at some level. So let's give it a try.
2016-04-03 16:55:23 +09:00
Yuya Nishihara
76a8e3fd6c revset: narrow scope of "except ParseError" block in _parsealiasdecl()
This helps to factor out a common function. "if True" will be removed soon.
2016-02-29 17:43:39 +09:00
Pierre-Yves David
0c17a400d6 revset: force ascending order for baseset initialized from a set
It is possible to initialize a baseset directly from a set object. However, in
this case the iteration order was inherited from the set. Set have undefined
iteration order (especially cpython and pypy will have different one) so we
should not rely on it anywhere.

Therefor we declare the baseset "ascending" to enforce a consistent iteration
order. The sorting is done lazily by the baseset class and should have no
performance impact when it does not matter.

This makes test-revset.t pass with pypy.
2016-04-04 17:45:54 -07:00
Pierre-Yves David
d25fcd8b52 revset: stabilize repr of baseset initialized with a set
Cpython and pypy have different way to build and order set, so the result of
list(myset) is different. We work around this by using the sorted version of the
data when displaying a list.

This get pypy closer to pass test-revset.t.
2016-04-04 17:45:15 -07:00
Yuya Nishihara
1ad58e18af parser: move parsererrordetail() function from revset module
This will be used by common alias functions introduced by future patches.
2016-02-29 17:02:56 +09:00
Maciej Fijalkowski
a47c59eeab revset: prevent infinite recursion on pypy
as explained in the commit, __len__ cannot do [x for x in self] because
that can potentially call __len__ again, causing infinite recursion
2016-04-01 10:09:34 +02:00
Maciej Fijalkowski
688a2c6f37 pypy: fix doctests for pypy optimizations
PyPy would sometime call __len__ at points where it things preallocating
the container makes sense. Change the doctests so they're using generator
expressions and not list comprehensions
2016-03-31 18:38:08 +02:00
Yuya Nishihara
5013ac73bc revset: make _parsealiasdecl() simply return the original parsed tree
It wasn't necessary to reconstruct the same tuple.
2016-02-29 17:46:06 +09:00
Yuya Nishihara
9cb56c55c8 revset: inline isvalidfunc(), getfuncname() and getfuncargs()
See the previous commit for why. These functions are also trivial.
2016-02-29 16:35:58 +09:00
Yuya Nishihara
8437ca0ff3 revset: inline isvalidsymbol() and getsymbol() into _parsealiasdecl()
Since I'm going to extract a common alias parser, I want to eliminate
dependencies to the revset parsing rules. These functions are trivial,
so we can go without them.
2016-02-29 16:32:18 +09:00
Yuya Nishihara
23d5f85790 revset: remove redundant checks for parsed tree of alias
If tree is a tuple, it must have at least one element. Also the length of node
tuple is guaranteed by the syntax elements. (e.g. 'func' must have 3 items.)

This change will help inlining these trivial functions in future patches.
2016-02-29 16:23:09 +09:00
Yuya Nishihara
0f4849fc71 revset: inline _getaliasarg() function
This function is now much simpler than before. Inlining small functions helps
to extract a reusable alias processor.
2016-02-14 20:43:30 +09:00
Yuya Nishihara
6c870f3b69 revset: drop redundant check for unknown alias arguments
Since _parsealiasdefn() rejects unknown alias arguments, _checkaliasarg() is
unnecessary. New test is added to make sure unknown '$n' symbols are rejected.
2016-02-14 20:27:08 +09:00
Yuya Nishihara
208bb924ba revset: move tagging of alias arguments from tokenization to parsing phase
In short, this patch moves the hack from tokenizedefn() to _relabelaliasargs(),
which is called after parsing. This change aims to eliminate tight dependency
on the revset tokenizer.

Before this patch, we had to rewrite an alias argument to a pseudo function:

  "$1" -> "_aliasarg('$1')"
  ('symbol', '$1') -> ('function', ('symbol', '_aliasarg'), ('string', '$1'))

This was because the tokenizer must generate tokens that are syntactically
valid. By moving the process to the parsing phase, we can assign a unique tag
to an alias argument.

  ('symbol', '$1') -> ('_aliasarg', '$1')

Since new _aliasarg node never be generated from a user input, we no longer
have to verify a user input at findaliases(). The test for _aliasarg("$1") is
removed as it is syntactically valid and should pass the parsing phase.
2016-02-14 19:48:33 +09:00
FUJIWARA Katsunori
bd921cd006 revset: remove useless extpredicate class (API)
Previous patch makes this classes useless by replacing it with
revsetpredicate of registrar.

BTW, extpredicate itself has already been broken by that patch,
because revsetpredicate of registrar doesn't have compatibility with
original predicate (derived from funcregistrar of registrar), in fact.
2016-03-11 04:14:54 +09:00
Yuya Nishihara
0703170780 revset: add inspection data to max() and min() functions
We are likely to be interested in how these functions build a result set.
2016-02-16 21:44:13 +09:00
Yuya Nishihara
80f2bcf6b5 revset: add inspection data to limit() and last() functions
We are likely to be interested in how these functions calculate a result set.
2016-02-16 21:43:51 +09:00