Patchwork [2,of,2] shelve: Permit shelves to contain unknown files

login
register
mail settings
Submitter Simon Farnsworth
Date Jan. 14, 2016, 9:09 p.m.
Message ID <c8180c721b341cbab9fc.1452805773@devvm148.ash3.facebook.com>
Download mbox | patch
Permalink /patch/12769/
State Accepted
Commit d73a5ab18015f61ac61e6e77256512fd82b03818
Delegated to: Pierre-Yves David
Headers show

Comments

Simon Farnsworth - Jan. 14, 2016, 9:09 p.m.
# HG changeset patch
# User Simon Farnsworth <simonfar@fb.com>
# Date 1452794611 28800
#      Thu Jan 14 10:03:31 2016 -0800
# Node ID c8180c721b341cbab9fc617e183d60003fc34c8b
# Parent  727b137e82bed8ac97cfa4d9ddaa29e92bf393f3
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.
David Soria Parra - Jan. 15, 2016, 11:25 a.m.
On 1/14/16 9:09 PM, Simon Farnsworth wrote:
> diff --git a/hgext/shelve.py b/hgext/shelve.py
> --- a/hgext/shelve.py
> +++ b/hgext/shelve.py
> @@ -304,6 +304,16 @@
>          if name.startswith('.'):
>              raise error.Abort(_("shelved change names may not start with '.'"))
>          interactive = opts.get('interactive', False)
> +        includeunknown = (opts.get('includeunknown', False) and
> +                          not opts.get('addremove', False))

This can be unexpected for people. We should check if they are both set
and raise an error, to make it clear to people that they are incompatible.

> +
> +        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')
> @@ -315,7 +325,7 @@
>                  editor = cmdutil.getcommiteditor(editform='shelve.shelve',
>                                                   **opts)
>                  return repo.commit(message, user, opts.get('date'), match,
> -                                   editor=editor)
> +                                   editor=editor, extra=extra)

If I understand cmdutil.commit (which will be called with commitfunc as
a parameter) correctly, you should be able to hand in 'extra' as an opt
and then use extra=opts.get('extra') to retrieve them.
This should remove the locality problem of having to reference and
external variable.

>              finally:
>                  repo.ui.restoreconfig(backup)
>                  if hasmq:
> @@ -679,8 +689,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"))
> @@ -745,6 +757,16 @@
>                  shelvectx = tmpwctx
>  


>    
>     -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.

Can you add a test for the incompatibility between --addremove and
--unknown.
Matt Mackall - Jan. 18, 2016, 2:29 a.m.
On Thu, 2016-01-14 at 13:09 -0800, Simon Farnsworth wrote:
> # HG changeset patch
> # User Simon Farnsworth <simonfar@fb.com>
> # Date 1452794611 28800
> #      Thu Jan 14 10:03:31 2016 -0800
> # Node ID c8180c721b341cbab9fc617e183d60003fc34c8b
> # Parent  727b137e82bed8ac97cfa4d9ddaa29e92bf393f3
> shelve: Permit shelves to contain unknown files

I've decided that this is good enough to go in as-is, thanks. Please consider
making a follow-up with David's suggestions.

-- 
Mathematics is the supreme nostalgia of our time.
Simon Farnsworth - Jan. 18, 2016, 11:47 p.m.
On 15/01/2016, 11:25, "David Soria Parra" <dsp@experimentalworks.net> wrote:



>

>

>On 1/14/16 9:09 PM, Simon Farnsworth wrote:

>> diff --git a/hgext/shelve.py b/hgext/shelve.py

>> --- a/hgext/shelve.py

>> +++ b/hgext/shelve.py

>> @@ -304,6 +304,16 @@

>>          if name.startswith('.'):

>>              raise error.Abort(_("shelved change names may not start with '.'"))

>>          interactive = opts.get('interactive', False)

>> +        includeunknown = (opts.get('includeunknown', False) and

>> +                          not opts.get('addremove', False))

>

>This can be unexpected for people. We should check if they are both set

>and raise an error, to make it clear to people that they are incompatible.


Patch in preparation for this. I'm going to delay sending it out while the freeze for 3.7 is in place.

>

>> +

>> +        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')

>> @@ -315,7 +325,7 @@

>>                  editor = cmdutil.getcommiteditor(editform='shelve.shelve',

>>                                                   **opts)

>>                  return repo.commit(message, user, opts.get('date'), match,

>> -                                   editor=editor)

>> +                                   editor=editor, extra=extra)

>

>If I understand cmdutil.commit (which will be called with commitfunc as

>a parameter) correctly, you should be able to hand in 'extra' as an opt

>and then use extra=opts.get('extra') to retrieve them.

>This should remove the locality problem of having to reference and

>external variable.


I marginally prefer this style - it means that if cmdutil.commit or cmdutil.dorecord (either of which can end up being called with this function) later learns to parse an opt called 'extra', I'll still do the right thing. And we've already used it, in as much as interactivecommitfunc depends on it working to get at the definition of commitfunc.

>

>>              finally:

>>                  repo.ui.restoreconfig(backup)

>>                  if hasmq:

>> @@ -679,8 +689,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"))

>> @@ -745,6 +757,16 @@

>>                  shelvectx = tmpwctx

>>  

>

>

>>    

>>     -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.

>

>Can you add a test for the incompatibility between --addremove and

>--unknown.


Will do.

-- 
Simon Farnsworth

Patch

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -304,6 +304,16 @@ 
         if name.startswith('.'):
             raise error.Abort(_("shelved change names may not start with '.'"))
         interactive = opts.get('interactive', False)
+        includeunknown = (opts.get('includeunknown', 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')
@@ -315,7 +325,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:
@@ -679,8 +689,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"))
@@ -745,6 +757,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,
@@ -766,6 +788,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', '',
@@ -816,6 +840,7 @@ 
     '''
     allowables = [
         ('addremove', set(['create'])), # 'create' is pseudo action
+        ('includeunknown', set(['create'])),
         ('cleanup', set(['cleanup'])),
 #       ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
         ('delete', set(['delete'])),
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -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 ..