revert: add flag to make revert interactive
authorLaurent Charignon <lcharignon@fb.com>
Mon, 16 Mar 2015 16:33:59 -0700
changeset 24359 521fe8287dd5
parent 24358 8d9e9063b040
child 24360 f554f89a2038
revert: add flag to make revert interactive
mercurial/cmdutil.py
mercurial/commands.py
tests/test-completion.t
tests/test-revert-interactive.t
--- a/mercurial/cmdutil.py	Mon Mar 16 15:37:00 2015 -0700
+++ b/mercurial/cmdutil.py	Mon Mar 16 16:33:59 2015 -0700
@@ -3018,8 +3018,8 @@
         if not opts.get('dry_run'):
             needdata = ('revert', 'add', 'undelete')
             _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
-
-            _performrevert(repo, parents, ctx, actions)
+            interactive = opts.get('interactive', False)
+            _performrevert(repo, parents, ctx, actions, interactive)
 
         # get the list of subrepos that must be reverted
         subrepomatch = scmutil.match(ctx, pats, opts)
@@ -3036,7 +3036,7 @@
     """Let extension changing the storage layer prefetch content"""
     pass
 
-def _performrevert(repo, parents, ctx, actions):
+def _performrevert(repo, parents, ctx, actions, interactive=False):
     """function that actually perform all the actions computed for revert
 
     This is an independent function to let extension to plug in and react to
@@ -3070,10 +3070,40 @@
             normal = repo.dirstate.normallookup
         else:
             normal = repo.dirstate.normal
-    for f in actions['revert'][0]:
-        checkout(f)
-        if normal:
-            normal(f)
+
+    if interactive:
+        # Prompt the user for changes to revert
+        torevert = [repo.wjoin(f) for f in actions['revert'][0]]
+        m = scmutil.match(ctx, torevert, {})
+        diff = patch.diff(repo, None, ctx.node(), m)
+        originalchunks = patch.parsepatch(diff)
+        try:
+            chunks = recordfilter(repo.ui, originalchunks)
+        except patch.PatchError, err:
+            raise util.Abort(_('error parsing patch: %s') % err)
+
+        # Apply changes
+        fp = cStringIO.StringIO()
+        for c in chunks:
+            c.write(fp)
+        dopatch = fp.tell()
+        fp.seek(0)
+        if dopatch:
+            try:
+                patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
+            except patch.PatchError, err:
+                raise util.Abort(str(err))
+        del fp
+
+        for f in actions['revert'][0]:
+            if normal:
+                normal(f)
+
+    else:
+        for f in actions['revert'][0]:
+            checkout(f)
+            if normal:
+                normal(f)
 
     for f in actions['add'][0]:
         checkout(f)
--- a/mercurial/commands.py	Mon Mar 16 15:37:00 2015 -0700
+++ b/mercurial/commands.py	Mon Mar 16 16:33:59 2015 -0700
@@ -5375,6 +5375,7 @@
     ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
     ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
     ('C', 'no-backup', None, _('do not save backup copies of files')),
+    ('i', 'interactive', None, _('interactively select the changes')),
     ] + walkopts + dryrunopts,
     _('[OPTION]... [-r REV] [NAME]...'))
 def revert(ui, repo, *pats, **opts):
--- a/tests/test-completion.t	Mon Mar 16 15:37:00 2015 -0700
+++ b/tests/test-completion.t	Mon Mar 16 16:33:59 2015 -0700
@@ -279,7 +279,7 @@
   recover: 
   rename: after, force, include, exclude, dry-run
   resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
-  revert: all, date, rev, no-backup, include, exclude, dry-run
+  revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
   rollback: dry-run, force
   root: 
   tag: force, local, rev, remove, edit, message, date, user
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-revert-interactive.t	Mon Mar 16 16:33:59 2015 -0700
@@ -0,0 +1,203 @@
+Revert interactive tests
+1 add and commit file f
+2 add commit file folder1/g
+3 add and commit file folder2/h
+4 add and commit file folder1/i
+5 commit change to file f
+6 commit changes to files folder1/g folder2/h
+7 commit changes to files folder1/g folder2/h
+8 revert interactive to commit id 2 (line 3 above), check that folder1/i is removed and
+9 make workdir match 7
+10 run the same test than 8 from within folder1 and check same expectations
+
+  $ cat <<EOF >> $HGRCPATH
+  > [ui]
+  > interactive = true
+  > [extensions]
+  > record =
+  > EOF
+
+
+  $ mkdir -p a/{folder1,folder2}
+  $ cd a
+  $ hg init
+  $ seq 1 5 > f ; hg add f ; hg commit -m "adding f"
+  $ seq 1 5 > folder1/g ; hg add folder1/g ; hg commit -m "adding folder1/g"
+  $ seq 1 5 > folder2/h ; hg add folder2/h ; hg commit -m "adding folder2/h"
+  $ seq 1 5 > folder1/i ; hg add folder1/i ; hg commit -m "adding folder1/i"
+  $ echo "a" > f ; seq 1 5 >> f ; echo "b" >> f ; hg commit -m "modifying f"
+  $ echo "c" > folder1/g ; seq 1 5 >> folder1/g ; echo "d" >> folder1/g ; hg commit -m "modifying folder1/g"
+  $ echo "e" > folder2/h ; seq 1 5 >> folder2/h ; echo "f" >> folder2/h ; hg commit -m "modifying folder2/h"
+  $ hg tip
+  changeset:   6:59dd6e4ab63a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     modifying folder2/h
+  
+  $ hg revert -i -r 2 --all -- << EOF
+  > y
+  > y
+  > y
+  > y
+  > y
+  > n
+  > n
+  > EOF
+  reverting f
+  reverting folder1/g (glob)
+  removing folder1/i (glob)
+  reverting folder2/h (glob)
+  diff -r 89ac3d72e4a4 f
+  2 hunks, 2 lines changed
+  examine changes to 'f'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,5 @@
+  -a
+   1
+   2
+   3
+   4
+   5
+  record change 1/6 to 'f'? [Ynesfdaq?] y
+  
+  @@ -2,6 +1,5 @@
+   1
+   2
+   3
+   4
+   5
+  -b
+  record change 2/6 to 'f'? [Ynesfdaq?] y
+  
+  diff -r 89ac3d72e4a4 folder1/g
+  2 hunks, 2 lines changed
+  examine changes to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,5 @@
+  -c
+   1
+   2
+   3
+   4
+   5
+  record change 3/6 to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -2,6 +1,5 @@
+   1
+   2
+   3
+   4
+   5
+  -d
+  record change 4/6 to 'folder1/g'? [Ynesfdaq?] n
+  
+  diff -r 89ac3d72e4a4 folder2/h
+  2 hunks, 2 lines changed
+  examine changes to 'folder2/h'? [Ynesfdaq?] n
+  
+  $ cat f
+  1
+  2
+  3
+  4
+  5
+  $ cat folder1/g
+  1
+  2
+  3
+  4
+  5
+  d
+  $ cat folder2/h
+  e
+  1
+  2
+  3
+  4
+  5
+  f
+  $ hg update -C 6
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg revert -i -r 2 --all -- << EOF
+  > y
+  > y
+  > y
+  > y
+  > y
+  > n
+  > n
+  > EOF
+  reverting f
+  reverting folder1/g (glob)
+  removing folder1/i (glob)
+  reverting folder2/h (glob)
+  diff -r 89ac3d72e4a4 f
+  2 hunks, 2 lines changed
+  examine changes to 'f'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,5 @@
+  -a
+   1
+   2
+   3
+   4
+   5
+  record change 1/6 to 'f'? [Ynesfdaq?] y
+  
+  @@ -2,6 +1,5 @@
+   1
+   2
+   3
+   4
+   5
+  -b
+  record change 2/6 to 'f'? [Ynesfdaq?] y
+  
+  diff -r 89ac3d72e4a4 folder1/g
+  2 hunks, 2 lines changed
+  examine changes to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,5 @@
+  -c
+   1
+   2
+   3
+   4
+   5
+  record change 3/6 to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -2,6 +1,5 @@
+   1
+   2
+   3
+   4
+   5
+  -d
+  record change 4/6 to 'folder1/g'? [Ynesfdaq?] n
+  
+  diff -r 89ac3d72e4a4 folder2/h
+  2 hunks, 2 lines changed
+  examine changes to 'folder2/h'? [Ynesfdaq?] n
+  
+  $ cat f
+  1
+  2
+  3
+  4
+  5
+  $ cat folder1/g
+  1
+  2
+  3
+  4
+  5
+  d
+  $ cat folder2/h
+  e
+  1
+  2
+  3
+  4
+  5
+  f