Patchwork [V2] uncommit: abort if an explicitly given file cannot be uncommitted

login
register
mail settings
Submitter Matt Harbison
Date March 30, 2019, 7:02 p.m.
Message ID <e0dfeb204949f70a3451.1553972571@Envy>
Download mbox | patch
Permalink /patch/39419/
State Superseded
Headers show

Comments

Matt Harbison - March 30, 2019, 7:02 p.m.
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1553910795 14400
#      Fri Mar 29 21:53:15 2019 -0400
# Node ID e0dfeb204949f70a34517d019b5f45037b943db7
# Parent  eec20025ada33889233e553c5825aac36b708f6c
uncommit: abort if an explicitly given file cannot be uncommitted

I've gotten burned several times by this in the last few days.  The former tests
look simple enough, but if a good file and a bad file are given, the bad files
are silently ignored.  Some commands like `forget` will warn about bogus files,
but that would likely get lost in the noise of an interactive uncommit.  The
commit command aborts if a bad file is given, so this seems more consistent for
commands that alter the repository.
Yuya Nishihara - March 31, 2019, 10:24 p.m.
On Sat, 30 Mar 2019 15:02:51 -0400, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_harbison@yahoo.com>
> # Date 1553910795 14400
> #      Fri Mar 29 21:53:15 2019 -0400
> # Node ID e0dfeb204949f70a34517d019b5f45037b943db7
> # Parent  eec20025ada33889233e553c5825aac36b708f6c
> uncommit: abort if an explicitly given file cannot be uncommitted
> 
> I've gotten burned several times by this in the last few days.  The former tests
> look simple enough, but if a good file and a bad file are given, the bad files
> are silently ignored.  Some commands like `forget` will warn about bogus files,
> but that would likely get lost in the noise of an interactive uncommit.  The
> commit command aborts if a bad file is given, so this seems more consistent for
> commands that alter the repository.
> 
> diff --git a/hgext/uncommit.py b/hgext/uncommit.py
> --- a/hgext/uncommit.py
> +++ b/hgext/uncommit.py
> @@ -133,8 +133,28 @@ def uncommit(ui, repo, *pats, **opts):
>          if len(old.parents()) > 1:
>              raise error.Abort(_("cannot uncommit merge changeset"))
>  
> +        match = scmutil.match(old, pats, opts)
> +
> +        # Check all explicitly given files; abort if there's a problem.
> +        if match.files():
> +            s = old.status(old.p1(), match, listclean=True)
> +            eligible = set(s.added) | set(s.modified) | set(s.removed)
> +
> +            for f in match.files():
> +                if f not in eligible:
> +                    if f in s.clean:
> +                        hint = _(
> +                            b"file was not changed in working directory parent")
> +                    elif repo.wvfs.exists(f):
> +                        hint = _(
> +                            b"file was untracked in working directory parent")
> +                    else:
> +                        hint = _(b"file does not exist")
> +
> +                    raise error.Abort(_(b'cannot uncommit "%s"')
> +                                      % scmutil.getuipathfn(repo)(f), hint=hint)

Doesn't it break "hg uncommit <dir>"? match.files() isn't a list of matched
files.
Matt Harbison - April 1, 2019, 1:36 a.m.
On Sun, 31 Mar 2019 18:24:25 -0400, Yuya Nishihara <yuya@tcha.org> wrote:

> On Sat, 30 Mar 2019 15:02:51 -0400, Matt Harbison wrote:
>> # HG changeset patch
>> # User Matt Harbison <matt_harbison@yahoo.com>
>> # Date 1553910795 14400
>> #      Fri Mar 29 21:53:15 2019 -0400
>> # Node ID e0dfeb204949f70a34517d019b5f45037b943db7
>> # Parent  eec20025ada33889233e553c5825aac36b708f6c
>> uncommit: abort if an explicitly given file cannot be uncommitted
>>
>> diff --git a/hgext/uncommit.py b/hgext/uncommit.py
>> --- a/hgext/uncommit.py
>> +++ b/hgext/uncommit.py
>> @@ -133,8 +133,28 @@ def uncommit(ui, repo, *pats, **opts):
>>          if len(old.parents()) > 1:
>>              raise error.Abort(_("cannot uncommit merge changeset"))
>>
>> +        match = scmutil.match(old, pats, opts)
>> +
>> +        # Check all explicitly given files; abort if there's a problem.
>> +        if match.files():
>> +            s = old.status(old.p1(), match, listclean=True)
>> +            eligible = set(s.added) | set(s.modified) | set(s.removed)
>> +
>> +            for f in match.files():
>> +                if f not in eligible:
>> +                    if f in s.clean:
>> +                        hint = _(
>> +                            b"file was not changed in working  
>> directory parent")
>> +                    elif repo.wvfs.exists(f):
>> +                        hint = _(
>> +                            b"file was untracked in working directory  
>> parent")
>> +                    else:
>> +                        hint = _(b"file does not exist")
>> +
>> +                    raise error.Abort(_(b'cannot uncommit "%s"')
>> +                                      % scmutil.getuipathfn(repo)(f),  
>> hint=hint)
>
> Doesn't it break "hg uncommit <dir>"? match.files() isn't a list of  
> matched
> files.

Yeah, you're right.  `.` seems to work, but not a subdirectory.  The  
`badfn` and `explicitdir` callbacks on the matcher seem to only be invoked  
when walking wdir.  Any ideas?

Patch

diff --git a/hgext/uncommit.py b/hgext/uncommit.py
--- a/hgext/uncommit.py
+++ b/hgext/uncommit.py
@@ -133,8 +133,28 @@  def uncommit(ui, repo, *pats, **opts):
         if len(old.parents()) > 1:
             raise error.Abort(_("cannot uncommit merge changeset"))
 
+        match = scmutil.match(old, pats, opts)
+
+        # Check all explicitly given files; abort if there's a problem.
+        if match.files():
+            s = old.status(old.p1(), match, listclean=True)
+            eligible = set(s.added) | set(s.modified) | set(s.removed)
+
+            for f in match.files():
+                if f not in eligible:
+                    if f in s.clean:
+                        hint = _(
+                            b"file was not changed in working directory parent")
+                    elif repo.wvfs.exists(f):
+                        hint = _(
+                            b"file was untracked in working directory parent")
+                    else:
+                        hint = _(b"file does not exist")
+
+                    raise error.Abort(_(b'cannot uncommit "%s"')
+                                      % scmutil.getuipathfn(repo)(f), hint=hint)
+
         with repo.transaction('uncommit'):
-            match = scmutil.match(old, pats, opts)
             keepcommit = pats
             if not keepcommit:
                 if opts.get('keep') is not None:
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -102,14 +102,16 @@  Recommit
   $ hg heads -T '{rev}:{node} {desc}'
   5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
 
-Uncommit of non-existent and unchanged files has no effect
+Uncommit of non-existent and unchanged files aborts
   $ hg uncommit nothinghere
-  nothing to uncommit
-  [1]
+  abort: cannot uncommit "nothinghere"
+  (file does not exist)
+  [255]
   $ hg status
   $ hg uncommit file-abc
-  nothing to uncommit
-  [1]
+  abort: cannot uncommit "file-abc"
+  (file was not changed in working directory parent)
+  [255]
   $ hg status
 
 Try partial uncommit, also moves bookmark
@@ -513,3 +515,12 @@  Copy a->b1 and a->b2, then rename b1->c 
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     add a
   
+Removes can be uncommitted
+
+  $ hg ci -m 'modified b'
+  $ hg rm b
+  $ hg ci -m 'remove b'
+  $ hg uncommit b
+  note: keeping empty commit
+  $ hg status
+  R b