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
This commit is contained in:
Eric Sumner 2015-05-13 16:24:51 -07:00
parent 6653644dc8
commit cc30c1fd65

103
fbconduit.py Normal file
View File

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