D6005: uncommit: added interactive mode and removed _fixdirstate(issue6062)

taapas1128 (Taapas Agrawal) phabricator at mercurial-scm.org
Fri Feb 22 13:33:09 EST 2019


taapas1128 updated this revision to Diff 14192.
taapas1128 retitled this revision from "uncommit: added interactive mode and removed _fixdirstate()(issue6062)" to "uncommit: added interactive mode and removed _fixdirstate(issue6062)".

REPOSITORY
  rHG Mercurial

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

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

AFFECTED FILES
  hgext/uncommit.py
  tests/test-unamend.t
  tests/test-uncommit-interactive.t
  tests/test-uncommit.t

CHANGE DETAILS

diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -34,6 +34,7 @@
   
   options ([+] can be repeated):
   
+   -i --interactive         interactive mode to uncommit
       --keep                allow an empty commit after uncommiting
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
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
+  discard change 1/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  discard change 2/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +9,3 @@
+   4
+   5
+  +babar
+  discard 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
+  discard change 1/3 to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  discard change 2/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +9,3 @@
+   4
+   5
+  +babar
+  discard change 3/3 to 'a'? [Ynesfdaq?] n
+  
+
+  $ hg log -G --hidden
+  @  changeset:   3:678a59e5ff90
+  |  tag:         tip
+  |  parent:      0:7733902a8d94
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     another one
+  |
+  | x  changeset:   2:e9635f4beaf1
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    pruned using uncommit
+  |    summary:     temporary commit for uncommiting f70fb463d5bf
+  |
+  | x  changeset:   1:f70fb463d5bf
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 3:678a59e5ff90
+  |    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 678a59e5ff90 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 678a59e5ff90754d5e94719bd82ad169be773c21
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 678a59e5ff90 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 678a59e5ff90 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
+  discard change 1/2 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +6,3 @@
+   4
+   5
+  +babar
+  discard 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 46e35360be47 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 46e35360be473bf761bedf3d05de4a68ffd9d9f8
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 46e35360be47 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:46e35360be47
+  |  tag:         tip
+  |  parent:      0:7733902a8d94
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     another one
+  |
+  | x  changeset:   4:7ca9935a62f1
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    pruned using uncommit
+  |    summary:     temporary commit for uncommiting 678a59e5ff90
+  |
+  | x  changeset:   3:678a59e5ff90
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 5:46e35360be47
+  |    summary:     another one
+  |
+  | x  changeset:   2:e9635f4beaf1
+  |/   parent:      0:7733902a8d94
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    pruned using uncommit
+  |    summary:     temporary commit for uncommiting f70fb463d5bf
+  |
+  | x  changeset:   1:f70fb463d5bf
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using uncommit as 3:678a59e5ff90
+  |    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:905eb2a23ea2 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
+  discard this change to 'foo'? [Ynesfdaq?] y
+  
+
+  $ hg status
+  A foo
+  $ hg diff
+  diff -r 857367499298 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 857367499298e999b5841bb01df65f73088b5d3b
+  # Parent  905eb2a23ea2d92073419d0e19165b90d36ea223
+  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 219cfe20964e93f8bb9bd82ceaa54d3b776046db
+  # Parent  42cc15efbec26c14d96d805dee2766ba91d1fd31
+  Removed a
+  
+  diff -r 42cc15efbec2 -r 219cfe20964e 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 737487f1e5f8 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 737487f1e5f853e55decb73ea31522c63e7f5980
+  # Parent  42cc15efbec26c14d96d805dee2766ba91d1fd31
+  Removed a
+  
+
+  $ hg prune .
+  hg: unknown command 'prune'
+  (use 'hg help' for a list of commands)
+  [255]
+  $ hg revert --all
+  undeleting a
+
+  $ glog
+  @  13:737487f1e5f8 at default(draft) Removed a
+  |
+  o  10:42cc15efbec2 at default(draft) Added foo
+  |
+  o  6:905eb2a23ea2 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
+  discard 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
+  discard 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 f7b39cf595081f8e63fe1119953cc7f669663720
+  # Parent  737487f1e5f853e55decb73ea31522c63e7f5980
+  Added x
+  
+  diff -r 737487f1e5f8 -r f7b39cf59508 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 f7b39cf59508 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:f7b39cf59508 at default(draft) Added x
+  |
+  o  13:737487f1e5f8 at default(draft) Removed a
+  |
+  o  10:42cc15efbec2 at default(draft) Added foo
+  |
+  o  6:905eb2a23ea2 at default(draft) another one
+  |
+  o  0:7733902a8d94 at default(draft) The base commit
+  
+  $ hg up 905eb2a23ea2
+  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 905eb2a23ea2d92073419d0e19165b90d36ea223
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 905eb2a23ea2 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
+  discard change 1/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  discard change 2/3 to 'a'? [Ynesfdaq?] n
+  
+  @@ -4,2 +9,3 @@
+   4
+   5
+  +babar
+  discard 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 676366511f95 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 676366511f95 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 676366511f95ca4122413dcf79b45eaab61fb387
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 676366511f95 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
+  discard change 1/2 to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,5 +4,7 @@
+   1
+   2
+   3
+  +foo
+  +bar
+   4
+   5
+  discard 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 62d907d0c4fa13b4b8bfeed05f13751035daf963
+  # Parent  7733902a8d94c789ca81d866bea1893d79442db6
+  another one
+  
+  diff -r 7733902a8d94 -r 62d907d0c4fa 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 62d907d0c4fa 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 62d907d0c4fa 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 be5c67225e80b050867862bbd9f4755c4e9207c5
+  # Parent  c280a907fddcef2ffe9fadcc2d87f29998e22b2f
+  some more changes
+  
+  diff -r c280a907fddc -r be5c67225e80 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 c280a907fddc -r be5c67225e80 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
+  discard this change to 'a'? [Ynesfdaq?] y
+  
+  $ hg status
+  M a
+  ? foo.orig
+  $ hg diff
+  diff -r c701d7c8d18b 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 c701d7c8d18be55a92688f4458c26bd74fb1f525
+  # Parent  c280a907fddcef2ffe9fadcc2d87f29998e22b2f
+  some more changes
+  
+  diff -r c280a907fddc -r c701d7c8d18b 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
+  discard change 1/2 to 'a'? [Ynesfdaq?] y
+  
+  @@ -9,3 +10,4 @@
+   4
+   5
+   babar
+  +celeste
+  discard change 2/2 to 'a'? [Ynesfdaq?] n
+  
+  $ hg status
+  M a
+  ? foo.orig
+
+  $ hg diff
+  diff -r 28d5de12b225 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 28d5de12b225d1e0951110cced8d8994227be026
+  # Parent  c280a907fddcef2ffe9fadcc2d87f29998e22b2f
+  some more changes
+  
+  diff -r c280a907fddc -r 28d5de12b225 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 c280a907fddc -r 28d5de12b225 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/tests/test-unamend.t b/tests/test-unamend.t
--- a/tests/test-unamend.t
+++ b/tests/test-unamend.t
@@ -83,9 +83,9 @@
   
   $ hg unamend
   $ hg glog --hidden
-  @  9:46d02d47eec6  Added h
+  o  9:46d02d47eec6  Added h
   |
-  | x  8:c9fa1a715c1b  Added h
+  | @  8:c9fa1a715c1b  Added h
   |/
   | x  7:ec2426147f0e  Added h
   |/
@@ -104,34 +104,28 @@
   o  0:18d04c59bb5d  Added a
   
   $ hg diff
-  diff -r 46d02d47eec6 h
-  --- a/h	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/h	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,2 @@
-   foo
-  +bar
 
   $ hg exp
   # HG changeset patch
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 46d02d47eec6ca096b8dcab3f8f5579c40c3dd9a
+  # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
   # Parent  87d6d66763085b629e6d7ed56778c79827273022
   Added h
   
-  diff -r 87d6d6676308 -r 46d02d47eec6 h
+  diff -r 87d6d6676308 -r c9fa1a715c1b h
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/h	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
+  @@ -0,0 +1,2 @@
   +foo
+  +bar
 
   $ hg status
-  M h
 
   $ hg log -r . -T '{extras % "{extra}\n"}' --config alias.log=log
+  amend_source=ec2426147f0e39dbc9cef599b066be6035ce691d
   branch=default
-  unamend_source=c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
 
 Using unamend to undo an unamed (intentional)
 
@@ -141,11 +135,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 850ddfc1bc662997ec6094ada958f01f0cc8070a
+  # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
   # Parent  87d6d66763085b629e6d7ed56778c79827273022
   Added h
   
-  diff -r 87d6d6676308 -r 850ddfc1bc66 h
+  diff -r 87d6d6676308 -r c9fa1a715c1b h
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/h	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,2 @@
@@ -157,6 +151,7 @@
 
   $ echo "bar" >> a
   $ hg amend
+  2 new content-divergent changesets
   $ echo "foobar" >> a
   $ echo "bar" >> b
   $ hg status
@@ -170,14 +165,14 @@
   M b
 
   $ hg diff
-  diff -r ec338db45d51 a
+  diff -r 924a1bde06b4 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,3 @@
+  @@ -1,2 +1,3 @@
    foo
-  +bar
+   bar
   +foobar
-  diff -r ec338db45d51 b
+  diff -r 924a1bde06b4 b
   --- a/b	Thu Jan 01 00:00:00 1970 +0000
   +++ b/b	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,1 +1,2 @@
@@ -187,6 +182,7 @@
 Unamending an added file
 
   $ hg ci -m "Added things to a and b"
+  1 new orphan changesets
   $ echo foo > bar
   $ hg add bar
   $ hg amend
@@ -202,45 +198,61 @@
 
   $ hg remove a
   $ hg amend
+  1 new orphan changesets
+  2 new content-divergent changesets
 
   $ hg unamend
   $ hg status
   R a
   ? bar
 
   $ hg revert --all
   undeleting a
+  abort: a at a7d3ff363911: not found in manifest!
+  [255]
 
 Unamending an added file with dirty wdir status
 
   $ hg add bar
   $ hg amend
+  1 new orphan changesets
+  1 new content-divergent changesets
   $ echo bar >> bar
   $ hg status
   M bar
 
   $ hg unamend
   $ hg status
-  A bar
+  M bar
   $ hg diff
-  diff -r 7f79409af972 bar
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  diff -r cfcfb29e2ff7 bar
+  --- a/bar	Thu Jan 01 00:00:00 1970 +0000
   +++ b/bar	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,2 @@
-  +foo
+  @@ -1,1 +1,2 @@
+   foo
   +bar
 
   $ hg revert --all
-  forgetting bar
+  reverting bar
   $ rm bar
 
 Unamending in middle of a stack
 
   $ hg glog
-  @  19:7f79409af972  Added things to a and b
+  *  18:93b9747d736b  Added things to a and b
   |
-  o  12:ec338db45d51  Added h
-  |
+  | @  17:cfcfb29e2ff7  Added things to a and b
+  |/
+  | *  16:f3e8006b11ed  Added things to a and b
+  |/
+  | *  14:61096993d6f1  Added things to a and b
+  |/
+  | *  11:f392ea0dfe43  Added h
+  | |
+  x |  10:924a1bde06b4  Added h
+  |/
+  | *  9:46d02d47eec6  Added h
+  |/
   o  6:87d6d6676308  Added g
   |
   o  5:825660c69f0c  Added f
@@ -263,14 +275,26 @@
   $ hg rebase -s 6 -d . -q
 
   $ hg glog
-  o  23:03ddd6fc5af1  Added things to a and b
+  *  22:744f14403439  Added h
   |
-  o  22:3e7b64ee157b  Added h
+  | *  21:cf05c9d36833  Added h
+  |/
+  o  20:49635b68477e  Added g
+  |
+  @  19:93f0e8ffab32  Added f
   |
-  o  21:49635b68477e  Added g
-  |
-  @  20:93f0e8ffab32  Added f
-  |
+  | *  18:93b9747d736b  Added things to a and b
+  | |
+  | | *  16:f3e8006b11ed  Added things to a and b
+  | |/
+  | | *  14:61096993d6f1  Added things to a and b
+  | |/
+  | x  10:924a1bde06b4  Added h
+  | |
+  | x  6:87d6d6676308  Added g
+  | |
+  | x  5:825660c69f0c  Added f
+  |/
   o  4:aa98ab95a928  Added e
   |
   o  3:62615734edd5  Added d
@@ -292,17 +316,17 @@
 Trying to unamend a public changeset
 
   $ hg up -C 23
-  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg phase -r . -p
-  1 new phase-divergent changesets
   $ hg unamend
   abort: cannot unamend public changesets
   (see 'hg help phases' for details)
   [255]
 
 Testing whether unamend retains copies or not
 
   $ hg status
+  ? bar.orig
 
   $ hg mv a foo
 
@@ -312,8 +336,8 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID cfef290346fbee5126313d7e1aab51d877679b09
-  # Parent  03ddd6fc5af19e028c44a2fd6d790dd22712f231
+  # Node ID 801c2513f166634bfae97bad18df32b999050c12
+  # Parent  7a9352c9ddcedbf857d38c13bca4679f7a3ba78a
   Moved a to foo
   
   diff --git a/a b/foo
@@ -332,8 +356,8 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID eca050985275bb271ce3092b54e56ea5c85d29a3
-  # Parent  03ddd6fc5af19e028c44a2fd6d790dd22712f231
+  # Node ID 7b9f94d173c7d15a9e32e33afb654de21dbc16bf
+  # Parent  7a9352c9ddcedbf857d38c13bca4679f7a3ba78a
   Moved a to foo
   
   diff --git a/a b/foo
@@ -353,24 +377,32 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 552e3af4f01f620f88ca27be1f898316235b736a
-  # Parent  03ddd6fc5af19e028c44a2fd6d790dd22712f231
+  # Node ID 7b9f94d173c7d15a9e32e33afb654de21dbc16bf
+  # Parent  7a9352c9ddcedbf857d38c13bca4679f7a3ba78a
   Moved a to foo
   
   diff --git a/a b/foo
   rename from a
   rename to foo
+  diff --git a/b b/foobar
+  rename from b
+  rename to foobar
 
 Retained copies in working directoy
 
   $ hg diff --git
-  diff --git a/b b/foobar
-  rename from b
-  rename to foobar
+  diff --git a/foobar b/foobar
+  new file mode 100644
+  --- /dev/null
+  +++ b/foobar
+  @@ -0,0 +1,1 @@
+  +foo
   diff --git a/c b/wat
   rename from c
   rename to wat
   $ hg revert -qa
+  abort: b at 7b9f94d173c7: not found in manifest!
+  [255]
   $ rm foobar wat
 
 Rename a->b, then amend b->c. After unamend, should look like b->c.
@@ -382,14 +414,17 @@
   $ hg amend
   $ hg unamend
   $ hg st --copies --change .
-  A b
+  A c
     a
   R a
   $ hg st --copies
   A c
     b
   R b
+  ? bar.orig
   $ hg revert -qa
+  abort: b at a629a52c2105: not found in manifest!
+  [255]
   $ rm c
 
 Rename a->b, then amend b->c, and working copy change c->d. After unamend, should look like b->d
@@ -402,10 +437,11 @@
   $ hg mv c d
   $ hg unamend
   $ hg st --copies --change .
-  A b
+  A c
     a
   R a
   $ hg st --copies
   A d
     b
   R b
+  ? bar.orig
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 = {}
@@ -45,6 +48,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
@@ -93,33 +98,83 @@
                          extra=ctx.extra())
     return repo.commitctx(new)
 
-def _fixdirstate(repo, oldctx, newctx, match=None):
-    """ fix the dirstate after switching the working directory from oldctx to
-    newctx which can be result of either unamend or uncommit.
+def _uncommitdirstate(repo, oldctx, newctx, match, interactive):
+    """Fix the dirstate after switching the working directory from
+    oldctx to a copy of oldctx not containing changed files matched by
+    match.
     """
+    ctx = repo['.']
     ds = repo.dirstate
-    ds.setparents(newctx.node(), node.nullid)
     copies = dict(ds.copies())
-    s = newctx.status(oldctx, match=match)
-    for f in s.modified:
-        if ds[f] == 'r':
-            # modified + removed -> removed
-            continue
-        ds.normallookup(f)
+    if interactive:
+        # In interactive cases, we will find the status between oldctx and ctx
+        # and considering only the files which are changed between oldctx and
+        # ctx, and the status of what changed between oldctx and ctx will help
+        # us in defining the exact behavior
+        m, a, r = repo.status(oldctx, ctx, match=match)[:3]
+        for f in m:
+            # These are files which are modified between oldctx and ctx which
+            # contains two cases: 1) Were modified in oldctx and some
+            # modifications are uncommitted
+            # 2) Were added in oldctx but some part is uncommitted (this cannot
+            # contain the case when added files are uncommitted completely as
+            # that will result in status as removed not modified.)
+            # Also any modifications to a removed file will result the status as
+            # added, so we have only two cases. So in either of the cases, the
+            # resulting status can be modified or clean.
+            if ds[f] == 'r':
+                # But the file is removed in the working directory, leaving that
+                # as removed
+                continue
+            ds.normallookup(f)
 
-    for f in s.added:
-        if ds[f] == 'r':
-            # added + removed -> unknown
-            ds.drop(f)
-        elif ds[f] != 'a':
-            ds.add(f)
+        for f in a:
+            # These are the files which are added between oldctx and ctx(new
+            # one), which means the files which were removed in oldctx
+            # but uncommitted completely while making the ctx
+            # This file should be marked as removed if the working directory
+            # does not adds it back. If it's adds it back, we do a normallookup.
+            # The file can't be removed in working directory, because it was
+            # removed in oldctx
+            if ds[f] == 'a':
+                ds.normallookup(f)
+                continue
+            ds.remove(f)
 
-    for f in s.removed:
-        if ds[f] == 'a':
-            # removed + added -> normal
+        for f in r:
+            # These are files which are removed between oldctx and ctx, which
+            # means the files which were added in oldctx and were completely
+            # uncommitted in ctx. If a added file is partially uncommitted, that
+            # would have resulted in modified status, not removed.
+            # So a file added in a commit, and uncommitting that addition must
+            # result in file being stated as unknown.
+            if ds[f] == 'r':
+                # The working directory say it's removed, so lets make the file
+                # unknown
+                ds.drop(f)
+                continue
+            ds.add(f)
+    else:
+        s = newctx.status(oldctx, match=match)
+        for f in s.modified:
+            if ds[f] == 'r':
+                # modified + removed -> removed
+                continue
             ds.normallookup(f)
-        elif ds[f] != 'r':
-            ds.remove(f)
+
+        for f in s.added:
+            if ds[f] == 'r':
+                # added + removed -> unknown
+                ds.drop(f)
+            elif ds[f] != 'a':
+                ds.add(f)
+
+        for f in s.removed:
+            if ds[f] == 'a':
+                # removed + added -> normal
+                ds.normallookup(f)
+            elif ds[f] != 'r':
+                ds.remove(f)
 
     # Merge old parent and old working dir copies
     oldcopies = copiesmod.pathcopies(newctx, oldctx, match)
@@ -133,7 +188,8 @@
         ds.copy(src, dst)
 
 @command('uncommit',
-    [('', 'keep', False, _('allow an empty commit after uncommiting')),
+    [('i', 'interactive', False, _('interactive mode to uncommit')),
+    ('', 'keep', False, _('allow an empty commit after uncommiting')),
     ] + commands.walkopts,
     _('[OPTION]... [FILE]...'),
     helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
@@ -149,6 +205,7 @@
     given.
     """
     opts = pycompat.byteskwargs(opts)
+    interactive = opts.get('interactive')
 
     with repo.wlock(), repo.lock():
 
@@ -164,6 +221,9 @@
             match = scmutil.match(old, pats, opts)
             keepcommit = opts.get('keep') or pats
             newid = _commitfiltered(repo, old, match, keepcommit)
+            if interactive:
+                newid = _interactiveuncommit(ui, repo, old, match)
+
             if newid is None:
                 ui.status(_("nothing to uncommit\n"))
                 return 1
@@ -177,10 +237,106 @@
                 mapping[old.node()] = ()
 
             with repo.dirstate.parentchange():
-                _fixdirstate(repo, old, repo[newid], match)
+                repo.dirstate.setparents(newid, node.nullid)
+                _uncommitdirstate(repo, old, repo[newid], match, interactive)
 
             scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
 
+def _interactiveuncommit(ui, repo, old, match):
+    """ The function which contains all the logic for interactively uncommiting
+    a commit. This function 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)
+    # creating obs marker temp -> ()
+    obsolete.createmarkers(repo, [(repo[tempnode], ())], operation="uncommit")
+    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.
+    # TODO: wrap the operations in mercurial/patch.py and mercurial/crecord.py
+    # to add uncommit as an operation taking care of BC.
+    chunks, opts = cmdutil.recordfilter(repo.ui, originalchunks,
+                                        operation='discard')
+    if not chunks:
+        raise error.Abort(_("nothing selected to uncommit"))
+    fp = stringio()
+    for c in chunks:
+            c.write(fp)
+
+    fp.seek(0)
+    oldnode = node.hex(old.node())[:12]
+    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, extras=None):
+    """ A function which will apply 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 extras:
+        extra['uncommit_source'] = extras
+    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(), ()):
@@ -239,7 +395,9 @@
         dirstate = repo.dirstate
 
         with dirstate.parentchange():
-            _fixdirstate(repo, curctx, newpredctx)
+            match = None
+            interactive = 0
+            _uncommitdirstate(repo, curctx, newpredctx, match, interactive)
 
         mapping = {curctx.node(): (newprednode,)}
         scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)



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


More information about the Mercurial-devel mailing list