sapling/phabstatus.py
Laurent Charignon 35d3252f3f phabstatus: improve reliability when conduit is down or sending errors
Summary:
This patches makes "hg sl" and requesting diff status in general more
reliable. We display "Error" for the diff status and log the error in the
console.

    $ ~/facebook-hg-rpms/fb-hgext hg ssl
    Could not call "arc call-conduit" No JSON object could be decoded
    @  8844c9  lcharignon  D2824091  Error
    |  phabstatus: improve reliability when conduit is down or sending errors
    |
    o  252837  lcharignon  D2821846  Error  remote/@
    |  setup: remove writecg2 from list of module
    |
    .
    .
    |
    | o  9ad683  ericsumner  remote/stable
    | |  pushrebase: add HG_PENDING to test
    | |
    | o  e2b354  ericsumner
    |/   writecg2: update test, no longer writing CG2 bundles
    |
    o  fcd19f  durham
    |  Add dirsync to setup.py
    |
    note: hiding 7 old heads without bookmarks


Test Plan: Checked with wifi off that the message is showed

Reviewers: #sourcecontrol, ttung

Differential Revision: https://phabricator.fb.com/D2824091
2016-01-14 10:45:47 -08:00

139 lines
4.8 KiB
Python

# phabstatus.py
#
# Copyright 2015 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 mercurial import templatekw, extensions
from mercurial import util as hgutil
from mercurial.i18n import _
import re
import subprocess
import os
import json
def memoize(f):
"""
NOTE: This is a hack
if f args are like (a, b1, b2, b3) and returns [o1, o2, o3] where
o1, o2, o3 are output of f respectively for (a, b1), (a, b2) and
(a, b3) then we memoize f(a, b1, b2, b3)'s result but also
f(a, b1) => o1 , f(a, b2) => o2 and f(a, b3) => o3.
Example:
>>> partialsum = lambda a, *b: [a + bn for bn in b]
>>> partialsum = memoize(partialsum)
Create a class that wraps the integer '3', otherwise we cannot add
_phabstatuscache to it for the test
>>> class IntWrapperClass(int):
... def __new__(cls, *args, **kwargs):
... return super(IntWrapperClass, cls).__new__(cls, 3)
>>> three = IntWrapperClass()
>>> partialsum(three, 1, 2, 3)
[4, 5, 6]
As expected, we have 4 entries in the cache for a call like f(a, b, c, d)
>>> pp(three._phabstatuscache)
{(3, 1): [4], (3, 1, 2, 3): [4, 5, 6], (3, 2): [5], (3, 3): [6]}
"""
def helper(*args):
repo = args[0]
if not hgutil.safehasattr(repo, '_phabstatuscache'):
repo._phabstatuscache = {}
if args not in repo._phabstatuscache:
u = f(*args)
repo._phabstatuscache[args] = u
if isinstance(u, list):
revs = args[1:]
for x, r in enumerate(revs):
repo._phabstatuscache[(repo, r)] = [u[x]]
return repo._phabstatuscache[args]
return helper
@memoize
def getdiffstatus(repo, *diffid):
"""Perform a Conduit API call by shelling out to `arc`
Returns status of the diff"""
try:
proc = subprocess.Popen(['arc', 'call-conduit', 'differential.query'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
preexec_fn=os.setsid)
input = json.dumps({'ids': diffid})
repo.ui.debug("[diffrev] echo '%s' | "
"arc call-conduit differential.query\n" %
input)
proc.stdin.write(input)
proc.stdin.close()
resp = proc.stdout.read()
jsresp = json.loads(resp)
if not jsresp:
return 'Could not decode Conduit response'
resp = jsresp.get('response')
if not resp:
error = jsresp.get('errorMessage', 'unknown error')
repo.ui.warn("%s\n" % error)
return ["Error"] * len(diffid)
# This makes the code more robust in case conduit does not return
# what we need
result = []
for diff in diffid:
matchingresponse = [r for r in resp
if r.get("id", None) == int(diff)]
if not matchingresponse:
result.append("Error")
else:
result.append(matchingresponse[0].get('statusName'))
return result
except Exception as e:
msg = _('Could not call "arc call-conduit". No diff information can be '
'provided.\n')
hint = _("(Possibly disconnected operations or phabricator problems)\n")
repo.ui.warn(msg)
repo.ui.warn(hint)
return ["Error"] * len(diffid)
def showphabstatus(repo, ctx, templ, **args):
""":phabstatus: String. Return the diff approval status for a given hg rev
"""
if hgutil.safehasattr(repo, '_smartlogrevs'):
alldiffnumbers = [getdiffnum(repo, repo[rev])
for rev in repo._smartlogrevs]
okdiffnumbers = [d for d in alldiffnumbers if d is not None]
# To populate the cache, the result will be used by the templater
getdiffstatus(repo, *okdiffnumbers)
# Do this once per smartlog call, not for every revs to be displayed
del repo._smartlogrevs
diffnum = getdiffnum(repo, ctx)
if diffnum is not None:
return getdiffstatus(repo, diffnum)[0]
def getdiffnum(repo, ctx):
descr = ctx.description()
match = re.search('Differential Revision: https://phabricator.fb.com/(D\d+)'
, descr)
revstr = match.group(1) if match else ''
if revstr.startswith('D') and revstr[1:].isdigit():
return revstr[1:]
return None
def _getdag(orig, *args):
repo = args[1]
# We retain the smartlogrevision, this way showphabstatus knows that there
# are multiple revisions to resolve
repo._smartlogrevs = args[2]
return orig(*args)
def extsetup(ui):
templatekw.keywords['phabstatus'] = showphabstatus
smartlog = extensions.find("smartlog")
extensions.wrapfunction(smartlog, 'getdag', _getdag)