[Request] [++++ ] D8972: [WIP] diff: add a `--tool` flag
pulkit (Pulkit Goyal)
phabricator at mercurial-scm.org
Sat Aug 29 14:18:56 UTC 2020
pulkit created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.
REVISION SUMMARY
This is a WIP because it's mostly plumbing at the moment and wanted to share it
with others.
REPOSITORY
rHG Mercurial
BRANCH
default
REVISION DETAIL
https://phab.mercurial-scm.org/D8972
AFFECTED FILES
hgext/extdiff.py
mercurial/configitems.py
mercurial/diffutil.py
mercurial/logcmdutil.py
mercurial/mdiff.py
CHANGE DETAILS
diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -40,6 +40,7 @@
# TODO: this looks like it could be an attrs, which might help pytype
class diffopts(object):
'''context is the number of context lines
+ external represents whether diff will be done using external tools
text treats all files as text
showfunc enables diff -p output
git enables the git extended patch format
@@ -56,6 +57,7 @@
defaults = {
b'context': 3,
+ b'external': False,
b'text': False,
b'showfunc': False,
b'git': False,
diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py
--- a/mercurial/logcmdutil.py
+++ b/mercurial/logcmdutil.py
@@ -91,6 +91,10 @@
relroot = b''
copysourcematch = None
+ if stat:
+ # explicitly set external tooling to false if we are processing stat
+ diffopts.external = False
+
def compose(f, g):
return lambda x: f(g(x))
diff --git a/mercurial/diffutil.py b/mercurial/diffutil.py
--- a/mercurial/diffutil.py
+++ b/mercurial/diffutil.py
@@ -77,6 +77,7 @@
b'context': get(b'unified', getter=ui.config),
}
buildopts[b'xdiff'] = ui.configbool(b'experimental', b'xdiff')
+ buildopts[b'external'] = get(b'tool')
if git:
buildopts[b'git'] = get(b'git')
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -135,6 +135,10 @@
coreconfigitem(
section, configprefix + b'nodates', default=False,
)
+ # TODO: this should be value one instead of boolean
+ coreconfigitem(
+ section, configprefix + b'tool', default=False,
+ )
coreconfigitem(
section, configprefix + b'showfunc', default=False,
)
diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -97,10 +97,13 @@
from mercurial import (
archival,
cmdutil,
+ commands,
encoding,
error,
+ extensions,
filemerge,
formatter,
+ patch,
pycompat,
registrar,
scmutil,
@@ -759,3 +762,178 @@
# tell hggettext to extract docstrings from these functions:
i18nfunctions = [savedcmd]
+
+_temproots = {}
+
+
+def _gettemproot(repo, node, tmproot):
+ global _temproots
+
+ if node not in _temproots:
+ dirname = os.path.basename(repo.root)
+ if dirname == b"":
+ dirname = b"root"
+ if node is not None:
+ dirname = b'%s.%s' % (dirname, short(node))
+ base = os.path.join(tmproot, dirname)
+ else:
+ base = repo.root
+ _temproots[node] = base
+ return base
+
+ return _temproots[node]
+
+
+def extdiffhunks(
+ orig,
+ repo,
+ ctx1,
+ ctx2,
+ match=None,
+ changes=None,
+ opts=None,
+ losedatafn=None,
+ pathfn=None,
+ copy=None,
+ copysourcematch=None,
+):
+ """ Wraps patch.diffhunks to show diff using external diff tools.
+
+ Does following things in order:
+ * Checks if we are diffing externally or not, if not call orig()
+ * Creates temporary directories where temporary files will be written
+ for external tools
+ * Calls orig(), we are wrapping `patch.diffcontent()` to write content
+ of both diff sides to files instead of producing diffs
+ * Gets the difftool to call from config and build the command
+ which needs to be run
+ * Once all diff sides are written to temp files (if required), runs
+ difftool for each file
+ * Deletes the temporary directory created
+ """
+ if opts is None or not opts.external:
+ # mdiffopts does not have the external part set, means
+ # we are not diffing externally
+ return orig(
+ repo,
+ ctx1,
+ ctx2,
+ match,
+ changes,
+ opts,
+ losedatafn,
+ pathfn,
+ copy,
+ copysourcematch,
+ )
+
+ # create the base paths for each changesets
+ tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
+ try:
+ node1 = ctx1.node()
+ node2 = ctx2.node()
+ root1 = _gettemproot(repo, node1, tmproot)
+ root2 = _gettemproot(repo, node2, tmproot)
+ if node1 is not None:
+ os.makedirs(root1)
+ if node2 is not None:
+ os.makedirs(root2)
+
+ changes = []
+ for c in orig(
+ repo,
+ ctx1,
+ ctx2,
+ match,
+ changes,
+ opts,
+ losedatafn,
+ pathfn,
+ copy,
+ copysourcematch,
+ ):
+ changes.append(c[0])
+
+ # TODO: we should get this from config option
+ program = b'vimdiff'
+ option = []
+ cmdline = b' '.join(map(procutil.shellquote, [program] + option))
+
+ _runperfilediff(
+ cmdline,
+ repo.root,
+ repo.ui,
+ False,
+ False,
+ False,
+ changes,
+ tmproot,
+ root1,
+ None,
+ root2,
+ node1 if node1 else '',
+ None,
+ node2 if node2 else '',
+ )
+
+ return []
+ finally:
+ repo.ui.note(_(b'cleaning up temp directory\n'))
+ shutil.rmtree(tmproot)
+
+
+def extdiffcontent(orig, data1, data2, header, binary, opts):
+ """ Wraps patch.diffcontent to write file contents to temporary files
+ instead of calling mdiff to produce diffs.
+
+ This is done only when we are using external tools to diff
+ """
+ if not opts.external:
+ # not diffing externally, go back to original way
+ return orig(data1, data2, header, binary, opts)
+
+ ctx1, fctx1, path1, flag1, content1, date1 = data1
+ ctx2, fctx2, path2, flag2, content2, date2 = data2
+
+ # Write content to temporary files instead of calling mdiff
+ # If node is None, means we need to diff with working directory, hence
+ # no need to write the file
+ # If content is empty, we can skip writing the file and _runperfilediff()
+ # will use /dev/null as the file is missing
+ for node, content, path in (
+ (ctx1.node(), content1, path1),
+ (ctx2.node(), content2, path2),
+ ):
+ if node is not None and content:
+ dirpath = _gettemproot(None, node, None)
+ fpath = os.path.join(dirpath, path)
+ dirfpath = os.path.dirname(fpath)
+ if not os.path.exists(dirfpath):
+ os.makedirs(dirfpath)
+
+ with open(fpath, 'wb') as fp:
+ fp.write(content)
+
+ return path1, path2, None, None
+
+
+def _diff(orig, ui, repo, *pats, **opts):
+ overrides = {}
+ if opts.get('tool'):
+ # stat cannot be show using an external tool
+ cmdutil.check_at_most_one_arg(opts, 'tool', 'stat')
+ # if we will be diffing using external tool, turn off the pager
+ overrides[(b'ui', b'paginate')] = False
+
+ with ui.configoverride(overrides, b'extdiff'):
+ orig(ui, repo, *pats, **opts)
+
+
+def extsetup(ui):
+ diffentry = extensions.wrapcommand(commands.table, b'diff', _diff)
+ diffentry[1].append(
+ (b'', b'tool', False, _(b'show diff using external tool'),)
+ )
+
+ extensions.wrapfunction(patch, b'diffhunks', extdiffhunks)
+ extensions.wrapfunction(patch, b'diffcontent', extdiffcontent)
To: pulkit, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mercurial-scm.org/pipermail/mercurial-patches/attachments/20200829/2d842bae/attachment-0001.html>
More information about the Mercurial-patches
mailing list