[PATCH RFC] extdiff: add support for subrepo diff
Mathias De Maré
mathias.demare at gmail.com
Sat Feb 7 08:56:09 UTC 2015
# HG changeset patch
# User Mathias De Maré <mathias.demare at gmail.com>
# Date 1423299130 -3600
# Sam Feb 07 09:52:10 2015 +0100
# Node ID dd4c16684d072b2eb35d0ef5f6e567eb9ea5b430
# Parent ff5caa8dfd993680d9602ca6ebb14da9de10d5f4
extdiff: add support for subrepo diff
Sending as RFC, I'd like to get some general feedback on
how to progress with this patch.
Up to this point, extdiff did not have a '--subrepos' flag.
This patch makes it possible to view changes in subrepos
as well, though not in sub-subrepos.
No special treatment of executable files
or links is done at this point.
diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -69,7 +69,7 @@ cmdtable = {}
command = cmdutil.command(cmdtable)
testedwith = 'internal'
-def snapshot(ui, repo, files, node, tmproot):
+def snapshot(ui, repo, files, subs, node, tmproot):
'''snapshot files as of some revision
if not using snapshot, -I/-X does not work and recursive diff
in tools like kdiff3 and meld displays too many files.'''
@@ -107,8 +107,40 @@ def snapshot(ui, repo, files, node, tmpr
if node is None:
fns_and_mtime.append((dest, repo.wjoin(fn),
os.lstat(dest).st_mtime))
+
+ for s in subs:
+ for fn in sorted(subs[s]):
+ wfn = util.pconvert(fn)
+ ws = util.pconvert(s)
+ wfnroot = os.path.join(ws, wfn)
+ matcher = scmutil.match(repo[node], ['path:' + wfnroot],
+ {"exact": True})
+ dest = os.path.join(base, wfnroot)
+ destdir = os.path.dirname(dest)
+ if not os.path.exists(destdir):
+ os.makedirs(destdir)
+ if node is None:
+ util.copyfile(wfnroot, dest)
+ fns_and_mtime.append((dest, repo.wjoin(ws),
+ os.lstat(dest).st_mtime))
+ else:
+ cmdutil.cat(ui, repo, repo[node], matcher, '', output=dest)
return dirname, fns_and_mtime
+def calcsubfiles(own, other):
+ '''Calculate the relevant changed files in the subrepos
+ '''
+ subfiles = dict()
+ for s in own:
+ (smodown, saddown, sremown) = own[s]
+ if s in other:
+ (smodother, saddother, sremother) = other[s]
+ files = smodown | sremown | ((smodother | saddother) - saddown)
+ else:
+ files = smodown | sremown
+ subfiles[s] = files
+ return subfiles
+
def dodiff(ui, repo, cmdline, pats, opts):
'''Do the actual diff:
@@ -120,6 +152,7 @@ def dodiff(ui, repo, cmdline, pats, opts
revs = opts.get('rev')
change = opts.get('change')
+ subrepos = opts.get('subrepos')
do3way = '$parent2' in cmdline
if revs and change:
@@ -148,18 +181,51 @@ def dodiff(ui, repo, cmdline, pats, opts
mod_b, add_b, rem_b = set(), set(), set()
modadd = mod_a | add_a | mod_b | add_b
common = modadd | rem_a | rem_b
- if not common:
+
+ subsa = {}
+ subsb = {}
+ subs = {}
+
+ subchanges = False
+ subsmodadd = {}
+ if subrepos:
+ targetsubs = sorted(s for s in repo[node2].substate if matcher(s))
+ for s in targetsubs:
+ if s in repo[node1a].substate:
+ substate = map(set, repo[node1a].sub(s).status(node2))
+ subsa[s] = substate[:3]
+ subs[s] = substate[:3]
+ if do3way and s in repo[node1b].substate:
+ substate = map(set, repo[node1b].sub(s).status(node2))
+ subsb[s] = substate[:3]
+ if s not in subs:
+ subs[s] = (set(), set(), set())
+ (smod, sadd, srem) = subs[s]
+ (bmod, badd, brem) = substate[:3]
+ subs[s] = (smod | bmod, sadd | badd, srem | brem)
+ if s in subs:
+ (smod, sadd, srem) = subs[s]
+ subsmodadd[s] = smod | sadd
+ if smod | sadd | srem:
+ subchanges = True
+
+ subsafiles = calcsubfiles(subsa, subsb)
+ subsbfiles = calcsubfiles(subsb, subsa)
+
+ if not common and not subchanges:
return 0
tmproot = tempfile.mkdtemp(prefix='extdiff.')
try:
# Always make a copy of node1a (and node1b, if applicable)
dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
- dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
+ dir1a = snapshot(ui, repo, dir1a_files,
+ subsafiles, node1a, tmproot)[0]
rev1a = '@%d' % repo[node1a].rev()
if do3way:
dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
- dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
+ dir1b = snapshot(ui, repo, dir1b_files,
+ subsbfiles, node1b, tmproot)[0]
rev1b = '@%d' % repo[node1b].rev()
else:
dir1b = None
@@ -171,14 +237,15 @@ def dodiff(ui, repo, cmdline, pats, opts
dir2root = ''
rev2 = ''
if node2:
- dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
+ dir2 = snapshot(ui, repo, modadd, subsmodadd, node2, tmproot)[0]
rev2 = '@%d' % repo[node2].rev()
- elif len(common) > 1:
+ elif len(common) > 1 or subchanges:
#we only actually need to get the files to copy back to
#the working dir in this case (because the other cases
#are: diffing 2 revisions or single file -- in which case
#the file is already directly passed to the diff tool).
- dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
+ dir2, fns_and_mtime = snapshot(ui, repo, modadd,
+ subsmodadd, None, tmproot)
else:
# This lets the diff tool open the changed file directly
dir2 = ''
@@ -190,7 +257,7 @@ def dodiff(ui, repo, cmdline, pats, opts
# If only one change, diff the files instead of the directories
# Handle bogus modifies correctly by checking if the files exist
- if len(common) == 1:
+ if len(common) == 1 and not subchanges:
common_file = util.localpath(common.pop())
dir1a = os.path.join(tmproot, dir1a, common_file)
label1a = common_file + rev1a
@@ -246,7 +313,7 @@ def dodiff(ui, repo, cmdline, pats, opts
_('pass option to comparison program'), _('OPT')),
('r', 'rev', [], _('revision'), _('REV')),
('c', 'change', '', _('change made by revision'), _('REV')),
- ] + commands.walkopts,
+ ] + commands.walkopts + commands.subrepoopts,
_('hg extdiff [OPT]... [FILE]...'),
inferrepo=True)
def extdiff(ui, repo, *pats, **opts):
diff --git a/tests/test-extdiff.t b/tests/test-extdiff.t
--- a/tests/test-extdiff.t
+++ b/tests/test-extdiff.t
@@ -48,6 +48,7 @@ Should diff cloned directories:
-c --change REV change made by revision
-I --include PATTERN [+] include names matching the given patterns
-X --exclude PATTERN [+] exclude names matching the given patterns
+ -S --subrepos recurse into subrepositories
(some details hidden, use --verbose to show complete help)
@@ -322,3 +323,52 @@ Test symlinks handling (issue1909)
$ cd ..
#endif
+
+Test subrepos
+ $ hg init toprepo
+ $ cd toprepo
+ $ echo foo > foo
+ $ hg add
+ adding foo
+ $ hg init subrepo
+ $ echo bar > subrepo/bar
+ $ echo foobar > subrepo/foobar
+ $ hg -R subrepo add
+ adding subrepo/bar
+ adding subrepo/foobar
+ $ hg -R subrepo commit -m "subrepo bar"
+ $ echo "subrepo = subrepo" > .hgsub
+ $ hg add .hgsub
+ $ hg commit -m "added subrepo and foo"
+
+ignore subrepository during the initial add
+(to avoid diffing an entire subrepository, similar behaviour as for diff)
+ $ hg extdiff -S --change . -p diff
+ Only in toprepo.935947dcc4e9: .hgsub
+ Only in toprepo.935947dcc4e9: .hgsubstate
+ Only in toprepo.935947dcc4e9: foo
+ [1]
+
+correctly diff changes in later subrepository changes
+ $ cat > subrepo/bar << EOF
+ > bar
+ > foo
+ > foobar
+ > EOF
+ $ echo foo > subrepo/foo
+ $ rm subrepo/foobar
+ $ hg -R subrepo addremove
+ adding foo
+ removing foobar
+ $ hg st -S
+ M subrepo/bar
+ A subrepo/foo
+ R subrepo/foobar
+ $ hg extdiff -S -p diff -o "-rn"
+ diff -rn toprepo.935947dcc4e9/subrepo/bar toprepo/subrepo/bar
+ a1 2
+ foo
+ foobar
+ Only in toprepo/subrepo: foo
+ Only in toprepo.935947dcc4e9/subrepo: foobar
+ [1]
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -394,6 +394,7 @@ Extension module help vs command help:
-c --change REV change made by revision
-I --include PATTERN [+] include names matching the given patterns
-X --exclude PATTERN [+] exclude names matching the given patterns
+ -S --subrepos recurse into subrepositories
(some details hidden, use --verbose to show complete help)
More information about the Mercurial-devel
mailing list