D6005: uncommit: added interactive mode and removed _fixdirstate(issue6062)
taapas1128 (Taapas Agrawal)
phabricator at mercurial-scm.org
Fri Feb 22 18:33:09 UTC 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