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')