Mercurial > public > mercurial-scm > hg
comparison mercurial/localrepo.py @ 33499:0407a51b9d8c
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# 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 os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
author | Jun Wu <quark@fb.com> |
---|---|
date | Fri, 14 Jul 2017 14:22:40 -0700 |
parents | 3c20d7ef42e1 |
children | f9e6e43c7987 |
comparison
equal
deleted
inserted
replaced
33498:b7a75b9a3386 | 33499:0407a51b9d8c |
---|---|
530 def _writecaches(self): | 530 def _writecaches(self): |
531 if self._revbranchcache: | 531 if self._revbranchcache: |
532 self._revbranchcache.write() | 532 self._revbranchcache.write() |
533 | 533 |
534 def _restrictcapabilities(self, caps): | 534 def _restrictcapabilities(self, caps): |
535 if self.ui.configbool('experimental', 'bundle2-advertise', True): | 535 if self.ui.configbool('experimental', 'bundle2-advertise'): |
536 caps = set(caps) | 536 caps = set(caps) |
537 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self)) | 537 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self)) |
538 caps.add('bundle2=' + urlreq.quote(capsblob)) | 538 caps.add('bundle2=' + urlreq.quote(capsblob)) |
539 return caps | 539 return caps |
540 | 540 |
949 return self | 949 return self |
950 | 950 |
951 def publishing(self): | 951 def publishing(self): |
952 # it's safe (and desirable) to trust the publish flag unconditionally | 952 # it's safe (and desirable) to trust the publish flag unconditionally |
953 # so that we don't finalize changes shared between users via ssh or nfs | 953 # so that we don't finalize changes shared between users via ssh or nfs |
954 return self.ui.configbool('phases', 'publish', True, untrusted=True) | 954 return self.ui.configbool('phases', 'publish', untrusted=True) |
955 | 955 |
956 def cancopy(self): | 956 def cancopy(self): |
957 # so statichttprepo's override of local() works | 957 # so statichttprepo's override of local() works |
958 if not self.local(): | 958 if not self.local(): |
959 return False | 959 return False |
1147 # "+A": tag is added, | 1147 # "+A": tag is added, |
1148 # "-M": tag is moved (old value), | 1148 # "-M": tag is moved (old value), |
1149 # "+M": tag is moved (new value), | 1149 # "+M": tag is moved (new value), |
1150 tracktags = lambda x: None | 1150 tracktags = lambda x: None |
1151 # experimental config: experimental.hook-track-tags | 1151 # experimental config: experimental.hook-track-tags |
1152 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags', | 1152 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags') |
1153 False) | |
1154 if desc != 'strip' and shouldtracktags: | 1153 if desc != 'strip' and shouldtracktags: |
1155 oldheads = self.changelog.headrevs() | 1154 oldheads = self.changelog.headrevs() |
1156 def tracktags(tr2): | 1155 def tracktags(tr2): |
1157 repo = reporef() | 1156 repo = reporef() |
1158 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads) | 1157 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads) |
1504 else: | 1503 else: |
1505 self.ui.warn(_("waiting for lock on %s held by %r\n") % | 1504 self.ui.warn(_("waiting for lock on %s held by %r\n") % |
1506 (desc, inst.locker)) | 1505 (desc, inst.locker)) |
1507 # default to 600 seconds timeout | 1506 # default to 600 seconds timeout |
1508 l = lockmod.lock(vfs, lockname, | 1507 l = lockmod.lock(vfs, lockname, |
1509 int(self.ui.config("ui", "timeout", "600")), | 1508 int(self.ui.config("ui", "timeout")), |
1510 releasefn=releasefn, acquirefn=acquirefn, | 1509 releasefn=releasefn, acquirefn=acquirefn, |
1511 desc=desc) | 1510 desc=desc) |
1512 self.ui.warn(_("got lock after %s seconds\n") % l.delay) | 1511 self.ui.warn(_("got lock after %s seconds\n") % l.delay) |
1513 return l | 1512 return l |
1514 | 1513 |
2216 if ui.configbool('format', 'usefncache'): | 2215 if ui.configbool('format', 'usefncache'): |
2217 requirements.add('fncache') | 2216 requirements.add('fncache') |
2218 if ui.configbool('format', 'dotencode'): | 2217 if ui.configbool('format', 'dotencode'): |
2219 requirements.add('dotencode') | 2218 requirements.add('dotencode') |
2220 | 2219 |
2221 compengine = ui.config('experimental', 'format.compression', 'zlib') | 2220 compengine = ui.config('experimental', 'format.compression') |
2222 if compengine not in util.compengines: | 2221 if compengine not in util.compengines: |
2223 raise error.Abort(_('compression engine %s defined by ' | 2222 raise error.Abort(_('compression engine %s defined by ' |
2224 'experimental.format.compression not available') % | 2223 'experimental.format.compression not available') % |
2225 compengine, | 2224 compengine, |
2226 hint=_('run "hg debuginstall" to list available ' | 2225 hint=_('run "hg debuginstall" to list available ' |
2230 if compengine != 'zlib': | 2229 if compengine != 'zlib': |
2231 requirements.add('exp-compression-%s' % compengine) | 2230 requirements.add('exp-compression-%s' % compengine) |
2232 | 2231 |
2233 if scmutil.gdinitconfig(ui): | 2232 if scmutil.gdinitconfig(ui): |
2234 requirements.add('generaldelta') | 2233 requirements.add('generaldelta') |
2235 if ui.configbool('experimental', 'treemanifest', False): | 2234 if ui.configbool('experimental', 'treemanifest'): |
2236 requirements.add('treemanifest') | 2235 requirements.add('treemanifest') |
2237 if ui.configbool('experimental', 'manifestv2', False): | 2236 if ui.configbool('experimental', 'manifestv2'): |
2238 requirements.add('manifestv2') | 2237 requirements.add('manifestv2') |
2239 | 2238 |
2240 revlogv2 = ui.config('experimental', 'revlogv2') | 2239 revlogv2 = ui.config('experimental', 'revlogv2') |
2241 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data': | 2240 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data': |
2242 requirements.remove('revlogv1') | 2241 requirements.remove('revlogv1') |