D6005: uncommit: added interactive mode -i(issue6062)

taapas1128 (Taapas Agrawal) phabricator at mercurial-scm.org
Mon Mar 18 14:38:10 EDT 2019


taapas1128 updated this revision to Diff 14547.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6005?vs=14546&id=14547

REVISION DETAIL
  https://phab.mercurial-scm.org/D6005

AFFECTED FILES
  hgext/uncommit.py
  mercurial/crecord.py
  mercurial/patch.py
  tests/test-uncommit-interactive.t

CHANGE DETAILS

diff --git a/tests/test-uncommit-interactive.t b/tests/test-uncommit-interactive.t
new file mode 100644
--- /dev/null
+++ b/tests/test-uncommit-interactive.t
@@ -0,0 +1,969 @@
+================================================
+||  The test for `hg uncommit --interactive`  ||
+================================================
+
+Repo Setup
+============
+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
+  > uncommitondirtywdir = true
+  > [extensions]
+  > uncommit =
+  > amend =
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+  $ glog() {
+  >   hg log -G --template '{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' "$@"
+  > }
+
+  $ hg init repo
+  $ cd repo
+
+  $ touch a
+  $ cat >> a << EOF
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > EOF
+
+  $ hg add a
+  $ hg ci -m "The base commit"
+
+Make sure aborting the interactive selection does no magic
+----------------------------------------------------------
+
+  $ hg status
+  $ hg uncommit -i<<EOF
+  > q
+  > EOF
+  diff --git a/a b/a
+  new file mode 100644
+  examine changes to 'a'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
+  $ hg status
+
+Make a commit with multiple hunks
+---------------------------------
+
+  $ cat > a << EOF
+  > -2
+  > -1
+  > 0
+  > 1
+  > 2
+  > 3
+  > foo
+  > bar
+  > 4
+  > 5
+  > babar
+  > EOF
+
+  $ hg diff
+  diff -r 7733902a8d94 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,5 +1,11 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  +babar
+
+  $ hg ci -m "another one"
+
+Not selecting anything to uncommit
+==================================
+
+  $ hg uncommit -i<<EOF
+  > y
+  > n
+  > n
+  > n
+  > EOF
+  diff --git a/a b/a
+  3 hunks, 6 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  uncommit change 1/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  uncommit change 2/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +9,3 @@
+   4
+   5
+  +babar
+  uncommit change 3/3 to 'a'? [Ynesfdaq?] n
+  
+  abort: nothing selected to uncommit
+  [255]
+  $ hg status
+
+Uncommit a chunk
+================
+
+  $ hg uncommit -i<<EOF
+  > y
+  > y
+  > n
+  > n
+  > EOF
+  diff --git a/a b/a
+  3 hunks, 6 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  uncommit change 1/3 to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  uncommit change 2/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +9,3 @@
+   4
+   5
+  +babar
+  uncommit change 3/3 to 'a'? [Ynesfdaq?] n
+  
+
+  $ hg log -G --hidden
+  @  changeset:   3:aa6c421e9e28
+  |  tag:         tip
+  |  parent:      0:7733902a8d94
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     another one
+  |
+  | x  changeset:   2:557dde779d03
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 3:aa6c421e9e28
+  |    summary:     temporary commit for uncommiting f70fb463d5bf9f0ffd38f105521d96e9f2591bc1
+  |
+  | x  changeset:   1:f70fb463d5bf
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 3:aa6c421e9e28
+  |    summary:     another one
+  |
+  o  changeset:   0:7733902a8d94
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     The base commit
+  
+The unselected part should be in the diff
+-----------------------------------------
+
+  $ hg diff
+  diff -r aa6c421e9e28 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+
+The commit should contain the rest of part
+------------------------------------------
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID aa6c421e9e28164a60bdc19ec0ed4c39e5cfa65c
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r aa6c421e9e28 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,5 +1,8 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  +babar
+
+Uncommiting on dirty working directory
+======================================
+
+  $ hg status
+  M a
+  $ hg diff
+  diff -r aa6c421e9e28 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+
+  $ hg uncommit -i<<EOF
+  > y
+  > n
+  > y
+  > EOF
+  diff --git a/a b/a
+  2 hunks, 3 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,5 +1,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  uncommit change 1/2 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +6,3 @@
+   4
+   5
+  +babar
+  uncommit change 2/2 to 'a'? [Ynesfdaq?] y
+  
+  patching file a
+  Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
+
+  $ hg diff
+  diff -r 32f76305dd23 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  @@ -5,3 +8,4 @@
+   bar
+   4
+   5
+  +babar
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 32f76305dd23a70de61867137744bfff0abc1344
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 32f76305dd23 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,5 +1,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+
+Checking the obsolescence history
+
+  $ hg log -G --hidden
+  @  changeset:   5:32f76305dd23
+  |  tag:         tip
+  |  parent:      0:7733902a8d94
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     another one
+  |
+  | x  changeset:   4:c983140931c0
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 5:32f76305dd23
+  |    summary:     temporary commit for uncommiting aa6c421e9e28164a60bdc19ec0ed4c39e5cfa65c
+  |
+  | x  changeset:   3:aa6c421e9e28
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 5:32f76305dd23
+  |    summary:     another one
+  |
+  | x  changeset:   2:557dde779d03
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 3:aa6c421e9e28
+  |    summary:     temporary commit for uncommiting f70fb463d5bf9f0ffd38f105521d96e9f2591bc1
+  |
+  | x  changeset:   1:f70fb463d5bf
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 3:aa6c421e9e28
+  |    summary:     another one
+  |
+  o  changeset:   0:7733902a8d94
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     The base commit
+  
+Push the changes back to the commit and more commits for more testing
+
+  $ hg amend
+  $ glog
+  @  6:c539671b1fd6 at default(draft) another one
+  |
+  o  0:7733902a8d94 at default(draft) The base commit
+  
+  $ touch foo
+  $ echo "hey" >> foo
+  $ hg ci -Am "Added foo"
+  adding foo
+
+Testing uncommiting a whole changeset and also for a file addition
+==================================================================
+
+  $ hg uncommit -i<<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/foo b/foo
+  new file mode 100644
+  examine changes to 'foo'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +hey
+  uncommit this change to 'foo'? [Ynesfdaq?] y
+  
+
+  $ hg status
+  A foo
+  $ hg diff
+  diff -r aa21648408ea foo
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/foo	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +hey
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID aa21648408ea8a4e32d1b45cb72d70dfbfb0c24d
+  # Parent  c539671b1fd601f2fb1da1141ef23d7198478c8a
+  Added foo
+  
+  $ hg amend
+
+Testing to uncommit removed files completely
+============================================
+
+  $ hg rm a
+  $ hg ci -m "Removed a"
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 56f9164c2dc8359aabcc4522c7b6382d2e26ed90
+  # Parent  1888d4695f614750ec3bb4f97cdd8a9ca86f0fb3
+  Removed a
+  
+  diff -r 1888d4695f61 -r 56f9164c2dc8 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,11 +0,0 @@
+  --2
+  --1
+  -0
+  -1
+  -2
+  -3
+  -foo
+  -bar
+  -4
+  -5
+  -babar
+
+Not examining the file
+----------------------
+
+  $ hg uncommit -i<<EOF
+  > n
+  > EOF
+  diff --git a/a b/a
+  deleted file mode 100644
+  examine changes to 'a'? [Ynesfdaq?] n
+  
+  abort: nothing selected to uncommit
+  [255]
+
+Examining the file
+------------------
+XXX: there is a bug in interactive selection as it is not letting to examine the
+file. Tried with curses too. In the curses UI, if you just unselect the hunks
+and the not file mod thing at the top, it will show the same "nothing unselected
+to uncommit" message which is a bug in interactive selection.
+
+  $ hg uncommit -i<<EOF
+  > y
+  > EOF
+  diff --git a/a b/a
+  deleted file mode 100644
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+
+  $ hg diff
+  diff -r 8a1b6b8b3d24 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,11 +0,0 @@
+  --2
+  --1
+  -0
+  -1
+  -2
+  -3
+  -foo
+  -bar
+  -4
+  -5
+  -babar
+  $ hg status
+  R a
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 8a1b6b8b3d24c125e1c4cd3966918d26e77c9b42
+  # Parent  1888d4695f614750ec3bb4f97cdd8a9ca86f0fb3
+  Removed a
+  
+
+  $ hg prune .
+  hg: unknown command 'prune'
+  (use 'hg help' for a list of commands)
+  [255]
+  $ hg revert --all
+  undeleting a
+
+  $ glog
+  @  13:8a1b6b8b3d24 at default(draft) Removed a
+  |
+  o  10:1888d4695f61 at default(draft) Added foo
+  |
+  o  6:c539671b1fd6 at default(draft) another one
+  |
+  o  0:7733902a8d94 at default(draft) The base commit
+  
+
+Testing when a new file is added in the last commit
+===================================================
+
+  $ echo "foo" >> foo
+  $ touch x
+  $ echo "abcd" >> x
+  $ hg add x
+  $ hg ci -m "Added x"
+  $ hg uncommit -i<<EOF
+  > y
+  > y
+  > y
+  > n
+  > EOF
+  diff --git a/foo b/foo
+  1 hunks, 1 lines changed
+  examine changes to 'foo'? [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+   hey
+  +foo
+  uncommit change 1/2 to 'foo'? [Ynesfdaq?] y
+  
+  diff --git a/x b/x
+  new file mode 100644
+  examine changes to 'x'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +abcd
+  uncommit change 2/2 to 'x'? [Ynesfdaq?] n
+  
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 039cbfbd600d0aab7f464c2eb8489af60e0385f5
+  # Parent  8a1b6b8b3d24c125e1c4cd3966918d26e77c9b42
+  Added x
+  
+  diff -r 8a1b6b8b3d24 -r 039cbfbd600d x
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/x	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abcd
+
+  $ hg diff
+  diff -r 039cbfbd600d foo
+  --- a/foo	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/foo	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   hey
+  +foo
+
+  $ hg status
+  M foo
+
+  $ hg revert --all
+  reverting foo
+
+Testing between the stack and with dirty working copy
+=====================================================
+
+  $ glog
+  @  16:039cbfbd600d at default(draft) Added x
+  |
+  o  13:8a1b6b8b3d24 at default(draft) Removed a
+  |
+  o  10:1888d4695f61 at default(draft) Added foo
+  |
+  o  6:c539671b1fd6 at default(draft) another one
+  |
+  o  0:7733902a8d94 at default(draft) The base commit
+  
+  $ hg up c539671b1fd6
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+  $ touch bar
+  $ echo "foo" >> bar
+  $ hg add bar
+  $ hg status
+  A bar
+  ? foo.orig
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID c539671b1fd601f2fb1da1141ef23d7198478c8a
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r c539671b1fd6 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,5 +1,11 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  +babar
+
+  $ hg uncommit -i<<EOF
+  > y
+  > n
+  > n
+  > y
+  > EOF
+  diff --git a/a b/a
+  3 hunks, 6 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  uncommit change 1/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  uncommit change 2/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +9,3 @@
+   4
+   5
+  +babar
+  uncommit change 3/3 to 'a'? [Ynesfdaq?] y
+  
+  patching file a
+  Hunk #1 succeeded at 1 with fuzz 1 (offset -1 lines).
+  3 new orphan changesets
+
+  $ hg diff
+  diff -r a2ee0cccc073 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -8,3 +8,4 @@
+   bar
+   4
+   5
+  +babar
+  diff -r a2ee0cccc073 bar
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/bar	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +foo
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID a2ee0cccc0738cdcd2ba75e8b418e1b489ab6aa9
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r a2ee0cccc073 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,5 +1,10 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  $ hg status
+  M a
+  A bar
+  ? foo.orig
+
+More uncommit on the same dirty working copy
+=============================================
+
+  $ hg uncommit -i<<EOF
+  > y
+  > y
+  > n
+  > EOF
+  diff --git a/a b/a
+  2 hunks, 5 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  uncommit change 1/2 to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  uncommit change 2/2 to 'a'? [Ynesfdaq?] n
+  
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 975a17b4321a7bd11286bbb1ce7cc5012806fb4a
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 975a17b4321a a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,5 +1,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+
+  $ hg diff
+  diff -r 975a17b4321a a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,3 +1,6 @@
+  +-2
+  +-1
+  +0
+   1
+   2
+   3
+  @@ -5,3 +8,4 @@
+   bar
+   4
+   5
+  +babar
+  diff -r 975a17b4321a bar
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/bar	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +foo
+
+  $ hg status
+  M a
+  A bar
+  ? foo.orig
+
+Interactive uncommit with a pattern
+-----------------------------------
+
+(more setup)
+
+  $ hg ci -m 'roaming changes'
+  $ cat > b << EOF
+  > a
+  > b
+  > c
+  > d
+  > e
+  > f
+  > h
+  > EOF
+  $ hg add b
+  $ hg ci -m 'add b'
+  $ echo 'celeste' >> a
+  $ echo 'i' >> b
+  $ hg ci -m 'some more changes'
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 2cca6971430342440b2141c347d1aae43a233b52
+  # Parent  eb07567cdf3c269f97ec3dcaa53bc32558016f18
+  some more changes
+  
+  diff -r eb07567cdf3c -r 2cca69714303 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -9,3 +9,4 @@
+   4
+   5
+   babar
+  +celeste
+  diff -r eb07567cdf3c -r 2cca69714303 b
+  --- a/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -5,3 +5,4 @@
+   e
+   f
+   h
+  +i
+
+  $ hg uncommit -i a << DONE
+  > y
+  > y
+  > DONE
+  diff --git a/a b/a
+  1 hunks, 1 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -9,3 +9,4 @@
+   4
+   5
+   babar
+  +celeste
+  uncommit this change to 'a'? [Ynesfdaq?] y
+  
+  $ hg status
+  M a
+  ? foo.orig
+  $ hg diff
+  diff -r 073013c9d352 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -9,3 +9,4 @@
+   4
+   5
+   babar
+  +celeste
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 073013c9d352701f96f3b1fd67c95ea8ff718e6a
+  # Parent  eb07567cdf3c269f97ec3dcaa53bc32558016f18
+  some more changes
+  
+  diff -r eb07567cdf3c -r 073013c9d352 b
+  --- a/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -5,3 +5,4 @@
+   e
+   f
+   h
+  +i
+
+(reset)
+
+  $ cat << EOF  > a
+  > -3
+  > -2
+  > -1
+  > 0
+  > 1
+  > 2
+  > 3
+  > foo
+  > bar
+  > 4
+  > 5
+  > babar
+  > celeste
+  > EOF
+  $ hg amend
+
+Same but do not select some change in 'a'
+
+  $ hg uncommit -i a << DONE
+  > y
+  > y
+  > n
+  > DONE
+  diff --git a/a b/a
+  2 hunks, 2 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,4 @@
+  +-3
+   -2
+   -1
+   0
+  uncommit change 1/2 to 'a'? [Ynesfdaq?] y
+  
+  @@ -9,3 +10,4 @@
+   4
+   5
+   babar
+  +celeste
+  uncommit change 2/2 to 'a'? [Ynesfdaq?] n
+  
+  $ hg status
+  M a
+  ? foo.orig
+
+  $ hg diff
+  diff -r db62d8510875 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,3 +1,4 @@
+  +-3
+   -2
+   -1
+   0
+
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID db62d8510875ea464d5287373946095971bfa056
+  # Parent  eb07567cdf3c269f97ec3dcaa53bc32558016f18
+  some more changes
+  
+  diff -r eb07567cdf3c -r db62d8510875 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -9,3 +9,4 @@
+   4
+   5
+   babar
+  +celeste
+  diff -r eb07567cdf3c -r db62d8510875 b
+  --- a/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -5,3 +5,4 @@
+   e
+   f
+   h
+  +i
+
+  $ cat b
+  a
+  b
+  c
+  d
+  e
+  f
+  h
+  i
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1012,11 +1012,13 @@
         'multiple': {
             'apply': _("apply change %d/%d to '%s'?"),
             'discard': _("discard change %d/%d to '%s'?"),
+            'uncommit': _("uncommit change %d/%d to '%s'?"),
             'record': _("record change %d/%d to '%s'?"),
         },
         'single': {
             'apply': _("apply this change to '%s'?"),
             'discard': _("discard this change to '%s'?"),
+            'uncommit': _("uncommit this change to '%s'?"),
             'record': _("record this change to '%s'?"),
         },
         'help': {
@@ -1040,6 +1042,16 @@
                          '$$ Discard &all changes to all remaining files'
                          '$$ &Quit, discarding no changes'
                          '$$ &? (display help)'),
+            'uncommit': _('[Ynesfdaq?]'
+                        '$$ &Yes, record this change'
+                        '$$ &No, skip this change'
+                        '$$ &Edit this change manually'
+                        '$$ &Skip remaining changes to this file'
+                        '$$ Record remaining changes to this &file'
+                        '$$ &Done, skip remaining changes and files'
+                        '$$ Record &all changes to all remaining files'
+                        '$$ &Quit, recording no changes'
+                        '$$ &? (display help)'),
             'record': _('[Ynesfdaq?]'
                         '$$ &Yes, record this change'
                         '$$ &No, skip this change'
diff --git a/mercurial/crecord.py b/mercurial/crecord.py
--- a/mercurial/crecord.py
+++ b/mercurial/crecord.py
@@ -566,6 +566,7 @@
 _headermessages = { # {operation: text}
     'apply': _('Select hunks to apply'),
     'discard': _('Select hunks to discard'),
+    'uncommit': _('Select hunks to uncommit'),
     None: _('Select hunks to record'),
 }
 
diff --git a/hgext/uncommit.py b/hgext/uncommit.py
--- a/hgext/uncommit.py
+++ b/hgext/uncommit.py
@@ -28,11 +28,14 @@
     copies as copiesmod,
     error,
     node,
+    obsolete,
     obsutil,
+    patch,
     pycompat,
     registrar,
     rewriteutil,
     scmutil,
+    util,
 )
 
 cmdtable = {}
@@ -48,6 +51,8 @@
     default=False,
 )
 
+stringio = util.stringio
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -139,7 +144,9 @@
         ds.copy(src, dst)
 
 @command('uncommit',
-    [('', 'keep', None, _('allow an empty commit after uncommiting')),
+    [('i', 'interactive', None,
+     _('interactively select which chunks to apply (EXPERIMENTAL)')),
+     ('', 'keep', None, _('allow an empty commit after uncommiting')),
      ('', 'allow-dirty-working-copy', False,
     _('allow uncommit with outstanding changes'))
     ] + commands.walkopts,
@@ -157,6 +164,7 @@
     given.
     """
     opts = pycompat.byteskwargs(opts)
+    interactive = opts.get('interactive')
 
     with repo.wlock(), repo.lock():
 
@@ -174,13 +182,19 @@
 
         with repo.transaction('uncommit'):
             match = scmutil.match(old, pats, opts)
+        #TODO: pass keep to _interactiveuncommit() and use it there
             keepcommit = pats
             if not keepcommit:
                 if opts.get('keep') is not None:
                     keepcommit = opts.get('keep')
                 else:
                     keepcommit = ui.configbool('experimental', 'uncommit.keep')
-            newid = _commitfiltered(repo, old, match, keepcommit)
+
+            if interactive:
+                newid = _interactiveuncommit(ui, repo, old, match)
+            else:
+                newid = _commitfiltered(repo, old, match, keepcommit)
+
             if newid is None:
                 ui.status(_("nothing to uncommit\n"))
                 return 1
@@ -194,10 +208,104 @@
                 mapping[old.node()] = ()
 
             with repo.dirstate.parentchange():
+                repo.dirstate.setparents(newid, node.nullid)
                 _fixdirstate(repo, old, repo[newid], match)
 
             scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
 
+def _interactiveuncommit(ui, repo, old, match):
+    """Makes a temporary commit with the chunks which user selected to uncommit
+    After that the diff of the parent and that commit is applied to the working
+    directory and committed again which results in the new commit which should
+    be one after uncommitted.
+    """
+
+    # create a temporary commit with hunks user selected
+    tempnode = _createtempcommit(ui, repo, old, match)
+
+    diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
+    diffopts.nodates = True
+    diffopts.git = True
+    fp = stringio()
+    for chunk, label in patch.diffui(repo, tempnode, old.node(), None,
+                                     opts=diffopts):
+        fp.write(chunk)
+
+    fp.seek(0)
+    newnode = _patchtocommit(ui, repo, old, fp)
+    mapping = {tempnode: (newnode,)}
+    scmutil.cleanupnodes(repo, mapping, 'uncommit',fixphase=True)
+    return newnode
+
+def _createtempcommit(ui, repo, old, match):
+    """Creates a temporary commit for `uncommit --interative` which contains
+    the hunks which were selected by the user to uncommit.
+    """
+
+    pold = old.p1()
+    # The logic to interactively selecting something copied from
+    # cmdutil.revert()
+    diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
+    diffopts.nodates = True
+    diffopts.git = True
+    diff = patch.diff(repo, pold.node(), old.node(), match, opts=diffopts)
+    originalchunks = patch.parsepatch(diff)
+    # XXX: The interactive selection is buggy and does not let you
+    # uncommit a removed file partially.
+    # to add uncommit as an operation taking care of BC.
+    chunks, opts = cmdutil.recordfilter(repo.ui, originalchunks,
+                                        operation='uncommit')
+    if not chunks:
+        raise error.Abort(_("nothing selected to uncommit"))
+    fp = stringio()
+    for c in chunks:
+        c.write(fp)
+
+    fp.seek(0)
+    oldnode = old.hex()
+    message = 'temporary commit for uncommiting %s' % oldnode
+    tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode)
+    return tempnode
+
+def _patchtocommit(ui, repo, old, fp, message=None, oldnode=None):
+    """Applies the patch to the working directory and make a commit whose
+    parents are same as that of old argument. The message argument tells us
+    whether to use the message of the old commit or a different message which
+    is passed. Returns the node of new commit made.
+    """
+    pold = old.p1()
+    parents = (old.p1().node(), old.p2().node())
+    date = old.date()
+    branch = old.branch()
+    user = old.user()
+    extra = old.extra()
+    if oldnode:
+        extra['uncommit_source'] = oldnode
+    if not message:
+        message = old.description()
+    store = patch.filestore()
+    try:
+        files = set()
+        try:
+            patch.patchrepo(ui, repo, pold, store, fp, 1, '',
+                            files=files, eolmode=None)
+        except patch.PatchError as err:
+            raise error.Abort(str(err))
+
+        finally:
+            del fp
+
+        memctx = context.memctx(repo, parents, message, files=files,
+                                filectxfn=store,
+                                user=user,
+                                date=date,
+                                branch=branch,
+                                extra=extra)
+        newcm = memctx.commit()
+    finally:
+        store.close()
+    return newcm
+
 def predecessormarkers(ctx):
     """yields the obsolete markers marking the given changeset as a successor"""
     for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):



To: taapas1128, #hg-reviewers
Cc: ryanmce, spectral, pulkit, martinvonz, mercurial-devel


More information about the Mercurial-devel mailing list