sapling/eden/scm/edenscm/hgext/fbscmquery.py
Jun Wu 1a1b3a3cb5 smartset: make repo required for baseset
Reviewed By: sfilipco

Differential Revision: D24365401

fbshipit-source-id: 4d0ee6d27717c1aa966086af68492295aa6ed372
2020-12-14 13:12:42 -08:00

227 lines
6.7 KiB
Python

# Copyright (c) Facebook, Inc. and its affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
# scmquery.py
# An extension to augement hg with information obtained from SCMQuery
import json
import re
from typing import Any, List
from edenscm.mercurial import (
extensions,
namespaces,
node,
registrar,
revset,
smartset,
templater,
)
from edenscm.mercurial.i18n import _, _x
from edenscm.mercurial.node import bin
from edenscm.mercurial.pycompat import range
from edenscm.mercurial.util import httplib
from .extlib.phabricator import arcconfig, graphql
namespacepredicate = registrar.namespacepredicate()
DEFAULT_TIMEOUT = 60
MAX_CONNECT_RETRIES = 3
githashre = re.compile("g([0-9a-f]{40})")
svnrevre = re.compile("^r[A-Z]+(\d+)$")
phabhashre = re.compile("^r([A-Z]+)([0-9a-f]{12,40})$")
def uisetup(ui):
def _globalrevswrapper(loaded):
if loaded:
globalrevsmod = extensions.find("globalrevs")
extensions.wrapfunction(
globalrevsmod, "_lookupglobalrev", _scmquerylookupglobalrev
)
if ui.configbool("globalrevs", "scmquerylookup"):
extensions.afterloaded("globalrevs", _globalrevswrapper)
revset.symbols["gitnode"] = gitnode
gitnode._weight = 10
@templater.templatefunc("mirrornode")
def mirrornode(ctx, mapping, args):
"""template: find this commit in other repositories"""
reponame = mapping["repo"].ui.config("fbscmquery", "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:
client = graphql.Client(repo=mapping["repo"])
return client.getmirroredrev(reponame, "hg", torepo, totype, node)
except arcconfig.ArcConfigError:
mapping["repo"].ui.warn(_("couldn't read .arcconfig or .arcrc\n"))
return ""
except graphql.ClientError as e:
mapping["repo"].ui.warn(_x(str(e) + "\n"))
return ""
templatekeyword = registrar.templatekeyword()
@templatekeyword("gitnode")
def showgitnode(repo, ctx, templ, **args):
"""Return the git revision corresponding to a given hg rev"""
# Try reading from commit extra first.
extra = ctx.extra()
if "hg-git-rename-source" in extra:
hexnode = extra.get("convert_revision")
if hexnode:
return hexnode
reponame = repo.ui.config("fbscmquery", "reponame")
if not reponame:
# We don't know who we are, so we can't ask for a translation
return ""
backingrepos = repo.ui.configlist("fbscmquery", "backingrepos", default=[reponame])
if ctx.mutable():
# Local commits don't have translations
return ""
matches = []
for backingrepo in backingrepos:
try:
client = graphql.Client(repo=repo)
githash = client.getmirroredrev(
reponame, "hg", backingrepo, "git", ctx.hex()
)
if githash != "":
matches.append((backingrepo, githash))
except (graphql.ClientError, arcconfig.ArcConfigError):
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("fbscmquery", "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("fbscmquery", "backingrepos", default=[reponame])
lasterror = None
hghash = None
for backingrepo in backingrepos:
try:
client = graphql.Client(repo=repo)
hghash = client.getmirroredrev(backingrepo, "git", reponame, "hg", 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(_x("Could not translate revision {0}\n".format(n)))
return subset.filter(lambda r: False)
rn = repo[node.bin(hghash)].rev()
return subset & smartset.baseset([rn], repo=repo)
@namespacepredicate("conduit", priority=70)
def _getnamespace(_repo):
return namespaces.namespace(
listnames=lambda repo: [], namemap=_phablookup, nodemap=lambda repo, node: []
)
def _phablookup(repo, phabrev):
# type: (Any, str) -> List[bytes]
# Is the given revset a phabricator hg hash (ie: rHGEXTaaacb34aacb34aa)
def gittohg(githash):
return list(repo.nodes("gitnode(%s)" % githash))
phabmatch = phabhashre.match(phabrev)
if phabmatch:
phabrepo = phabmatch.group(1)
phabhash = phabmatch.group(2)
# The hash may be a git hash
if phabrepo in repo.ui.configlist("fbscmquery", "gitcallsigns", []):
return gittohg(phabhash)
return [repo[phabhash].node()]
# TODO: 's/svnrev/globalrev' after turning off Subversion servers. We will
# know about this when we remove the `svnrev` revset.
svnrevmatch = svnrevre.match(phabrev)
if svnrevmatch is not None:
svnrev = svnrevmatch.group(1)
return list(repo.nodes("svnrev(%s)" % svnrev))
m = githashre.match(phabrev)
if m is not None:
githash = m.group(1)
if len(githash) == 40:
return gittohg(githash)
return []
def _scmquerylookupglobalrev(orig, repo, rev):
reponame = repo.ui.config("fbscmquery", "reponame")
if reponame:
try:
client = graphql.Client(repo=repo)
hghash = str(
client.getmirroredrev(reponame, "GLOBAL_REV", reponame, "hg", str(rev))
)
matchedrevs = []
if hghash:
matchedrevs.append(bin(hghash))
return matchedrevs
except Exception as exc:
repo.ui.warn(
_("failed to lookup globalrev %s from scmquery: %s\n") % (rev, exc)
)
return orig(repo, rev)