[PATCH] Add a --reverse option to patch

Brendan Cully brendan at kublai.com
Tue Aug 28 15:52:30 CDT 2007


On Tuesday, 28 August 2007 at 15:07, Alexis S. L. Carvalho wrote:
> I don't have a strong opinion about the feature (well, --reverse is a
> bit unfriendly towards TAB-completion, but that's smaller than minor),
> but two comments:

Possibly we could call it --swap, though I like the sound of --reverse
a bit more.

> Thus spake Brendan Cully:
> > +    if reverse:
> > +        node1, ctx1, node2, ctx2 = node2, ctx2, node1, ctx1
> > +        execf1, linkf1, execf2, linkf2 = execf2, linkf2, execf1, linkf1
> > +        added, removed = removed, added
> > +        man1 = ctx1.manifest()
> > +        
> 
> If I'm reading the code correctly, the call to ctx1.manifest() here will
> force another walk of the working dir, making a hg diff --reverse quite
> a bit slower than a regular hg diff.

I've avoided using ctx1.manifest() in this patch, but it turned out to
be messier than I was expecting for renames and for diffs of working
directory merges. Ideas would be welcome.

> API-wise it might be better to just have the caller reverse node1 and
> node2 and interpret "node1 is None and node2 is not None" as "compare
> working dir with node2"[1].  But that would probably require some deeper
> changes in localrepo.status and would also have the double walk problem
> I mentioned above.

I've changed the interface to patch.diff, but kept the implementation
more or less the same (I just synthesize a reverse flag on
entry). This was to avoid having to deal with repo.status :)
-------------- next part --------------
# HG changeset patch
# User Brendan Cully <brendan at kublai.com>
# Date 1188333860 25200
# Node ID 893b1a204028ea5a8cd761d9da1ecfcec948d8ff
# Parent  8040f2e4cad04e82cf7b67b7d671024f3a9af90f
Add a --reverse option to patch.
This is necessary to create a reverse diff based on the working
directory, because only one revision is given in this case. It is also
useful for the rdiff extension, where -r flags are interpreted in
order as local revision, remote revision.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1015,6 +1015,8 @@ def diff(ui, repo, *pats, **opts):
     probably with undesirable results.
     """
     node1, node2 = cmdutil.revpair(repo, opts['rev'])
+    if opts['reverse']:
+        node1, node2 = node2, node1
 
     fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
 
@@ -2886,6 +2888,7 @@ table = {
            _('ignore changes in the amount of white space')),
           ('B', 'ignore-blank-lines', None,
            _('ignore changes whose lines are all blank')),
+          ('', 'reverse', None, _('reverse patch direction'))
          ] + walkopts,
          _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
     "^export":
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1114,6 +1114,9 @@ def diff(repo, node1=None, node2=None, f
         opts = mdiff.defaultopts
     if fp is None:
         fp = repo.ui
+    reverse = node2 and not node1
+    if reverse:
+        node1, node2 = node2, node1
 
     if not node1:
         node1 = repo.dirstate.parents()[0]
@@ -1145,6 +1148,8 @@ def diff(repo, node1=None, node2=None, f
     if not modified and not added and not removed:
         return
 
+    execf1 = man1.execf
+    linkf1 = man1.linkf
     if node2:
         ctx2 = context.changectx(repo, node2)
         execf2 = ctx2.manifest().execf
@@ -1158,9 +1163,13 @@ def diff(repo, node1=None, node2=None, f
             execf2 = mc.execf
             linkf2 = mc.linkf
 
+    if reverse:
+        node1, ctx1, node2, ctx2 = node2, ctx2, node1, ctx1
+        execf1, linkf1, execf2, linkf2 = execf2, linkf2, execf1, linkf1
+        added, removed = removed, added
+        
     # returns False if there was no rename between ctx1 and ctx2
-    # returns None if the file was created between ctx1 and ctx2
-    # returns the (file, node) present in ctx1 that was renamed to f in ctx2
+    # returns the oldest name for f starting from ctx1
     def renamed(f):
         startrev = ctx1.rev()
         c = ctx2
@@ -1179,7 +1188,7 @@ def diff(repo, node1=None, node2=None, f
             crev = c.parents()[0].rev()
             # try to reuse
             c = getctx(crev)
-        if f not in man1:
+        if f in added:
             return None
         if f == orig:
             return False
@@ -1208,8 +1217,14 @@ def diff(repo, node1=None, node2=None, f
         tn = None
         dodiff = True
         header = []
-        if f in man1:
-            to = getfilectx(f, ctx1).data()
+        try:
+            if f not in added:
+                to = getfilectx(f, ctx1).data()
+        except revlog.LookupError:
+            # f may be missing from ctx1 anyway if ctx1 is a working dir
+            # merge. But we want to avoid using wctx.manifest because it
+            # is slow to build.
+            pass
         if f not in removed:
             tn = getfilectx(f, ctx2).data()
         if opts.git:
@@ -1225,17 +1240,22 @@ def diff(repo, node1=None, node2=None, f
                 mode = gitmode(execf2(f), linkf2(f))
                 if f in copied:
                     a = copied[f]
-                    omode = gitmode(man1.execf(a), man1.linkf(a))
-                    addmodehdr(header, omode, mode)
-                    if a in removed and a not in gone:
-                        op = 'rename'
-                        gone[a] = 1
-                    else:
-                        op = 'copy'
-                    header.append('%s from %s\n' % (op, a))
-                    header.append('%s to %s\n' % (op, f))
-                    to = getfilectx(a, ctx1).data()
-                else:
+                    try:
+                        to = getfilectx(a, ctx1).data()
+                        omode = gitmode(execf1(a), linkf1(a))
+                        addmodehdr(header, omode, mode)
+                        if a in removed and a not in gone:
+                            op = 'rename'
+                            gone[a] = 1
+                        else:
+                            op = 'copy'
+                        header.append('%s from %s\n' % (op, a))
+                        header.append('%s to %s\n' % (op, f))
+                    except revlog.LookupError:
+                        # file was created after start rev
+                        a = f
+                        del copied[f]
+                if f not in copied:
                     header.append('new file mode %s\n' % mode)
                 if util.binary(tn):
                     dodiff = 'binary'
@@ -1243,10 +1263,10 @@ def diff(repo, node1=None, node2=None, f
                 if f in srcs:
                     dodiff = False
                 else:
-                    mode = gitmode(man1.execf(f), man1.linkf(f))
+                    mode = gitmode(execf1(f), linkf1(f))
                     header.append('deleted file mode %s\n' % mode)
             else:
-                omode = gitmode(man1.execf(f), man1.linkf(f))
+                omode = gitmode(execf1(f), linkf1(f))
                 nmode = gitmode(execf2(f), linkf2(f))
                 addmodehdr(header, omode, nmode)
                 if util.binary(to) or util.binary(tn):
diff --git a/tests/test-help.out b/tests/test-help.out
--- a/tests/test-help.out
+++ b/tests/test-help.out
@@ -205,6 +205,7 @@ options:
  -w --ignore-all-space     ignore white space when comparing lines
  -b --ignore-space-change  ignore changes in the amount of white space
  -B --ignore-blank-lines   ignore changes whose lines are all blank
+    --reverse              reverse patch direction
  -I --include              include names matching the given patterns
  -X --exclude              exclude names matching the given patterns
 


More information about the Mercurial-devel mailing list