[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