diff --git a/mercurial/registrar.py b/mercurial/registrar.py --- a/mercurial/registrar.py +++ b/mercurial/registrar.py @@ -166,6 +166,16 @@ Optional argument 'takeorder' indicates whether a predicate function takes ordering policy as the last argument. + Optional argument 'weight' indicates the estimated run-time cost, useful + for static optimization, default is 1. Higher weight means more expensive. + Usually, revsets that are fast and returns only one single revision has a + weight of 0.5 (ex. a string symbol); revsets with O(changelog) complexity + have weight 10 (ex. author); revsets which also needs reading manifest + diffs usually have weight 30 (ex. adds); revset reading file contents + usually have weight 100 (ex. contains). Note: those values are not + absolute, if the revset has a same big-O time complexity as `contains`, + but with a smaller constant, it might have a weight of 90. + 'revsetpredicate' instance in example above can be used to decorate multiple functions. @@ -178,9 +188,10 @@ _getname = _funcregistrarbase._parsefuncdecl _docformat = "``%s``\n %s" - def _extrasetup(self, name, func, safe=False, takeorder=False): + def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1): func._safe = safe func._takeorder = takeorder + func._weight = weight class filesetpredicate(_funcregistrarbase): """Decorator to register fileset predicate diff --git a/mercurial/revset.py b/mercurial/revset.py --- a/mercurial/revset.py +++ b/mercurial/revset.py @@ -253,7 +253,7 @@ # repo - current repository instance # subset - of revisions to be examined # x - argument in tree form -symbols = {} +symbols = revsetlang.symbols # symbols which can't be used for a DoS attack for any given input # (e.g. those which accept regexes as plain strings shouldn't be included) @@ -276,7 +276,7 @@ sourceset = getset(repo, fullreposet(repo), x) return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)]) -@predicate('adds(pattern)', safe=True) +@predicate('adds(pattern)', safe=True, weight=30) def adds(repo, subset, x): """Changesets that add a file matching pattern. @@ -288,7 +288,7 @@ pat = getstring(x, _("adds requires a pattern")) return checkstatus(repo, subset, pat, 1) -@predicate('ancestor(*changeset)', safe=True) +@predicate('ancestor(*changeset)', safe=True, weight=0.5) def ancestor(repo, subset, x): """A greatest common ancestor of the changesets. @@ -394,7 +394,7 @@ ps.add(r) return subset & ps -@predicate('author(string)', safe=True) +@predicate('author(string)', safe=True, weight=10) def author(repo, subset, x): """Alias for ``user(string)``. """ @@ -462,7 +462,7 @@ bms -= {node.nullrev} return subset & bms -@predicate('branch(string or set)', safe=True) +@predicate('branch(string or set)', safe=True, weight=10) def branch(repo, subset, x): """ All changesets belonging to the given branch or the branches of the given @@ -595,7 +595,7 @@ cs = _children(repo, subset, s) return subset & cs -@predicate('closed()', safe=True) +@predicate('closed()', safe=True, weight=10) def closed(repo, subset, x): """Changeset is closed. """ @@ -604,7 +604,7 @@ return subset.filter(lambda r: repo[r].closesbranch(), condrepr='') -@predicate('contains(pattern)') +@predicate('contains(pattern)', weight=100) def contains(repo, subset, x): """The revision's manifest contains a file matching pattern (but might not modify it). See :hg:`help patterns` for information about file patterns. @@ -654,7 +654,7 @@ return subset.filter(lambda r: _matchvalue(r), condrepr=('', rev)) -@predicate('date(interval)', safe=True) +@predicate('date(interval)', safe=True, weight=10) def date(repo, subset, x): """Changesets within the interval, see :hg:`help dates`. """ @@ -664,7 +664,7 @@ return subset.filter(lambda x: dm(repo[x].date()[0]), condrepr=('', ds)) -@predicate('desc(string)', safe=True) +@predicate('desc(string)', safe=True, weight=10) def desc(repo, subset, x): """Search commit message for string. The match is case-insensitive. @@ -722,7 +722,7 @@ # Like ``descendants(set)`` but follows only the first parents. return _descendants(repo, subset, x, followfirst=True) -@predicate('destination([set])', safe=True) +@predicate('destination([set])', safe=True, weight=10) def destination(repo, subset, x): """Changesets that were created by a graft, transplant or rebase operation, with the given revisions specified as the source. Omitting the optional set @@ -891,7 +891,7 @@ return subset & s -@predicate('first(set, [n])', safe=True, takeorder=True) +@predicate('first(set, [n])', safe=True, takeorder=True, weight=0) def first(repo, subset, x, order): """An alias for limit(). """ @@ -1012,7 +1012,7 @@ getargs(x, 0, 0, _("all takes no arguments")) return subset & spanset(repo) # drop "null" if any -@predicate('grep(regex)') +@predicate('grep(regex)', weight=10) def grep(repo, subset, x): """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')`` to ensure special escape characters are handled correctly. Unlike @@ -1097,7 +1097,7 @@ 'exclude=%r, default=%r, rev=%r>', pats, inc, exc, default, rev)) -@predicate('file(pattern)', safe=True) +@predicate('file(pattern)', safe=True, weight=10) def hasfile(repo, subset, x): """Changesets affecting files matched by pattern. @@ -1139,7 +1139,7 @@ hiddenrevs = repoview.filterrevs(repo, 'visible') return subset & hiddenrevs -@predicate('keyword(string)', safe=True) +@predicate('keyword(string)', safe=True, weight=10) def keyword(repo, subset, x): """Search commit message, user name, and names of changed files for string. The match is case-insensitive. @@ -1157,7 +1157,7 @@ return subset.filter(matches, condrepr=('', kw)) -@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True) +@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0) def limit(repo, subset, x, order): """First n members of set, defaulting to 1, starting from offset. """ @@ -1259,7 +1259,7 @@ pass return baseset(datarepr=('', subset, os)) -@predicate('modifies(pattern)', safe=True) +@predicate('modifies(pattern)', safe=True, weight=30) def modifies(repo, subset, x): """Changesets modifying files matched by pattern. @@ -1403,7 +1403,7 @@ # some optimizations from the fact this is a baseset. return subset & o -@predicate('outgoing([path])', safe=False) +@predicate('outgoing([path])', safe=False, weight=10) def outgoing(repo, subset, x): """Changesets not found in the specified destination repository, or the default push location. @@ -1654,7 +1654,7 @@ return baseset([r]) return baseset() -@predicate('removes(pattern)', safe=True) +@predicate('removes(pattern)', safe=True, weight=30) def removes(repo, subset, x): """Changesets which remove files matching pattern. @@ -1794,7 +1794,7 @@ return subset.filter(matches, condrepr=('', fields, revs)) -@predicate('reverse(set)', safe=True, takeorder=True) +@predicate('reverse(set)', safe=True, takeorder=True, weight=0) def reverse(repo, subset, x, order): """Reverse order of set. """ @@ -1862,7 +1862,8 @@ return args['set'], keyflags, opts -@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True) +@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, + weight=10) def sort(repo, subset, x, order): """Sort set by keys. The default sort order is ascending, specify a key as ``-key`` to sort in descending order. @@ -2033,7 +2034,7 @@ return subset & orphan -@predicate('user(string)', safe=True) +@predicate('user(string)', safe=True, weight=10) def user(repo, subset, x): """User name contains string. The match is case-insensitive. @@ -2042,7 +2043,7 @@ """ return author(repo, subset, x) -@predicate('wdir()', safe=True) +@predicate('wdir()', safe=True, weight=0) def wdir(repo, subset, x): """Working directory. (EXPERIMENTAL)""" # i18n: "wdir" is a keyword @@ -2097,7 +2098,7 @@ return baseset([r for r in ls if r in s]) # for internal use -@predicate('_intlist', safe=True, takeorder=True) +@predicate('_intlist', safe=True, takeorder=True, weight=0) def _intlist(repo, subset, x, order): if order == followorder: # slow path to take the subset order diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py --- a/mercurial/revsetlang.py +++ b/mercurial/revsetlang.py @@ -49,6 +49,8 @@ keywords = {'and', 'or', 'not'} +symbols = {} + _quoteletters = {'"', "'"} _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%")) @@ -441,21 +443,7 @@ elif op == 'func': f = getsymbol(x[1]) wa, ta = _optimize(x[2]) - if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep', - 'keyword', 'outgoing', 'user', 'destination'): - w = 10 # slow - elif f in ('modifies', 'adds', 'removes'): - w = 30 # slower - elif f == "contains": - w = 100 # very slow - elif f == "ancestor": - w = 0.5 - elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'): - w = 0 - elif f == "sort": - w = 10 # assume most sorts look at changelog - else: - w = 1 + w = getattr(symbols.get(f), '_weight', 1) return w + wa, (op, x[1], ta) raise ValueError('invalid operator %r' % op)