From cc30c1fd65b7103689e8b997761584a0a2410b73 Mon Sep 17 00:00:00 2001 From: Eric Sumner Date: Wed, 13 May 2015 16:24:51 -0700 Subject: [PATCH] fbconduit extension Summary: Adds a template method to fetch hash translations from scmquery Test Plan: [ericsumner@dev2048 ~/fbcode] time hg log -T '{node} {mirrornode("git")}\n' --config extensions.conduit=~/fb-hgext/fbconduit.py -r 'master~5::master' --config fbconduit.reponame=fbcode 744def84d4821ae847ca9e2c31147ec495b0ff2a e498cef35914c5a7a8de648888e47b0d6c452044 86d589bbed310cac765d1d2822f350c66f55ee30 a49473c0f0a0eaa532f0f25f8e632015dc57de87 74e5eddf18a9e911b561d2f2f79c433971e36234 608e41cd9c4e8a0ba8dcd3f53162fed656354db0 95a4cce9686558864af97631142a23eee1ffd1b0 228cbfc779da14306ae43002317bf59be9b3844d 9ec5dfd21c5360bcff66ddd3db4747742da9bd56 115df93aa18a5003323f5419bbd135a9a08d96b9 721fd7f98313aec4513bf86817bf98e01238ebdc 170cde24839d9213efa11f4f965402fd5e13cc9b real 0m0.407s user 0m0.229s sys 0m0.080s [ericsumner@dev2048 ~/fbcode] time hg log -T '{node} {mirrornode("git")}\n' --config extensions.conduit=~/fb-hgext/fbconduit.py -r 'ancestor(.,master)::master' --config fbconduit.reponame=fbcode | wc -l 157 real 0m2.718s user 0m0.504s sys 0m0.066s Reviewers: #sourcecontrol, durham Reviewed By: #sourcecontrol, durham Subscribers: sid0, durham, lcharignon Differential Revision: https://phabricator.fb.com/D2070465 --- fbconduit.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 fbconduit.py diff --git a/fbconduit.py b/fbconduit.py new file mode 100644 index 0000000000..ae02549c68 --- /dev/null +++ b/fbconduit.py @@ -0,0 +1,103 @@ +# fbconduit.py +# +# An extension to query remote servers for extra information via conduit RPC +# +# Copyright 2015 Facebook, Inc. + +from mercurial import templater +import json +from urllib import urlencode +import httplib + +conduit_host = None +conduit_path = None +connection = None + +MAX_CONNECT_RETRIES = 3 + +class ConduitError(Exception): + pass + +class HttpError(Exception): + pass + +def extsetup(ui): + global conduit_host, conduit_path + conduit_host = ui.config('fbconduit', 'host') + conduit_path = ui.config('fbconduit', 'path') + + if not conduit_host: + ui.warn('No conduit host specified in config; disabling fbconduit\n') + return + templater.funcs['mirrornode'] = mirrornode + +def _call_conduit(method, **kwargs): + global connection, conduit_host, conduit_path + + # start connection + if connection is None: + connection = httplib.HTTPSConnection(conduit_host) + + # send request + path = conduit_path + method + args = urlencode({'params': json.dumps(kwargs)}) + for attempt in xrange(MAX_CONNECT_RETRIES): + try: + connection.request('POST', path, args, {'Connection': 'Keep-Alive'}) + break; + except httplib.HTTPException as e: + connection.connect() + else: + 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 + +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, '')