2015-11-10 00:24:13 +03:00
|
|
|
# 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.
|
|
|
|
|
2016-01-11 21:30:13 +03:00
|
|
|
from mercurial import templatekw, extensions
|
2015-11-10 00:24:13 +03:00
|
|
|
from mercurial import util as hgutil
|
2016-01-14 21:45:47 +03:00
|
|
|
from mercurial.i18n import _
|
2016-08-05 21:33:36 +03:00
|
|
|
from mercurial import obsolete
|
2015-11-10 00:24:13 +03:00
|
|
|
|
2016-04-27 19:50:13 +03:00
|
|
|
from phabricator import (
|
|
|
|
arcconfig,
|
|
|
|
conduit,
|
|
|
|
diffprops,
|
|
|
|
)
|
2016-04-27 19:27:56 +03:00
|
|
|
|
2015-11-17 18:13:12 +03:00
|
|
|
def memoize(f):
|
2016-01-11 21:30:13 +03:00
|
|
|
"""
|
|
|
|
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]}
|
|
|
|
"""
|
2015-11-17 18:13:12 +03:00
|
|
|
def helper(*args):
|
|
|
|
repo = args[0]
|
|
|
|
if not hgutil.safehasattr(repo, '_phabstatuscache'):
|
|
|
|
repo._phabstatuscache = {}
|
|
|
|
if args not in repo._phabstatuscache:
|
2016-01-11 21:30:13 +03:00
|
|
|
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]]
|
2015-11-17 18:13:12 +03:00
|
|
|
return repo._phabstatuscache[args]
|
|
|
|
return helper
|
|
|
|
|
2016-04-06 22:49:43 +03:00
|
|
|
def _fail(repo, diffids, *msgs):
|
|
|
|
for msg in msgs:
|
|
|
|
repo.ui.warn(msg)
|
|
|
|
return ["Error"] * len(diffids)
|
|
|
|
|
|
|
|
|
2015-11-17 18:13:12 +03:00
|
|
|
@memoize
|
2016-01-11 21:30:13 +03:00
|
|
|
def getdiffstatus(repo, *diffid):
|
2016-04-27 19:27:56 +03:00
|
|
|
"""Perform a Conduit API call to get the diff status
|
2015-11-10 00:24:13 +03:00
|
|
|
|
|
|
|
Returns status of the diff"""
|
|
|
|
|
2016-04-06 22:49:43 +03:00
|
|
|
if not diffid:
|
|
|
|
return []
|
|
|
|
timeout = repo.ui.configint('ssl', 'timeout', 5)
|
2016-04-27 19:27:56 +03:00
|
|
|
|
2015-11-10 00:24:13 +03:00
|
|
|
try:
|
2016-08-05 21:33:36 +03:00
|
|
|
resp = conduit.call_conduit('differential.querydiffhashes',
|
|
|
|
{'revisionIDs': diffid},
|
|
|
|
timeout=timeout)
|
2016-04-06 22:49:43 +03:00
|
|
|
|
2016-04-27 19:27:56 +03:00
|
|
|
except conduit.ClientError as ex:
|
2016-04-06 22:49:43 +03:00
|
|
|
msg = _('Error talking to phabricator. No diff information can be '
|
2016-01-14 21:45:47 +03:00
|
|
|
'provided.\n')
|
2016-04-27 19:27:56 +03:00
|
|
|
hint = _("Error info: ") + str(ex)
|
|
|
|
return _fail(repo, diffid, msg, hint)
|
|
|
|
except arcconfig.ArcConfigError as ex:
|
|
|
|
msg = _('arcconfig configuration problem. No diff information can be '
|
|
|
|
'provided.\n')
|
|
|
|
hint = _("Error info: ") + str(ex)
|
2016-04-06 22:49:43 +03:00
|
|
|
return _fail(repo, diffid, msg, hint)
|
|
|
|
|
|
|
|
if not resp:
|
2016-08-05 21:33:36 +03:00
|
|
|
resp = {}
|
2016-04-06 22:49:43 +03:00
|
|
|
|
|
|
|
# This makes the code more robust in case conduit does not return
|
|
|
|
# what we need
|
|
|
|
result = []
|
|
|
|
for diff in diffid:
|
2016-08-05 21:33:36 +03:00
|
|
|
matchingresponse = resp.get(diff)
|
2016-04-06 22:49:43 +03:00
|
|
|
if not matchingresponse:
|
|
|
|
result.append("Error")
|
|
|
|
else:
|
2016-08-05 21:33:36 +03:00
|
|
|
result.append(matchingresponse)
|
2016-04-06 22:49:43 +03:00
|
|
|
return result
|
2015-11-10 00:24:13 +03:00
|
|
|
|
2016-08-05 21:33:36 +03:00
|
|
|
def populateresponseforphab(repo, ctx):
|
|
|
|
""":populateresponse: Runs the memoization function
|
|
|
|
for use of phabstatus and sync status
|
2015-12-08 22:28:38 +03:00
|
|
|
"""
|
2016-01-11 21:30:13 +03:00
|
|
|
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
|
|
|
|
|
2016-08-05 21:33:36 +03:00
|
|
|
def showphabstatus(repo, ctx, templ, **args):
|
|
|
|
""":phabstatus: String. Return the diff approval status for a given hg rev
|
|
|
|
"""
|
|
|
|
populateresponseforphab(repo, ctx)
|
|
|
|
|
2016-01-11 21:30:13 +03:00
|
|
|
diffnum = getdiffnum(repo, ctx)
|
|
|
|
if diffnum is not None:
|
2016-08-05 21:33:36 +03:00
|
|
|
result = getdiffstatus(repo, diffnum)[0]
|
|
|
|
if isinstance(result, dict) and "status" in result:
|
|
|
|
return result.get("status")
|
|
|
|
else:
|
|
|
|
return "Error"
|
|
|
|
|
|
|
|
"""
|
2016-09-21 17:45:25 +03:00
|
|
|
In order to determine whether the local changeset is in sync with the
|
|
|
|
remote one we compare the hash of the current changeset with the one we
|
2016-08-05 21:33:36 +03:00
|
|
|
get from the remote (phabricator) repo. There are three different cases
|
|
|
|
and we deal with them seperately.
|
|
|
|
1) If this is the first revision in a diff: We look at the count field and
|
2016-09-21 17:45:25 +03:00
|
|
|
understand that this is the first changeset, so we compare the hash we get
|
|
|
|
from remote repo with the predessesor's hash from the local changeset. The
|
|
|
|
reason for that is the D number is ammended on the changeset after it is
|
2016-08-05 21:33:36 +03:00
|
|
|
sent to phabricator.
|
|
|
|
2) If this is the last revision, i.e. it is alread committed: Then we
|
|
|
|
don't say anything. All good.
|
|
|
|
3) If this is a middle revision: Then we compare the hashes as regular.
|
|
|
|
"""
|
|
|
|
def showsyncstatus(repo, ctx, templ, **args):
|
|
|
|
""":syncstatus: String. Return whether the local revision is in sync
|
|
|
|
with the remote (phabricator) revision
|
|
|
|
"""
|
|
|
|
populateresponseforphab(repo, ctx)
|
|
|
|
|
|
|
|
diffnum = getdiffnum(repo, ctx)
|
|
|
|
local = ctx.hex()
|
|
|
|
if diffnum is not None:
|
|
|
|
result = getdiffstatus(repo, diffnum)[0]
|
|
|
|
|
|
|
|
if isinstance(result, dict) and "hash" in result \
|
|
|
|
and "status" in result and "count" in result:
|
|
|
|
remote = getdiffstatus(repo, diffnum)[0].get("hash")
|
|
|
|
status = getdiffstatus(repo, diffnum)[0].get("status")
|
|
|
|
count = int(getdiffstatus(repo, diffnum)[0].get("count"))
|
|
|
|
|
|
|
|
if local == remote:
|
|
|
|
return "sync"
|
|
|
|
elif count == 1:
|
|
|
|
precursors = list(obsolete.allprecursors(repo.obsstore,
|
|
|
|
[ctx.node()]))
|
|
|
|
hashes = [repo.unfiltered()[h].hex() for h in precursors]
|
|
|
|
# hashes[0] is the current
|
|
|
|
# hashes[1] is the previous
|
|
|
|
if len(hashes) > 1 and hashes[1] == remote:
|
|
|
|
return "sync"
|
|
|
|
else:
|
|
|
|
return "unsync"
|
|
|
|
elif status == "Committed":
|
|
|
|
return "committed"
|
|
|
|
else:
|
|
|
|
return "unsync"
|
|
|
|
else:
|
|
|
|
return "Error"
|
2016-01-11 21:30:13 +03:00
|
|
|
|
|
|
|
def getdiffnum(repo, ctx):
|
2016-04-27 19:50:13 +03:00
|
|
|
return diffprops.parserevfromcommitmsg(ctx.description())
|
2015-11-10 00:24:13 +03:00
|
|
|
|
2016-01-11 21:30:13 +03:00
|
|
|
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)
|
2015-11-10 00:24:13 +03:00
|
|
|
|
|
|
|
def extsetup(ui):
|
|
|
|
templatekw.keywords['phabstatus'] = showphabstatus
|
2016-08-05 21:33:36 +03:00
|
|
|
templatekw.keywords['syncstatus'] = showsyncstatus
|
2016-01-11 21:30:13 +03:00
|
|
|
smartlog = extensions.find("smartlog")
|
|
|
|
extensions.wrapfunction(smartlog, 'getdag', _getdag)
|