# HG changeset patch # User Matt Harbison # Date 2019-03-30 01:53:15 # Node ID f4147ca63d399743b0f3bdb300599b24928ec8a7 # Parent 03f6480bfddabb5d0faa817bd895d2b0437c472a uncommit: abort if an explicitly given file cannot be uncommitted (BC) 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 @@ -33,6 +33,7 @@ from mercurial import ( registrar, rewriteutil, scmutil, + util, ) cmdtable = {} @@ -133,8 +134,34 @@ 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) + + badfiles = set(match.files()) - eligible + + # Naming a parent directory of an eligible file is OK, even + # if not everything tracked in that directory can be + # uncommitted. + if badfiles: + badfiles -= set([f for f in util.dirs(eligible)]) + + for f in sorted(badfiles): + if f in s.clean: + hint = _(b"file was not changed in working directory " + b"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,57 @@ 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 + +Uncommitting a directory won't run afoul of the checks that an explicit file +can be uncommitted. + + $ mkdir dir + $ echo 1 > dir/file.txt + $ hg ci -Aqm 'add file in directory' + $ hg uncommit dir + $ hg status + A dir/file.txt + +`uncommit ` and `cd && uncommit .` behave the same... + + $ hg rollback -q --config ui.rollback=True + $ echo 2 > dir/file2.txt + $ hg ci -Aqm 'add file2 in directory' + $ hg uncommit dir + note: keeping empty commit + $ hg status + A dir/file2.txt + + $ hg rollback -q --config ui.rollback=True + $ cd dir + $ hg uncommit . + note: keeping empty commit + $ hg status + A dir/file2.txt + $ cd .. + +... and errors out the same way when nothing can be uncommitted + + $ hg rollback -q --config ui.rollback=True + $ mkdir emptydir + $ hg uncommit emptydir + abort: cannot uncommit "emptydir" + (file was untracked in working directory parent) + [255] + + $ cd emptydir + $ hg uncommit . + abort: cannot uncommit "emptydir" + (file was untracked in working directory parent) + [255] + $ hg status + $ cd ..