shelve: permit shelves to contain unknown files
If an emergency comes in while you're in the middle of an experimental
change, it can be useful to shelve not just files hg already tracks but
also your unknown files while you handle the emergency. This is
especially true if you have hooks intended to prevent you from
forgetting to add new code before you push.
Teach "hg shelve" to optionally shelve unknown files, not just tracked
files. This is functionally similar to --addremove, but with two
differences:
1) Deleted files are not removed.
2) Files added during shelve creation are tracked in extra so that they
can be forgotten by "hg unshelve".
When unshelving, we take care to only forget files if they've been
created during the unshelve operation; if you add a file that's being
tracked in a shelve as an unknown file, it should not become unknown
again when the shelve is unshelved.
--- a/hgext/shelve.py Sun Jan 17 14:14:15 2016 -0800
+++ b/hgext/shelve.py Thu Jan 14 10:03:31 2016 -0800
@@ -301,6 +301,16 @@
if name.startswith('.'):
raise error.Abort(_("shelved change names may not start with '.'"))
interactive = opts.get('interactive', False)
+ includeunknown = (opts.get('unknown', False) and
+ not opts.get('addremove', False))
+
+ extra={}
+ if includeunknown:
+ s = repo.status(match=scmutil.match(repo[None], pats, opts),
+ unknown=True)
+ if s.unknown:
+ extra['shelve_unknown'] = '\0'.join(s.unknown)
+ repo[None].add(s.unknown)
def commitfunc(ui, repo, message, match, opts):
hasmq = util.safehasattr(repo, 'mq')
@@ -312,7 +322,7 @@
editor = cmdutil.getcommiteditor(editform='shelve.shelve',
**opts)
return repo.commit(message, user, opts.get('date'), match,
- editor=editor)
+ editor=editor, extra=extra)
finally:
repo.ui.restoreconfig(backup)
if hasmq:
@@ -656,8 +666,10 @@
# and shelvectx is the unshelved changes. Then we merge it all down
# to the original pctx.
- # Store pending changes in a commit
+ # Store pending changes in a commit and remember added in case a shelve
+ # contains unknown files that are part of the pending change
s = repo.status()
+ addedbefore = frozenset(s.added)
if s.modified or s.added or s.removed or s.deleted:
ui.status(_("temporarily committing pending changes "
"(restore with 'hg unshelve --abort')\n"))
@@ -722,6 +734,16 @@
shelvectx = tmpwctx
mergefiles(ui, repo, pctx, shelvectx)
+
+ # Forget any files that were unknown before the shelve, unknown before
+ # unshelve started, but are now added.
+ shelveunknown = shelvectx.extra().get('shelve_unknown')
+ if shelveunknown:
+ shelveunknown = frozenset(shelveunknown.split('\0'))
+ addedafter = frozenset(repo.status().added)
+ toforget = (addedafter & shelveunknown) - addedbefore
+ repo[None].forget(toforget)
+
shelvedstate.clear(repo)
# The transaction aborting will strip all the commits for us,
@@ -743,6 +765,8 @@
@command('shelve',
[('A', 'addremove', None,
_('mark new/missing files as added/removed before shelving')),
+ ('u', 'unknown', None,
+ _('Store unknown files in the shelve')),
('', 'cleanup', None,
_('delete all shelved changes')),
('', 'date', '',
@@ -793,6 +817,7 @@
'''
allowables = [
('addremove', set(['create'])), # 'create' is pseudo action
+ ('unknown', set(['create'])),
('cleanup', set(['cleanup'])),
# ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
('delete', set(['delete'])),
--- a/tests/test-shelve.t Sun Jan 17 14:14:15 2016 -0800
+++ b/tests/test-shelve.t Thu Jan 14 10:03:31 2016 -0800
@@ -54,6 +54,7 @@
-A --addremove mark new/missing files as added/removed before
shelving
+ -u --unknown Store unknown files in the shelve
--cleanup delete all shelved changes
--date DATE shelve with the specified commit date
-d --delete delete the named shelved change(s)
@@ -1245,3 +1246,71 @@
test 4:33f7f61e6c5e
$ 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 unknowns
+ $ cd unknowns
+
+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 1:098df96e7410 "(changes in empty repository)" (tip)
+ 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 1:098df96e7410 "(changes in empty repository)" (tip)
+ merging unknown
+ $ hg status
+ M unknown
+ $ hg remove --force unknown
+ $ hg commit -qm "Remove unknown"
+
+ $ cd ..