mirror of
https://github.com/facebook/sapling.git
synced 2024-10-16 11:52:02 +03:00
a2c598e9ba
Summary: Second-order diff was hardcoded to use "." even if another rev was specified on the CLI. Reviewed By: shashkambh Differential Revision: D10212547 fbshipit-source-id: b9867ba3e2141ee3c9a8c67a83e9c039da2abf41
214 lines
6.9 KiB
Python
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 mercurial import (
|
|
commands,
|
|
error,
|
|
extensions,
|
|
hintutil,
|
|
mdiff,
|
|
patch,
|
|
registrar,
|
|
revset,
|
|
scmutil,
|
|
smartset,
|
|
)
|
|
from mercurial.i18n import _
|
|
from 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))
|