improved semantics for remove (issue438)
authorDirkjan Ochtman <dirkjan@ochtman.nl>
Sat, 22 Mar 2008 10:07:49 +0100
changeset 6346 8e3b651382f5
parent 6345 351da911f739
child 6347 3b42f7ac6916
improved semantics for remove (issue438) - Added files are never deleted (only removed with --force). - Modified files can only be removed with --force. - With --after, only deleted files are removed. - With --after --force, all files are removed but not deleted.
mercurial/commands.py
tests/test-remove
tests/test-remove.out
--- a/mercurial/commands.py	Fri Mar 21 17:22:47 2008 -0500
+++ b/mercurial/commands.py	Sat Mar 22 10:07:49 2008 +0100
@@ -2127,51 +2127,74 @@
 
     Schedule the indicated files for removal from the repository.
 
-    This only removes files from the current branch, not from the
-    entire project history.  If the files still exist in the working
-    directory, they will be deleted from it.  If invoked with --after,
-    files are marked as removed, but not actually unlinked unless --force
-    is also given. Without exact file names, --after will only mark
-    files as removed if they are no longer in the working directory.
+    This only removes files from the current branch, not from the entire
+    project history. -A can be used to remove only files that have already
+    been deleted, -f can be used to force deletion, and -Af can be used
+    to remove files from the next revision without deleting them.
+    
+    The following table details the behavior of remove for different file
+    states (columns) and option combinations (rows). The file states are
+    Added, Clean, Modified and Missing (as reported by hg status). The
+    actions are Warn, Remove (from branch) and Delete (from disk).
+
+           A  C  M  !
+    none   W  RD W  R
+    -f     R  RD RD R
+    -A     W  W  W  R
+    -Af    R  R  R  R
 
     This command schedules the files to be removed at the next commit.
     To undo a remove before that, see hg revert.
-
-    Modified files and added files are not removed by default.  To
-    remove them, use the -f/--force option.
     """
-    if not opts['after'] and not pats:
+
+    after, force = opts.get('after'), opts.get('force')
+    if not pats and not after:
         raise util.Abort(_('no files specified'))
+
     files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
-    exact = dict.fromkeys(files)
     mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
     modified, added, removed, deleted, unknown = mardu
+
     remove, forget = [], []
     for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
+
         reason = None
-        if abs in modified and not opts['force']:
-            reason = _('is modified (use -f to force removal)')
+        if abs in removed or abs in unknown:
+            continue
+
+        # last column
+        elif abs in deleted:
+            remove.append(abs)
+
+        # rest of the third row
+        elif after and not force:
+            reason = _('still exists (use -f to force removal)')
+
+        # rest of the first column
         elif abs in added:
-            if opts['force']:
+            if not force:
+                reason = _('has been marked for add (use -f to force removal)')
+            else:
                 forget.append(abs)
-                continue
-            reason = _('has been marked for add (use -f to force removal)')
-            exact = 1 # force the message
-        elif abs not in repo.dirstate:
-            reason = _('is not managed')
-        elif opts['after'] and not exact and abs not in deleted:
-            continue
-        elif abs in removed:
-            continue
+
+        # rest of the third column
+        elif abs in modified:
+            if not force:
+                reason = _('is modified (use -f to force removal)')
+            else:
+                remove.append(abs)
+
+        # rest of the second column
+        elif not reason:
+            remove.append(abs)
+
         if reason:
-            if exact:
-                ui.warn(_('not removing %s: file %s\n') % (rel, reason))
-        else:
-            if ui.verbose or not exact:
-                ui.status(_('removing %s\n') % rel)
-            remove.append(abs)
+            ui.warn(_('not removing %s: file %s\n') % (rel, reason))
+        elif ui.verbose or not exact:
+            ui.status(_('removing %s\n') % rel)
+
     repo.forget(forget)
-    repo.remove(remove, unlink=opts['force'] or not opts['after'])
+    repo.remove(remove, unlink=not after)
 
 def rename(ui, repo, *pats, **opts):
     """rename files; equivalent of copy + remove
@@ -3125,8 +3148,9 @@
     "recover": (recover, [], _('hg recover')),
     "^remove|rm":
         (remove,
-         [('A', 'after', None, _('record remove without deleting')),
-          ('f', 'force', None, _('remove file even if modified')),
+         [('A', 'after', None, _('record delete for missing files')),
+          ('f', 'force', None,
+		  _('remove (and delete) file even if added or modified')),
          ] + walkopts,
          _('hg remove [OPTION]... FILE...')),
     "rename|mv":
--- a/tests/test-remove	Fri Mar 21 17:22:47 2008 -0500
+++ b/tests/test-remove	Sat Mar 22 10:07:49 2008 +0100
@@ -1,45 +1,109 @@
 #!/bin/sh
 
+remove() {
+    hg rm $@
+    hg st
+    ls -R
+    hg up -C
+}
+
 hg init a
 cd a
 echo a > foo
-hg rm foo
+
+echo % file not managed
+remove foo
+
 hg add foo
-hg commit -m 1 -d "1000000 0"
-hg remove
+hg commit -m1
+
+# the table cases
+
+echo % 00 state added, options none
+echo b > bar
+hg add bar
+remove bar
+
+echo % 01 state clean, options none
+remove foo
+
+echo % 02 state modified, options none
+echo b >> foo
+remove foo
+
+echo % 03 state missing, options none
 rm foo
-hg remove foo
-hg revert --all
+remove foo
+
+echo % 10 state added, options -f
+echo b > bar
+hg add bar
+remove -f bar
+rm bar
+
+echo % 11 state clean, options -f
+remove -f foo
+
+echo % 12 state modified, options -f
+echo b >> foo
+remove -f foo
+
+echo % 13 state missing, options -f
 rm foo
-hg remove --after
-hg commit -m 2 -d "1000000 0"
-hg export --nodates 0
-hg export --nodates 1
-hg log -p -r 0
-hg log -p -r 1
+remove -f foo
+
+echo % 20 state added, options -A
+echo b > bar
+hg add bar
+remove -A bar
 
-echo a > a
-hg add a
-hg rm a
-hg rm -f a
-echo b > b
-mkdir c
-echo d > c/d
-hg ci -A -m 3 -d "1000001 0"
-echo c >> b
-hg rm b
-hg rm -f b
-hg rm -A c/d
-hg st
-cat c/d
-hg revert c
-hg rm -A
-hg st
-hg rm -A c
-hg st
-rm c/d
-hg rm -A
-hg st
+echo % 21 state clean, options -A
+remove -A foo
+
+echo % 22 state modified, options -A
+echo b >> foo
+remove -A foo
+
+echo % 23 state missing, options -A
+rm foo
+remove -A foo
+
+echo % 30 state added, options -Af
+echo b > bar
+hg add bar
+remove -Af bar
+rm bar
+
+echo % 31 state clean, options -Af
+remove -Af foo
+
+echo % 32 state modified, options -Af
+echo b >> foo
+remove -Af foo
 
-cd ..
-hg clone a b
+echo % 33 state missing, options -Af
+rm foo
+remove -Af foo
+
+# test some directory stuff
+
+mkdir test
+echo a > test/foo
+echo b > test/bar
+hg ci -Am2
+
+echo % dir, options none
+rm test/bar
+remove test
+
+echo % dir, options -f
+rm test/bar
+remove -f test
+
+echo % dir, options -A
+rm test/bar
+remove -A test
+
+echo % dir, options -Af
+rm test/bar
+remove -Af test
--- a/tests/test-remove.out	Fri Mar 21 17:22:47 2008 -0500
+++ b/tests/test-remove.out	Sat Mar 22 10:07:49 2008 +0100
@@ -1,67 +1,127 @@
-not removing foo: file is not managed
-abort: no files specified
-undeleting foo
-removing foo
-# HG changeset patch
-# User test
-# Date 1000000 0
-# Node ID 8ba83d44753d6259db5ce6524974dd1174e90f47
-# Parent  0000000000000000000000000000000000000000
-1
-
-diff -r 000000000000 -r 8ba83d44753d foo
---- /dev/null
-+++ b/foo
-@@ -0,0 +1,1 @@
-+a
-# HG changeset patch
-# User test
-# Date 1000000 0
-# Node ID a1fce69c50d97881c5c014ab23f580f720c78678
-# Parent  8ba83d44753d6259db5ce6524974dd1174e90f47
-2
-
-diff -r 8ba83d44753d -r a1fce69c50d9 foo
---- a/foo
-+++ /dev/null
-@@ -1,1 +0,0 @@
--a
-changeset:   0:8ba83d44753d
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     1
+% file not managed
+? foo
+.:
+foo
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 00 state added, options none
+not removing bar: file has been marked for add (use -f to force removal)
+A bar
+.:
+bar
+foo
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+% 01 state clean, options none
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 02 state modified, options none
+not removing foo: file is modified (use -f to force removal)
+M foo
+.:
+foo
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 03 state missing, options none
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 10 state added, options -f
+? bar
+.:
+bar
+foo
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 11 state clean, options -f
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 12 state modified, options -f
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 13 state missing, options -f
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 20 state added, options -A
+not removing bar: file still exists (use -f to force removal)
+A bar
+.:
+bar
+foo
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+% 21 state clean, options -A
+not removing foo: file still exists (use -f to force removal)
+.:
+foo
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 22 state modified, options -A
+not removing foo: file still exists (use -f to force removal)
+M foo
+.:
+foo
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 23 state missing, options -A
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 30 state added, options -Af
+? bar
+.:
+bar
+foo
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 31 state clean, options -Af
+R foo
+.:
+foo
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 32 state modified, options -Af
+R foo
+.:
+foo
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% 33 state missing, options -Af
+R foo
+.:
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+adding test/bar
+adding test/foo
+% dir, options none
+removing test/foo
+removing test/bar
+R test/bar
+R test/foo
+.:
+foo
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% dir, options -f
+removing test/foo
+removing test/bar
+R test/bar
+R test/foo
+.:
+foo
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% dir, options -A
+not removing test/foo: file still exists (use -f to force removal)
+removing test/bar
+R test/bar
+.:
+foo
+test
 
-diff -r 000000000000 -r 8ba83d44753d foo
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/foo	Mon Jan 12 13:46:40 1970 +0000
-@@ -0,0 +1,1 @@
-+a
-
-changeset:   1:a1fce69c50d9
-tag:         tip
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     2
+./test:
+foo
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% dir, options -Af
+removing test/foo
+removing test/bar
+R test/bar
+R test/foo
+.:
+foo
+test
 
-diff -r 8ba83d44753d -r a1fce69c50d9 foo
---- a/foo	Mon Jan 12 13:46:40 1970 +0000
-+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
-@@ -1,1 +0,0 @@
--a
-
-not removing a: file has been marked for add (use -f to force removal)
-adding a
-adding b
-adding c/d
-not removing b: file is modified (use -f to force removal)
-R b
-R c/d
-d
-undeleting c/d
-R b
-R b
-removing c/d
-R b
-R c/d
-updating working directory
-3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+./test:
+foo
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved