revset: infer ordering flag to teach if operation should define/follow order
New flag 'order' is the hint to determine if a function or operation can
enforce its ordering requirement or take the ordering already defined. It
will be used to fix a couple of ordering bugs, such as:
a) 'x & (y | z)' disregards the order of 'x' (issue5100)
b) 'x & y:z' is listed from 'y' to 'z'
c) 'x & y' can be rewritten as 'y & x' if weight(x) > weight(y)
(a) and (b) are bugs of the revset core. Before this, there was no way to
tell if 'orset()' and 'rangeset()' can enforce its ordering. These bugs
could be addressed by overriding __and__() of the initial set to take the
ordering of the other set:
class fullreposet:
def __and__(self, other):
# allow other to enforce its ordering
return other
but it would expose (c), which is a hidden bug of optimize(). So, in either
ways, optimize() have to know the current ordering requirement. Otherwise,
it couldn't rewrite expressions by weights with no output change, nor tell
how a revset function or operation should order the entries.
'order' is tri-state. It starts with 'define', and shifts to 'follow' by
'x & y'. It changes back to 'define' on function call 'f(x)' or function-like
operation 'x (f) y' because 'f' may have its own ordering requirement for 'x'
and 'y'. The state 'any' will allow us to avoid extra cost that would be
necessary to constrain ordering where it isn't important, 'not x'.
#!/usr/bin/env python
#
# check-config - a config flag documentation checker for Mercurial
#
# Copyright 2015 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import re
import sys
foundopts = {}
documented = {}
configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"],\s*"""
r"""['"](\S+)['"](,\s+(?:default=)?(\S+?))?\)""")
configpartialre = (r"""ui\.config""")
def main(args):
for f in args:
sect = ''
prevname = ''
confsect = ''
carryover = ''
for l in open(f):
# check topic-like bits
m = re.match('\s*``(\S+)``', l)
if m:
prevname = m.group(1)
if re.match('^\s*-+$', l):
sect = prevname
prevname = ''
if sect and prevname:
name = sect + '.' + prevname
documented[name] = 1
# check docstring bits
m = re.match(r'^\s+\[(\S+)\]', l)
if m:
confsect = m.group(1)
continue
m = re.match(r'^\s+(?:#\s*)?(\S+) = ', l)
if m:
name = confsect + '.' + m.group(1)
documented[name] = 1
# like the bugzilla extension
m = re.match(r'^\s*(\S+\.\S+)$', l)
if m:
documented[m.group(1)] = 1
# like convert
m = re.match(r'^\s*:(\S+\.\S+):\s+', l)
if m:
documented[m.group(1)] = 1
# quoted in help or docstrings
m = re.match(r'.*?``(\S+\.\S+)``', l)
if m:
documented[m.group(1)] = 1
# look for ignore markers
m = re.search(r'# (?:internal|experimental|deprecated|developer)'
' config: (\S+\.\S+)$', l)
if m:
documented[m.group(1)] = 1
# look for code-like bits
line = carryover + l
m = re.search(configre, line, re.MULTILINE)
if m:
ctype = m.group(1)
if not ctype:
ctype = 'str'
name = m.group(2) + "." + m.group(3)
default = m.group(5)
if default in (None, 'False', 'None', '0', '[]', '""', "''"):
default = ''
if re.match('[a-z.]+$', default):
default = '<variable>'
if name in foundopts and (ctype, default) != foundopts[name]:
print(l)
print("conflict on %s: %r != %r" % (name, (ctype, default),
foundopts[name]))
foundopts[name] = (ctype, default)
carryover = ''
else:
m = re.search(configpartialre, line)
if m:
carryover = line
else:
carryover = ''
for name in sorted(foundopts):
if name not in documented:
if not (name.startswith("devel.") or
name.startswith("experimental.") or
name.startswith("debug.")):
ctype, default = foundopts[name]
if default:
default = ' [%s]' % default
print("undocumented: %s (%s)%s" % (name, ctype, default))
if __name__ == "__main__":
if len(sys.argv) > 1:
sys.exit(main(sys.argv[1:]))
else:
sys.exit(main([l.rstrip() for l in sys.stdin]))