D3684: shelve: merge in obsshelve changes implemented at facebook
durin42 (Augie Fackler)
phabricator at mercurial-scm.org
Mon Jun 4 22:16:19 UTC 2018
durin42 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.
REVISION SUMMARY
test-obsshelve.t could be merged with test-shelve.t at some point, but
for now I want the comprehensive coverage. Note that obsshelve in
hg-experimental is a fork of shelve, so we're just merging things back
together. The path to graduation from experimental for obsshelve is to
have a better solution around hiding revisions without creating a ton
of markers that'll get exchanged. We've spent a fair amount of time
talking about what that should look like, but in the interim the
experimental-branded obsshelve is a good thing we should have in core.
As a follow-up I'd like to make obsshelve a case of test-shelve.t, but
I thought this would be easier for the initial import.
REPOSITORY
rHG Mercurial
REVISION DETAIL
https://phab.mercurial-scm.org/D3684
AFFECTED FILES
hgext/shelve.py
tests/test-obsshelve.t
CHANGE DETAILS
diff --git a/tests/test-obsshelve.t b/tests/test-obsshelve.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsshelve.t
@@ -0,0 +1,1597 @@
+ $ PYTHONPATH=$TESTDIR/..:$PYTHONPATH
+ $ export PYTHONPATH
+
+ $ cat <<EOF >> $HGRCPATH
+ > [extensions]
+ > mq =
+ > shelve=
+ > [defaults]
+ > diff = --nodates --git
+ > qnew = --date '0 0'
+ > [shelve]
+ > maxbackups = 2
+ > [experimental]
+ > evolution=createmarkers
+ > EOF
+
+Make sure obs-based shelve can be used with an empty repo
+ $ cd $TESTTMP
+ $ hg init obsrepo
+ $ cd obsrepo
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > obsshelve=True
+ > EOF
+
+ $ mkdir a b
+ $ echo a > a/a
+ $ echo b > b/b
+ $ echo c > c
+ $ echo d > d
+ $ echo x > x
+ $ hg addremove -q
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
+ $ hg shelve --list
+ default (*s ago) (changes in empty repository) (glob)
+ $ hg revert --all
+ $ hg unshelve
+ unshelving change 'default'
+ $ hg diff
+ diff --git a/a/a b/a/a
+ new file mode 100644
+ --- /dev/null
+ +++ b/a/a
+ @@ -0,0 +1,1 @@
+ +a
+ diff --git a/b/b b/b/b
+ new file mode 100644
+ --- /dev/null
+ +++ b/b/b
+ @@ -0,0 +1,1 @@
+ +b
+ diff --git a/c b/c
+ new file mode 100644
+ --- /dev/null
+ +++ b/c
+ @@ -0,0 +1,1 @@
+ +c
+ diff --git a/d b/d
+ new file mode 100644
+ --- /dev/null
+ +++ b/d
+ @@ -0,0 +1,1 @@
+ +d
+ diff --git a/x b/x
+ new file mode 100644
+ --- /dev/null
+ +++ b/x
+ @@ -0,0 +1,1 @@
+ +x
+ $ hg ci -qm "initial commit"
+ $ hg shelve
+ nothing changed
+ [1]
+
+Make sure shelve files were backed up
+ $ ls .hg/shelve-backup
+ default.oshelve
+ default.patch
+
+Create an mq patch - shelving should work fine with a patch applied
+ $ echo n > n
+ $ hg add n
+ $ hg commit n -m second
+ $ hg qnew second.patch
+
+Shelve a change that we will delete later
+ $ echo a >> a/a
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Set up some more complex changes to shelve
+ $ echo a >> a/a
+ $ hg mv b b.rename
+ moving b/b to b.rename/b (glob)
+ $ hg cp c c.copy
+ $ hg status -C
+ M a/a
+ A b.rename/b
+ b/b
+ A c.copy
+ c
+ R b/b
+
+The common case - no options or filenames
+ $ hg shelve
+ shelved as default-01
+ 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg status -C
+
+Ensure that our shelved changes exist
+ $ hg shelve -l
+ default-01 (*)* changes to: [mq]: second.patch (glob)
+ default (*)* changes to: [mq]: second.patch (glob)
+ $ hg shelve -l -p default
+ default (*)* changes to: [mq]: second.patch (glob)
+
+ diff --git a/a/a b/a/a
+ --- a/a/a
+ +++ b/a/a
+ @@ -1,1 +1,2 @@
+ a
+ +a
+
+ $ hg shelve --list --addremove
+ abort: options '--list' and '--addremove' may not be used together
+ [255]
+
+Delete our older shelved change
+ $ hg shelve -d default
+ $ hg qfinish -a -q
+
+Ensure shelve backups aren't overwritten
+ $ ls .hg/shelve-backup/
+ default-1.oshelve
+ default-1.patch
+ default.oshelve
+ default.patch
+
+Local edits should not prevent a shelved change from applying
+ $ printf "z\na\n" > a/a
+ $ hg unshelve --keep
+ unshelving change 'default-01'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 5:32c69314e062 "changes to: [mq]: second.patch"
+ merging a/a
+
+ $ hg revert --all -q
+ $ rm a/a.orig b.rename/b c.copy
+
+Apply it and make sure our state is as expected
+(this also tests that same timestamp prevents backups from being
+removed, even though there are more than 'maxbackups' backups)
+ $ f -t .hg/shelve-backup/default.patch
+ .hg/shelve-backup/default.patch: file
+ $ touch -t 200001010000 .hg/shelve-backup/default.patch
+ $ f -t .hg/shelve-backup/default-1.patch
+ .hg/shelve-backup/default-1.patch: file
+ $ touch -t 200001010000 .hg/shelve-backup/default-1.patch
+
+ $ hg unshelve
+ unshelving change 'default-01'
+ $ hg status -C
+ M a/a
+ A b.rename/b
+ b/b
+ A c.copy
+ c
+ R b/b
+ $ hg shelve -l
+
+(both of default.oshelve and default-1.oshelve should be still kept,
+because it is difficult to decide actual order of them from same timestamp)
+ $ ls .hg/shelve-backup/
+ default-01.oshelve
+ default-01.patch
+ default-1.oshelve
+ default-1.patch
+ default.oshelve
+ default.patch
+ $ hg unshelve
+ abort: no shelved changes to apply!
+ [255]
+ $ hg unshelve foo
+ abort: shelved change 'foo' not found
+ [255]
+
+Named shelves, specific filenames, and "commit messages" should all work
+(this tests also that editor is invoked, if '--edit' is specified)
+ $ hg status -C
+ M a/a
+ A b.rename/b
+ b/b
+ A c.copy
+ c
+ R b/b
+ $ HGEDITOR=cat hg shelve -q -n wibble -m wat -e a
+ wat
+
+
+ HG: Enter commit message. Lines beginning with 'HG:' are removed.
+ HG: Leave message empty to abort commit.
+ HG: --
+ HG: user: shelve at localhost
+ HG: branch 'default'
+ HG: changed a/a
+
+Expect "a" to no longer be present, but status otherwise unchanged
+ $ hg status -C
+ A b.rename/b
+ b/b
+ A c.copy
+ c
+ R b/b
+ $ hg shelve -l --stat
+ wibble (*) wat (glob)
+ a/a | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+
+And now "a/a" should reappear
+ $ cd a
+ $ hg unshelve -q wibble
+ $ cd ..
+ $ hg status -C
+ M a/a
+ A b.rename/b
+ b/b
+ A c.copy
+ c
+ R b/b
+
+Ensure old shelve backups are being deleted automatically
+ $ ls .hg/shelve-backup/
+ default-01.oshelve
+ default-01.patch
+ wibble.oshelve
+ wibble.patch
+
+Cause unshelving to result in a merge with 'a' conflicting
+ $ hg shelve -q
+ $ echo c>>a/a
+ $ hg commit -m second
+ $ hg tip --template '{files}\n'
+ a/a
+
+Add an unrelated change that should be preserved
+ $ mkdir foo
+ $ echo foo > foo/foo
+ $ hg add foo/foo
+
+Force a conflicted merge to occur
+ $ hg unshelve
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 5:32c69314e062 "changes to: [mq]: second.patch"
+ merging a/a
+ warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+
+Ensure that we have a merge with unresolved conflicts
+ $ hg heads -q --template '{rev}\n'
+ 12
+ 5
+ $ hg parents -q --template '{rev}\n'
+ 12
+ 5
+ $ hg status
+ M a/a
+ M b.rename/b
+ M c.copy
+ R b/b
+ ? a/a.orig
+ $ hg diff
+ diff --git a/a/a b/a/a
+ --- a/a/a
+ +++ b/a/a
+ @@ -1,2 +1,6 @@
+ a
+ +<<<<<<< dest: * - shelve: pending changes temporary commit (glob)
+ c
+ +=======
+ +a
+ +>>>>>>> source: 32c69314e062 - shelve: changes to: [mq]: second.patch
+ diff --git a/b/b b/b.rename/b
+ rename from b/b
+ rename to b.rename/b
+ diff --git a/c b/c.copy
+ copy from c
+ copy to c.copy
+ $ hg resolve -l
+ U a/a
+
+ $ hg shelve
+ abort: unshelve already in progress
+ (use 'hg unshelve --continue' or 'hg unshelve --abort')
+ [255]
+
+Abort the unshelve and be happy
+ $ hg status
+ M a/a
+ M b.rename/b
+ M c.copy
+ R b/b
+ ? a/a.orig
+ $ hg unshelve -a
+ rebase aborted
+ unshelve of 'default' aborted
+ $ hg heads -q
+ 11:2e69b451d1ea
+ $ hg parents | grep changeset
+ changeset: 11:2e69b451d1ea
+ $ hg resolve -l
+ $ hg status
+ A foo/foo
+ ? a/a.orig
+
+Try to continue with no unshelve underway
+ $ hg unshelve -c
+ abort: no unshelve in progress
+ [255]
+ $ hg status
+ A foo/foo
+ ? a/a.orig
+
+Redo the unshelve to get a conflict
+ $ hg unshelve -q
+ warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+
+Attempt to continue
+ $ hg unshelve -c
+ abort: unresolved conflicts, can't continue
+ (see 'hg resolve', then 'hg unshelve --continue')
+ [255]
+ $ hg revert -r . a/a
+ $ hg resolve -m a/a
+ (no more unresolved files)
+ continue: hg unshelve --continue
+ $ hg commit -m 'commit while unshelve in progress'
+ abort: unshelve already in progress
+ (use 'hg unshelve --continue' or 'hg unshelve --abort')
+ [255]
+ $ hg graft --continue
+ abort: no graft in progress
+ (continue: hg unshelve --continue)
+ [255]
+ $ hg unshelve -c --trace
+ rebasing 5:32c69314e062 "changes to: [mq]: second.patch"
+ 1 new orphan changesets
+ unshelve of 'default' complete
+
+Ensure the repo is as we hope
+ $ hg parents | grep changeset
+ changeset: 11:2e69b451d1ea
+ $ hg heads -q
+ 11:2e69b451d1ea
+ $ hg status -C
+ A b.rename/b
+ b/b
+ A c.copy
+ c
+ A foo/foo
+ R b/b
+ ? a/a.orig
+
+There should be no shelves left
+ $ hg shelve -l
+
+#if execbit
+Ensure that metadata-only changes are shelved
+ $ chmod +x a/a
+ $ hg shelve -q -n execbit a/a
+ $ hg status a/a
+ $ hg unshelve -q execbit
+ $ hg status a/a
+ M a/a
+ $ hg revert a/a
+#endif
+
+#if symlink
+Ensure symlinks are properly handled
+ $ rm a/a
+ $ ln -s foo a/a
+ $ hg shelve -q -n symlink a/a
+ $ hg status a/a
+ $ hg unshelve -q symlink
+ $ hg status a/a
+ M a/a
+ $ hg revert a/a
+#endif
+
+Set up another conflict between a commit and a shelved change
+ $ hg revert -q -C -a
+ $ rm a/a.orig b.rename/b c.copy
+ $ echo a >> a/a
+ $ hg shelve -q
+ $ echo x >> a/a
+ $ hg ci -m 'create conflict'
+ $ hg add foo/foo
+
+If we resolve a conflict while unshelving, the unshelve should succeed
+ $ hg unshelve --tool :merge-other --keep
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing .* "changes to: second" (re)
+ merging a/a
+ $ hg shelve -l
+ default (*)* changes to: second (glob)
+ $ hg status
+ M a/a
+ A foo/foo
+ $ cat a/a
+ a
+ c
+ a
+ $ cat > a/a << EOF
+ > a
+ > c
+ > x
+ > EOF
+ $ sleep 1
+ $ HGMERGE=true hg unshelve
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing .* "changes to: second" (re)
+ merging a/a
+ note: rebase of .* created no changes to commit (re)
+ $ hg shelve -l
+ $ hg status
+ M a/a
+ A foo/foo
+ $ cat a/a
+ a
+ c
+ a
+Later tests don't expect modifications to a/a, so undo those edits.
+ $ hg revert a/a
+
+Test keep and cleanup
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg shelve --list
+ default (*)* changes to: create conflict (glob)
+ $ hg unshelve -k
+ unshelving change 'default'
+ $ hg shelve --list
+ default (*)* changes to: create conflict (glob)
+ $ hg shelve --cleanup
+ $ hg shelve --list
+
+Test bookmarks
+ $ hg bookmark test
+ $ hg bookmark
+ \* test * (glob)
+ $ hg shelve
+ shelved as test
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg bookmark
+ \* test * (glob)
+ $ hg unshelve
+ unshelving change 'test'
+ $ hg bookmark
+ \* test * (glob)
+
+Shelve should still work even if mq is disabled
+ $ hg --config extensions.mq=! shelve
+ shelved as test
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg --config extensions.mq=! shelve --list
+ test (*)* changes to: create conflict (glob)
+ $ hg bookmark
+ * test * (glob)
+ $ hg --config extensions.mq=! unshelve
+ unshelving change 'test'
+ $ hg bookmark
+ * test * (glob)
+ $ cd ..
+
+Shelve should leave dirstate clean (issue4055)
+ $ hg init obsshelverebase
+ $ cd obsshelverebase
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ printf 'x\ny\n' > x
+ $ echo z > z
+ $ hg commit -Aqm xy
+ $ echo z >> x
+ $ hg commit -Aqm z
+ $ hg up 0
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ printf 'a\nx\ny\nz\n' > x
+ $ hg commit -Aqm xyz
+ $ echo c >> z
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg rebase -d 1 --config extensions.rebase=
+ rebasing 2:323bfa07f744 "xyz"
+ merging x
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 3:82a0d7d6ba61 "changes to: xyz"
+ $ hg status
+ M z
+ $ cd ..
+
+Shelve should only unshelve pending changes (issue4068)
+ $ hg init obssh-onlypendingchanges
+ $ cd obssh-onlypendingchanges
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ touch a
+ $ hg ci -Aqm a
+ $ touch b
+ $ hg ci -Aqm b
+ $ hg up -q 0
+ $ touch c
+ $ hg ci -Aqm c
+ $ touch d
+ $ hg add d
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg up -q 1
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 3:958bcbd1776e "changes to: c" (tip)
+ $ hg status
+ A d
+
+Unshelve should work on an ancestor of the original commit
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg up 0
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 5:013284d9655e "changes to: b" (tip)
+ $ hg status
+ A d
+
+Test bug 4073 we need to enable obsolete markers for it
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg debugobsolete `hg --debug id -i -r 1`
+ obsoleted 1 changesets
+ $ hg unshelve
+ unshelving change 'default'
+
+Unshelve should leave unknown files alone (issue4113)
+ $ echo e > e
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg status
+ ? e
+ $ hg unshelve
+ unshelving change 'default'
+ $ hg status
+ A d
+ ? e
+ $ cat e
+ e
+
+139. Unshelve should keep a copy of unknown files
+
+ $ hg add e
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ echo z > e
+ $ hg unshelve
+ unshelving change 'default'
+ $ cat e
+ e
+ $ cat e.orig
+ z
+
+140. Unshelve and conflicts with tracked and untracked files
+
+ preparing:
+
+ $ rm *.orig
+ $ hg ci -qm 'commit stuff'
+ $ hg phase -p null:
+
+ no other changes - no merge:
+
+ $ echo f > f
+ $ hg add f
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo g > f
+ $ hg unshelve
+ unshelving change 'default'
+ $ hg st
+ A f
+ ? f.orig
+ $ cat f
+ f
+ $ cat f.orig
+ g
+
+ other uncommitted changes - merge:
+
+ $ hg st
+ A f
+ ? f.orig
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg log -G --template '{rev} {desc|firstline} {author}'
+ @ 9 commit stuff test
+ |
+ | o 2 c test
+ |/
+ o 0 a test
+
+ $ mv f.orig f
+ $ echo 1 > a
+ $ hg unshelve --date '1073741824 0'
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 10:81152db69da7 "changes to: commit stuff"
+ merging f
+ warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ hg parents -T "{desc|firstline}\n" | sort
+ changes to: commit stuff
+ pending changes temporary commit
+
+ $ hg st
+ M f
+ ? f.orig
+ $ cat f
+ <<<<<<< dest: 5f6b880e719b - shelve: pending changes temporary commit
+ g
+ =======
+ f
+ >>>>>>> source: 81152db69da7 - shelve: changes to: commit stuff
+ $ cat f.orig
+ g
+ $ hg unshelve --abort -t false
+ tool option will be ignored
+ rebase aborted
+ unshelve of 'default' aborted
+ $ hg st
+ M a
+ ? f.orig
+ $ cat f.orig
+ g
+ $ hg unshelve
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 10:81152db69da7 "changes to: commit stuff"
+ $ hg st
+ M a
+ A f
+ ? f.orig
+
+ other committed changes - merge:
+
+ $ hg shelve f
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg ci a -m 'intermediate other change'
+ $ mv f.orig f
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 10:81152db69da7 "changes to: commit stuff"
+ merging f
+ warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ hg st
+ M f
+ ? f.orig
+ $ cat f
+ <<<<<<< dest: * - test: intermediate other change (glob)
+ g
+ =======
+ f
+ >>>>>>> source: 81152db69da7 - shelve: changes to: commit stuff
+ $ cat f.orig
+ g
+ $ hg unshelve --abort
+ rebase aborted
+ unshelve of 'default' aborted
+ $ hg st
+ ? f.orig
+ $ cat f.orig
+ g
+ $ hg shelve --delete default
+
+Recreate some conflict again
+ $ cd ../obsrepo
+ $ hg up -C -r 'test^'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark test)
+ $ echo y >> a/a
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg up test
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (activating bookmark test)
+ $ hg bookmark
+ * test * (glob)
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing * "changes to: second" (tip) (glob)
+ merging a/a
+ warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ hg bookmark
+ test * (glob)
+
+Test that resolving all conflicts in one direction (so that the rebase
+is a no-op), works (issue4398)
+
+ $ hg revert -a -r .
+ reverting a/a (glob)
+ $ hg resolve -m a/a
+ (no more unresolved files)
+ continue: hg unshelve --continue
+ $ hg unshelve -c
+ rebasing * "changes to: second" (tip) (glob)
+ note: rebase of * created no changes to commit (glob)
+ unshelve of 'default' complete
+ $ hg bookmark
+ * test * (glob)
+ $ hg diff
+ $ hg status
+ ? a/a.orig
+ ? foo/foo
+ $ hg summary | egrep "(bookmarks|commit)"
+ bookmarks: *test
+ commit: 2 unknown (clean)
+
+ $ hg shelve --delete --stat
+ abort: options '--delete' and '--stat' may not be used together
+ [255]
+ $ hg shelve --delete --name NAME
+ abort: options '--delete' and '--name' may not be used together
+ [255]
+
+Test interactive shelve
+ $ cat <<EOF >> $HGRCPATH
+ > [ui]
+ > interactive = true
+ > EOF
+ $ echo 'a' >> a/b
+ $ cat a/a >> a/b
+ $ echo 'x' >> a/b
+ $ mv a/b a/a
+ $ echo 'a' >> foo/foo
+ $ hg st
+ M a/a
+ ? a/a.orig
+ ? foo/foo
+ $ cat a/a
+ a
+ a
+ c
+ x
+ x
+ $ cat foo/foo
+ foo
+ a
+ $ hg shelve --interactive --config ui.interactive=false
+ abort: running non-interactively
+ [255]
+ $ hg shelve --interactive << EOF
+ > y
+ > y
+ > n
+ > EOF
+ diff --git a/a/a b/a/a
+ 2 hunks, 2 lines changed
+ examine changes to 'a/a'? [Ynesfdaq?] y
+
+ @@ -1,3 +1,4 @@
+ +a
+ a
+ c
+ x
+ record change 1/2 to 'a/a'? [Ynesfdaq?] y
+
+ @@ -1,3 +2,4 @@
+ a
+ c
+ x
+ +x
+ record change 2/2 to 'a/a'? [Ynesfdaq?] n
+
+ shelved as test
+ merging a/a
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ $ cat a/a
+ a
+ c
+ x
+ x
+ $ cat foo/foo
+ foo
+ a
+ $ hg st
+ M a/a
+ ? foo/foo
+ $ hg bookmark
+ * test * (glob)
+ $ hg log -r . -T "{desc|firstline}\n"
+ create conflict
+ $ hg unshelve
+ unshelving change 'test'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing * "changes to: create conflict" (glob)
+ merging a/a
+ $ hg bookmark
+ * test * (glob)
+ $ hg log -r . -T "{desc|firstline}\n"
+ create conflict
+ $ cat a/a
+ a
+ a
+ c
+ x
+ x
+
+Shelve --patch and shelve --stat should work with a single valid shelfname
+ $ hg up --clean .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (leaving bookmark test)
+ $ hg shelve --list
+ $ echo 'patch a' > shelf-patch-a
+ $ hg add shelf-patch-a
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo 'patch b' > shelf-patch-b
+ $ hg add shelf-patch-b
+ $ hg shelve
+ shelved as default-01
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg shelve --patch default default-01
+ default-01 (*s ago) changes to: create conflict (glob)
+
+ diff --git a/shelf-patch-b b/shelf-patch-b
+ new file mode 100644
+ --- /dev/null
+ +++ b/shelf-patch-b
+ @@ -0,0 +1,1 @@
+ +patch b
+ default (*s ago) changes to: create conflict (glob)
+
+ diff --git a/shelf-patch-a b/shelf-patch-a
+ new file mode 100644
+ --- /dev/null
+ +++ b/shelf-patch-a
+ @@ -0,0 +1,1 @@
+ +patch a
+ $ hg shelve --stat default default-01
+ default-01 (*s ago) changes to: create conflict (glob)
+ shelf-patch-b | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ default (*s ago) changes to: create conflict (glob)
+ shelf-patch-a | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ $ hg shelve --patch default
+ default (*)* changes to: create conflict (glob)
+
+ diff --git a/shelf-patch-a b/shelf-patch-a
+ new file mode 100644
+ --- /dev/null
+ +++ b/shelf-patch-a
+ @@ -0,0 +1,1 @@
+ +patch a
+ $ hg shelve --stat default
+ default (*)* changes to: create conflict (glob)
+ shelf-patch-a | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ $ hg shelve --patch nonexistentshelf
+ abort: cannot find shelf nonexistentshelf
+ [255]
+ $ hg shelve --stat nonexistentshelf
+ abort: cannot find shelf nonexistentshelf
+ [255]
+
+Test visibility of in-memory changes inside transaction to external hook
+------------------------------------------------------------------------
+ $ echo xxxx >> x
+ $ hg commit -m "changes to invoke rebase"
+ $ hg bookmark unshelvedest
+
+ $ cat > $TESTTMP/checkvisibility.sh <<EOF
+ > echo "==== \$1:"
+ > hg parents --template "VISIBLE {node|short}\n"
+ > # test that pending changes are hidden
+ > unset HG_PENDING
+ > hg parents --template "ACTUAL {node|short}\n"
+ > echo "===="
+ > EOF
+
+ $ cat >> .hg/hgrc <<EOF
+ > [defaults]
+ > # to fix hash id of temporary revisions
+ > unshelve = --date '0 0'
+ > EOF
+
+"hg unshelve"implies steps below:
+(1) commit changes in the working directory
+(2) note shelved revision
+(3) rebase: merge shelved revision into temporary wc changes
+(4) rebase: commit merged revision
+(5) rebase: update to a new commit
+(6) update to original working copy parent
+
+== test visibility to external preupdate hook
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > preupdate.visibility = sh $TESTTMP/checkvisibility.sh preupdate
+ > EOF
+
+ $ echo nnnn >> n
+
+ $ sh $TESTTMP/checkvisibility.sh before-unshelving
+ ==== before-unshelving:
+ VISIBLE f77bf047d4c5
+ ACTUAL f77bf047d4c5
+ ====
+
+ $ hg unshelve --keep default
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing *206bf5d4f922 "changes to: create conflict" (glob)
+ ==== preupdate:
+ VISIBLE (?!f77bf047d4c5).* (re)
+ ACTUAL f77bf047d4c5
+ ====
+ ==== preupdate:
+ VISIBLE (?!f77bf047d4c5).* (re)
+ ACTUAL f77bf047d4c5
+ ====
+ ==== preupdate:
+ VISIBLE (?!f77bf047d4c5).* (re)
+ ACTUAL f77bf047d4c5
+ ====
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > preupdate.visibility =
+ > EOF
+
+ $ sh $TESTTMP/checkvisibility.sh after-unshelving
+ ==== after-unshelving:
+ VISIBLE f77bf047d4c5
+ ACTUAL f77bf047d4c5
+ ====
+
+== test visibility to external update hook
+
+ $ hg update -q -C unshelvedest
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > update.visibility = sh $TESTTMP/checkvisibility.sh update
+ > EOF
+
+ $ echo nnnn >> n
+
+ $ sh $TESTTMP/checkvisibility.sh before-unshelving
+ ==== before-unshelving:
+ VISIBLE f77bf047d4c5
+ ACTUAL f77bf047d4c5
+ ====
+
+ $ hg unshelve --keep default
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing *:206bf5d4f922 "changes to: create conflict" (glob)
+ ==== update:
+ VISIBLE f3a8cb815d40
+ VISIBLE 206bf5d4f922
+ ACTUAL f77bf047d4c5
+ ====
+ ==== update:
+ VISIBLE f3a8cb815d40
+ ACTUAL f77bf047d4c5
+ ====
+ ==== update:
+ VISIBLE f77bf047d4c5
+ ACTUAL f77bf047d4c5
+ ====
+
+ $ cat >> .hg/hgrc <<EOF
+ > [hooks]
+ > update.visibility =
+ > EOF
+
+ $ sh $TESTTMP/checkvisibility.sh after-unshelving
+ ==== after-unshelving:
+ VISIBLE f77bf047d4c5
+ ACTUAL f77bf047d4c5
+ ====
+ $ hg bookmark -d unshelvedest
+ $ cd ..
+
+Test .orig files go where the user wants them to
+---------------------------------------------------------------
+ $ hg init obssh-salvage
+ $ cd obssh-salvage
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo 'content' > root
+ $ hg commit -A -m 'root' -q
+ $ echo '' > root
+ $ hg shelve -q
+ $ echo 'contADDent' > root
+ $ hg unshelve -q --config 'ui.origbackuppath=.hg/origbackups'
+ warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ ls .hg/origbackups
+ root
+ $ rm -rf .hg/origbackups
+
+Test Abort unshelve always gets user out of the unshelved state
+---------------------------------------------------------------
+Wreak havoc on the unshelve process
+ $ rm .hg/unshelverebasestate
+ $ hg unshelve --abort
+ unshelve of 'default' aborted
+ abort: $ENOENT$
+ [255]
+Can the user leave the current state?
+ $ hg up -C .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Try again but with a corrupted shelve state file
+ $ hg up -r 0 -q
+ $ echo '' > root
+ $ hg shelve -q
+ $ echo 'contADDent' > root
+ $ hg unshelve -q
+ warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ sed 's/ae8c668541e8/123456789012/' .hg/shelvedstate > ../corrupt-shelvedstate
+ $ mv ../corrupt-shelvedstate .hg/histedit-state
+ $ hg unshelve --abort 2>&1 | grep 'rebase aborted'
+ rebase aborted
+ $ hg up -C .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd ..
+
+Keep active bookmark while (un)shelving even on shared repo (issue4940)
+-----------------------------------------------------------------------
+ $ cat <<EOF >> $HGRCPATH
+ > [extensions]
+ > share =
+ > [experimnetal]
+ > evolution=createmarkers
+ > EOF
+ $ hg bookmarks -R obsrepo
+ test *:33f7f61e6c5e (glob)
+ $ hg share -B obsrepo obsshare
+ updating working directory
+ 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd obsshare
+
+ $ hg bookmarks
+ test *:33f7f61e6c5e (glob)
+ $ hg bookmarks foo
+ $ hg bookmarks
+ \* foo *:f77bf047d4c5 (glob)
+ test *:33f7f61e6c5e (glob)
+ $ echo x >> x
+ $ hg shelve
+ shelved as foo
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg bookmarks
+ \* foo *:f77bf047d4c5 (glob)
+ test *:33f7f61e6c5e (glob)
+
+ $ hg unshelve
+ unshelving change 'foo'
+ $ hg bookmarks
+ \* foo *:f77bf047d4c5 (glob)
+ test *:33f7f61e6c5e (glob)
+
+ $ cd ..
+
+Shelve and unshelve unknown files. For the purposes of unshelve, a shelved
+unknown file is the same as a shelved added file, except that it will be in
+unknown state after unshelve if and only if it was either absent or unknown
+before the unshelve operation.
+ $ hg init obssh-unknowns
+ $ cd obssh-unknowns
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+
+The simplest case is if I simply have an unknown file that I shelve and unshelve
+ $ echo unknown > unknown
+ $ hg status
+ ? unknown
+ $ hg shelve --unknown
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg status
+ $ hg unshelve
+ unshelving change 'default'
+ $ hg status
+ ? unknown
+ $ rm unknown
+
+If I shelve, add the file, and unshelve, does it stay added?
+ $ echo unknown > unknown
+ $ hg shelve -u
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg status
+ $ touch unknown
+ $ hg add unknown
+ $ hg status
+ A unknown
+ $ hg unshelve
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 0:098df96e7410 "(changes in empty repository)"
+ merging unknown
+ $ hg status
+ A unknown
+ $ hg forget unknown
+ $ rm unknown
+
+And if I shelve, commit, then unshelve, does it become modified?
+ $ echo unknown > unknown
+ $ hg shelve -u
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg status
+ $ touch unknown
+ $ hg add unknown
+ $ hg commit -qm "Add unknown"
+ $ hg status
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 0:098df96e7410 "(changes in empty repository)"
+ merging unknown
+ $ hg status
+ M unknown
+ $ hg remove --force unknown
+ $ hg commit -qm "Remove unknown"
+ $ cd ..
+
+We expects that non-bare shelve keeps newly created branch in
+working directory.
+ $ hg init obs-shelve-preserve-new-branch
+ $ cd obs-shelve-preserve-new-branch
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo "a" >> a
+ $ hg add a
+ $ echo "b" >> b
+ $ hg add b
+ $ hg commit -m "ab"
+ $ echo "aa" >> a
+ $ echo "bb" >> b
+ $ hg branch new-branch
+ marked working directory as branch new-branch
+ (branches are permanent and global, did you want a bookmark?)
+ $ hg status
+ M a
+ M b
+ $ hg branch
+ new-branch
+ $ hg shelve a
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch
+ new-branch
+ $ hg status
+ M b
+ $ touch "c" >> c
+ $ hg add c
+ $ hg status
+ M b
+ A c
+ $ hg shelve --exclude c
+ shelved as default-01
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch
+ new-branch
+ $ hg status
+ A c
+ $ hg shelve --include c
+ shelved as default-02
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg branch
+ new-branch
+ $ hg status
+ $ echo "d" >> d
+ $ hg add d
+ $ hg status
+ A d
+
+We expect that bare-shelve will not keep branch in current working directory.
+
+ $ hg shelve
+ shelved as default-03
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg branch
+ default
+ $ cd ..
+
+When i shelve commit on newly created branch i expect
+that after unshelve newly created branch will be preserved.
+ $ hg init obs-shelve_on_new_branch_simple
+ $ cd obs-shelve_on_new_branch_simple
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo "aaa" >> a
+ $ hg commit -A -m "a"
+ adding a
+ $ hg branch
+ default
+ $ hg branch test
+ marked working directory as branch test
+ (branches are permanent and global, did you want a bookmark?)
+ $ echo "bbb" >> a
+ $ hg status
+ M a
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch
+ default
+ $ echo "bbb" >> b
+ $ hg status
+ ? b
+ $ hg unshelve
+ unshelving change 'default'
+ marked working directory as branch test
+ $ hg status
+ M a
+ ? b
+ $ hg branch
+ test
+ $ cd ..
+
+When i shelve commit on newly created branch, make
+some changes, unshelve it and running into merge
+conflicts i expect that after fixing them and
+running unshelve --continue newly created branch
+will be preserved.
+ $ hg init obs-shelve_on_new_branch_conflict
+ $ cd obs-shelve_on_new_branch_conflict
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo "aaa" >> a
+ $ hg commit -A -m "a"
+ adding a
+ $ hg branch
+ default
+ $ hg branch test
+ marked working directory as branch test
+ (branches are permanent and global, did you want a bookmark?)
+ $ echo "bbb" >> a
+ $ hg status
+ M a
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch
+ default
+ $ echo "ccc" >> a
+ $ hg status
+ M a
+ $ hg unshelve
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 1:425c97ef07f3 "changes to: a"
+ merging a
+ warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ echo "aaabbbccc" > a
+ $ rm a.orig
+ $ hg resolve --mark a
+ (no more unresolved files)
+ continue: hg unshelve --continue
+ $ hg unshelve --continue
+ rebasing 1:425c97ef07f3 "changes to: a"
+ marked working directory as branch test
+ unshelve of 'default' complete
+ $ cat a
+ aaabbbccc
+ $ hg status
+ M a
+ $ hg branch
+ test
+ $ hg commit -m "test-commit"
+
+When i shelve on test branch, update to default branch
+and unshelve i expect that it will not preserve previous
+test branch.
+ $ echo "xxx" > b
+ $ hg add b
+ $ hg shelve
+ shelved as test
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg update -r default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg unshelve
+ unshelving change 'test'
+ rebasing shelved changes
+ rebasing *:357525f34729 "changes to: test-commit"* (glob)
+ $ hg status
+ A b
+ $ hg branch
+ default
+ $ cd ..
+
+When i unshelve resulting in merge conflicts and makes saved
+file shelvedstate looks like in previous versions in
+mercurial(without restore branch information in 7th line) i
+expect that after resolving conflicts and successfully
+running 'shelve --continue' the branch information won't be
+restored and branch will be unchanged.
+
+shelve on new branch, conflict with previous shelvedstate
+ $ hg init obs-conflict
+ $ cd obs-conflict
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo "aaa" >> a
+ $ hg commit -A -m "a"
+ adding a
+ $ hg branch
+ default
+ $ hg branch test
+ marked working directory as branch test
+ (branches are permanent and global, did you want a bookmark?)
+ $ echo "bbb" >> a
+ $ hg status
+ M a
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch
+ default
+ $ echo "ccc" >> a
+ $ hg status
+ M a
+ $ hg unshelve
+ unshelving change 'default'
+ temporarily committing pending changes (restore with 'hg unshelve --abort')
+ rebasing shelved changes
+ rebasing 1:425c97ef07f3 "changes to: a"
+ merging a
+ warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+
+Removing restore branch information from shelvedstate file(making it looks like
+in previous versions) and running unshelve --continue
+ $ cp .hg/shelvedstate .hg/shelvedstate_old
+ $ cat .hg/shelvedstate_old | grep -v 'branchtorestore' > .hg/shelvedstate
+ $ echo "aaabbbccc" > a
+ $ rm a.orig
+ $ hg resolve --mark a
+ (no more unresolved files)
+ continue: hg unshelve --continue
+ $ hg unshelve --continue
+ rebasing 1:425c97ef07f3 "changes to: a"
+ unshelve of 'default' complete
+ $ cat a
+ aaabbbccc
+ $ hg status
+ M a
+ $ hg branch
+ default
+ $ cd ..
+
+On non bare shelve the branch information shouldn't be restored
+ $ hg init obssh-bare_shelve_on_new_branch
+ $ cd obssh-bare_shelve_on_new_branch
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo "aaa" >> a
+ $ hg commit -A -m "a"
+ adding a
+ $ hg branch
+ default
+ $ hg branch test
+ marked working directory as branch test
+ (branches are permanent and global, did you want a bookmark?)
+ $ echo "bbb" >> a
+ $ hg status
+ M a
+ $ hg shelve a
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch
+ test
+ $ hg branch default
+ marked working directory as branch default
+ (branches are permanent and global, did you want a bookmark?)
+ $ echo "bbb" >> b
+ $ hg status
+ ? b
+ $ hg unshelve
+ unshelving change 'default'
+ $ hg status
+ M a
+ ? b
+ $ hg branch
+ default
+ $ cd ..
+
+Prepare unshelve with a corrupted shelvedstate
+ $ hg init obssh-r1 && cd obssh-r1
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo text1 > file && hg add file
+ $ hg shelve
+ shelved as default
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo text2 > file && hg ci -Am text1
+ adding file
+ $ hg unshelve
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 0:396ea74229f9 "(changes in empty repository)"
+ merging file
+ warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ echo somethingsomething > .hg/shelvedstate
+
+Unshelve --continue fails with appropriate message if shelvedstate is corrupted
+ $ hg unshelve --continue
+ abort: corrupted shelved state file
+ (please run hg unshelve --abort to abort unshelve operation)
+ [255]
+
+Unshelve --abort works with a corrupted shelvedstate
+ $ hg unshelve --abort
+ could not read shelved state file, your working copy may be in an unexpected state
+ please update to some commit
+
+Unshelve --abort fails with appropriate message if there's no unshelve in
+progress
+ $ hg unshelve --abort
+ abort: no unshelve in progress
+ [255]
+ $ cd ..
+
+Unshelve respects --keep even if user intervention is needed
+ $ hg init obs-unshelvekeep && cd obs-unshelvekeep
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo 1 > file && hg ci -Am 1
+ adding file
+ $ echo 2 >> file
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo 3 >> file && hg ci -Am 13
+ $ hg shelve --list
+ default (*s ago) changes to: 1 (glob)
+ $ hg unshelve --keep
+ unshelving change 'default'
+ rebasing shelved changes
+ rebasing 1:3fbe6fbb0bef "changes to: 1"
+ merging file
+ warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+ unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+ [1]
+ $ hg resolve --mark file
+ (no more unresolved files)
+ continue: hg unshelve --continue
+ $ hg unshelve --continue
+ rebasing 1:3fbe6fbb0bef "changes to: 1"
+ unshelve of 'default' complete
+ $ hg shelve --list
+ default (*s ago) changes to: 1 (glob)
+ $ cd ..
+
+Unshelving a stripped commit aborts with an explanatory message
+ $ hg init obs-unshelve-stripped-commit && cd obs-unshelve-stripped-commit
+ $ cat <<EOF >> .hg/hgrc
+ > [experimental]
+ > evolution=createmarkers
+ > obsshelve=True
+ > EOF
+ $ echo 1 > file && hg ci -Am 1
+ adding file
+ $ echo 2 >> file
+ $ hg shelve
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg strip -r 1 --config experimental.evolution=! --hidden
+ obsolete feature not enabled but 1 markers found!
+ saved backup bundle to .* (re)
+ $ hg unshelve
+ unshelving change 'default'
+ abort: shelved node 3fbe6fbb0bef4b761af46e9a7456f02877469fa0 not found in repo
+ [255]
+
+Obsshelve knows how to unshelve traditional shelves
+ $ hg init tradshelves && cd tradshelves
+ $ echo root > root && hg ci -Am root
+ adding root
+ $ echo something >> root
+ $ hg diff
+ diff --git a/root b/root
+ --- a/root
+ +++ b/root
+ @@ -1,1 +1,2 @@
+ root
+ +something
+ $ hg shelve --config extensions.obsshelve=! --config extensions.shelve=
+ shelved as default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ ls .hg/shelved/default.hg # .hg extension indicates a traditional shelve
+ .hg/shelved/default.hg
+ $ hg unshelve --keep
+ unshelving change 'default'
+ $ hg diff
+ diff --git a/root b/root
+ --- a/root
+ +++ b/root
+ @@ -1,1 +1,2 @@
+ root
+ +something
+ $ cd ..
+
diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -26,6 +26,7 @@
import errno
import itertools
import stat
+import time
from mercurial.i18n import _
from mercurial import (
@@ -42,6 +43,7 @@
mdiff,
merge,
node as nodemod,
+ obsolete,
patch,
phases,
pycompat,
@@ -75,6 +77,9 @@
configitem('shelve', 'maxbackups',
default=10,
)
+configitem('experimental', 'obsshelve',
+ default=False,
+)
backupdir = 'shelve-backup'
shelvedir = 'shelved'
@@ -185,6 +190,9 @@
_nokeep = 'nokeep'
# colon is essential to differentiate from a real bookmark name
_noactivebook = ':no-active-bookmark'
+ # Mechanism used to store shelved change
+ _obsbased = 'obsbased'
+ _traditional = 'traditional'
@classmethod
def _verifyandtransform(cls, d):
@@ -218,7 +226,8 @@
# to detemine values of fields (i.g. name is on the second line,
# originalwctx is on the third and so forth). Please do not change.
keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents',
- 'nodestoremove', 'branchtorestore', 'keep', 'activebook']
+ 'nodestoremove', 'branchtorestore', 'keep', 'activebook',
+ 'obsshelve']
# this is executed only seldomly, so it is not a big deal
# that we open this file twice
fp = repo.vfs(cls._filename)
@@ -249,20 +258,23 @@
obj.wctx = repo[d['originalwctx']]
obj.pendingctx = repo[d['pendingctx']]
obj.parents = d['parents']
- obj.nodestoremove = d['nodestoremove']
+ # filter out empty nodes from nodestoremove
+ obj.nodestoremove = [n for n in d['nodestoremove'] if n]
obj.branchtorestore = d.get('branchtorestore', '')
obj.keep = d.get('keep') == cls._keep
obj.activebookmark = ''
if d.get('activebook', '') != cls._noactivebook:
obj.activebookmark = d.get('activebook', '')
+ obj.obsshelve = d.get('obsshelve',
+ cls._traditional) == cls._obsbased
except (error.RepoLookupError, KeyError) as err:
raise error.CorruptedState(pycompat.bytestr(err))
return obj
@classmethod
def save(cls, repo, name, originalwctx, pendingctx, nodestoremove,
- branchtorestore, keep=False, activebook=''):
+ branchtorestore, keep=False, activebook='', obsshelve=False):
info = {
"name": name,
"originalwctx": nodemod.hex(originalwctx.node()),
@@ -273,15 +285,26 @@
for n in nodestoremove]),
"branchtorestore": branchtorestore,
"keep": cls._keep if keep else cls._nokeep,
- "activebook": activebook or cls._noactivebook
+ "activebook": activebook or cls._noactivebook,
+ "obsshelve": cls._obsbased if obsshelve else cls._traditional,
}
scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\
.write(info, firstline=("%d" % cls._version))
@classmethod
def clear(cls, repo):
repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
+ def removenodes(self, ui, repo):
+ """Cleanup temporary nodes from the repo"""
+ if self.obsshelve:
+ unfi = repo.unfiltered()
+ relations = [(unfi[n or '.'], ()) for n in self.nodestoremove]
+ obsolete.createmarkers(repo, relations)
+ elif self.nodestoremove:
+ repair.strip(ui, repo, self.nodestoremove, backup=False,
+ topic='shelve')
+
def cleanupoldbackups(repo):
vfs = vfsmod.vfs(repo.vfs.join(backupdir))
maxbackups = repo.ui.configint('shelve', 'maxbackups')
@@ -410,9 +433,14 @@
else:
ui.status(_("nothing changed\n"))
-def _shelvecreatedcommit(repo, node, name):
- bases = list(mutableancestors(repo[node]))
- shelvedfile(repo, name, 'hg').writebundle(bases, node)
+def _shelvecreatedcommit(ui, repo, node, name, tr):
+ if ui.configbool('experimental', 'obsshelve'):
+ shelvedfile(repo, name, 'oshelve').writeobsshelveinfo({
+ 'node': nodemod.hex(node)
+ })
+ else:
+ bases = list(mutableancestors(repo[node]))
+ shelvedfile(repo, name, 'hg').writebundle(bases, node)
with shelvedfile(repo, name, patchextension).opener('wb') as fp:
cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True))
@@ -423,8 +451,15 @@
extra['shelve_unknown'] = '\0'.join(s.unknown)
repo[None].add(s.unknown)
-def _finishshelve(repo):
- _aborttransaction(repo)
+def _finishshelve(ui, repo, tr, node, activebookmark):
+ if not ui.configbool('experimental', 'obsshelve'):
+ _aborttransaction(repo)
+ return
+ if activebookmark:
+ bookmarks.activate(repo, activebookmark)
+ obsolete.createmarkers(repo, [(repo.unfiltered()[node], ())])
+ tr.close()
+ tr.release()
def _docreatecmd(ui, repo, pats, opts):
wctx = repo[None]
@@ -476,16 +511,19 @@
_nothingtoshelvemessaging(ui, repo, pats, opts)
return 1
- _shelvecreatedcommit(repo, node, name)
+ _shelvecreatedcommit(ui, repo, node, name, tr)
if ui.formatted():
desc = stringutil.ellipsis(desc, ui.termwidth())
ui.status(_('shelved as %s\n') % name)
- hg.update(repo, parent.node())
+ # current wdir parent could already be obsolete if shelve
+ # managed to reuse a commit node from a previous shelve, so
+ # use an unfiltered repo to avoid that potential issue.
+ hg.update(repo.unfiltered(), parent.node())
if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
repo.dirstate.setbranch(origbranch)
- _finishshelve(repo)
+ _finishshelve(ui, repo, tr, node, activebookmark)
finally:
_restoreactivebookmark(repo, activebookmark)
lockmod.release(tr, lock)
@@ -631,8 +669,7 @@
raise
mergefiles(ui, repo, state.wctx, state.pendingctx)
- repair.strip(ui, repo, state.nodestoremove, backup=False,
- topic='shelve')
+ state.removenodes(ui, repo)
finally:
shelvedstate.clear(repo)
ui.warn(_("unshelve of '%s' aborted\n") % state.name)
@@ -685,9 +722,14 @@
repo.vfs.rename('unshelverebasestate', 'rebasestate')
try:
- rebase.rebase(ui, repo, **{
- r'continue' : True
- })
+ # if shelve is obs-based, we want rebase to be able
+ # to create markers to already-obsoleted commits
+ _repo = repo.unfiltered() if state.obsshelve else repo
+ with ui.configoverride({('experimental', 'rebaseskipobsolete'):
+ 'off'}, 'unshelve'):
+ rebase.rebase(ui, _repo, **{
+ 'continue' : True,
+ })
except Exception:
repo.vfs.rename('rebasestate', 'unshelverebasestate')
raise
@@ -703,8 +745,7 @@
mergefiles(ui, repo, state.wctx, shelvectx)
restorebranch(ui, repo, state.branchtorestore)
- repair.strip(ui, repo, state.nodestoremove, backup=False,
- topic='shelve')
+ state.removenodes(ui, repo)
_restoreactivebookmark(repo, state.activebookmark)
shelvedstate.clear(repo)
unshelvecleanup(ui, repo, state.name, opts)
@@ -730,37 +771,63 @@
tmpwctx = repo[node]
return tmpwctx, addedbefore
-def _unshelverestorecommit(ui, repo, basename):
+def _unshelverestorecommit(ui, repo, basename, obsshelve):
"""Recreate commit in the repository during the unshelve"""
with ui.configoverride({('ui', 'quiet'): True}):
- shelvedfile(repo, basename, 'hg').applybundle()
- shelvectx = repo['tip']
+ if obsshelve:
+ md = shelvedfile(repo, basename, 'oshelve').readobsshelveinfo()
+ shelvenode = nodemod.bin(md['node'])
+ repo = repo.unfiltered()
+ try:
+ shelvectx = repo[shelvenode]
+ except error.RepoLookupError:
+ m = _("shelved node %s not found in repo")
+ raise error.Abort(m % md['node'])
+ else:
+ shelvedfile(repo, basename, 'hg').applybundle()
+ shelvectx = repo['tip']
return repo, shelvectx
def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
tmpwctx, shelvectx, branchtorestore,
- activebookmark):
+ activebookmark, obsshelve):
"""Rebase restored commit from its original location to a destination"""
# If the shelve is not immediately on top of the commit
# we'll be merging with, rebase it to be on top.
if tmpwctx.node() == shelvectx.parents()[0].node():
return shelvectx
+ # we need a new commit extra every time we perform a rebase to ensure
+ # that "nothing to rebase" does not happen with obs-based shelve
+ # "nothing to rebase" means that tip does not point to a "successor"
+ # commit after a rebase and we have no way to learn which commit
+ # should be a "shelvectx". this is a dirty hack until we implement
+ # some way to learn the results of rebase operation, other than
+ # text output and return code
+ def extrafn(ctx, extra):
+ extra[b'unshelve_time'] = pycompat.bytestr(time.time())
+
ui.status(_('rebasing shelved changes\n'))
try:
+ # we only want keep to be true if shelve is traditional, since
+ # for obs-based shelve, rebase will also be obs-based and
+ # markers created help us track the relationship between shelvectx
+ # and its new version
rebase.rebase(ui, repo, **{
r'rev': [shelvectx.rev()],
r'dest': "%d" % tmpwctx.rev(),
- r'keep': True,
+ r'keep': not obsshelve,
r'tool': opts.get('tool', ''),
+ r'extrafn': extrafn if obsshelve else None
})
except error.InterventionRequired:
tr.close()
nodestoremove = [repo.changelog.node(rev)
for rev in xrange(oldtiprev, len(repo))]
shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
- branchtorestore, opts.get('keep'), activebookmark)
+ branchtorestore, opts.get('keep'), activebookmark,
+ obsshelve)
repo.vfs.rename('rebasestate', 'unshelverebasestate')
raise error.InterventionRequired(
@@ -786,8 +853,11 @@
toforget = (addedafter & shelveunknown) - addedbefore
repo[None].forget(toforget)
-def _finishunshelve(repo, oldtiprev, tr, activebookmark):
+def _finishunshelve(repo, oldtiprev, tr, activebookmark, obsshelve):
_restoreactivebookmark(repo, activebookmark)
+ if obsshelve:
+ tr.close()
+ return
# The transaction aborting will strip all the commits for us,
# but it doesn't update the inmemory structures, so addchangegroup
# hooks still fire and try to operate on the missing commits.
@@ -806,6 +876,20 @@
hint = _("run hg status to see which files are missing")
raise error.Abort(m, hint=hint)
+def _obsoleteredundantnodes(repo, tr, pctx, shelvectx, tmpwctx):
+ # order is important in the list of [shelvectx, tmpwctx] below
+ # some nodes may already be obsolete
+ unfi = repo.unfiltered()
+ nodestoobsolete = filter(lambda x: x != pctx, [shelvectx, tmpwctx])
+ seen = set()
+ relations = []
+ for nto in nodestoobsolete:
+ if nto in seen:
+ continue
+ seen.add(nto)
+ relations.append((unfi[nto.rev()], ()))
+ obsolete.createmarkers(unfi, relations)
+
@command('unshelve',
[('a', 'abort', None,
_('abort an incomplete unshelve operation')),
@@ -919,6 +1003,15 @@
raise error.Abort(_("shelved change '%s' not found") % basename)
lock = tr = None
+ # Only look for obsolete-based shelves if we've got obsshelve enabled
+ obsshelve = ui.configbool('experimental', 'obsshelve', False)
+ obsshelvedfile = shelvedfile(repo, basename, 'oshelve')
+ if obsshelvedfile and not obsshelvedfile.exists():
+ # although we can unshelve a obs-based shelve technically,
+ # this particular shelve was created using a traditional way
+ obsshelve = False
+ ui.note(_("falling back to traditional unshelve since "
+ "shelve was traditional"))
try:
lock = repo.lock()
tr = repo.transaction('unshelve', report=lambda x: None)
@@ -935,24 +1028,29 @@
activebookmark = _backupactivebookmark(repo)
tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
tmpwctx)
- repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
+ repo, shelvectx = _unshelverestorecommit(ui, repo, basename, obsshelve)
_checkunshelveuntrackedproblems(ui, repo, shelvectx)
branchtorestore = ''
if shelvectx.branch() != shelvectx.p1().branch():
branchtorestore = shelvectx.branch()
- shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
- basename, pctx, tmpwctx,
- shelvectx, branchtorestore,
- activebookmark)
+ rebaseoverrides = {('experimental', 'rebaseskipobsolete'): 'off'}
+ with ui.configoverride(rebaseoverrides, 'unshelve'):
+ shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
+ basename, pctx, tmpwctx,
+ shelvectx, branchtorestore,
+ activebookmark, obsshelve)
overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
with ui.configoverride(overrides, 'unshelve'):
mergefiles(ui, repo, pctx, shelvectx)
restorebranch(ui, repo, branchtorestore)
_forgetunknownfiles(repo, shelvectx, addedbefore)
+ if obsshelve:
+ _obsoleteredundantnodes(repo, tr, pctx, shelvectx, tmpwctx)
+
shelvedstate.clear(repo)
- _finishunshelve(repo, oldtiprev, tr, activebookmark)
+ _finishunshelve(repo, oldtiprev, tr, activebookmark, obsshelve)
unshelvecleanup(ui, repo, basename, opts)
finally:
if tr:
To: durin42, #hg-reviewers
Cc: mercurial-devel
More information about the Mercurial-devel
mailing list