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