comparison mercurial/exchange.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 78fc540c53e1
children 03039ff3082b
comparison
equal deleted inserted replaced
33498:b7a75b9a3386 33499:0407a51b9d8c
545 (computed for both success and failure case for changesets push)""" 545 (computed for both success and failure case for changesets push)"""
546 outgoing = pushop.outgoing 546 outgoing = pushop.outgoing
547 unfi = pushop.repo.unfiltered() 547 unfi = pushop.repo.unfiltered()
548 remotephases = pushop.remote.listkeys('phases') 548 remotephases = pushop.remote.listkeys('phases')
549 publishing = remotephases.get('publishing', False) 549 publishing = remotephases.get('publishing', False)
550 if (pushop.ui.configbool('ui', '_usedassubrepo', False) 550 if (pushop.ui.configbool('ui', '_usedassubrepo')
551 and remotephases # server supports phases 551 and remotephases # server supports phases
552 and not pushop.outgoing.missing # no changesets to be pushed 552 and not pushop.outgoing.missing # no changesets to be pushed
553 and publishing): 553 and publishing):
554 # When: 554 # When:
555 # - this is a subrepo push 555 # - this is a subrepo push
991 def _pushsyncphase(pushop): 991 def _pushsyncphase(pushop):
992 """synchronise phase information locally and remotely""" 992 """synchronise phase information locally and remotely"""
993 cheads = pushop.commonheads 993 cheads = pushop.commonheads
994 # even when we don't push, exchanging phase data is useful 994 # even when we don't push, exchanging phase data is useful
995 remotephases = pushop.remote.listkeys('phases') 995 remotephases = pushop.remote.listkeys('phases')
996 if (pushop.ui.configbool('ui', '_usedassubrepo', False) 996 if (pushop.ui.configbool('ui', '_usedassubrepo')
997 and remotephases # server supports phases 997 and remotephases # server supports phases
998 and pushop.cgresult is None # nothing was pushed 998 and pushop.cgresult is None # nothing was pushed
999 and remotephases.get('publishing', False)): 999 and remotephases.get('publishing', False)):
1000 # When: 1000 # When:
1001 # - this is a subrepo push 1001 # - this is a subrepo push
1724 # need a transaction when processing a bundle2 stream 1724 # need a transaction when processing a bundle2 stream
1725 # [wlock, lock, tr] - needs to be an array so nested functions can modify it 1725 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1726 lockandtr = [None, None, None] 1726 lockandtr = [None, None, None]
1727 recordout = None 1727 recordout = None
1728 # quick fix for output mismatch with bundle2 in 3.4 1728 # quick fix for output mismatch with bundle2 in 3.4
1729 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture', 1729 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
1730 False)
1731 if url.startswith('remote:http:') or url.startswith('remote:https:'): 1730 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1732 captureoutput = True 1731 captureoutput = True
1733 try: 1732 try:
1734 # note: outside bundle1, 'heads' is expected to be empty and this 1733 # note: outside bundle1, 'heads' is expected to be empty and this
1735 # 'check_heads' call wil be a no-op 1734 # 'check_heads' call wil be a no-op
1790 """Apply a clone bundle from a remote, if possible.""" 1789 """Apply a clone bundle from a remote, if possible."""
1791 1790
1792 repo = pullop.repo 1791 repo = pullop.repo
1793 remote = pullop.remote 1792 remote = pullop.remote
1794 1793
1795 if not repo.ui.configbool('ui', 'clonebundles', True): 1794 if not repo.ui.configbool('ui', 'clonebundles'):
1796 return 1795 return
1797 1796
1798 # Only run if local repo is empty. 1797 # Only run if local repo is empty.
1799 if len(repo): 1798 if len(repo):
1800 return 1799 return
1839 # Bundle failed. 1838 # Bundle failed.
1840 # 1839 #
1841 # We abort by default to avoid the thundering herd of 1840 # We abort by default to avoid the thundering herd of
1842 # clients flooding a server that was expecting expensive 1841 # clients flooding a server that was expecting expensive
1843 # clone load to be offloaded. 1842 # clone load to be offloaded.
1844 elif repo.ui.configbool('ui', 'clonebundlefallback', False): 1843 elif repo.ui.configbool('ui', 'clonebundlefallback'):
1845 repo.ui.warn(_('falling back to normal clone\n')) 1844 repo.ui.warn(_('falling back to normal clone\n'))
1846 else: 1845 else:
1847 raise error.Abort(_('error applying bundle'), 1846 raise error.Abort(_('error applying bundle'),
1848 hint=_('if this error persists, consider contacting ' 1847 hint=_('if this error persists, consider contacting '
1849 'the server operator or disable clone ' 1848 'the server operator or disable clone '