sapling/hgext/fbconduit.py
Jun Wu f1c575a099 flake8: enable F821 check
Summary:
This check is useful and detects real errors (ex. fbconduit).  Unfortunately
`arc lint` will run it with both py2 and py3 so a lot of py2 builtins will
still be warned.

I didn't find a clean way to disable py3 check. So this diff tries to fix them.
For `xrange`, the change was done by a script:

```
import sys
import redbaron

headertypes = {'comment', 'endl', 'from_import', 'import', 'string',
               'assignment', 'atomtrailers'}

xrangefix = '''try:
    xrange(0)
except NameError:
    xrange = range

'''

def isxrange(x):
    try:
        return x[0].value == 'xrange'
    except Exception:
        return False

def main(argv):
    for i, path in enumerate(argv):
        print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
        content = open(path).read()
        try:
            red = redbaron.RedBaron(content)
        except Exception:
            print('  warning: failed to parse')
            continue
        hasxrange = red.find('atomtrailersnode', value=isxrange)
        hasxrangefix = 'xrange = range' in content
        if hasxrangefix or not hasxrange:
            print('  no need to change')
            continue

        # find a place to insert the compatibility  statement
        changed = False
        for node in red:
            if node.type in headertypes:
                continue
            # node.insert_before is an easier API, but it has bugs changing
            # other "finally" and "except" positions. So do the insert
            # manually.
            # # node.insert_before(xrangefix)
            line = node.absolute_bounding_box.top_left.line - 1
            lines = content.splitlines(1)
            content = ''.join(lines[:line]) + xrangefix + ''.join(lines[line:])
            changed = True
            break

        if changed:
            # "content" is faster than "red.dumps()"
            open(path, 'w').write(content)
            print('  updated')

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
```

For other py2 builtins that do not have a py3 equivalent, some `# noqa`
were added as a workaround for now.

Reviewed By: DurhamG

Differential Revision: D6934535

fbshipit-source-id: 546b62830af144bc8b46788d2e0fd00496838939
2018-04-13 21:51:09 -07:00

272 lines
8.1 KiB
Python

# fbconduit.py
#
# An extension to query remote servers for extra information via conduit RPC
#
# Copyright 2015 Facebook, Inc.
from mercurial import (
error,
extensions,
node,
registrar,
revset,
templater,
)
from mercurial.i18n import _
import re
import json
from urllib import urlencode
from mercurial.util import httplib
conduit_host = None
conduit_path = None
conduit_protocol = None
connection = None
DEFAULT_TIMEOUT = 60
MAX_CONNECT_RETRIES = 3
try:
xrange(0)
except NameError:
xrange = range
class ConduitError(Exception):
pass
class HttpError(Exception):
pass
githashre = re.compile('g([0-9a-fA-F]{12,40})')
phabhashre = re.compile('^r([A-Z]+)([0-9a-f]{12,40})$')
fbsvnhash = re.compile('^r[A-Z]+(\d+)$')
def extsetup(ui):
if not conduit_config(ui):
ui.warn(_('No conduit host specified in config; disabling fbconduit\n'))
return
revset.symbols['gitnode'] = gitnode
extensions.wrapfunction(revset, 'stringset', overridestringset)
revset.methods['string'] = revset.stringset
revset.methods['symbol'] = revset.stringset
def conduit_config(ui, host=None, path=None, protocol=None):
global conduit_host, conduit_path, conduit_protocol
conduit_host = host or ui.config('fbconduit', 'host')
conduit_path = path or ui.config('fbconduit', 'path')
conduit_protocol = protocol or ui.config('fbconduit', 'protocol')
if conduit_host is None:
return False
if conduit_protocol is None:
conduit_protocol = 'https'
return True
def call_conduit(method, timeout=DEFAULT_TIMEOUT, **kwargs):
global connection, conduit_host, conduit_path, conduit_protocol
# start connection
# TODO: move to python-requests
if connection is None:
if conduit_protocol == 'https':
connection = httplib.HTTPSConnection(conduit_host, timeout=timeout)
elif conduit_protocol == 'http':
connection = httplib.HTTPConnection(conduit_host, timeout=timeout)
# send request
path = conduit_path + method
args = urlencode({'params': json.dumps(kwargs)})
headers = {
'Connection': 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded',
}
e = None
for attempt in xrange(MAX_CONNECT_RETRIES):
try:
connection.request('POST', path, args, headers)
break
except httplib.HTTPException as e:
connection.connect()
if e:
raise e
# read http response
response = connection.getresponse()
if response.status != 200:
raise HttpError(response.reason)
result = response.read()
# strip jsonp header and parse
assert result.startswith('for(;;);')
result = json.loads(result[8:])
# check for conduit errors
if result['error_code']:
raise ConduitError(result['error_info'])
# return RPC result
return result['result']
# don't close the connection b/c we want to avoid the connection overhead
@templater.templatefunc('mirrornode')
def mirrornode(ctx, mapping, args):
'''template: find this commit in other repositories'''
reponame = mapping['repo'].ui.config('fbconduit', 'reponame')
if not reponame:
# We don't know who we are, so we can't ask for a translation
return ''
if mapping['ctx'].mutable():
# Local commits don't have translations
return ''
node = mapping['ctx'].hex()
args = [f(ctx, mapping, a) for f, a in args]
if len(args) == 1:
torepo, totype = reponame, args[0]
else:
torepo, totype = args
try:
result = call_conduit('scmquery.get.mirrored.revs',
from_repo=reponame,
from_scm='hg',
to_repo=torepo,
to_scm=totype,
revs=[node]
)
except ConduitError as e:
if 'unknown revision' not in str(e.args):
mapping['repo'].ui.warn((str(e.args) + '\n'))
return ''
return result.get(node, '')
templatekeyword = registrar.templatekeyword()
@templatekeyword("gitnode")
def showgitnode(repo, ctx, templ, **args):
"""Return the git revision corresponding to a given hg rev"""
reponame = repo.ui.config('fbconduit', 'reponame')
if not reponame:
# We don't know who we are, so we can't ask for a translation
return ''
backingrepos = repo.ui.configlist('fbconduit', 'backingrepos',
default=[reponame])
if ctx.mutable():
# Local commits don't have translations
return ''
matches = []
for backingrepo in backingrepos:
try:
result = call_conduit('scmquery.get.mirrored.revs',
from_repo=reponame,
from_scm='hg',
to_repo=backingrepo,
to_scm='git',
revs=[ctx.hex()]
)
githash = result[ctx.hex()]
if githash != "":
matches.append((backingrepo, githash))
except ConduitError:
pass
if len(matches) == 0:
return ''
elif len(backingrepos) == 1:
return matches[0][1]
else:
# in case it's not clear, the sort() is to ensure the output is in a
# deterministic order.
matches.sort()
return "; ".join(["{0}: {1}".format(*match)
for match in matches])
def gitnode(repo, subset, x):
"""``gitnode(id)``
Return the hg revision corresponding to a given git rev."""
l = revset.getargs(x, 1, 1, _("id requires one argument"))
n = revset.getstring(l[0], _("id requires a string"))
reponame = repo.ui.config('fbconduit', 'reponame')
if not reponame:
# We don't know who we are, so we can't ask for a translation
return subset.filter(lambda r: False)
backingrepos = repo.ui.configlist('fbconduit', 'backingrepos',
default=[reponame])
lasterror = None
hghash = None
for backingrepo in backingrepos:
try:
result = call_conduit('scmquery.get.mirrored.revs',
from_repo=backingrepo,
from_scm='git',
to_repo=reponame,
to_scm='hg',
revs=[n]
)
hghash = result[n]
if hghash != '':
break
except Exception as ex:
lasterror = ex
if not hghash:
if lasterror:
repo.ui.warn(("Could not translate revision {0}: {1}\n".format(
n, lasterror)))
else:
repo.ui.warn(("Could not translate revision {0}\n".format(n)))
return subset.filter(lambda r: False)
rn = repo[node.bin(hghash)].rev()
return subset.filter(lambda r: r == rn)
def overridestringset(orig, repo, subset, x, *args, **kwargs):
# Is the given revset a phabricator hg hash (ie: rHGEXTaaacb34aacb34aa)
phabmatch = phabhashre.match(x)
if phabmatch:
phabrepo = phabmatch.group(1)
phabhash = phabmatch.group(2)
# The hash may be a git hash
if phabrepo in repo.ui.configlist('fbconduit', 'gitcallsigns', []):
return overridestringset(orig, repo, subset, 'g%s' % phabhash)
if phabhash in repo:
return orig(repo, subset, phabhash, *args, **kwargs)
# Is the given revset a phabricator svn revision (rO11223232323232)?
svnrev = fbsvnhash.match(x)
if svnrev and not x in repo:
try:
extensions.find('hgsubversion')
meta = repo.svnmeta()
desiredrevision = int(svnrev.group(1))
# For some odd reason, the key is a tuple instead of a revision num
# The second member always seems to be None
revmapkey = (desiredrevision, None)
hghash = meta.revmap.get(revmapkey)
if hghash:
return orig(repo, subset, hghash)
except KeyError:
pass
m = githashre.match(x)
if m is not None:
githash = m.group(1)
if len(githash) == 40:
return gitnode(repo, subset, ('string', githash))
else:
raise error.Abort('git hash must be 40 characters')
return orig(repo, subset, x, *args, **kwargs)