sapling/edenscm/hgext/arcdiff.py
Jun Wu 9dc21f8d0b codemod: import from the edenscm package
Summary:
D13853115 adds `edenscm/` to `sys.path` and code still uses `import mercurial`.
That has nasty problems if both `import mercurial` and
`import edenscm.mercurial` are used, because Python would think `mercurial.foo`
and `edenscm.mercurial.foo` are different modules so code like
`try: ... except mercurial.error.Foo: ...`, or `isinstance(x, mercurial.foo.Bar)`
would fail to handle the `edenscm.mercurial` version. There are also some
module-level states (ex. `extensions._extensions`) that would cause trouble if
they have multiple versions in a single process.

Change imports to use the `edenscm` so ideally the `mercurial` is no longer
imported at all. Add checks in extensions.py to catch unexpected extensions
importing modules from the old (wrong) locations when running tests.

Reviewed By: phillco

Differential Revision: D13868981

fbshipit-source-id: f4e2513766957fd81d85407994f7521a08e4de48
2019-01-29 17:25:32 -08:00

214 lines
6.9 KiB
Python

# arcdiff.py - extension adding an option to the diff command to show changes
# since the last arcanist diff
#
# Copyright 2016 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.
import os
from edenscm.mercurial import (
commands,
error,
extensions,
hintutil,
mdiff,
patch,
registrar,
revset,
scmutil,
smartset,
)
from edenscm.mercurial.i18n import _
from edenscm.mercurial.node import hex
from .extlib.phabricator import arcconfig, diffprops, graphql
hint = registrar.hint()
revsetpredicate = registrar.revsetpredicate()
@hint("since-last-arc-diff")
def sincelastarcdiff():
return _("--since-last-arc-diff is deprecated, use --since-last-submit")
def extsetup(ui):
entry = extensions.wrapcommand(commands.table, "diff", _diff)
options = entry[1]
options.append(
("", "since-last-arc-diff", None, _("Deprecated alias for --since-last-submit"))
)
options.append(
("", "since-last-submit", None, _("show changes since last Phabricator submit"))
)
options.append(
(
"",
"since-last-submit-2o",
None,
_("show diff of current diff and last Phabricator submit"),
)
)
def _differentialhash(ui, repo, phabrev):
timeout = repo.ui.configint("ssl", "timeout", 5)
ca_certs = repo.ui.configpath("web", "cacerts")
try:
client = graphql.Client(repodir=repo.root, ca_bundle=ca_certs, repo=repo)
info = client.getrevisioninfo(timeout, [phabrev]).get(str(phabrev))
if not info:
return None
return info
except graphql.ClientError as e:
ui.warn(_("Error calling graphql: %s\n") % str(e))
return None
except arcconfig.ArcConfigError as e:
raise error.Abort(str(e))
def _diff2o(ui, repo, rev1, rev2, *pats, **opts):
# Phabricator revs are often filtered (hidden)
repo = repo.unfiltered()
# First reconstruct textual diffs for rev1 and rev2 independently.
def changediff(node):
nodebase = repo[node].p1().node()
m = scmutil.matchall(repo)
diff = patch.diffhunks(repo, nodebase, node, m, opts=mdiff.diffopts(context=0))
filepatches = {}
for _fctx1, _fctx2, headerlines, hunks in diff:
difflines = []
for hunkrange, hunklines in hunks:
difflines += list(hunklines)
header = patch.header(headerlines)
filepatches[header.files()[0]] = "".join(difflines)
return (set(filepatches.keys()), filepatches, node)
rev1node = repo[rev1].node()
rev2node = repo[rev2].node()
files1, filepatches1, node1 = changediff(rev1node)
files2, filepatches2, node2 = changediff(rev2node)
ui.write(_("Phabricator rev: %s\n") % hex(node1)),
ui.write(_("Local rev: %s (%s)\n") % (hex(node2), rev2))
# For files have changed, produce a diff of the diffs first using a normal
# text diff of the input diffs, then fixing-up the output for readability.
changedfiles = files1 & files2
for f in changedfiles:
opts["context"] = 0
diffopts = mdiff.diffopts(**opts)
header, hunks = mdiff.unidiff(
filepatches1[f], "", filepatches2[f], "", f, f, opts=diffopts
)
hunklines = []
for hunk in hunks:
hunklines += hunk[1]
changelines = []
i = 0
while i < len(hunklines):
line = hunklines[i]
i += 1
if line[:2] == "++":
changelines.append("+" + line[2:])
elif line[:2] == "+-":
changelines.append("-" + line[2:])
elif line[:2] == "-+":
changelines.append("-" + line[2:])
elif line[:2] == "--":
changelines.append("+" + line[2:])
elif line[:2] == "@@" or line[1:3] == "@@":
if len(changelines) < 1 or changelines[-1] != "...\n":
changelines.append("...\n")
else:
changelines.append(line)
if len(changelines):
ui.write(_("Changed: %s\n") % f)
for line in changelines:
ui.write("| " + line)
wholefilechanges = files1 ^ files2
for f in wholefilechanges:
ui.write(_("Added/removed: %s\n") % f)
def _maybepull(repo, hexrev):
if extensions.enabled().get("commitcloud", False):
repo.revs("cloudremote(%s)" % hexrev)
def _diff(orig, ui, repo, *pats, **opts):
if (
not opts.get("since_last_submit")
and not opts.get("since_last_arc_diff")
and not opts.get("since_last_submit_2o")
):
return orig(ui, repo, *pats, **opts)
if opts.get("since_last_arc_diff"):
hintutil.trigger("since-last-arc-diff")
if len(opts["rev"]) > 1:
mess = _("cannot specify --since-last-arc-diff with multiple revisions")
raise error.Abort(mess)
try:
targetrev = opts["rev"][0]
except IndexError:
targetrev = "."
ctx = repo[targetrev]
phabrev = diffprops.parserevfromcommitmsg(ctx.description())
if phabrev is None:
mess = _("local changeset is not associated with a differential " "revision")
raise error.Abort(mess)
rev = _differentialhash(ui, repo, phabrev)
if rev is None or not isinstance(rev, dict) or "hash" not in rev:
mess = _("unable to determine previous changeset hash")
raise error.Abort(mess)
rev = str(rev["hash"])
_maybepull(repo, rev)
opts["rev"] = [rev, targetrev]
# if patterns aren't provided, restrict diff to files in both changesets
# this prevents performing a diff on rebased changes
if len(pats) == 0:
prev = set(repo.unfiltered()[rev].files())
curr = set(repo[targetrev].files())
pats = tuple(os.path.join(repo.root, p) for p in prev | curr)
if opts.get("since_last_submit_2o"):
return _diff2o(ui, repo, rev, targetrev, **opts)
else:
return orig(ui, repo.unfiltered(), *pats, **opts)
@revsetpredicate("lastsubmitted(set)")
def lastsubmitted(repo, subset, x):
revs = revset.getset(repo, revset.fullreposet(repo), x)
phabrevs = set()
for rev in revs:
phabrev = diffprops.parserevfromcommitmsg(repo[rev].description())
if phabrev is None:
mess = _("local changeset is not associated with a differential revision")
raise error.Abort(mess)
phabrevs.add(phabrev)
resultrevs = set()
for phabrev in phabrevs:
diffrev = _differentialhash(repo.ui, repo, phabrev)
if diffrev is None or not isinstance(diffrev, dict) or "hash" not in diffrev:
mess = _("unable to determine previous changeset hash")
raise error.Abort(mess)
lasthash = str(diffrev["hash"])
_maybepull(repo, lasthash)
resultrevs.add(repo.unfiltered()[lasthash])
return subset & smartset.baseset(sorted(resultrevs))