shelve: permit shelves to contain unknown files
authorSimon Farnsworth <simonfar@fb.com>
Thu, 14 Jan 2016 10:03:31 -0800
changeset 27908 d73a5ab18015
parent 27907 e219dbfd0342
child 27909 3203dfe341f9
child 27968 8e79ad2da8a6
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.
hgext/shelve.py
tests/test-shelve.t
--- 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 ..