Rev 2455: bzr rm removes the working file in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Wed Apr 25 07:50:30 BST 2007


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 2455
revision-id: pqm at pqm.ubuntu.com-20070425065022-rsmpi4x2q1gn8536
parent: pqm at pqm.ubuntu.com-20070425054241-urh0t3nequwc2j6q
parent: amanic at gmail.com-20070420071526-8e395b3gqvp1by61
committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2007-04-25 07:50:22 +0100
message:
  bzr rm removes the working file
added:
  bzrlib/tests/workingtree_implementations/test_remove.py test_remove.py-20070413183901-rvnp85rtc0q0sclp-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/delta.py                delta.py-20050729221636-54cf14ef94783d0a
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/blackbox/test_mv.py test_mv.py-20060705114902-33tkxz0o9cdshemo-1
  bzrlib/tests/blackbox/test_remove.py test_remove.py-20060530011439-fika5rm84lon0goe-1
  bzrlib/tests/blackbox/test_selftest.py test_selftest.py-20060123024542-01c5f1bbcb596d78
  bzrlib/tests/blackbox/test_too_much.py blackbox.py-20050620052131-a7370d756399f615
  bzrlib/tests/workingtree_implementations/__init__.py __init__.py-20060203003124-b2aa5aca21a8bfad
  bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
    ------------------------------------------------------------
    revno: 2292.1.34
    merged: amanic at gmail.com-20070420071526-8e395b3gqvp1by61
    parent: amanic at gmail.com-20070420070001-lbknio8gvry1au0w
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-04-20 09:15:26 +0200
    message:
      Move "magically convert commands like 'remove abc' to ['remove', 'abc']"
      from tests/__init__.run_bzr_captured to tests/blackbox/test_remove
    ------------------------------------------------------------
    revno: 2292.1.33
    merged: amanic at gmail.com-20070420070001-lbknio8gvry1au0w
    parent: amanic at gmail.com-20070419055947-x776pmmp7uj7utva
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-04-20 09:00:01 +0200
    message:
      * Remove workingtree.canonicalpath
    ------------------------------------------------------------
    revno: 2292.1.32
    merged: amanic at gmail.com-20070419055947-x776pmmp7uj7utva
    parent: amanic at gmail.com-20070418202208-8081vgmmyr6k87op
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-19 07:59:47 +0200
    message:
      * tests/__init__.run_bzr
        Now gives better assert message for 'Unexpected return code'
      * blackbox/test_remove
        - Add changed file tests
        - When checking changed/unknown rm fails, always wheck if we can forec it.
        - Move one file tests to top
        - Fixed copy-paste pattern induced mishaps where more-than-one-file-tests
          were actually just testing one-file
        - Add test to remove while in a child dir
        - Add remove deleted file test
    ------------------------------------------------------------
    revno: 2292.1.31
    merged: amanic at gmail.com-20070418202208-8081vgmmyr6k87op
    parent: amanic at gmail.com-20070418201215-upzz7pof6a4433n7
    parent: pqm at pqm.ubuntu.com-20070417080415-5vn25svmf95ki88z
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Wed 2007-04-18 22:22:08 +0200
    message:
      Merge bzr.dev
    ------------------------------------------------------------
    revno: 2292.1.30
    merged: amanic at gmail.com-20070418201215-upzz7pof6a4433n7
    parent: amanic at gmail.com-20070416183544-tdtramoul71svkhh
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Wed 2007-04-18 22:12:15 +0200
    message:
      * Minor text fixes.
      * Remove trailing whitespace added by this branch.
      * Add tests for removing empty directories.
      * workingtree.remove
        - Don't recurse into empty directories.
        - Use absolute path when deleting from the file system.
    ------------------------------------------------------------
    revno: 2292.1.29
    merged: amanic at gmail.com-20070416183544-tdtramoul71svkhh
    parent: amanic at gmail.com-20070416182641-8ewx7hfmkehjq5li
    parent: pqm at pqm.ubuntu.com-20070416080254-bf3rfk77k5bgfdl7
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-04-16 20:35:44 +0200
    message:
      Merge bzr.dev
    ------------------------------------------------------------
    revno: 2292.1.28
    merged: amanic at gmail.com-20070416182641-8ewx7hfmkehjq5li
    parent: amanic at gmail.com-20070416062659-z2yrpp5118a1yeyr
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-04-16 20:26:41 +0200
    message:
      * NEWS
        - move my news to NOTES WHEN UPGRADING section.
      * bzrlib/builtins.py
        - Use RegistryOption for bzr rm --force and --keep
      * bzrlib/tests/__init__.py
        - remove a white space
      * bzrlib/tests/blackbox/test_selftest.py
        - make blackbox/test_selftest.py pass again:
          Its run_bzr_captured method did not return anything, although bzr_run
          expects it now.
    ------------------------------------------------------------
    revno: 2292.1.27
    merged: amanic at gmail.com-20070416062659-z2yrpp5118a1yeyr
    parent: amanic at gmail.com-20070416025505-spmkhtjsrbiv21m3
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-04-16 08:26:59 +0200
    message:
      * tests/__init__.TestCase.run_bzr_captured
        - Support single text commands with quoted sub-strings eg.:
          "bzr commit 'some message'"
        - Move support for error_regexes from run_bzr_error() to run_bzr()
          as it is quite usefull to assert output from notes.
      * blackbox/test_remove
        - convert the rest of the tests to use the new improved self.run_bzr
    ------------------------------------------------------------
    revno: 2292.1.26
    merged: amanic at gmail.com-20070416025505-spmkhtjsrbiv21m3
    parent: amanic at gmail.com-20070416010020-3jan8hje4qmbwdsh
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-04-16 04:55:05 +0200
    message:
      * tests/__init__
        - make run_bzr_captured abit more clever, so that it will
          split up argunents if they are passed as a single string.
      * blackbox/test_remove
        - start converting to run_bzr and run_bzr_error
    ------------------------------------------------------------
    revno: 2292.1.25
    merged: amanic at gmail.com-20070416010020-3jan8hje4qmbwdsh
    parent: amanic at gmail.com-20070414124037-ewx6hp70tw4vdtdb
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-04-16 03:00:20 +0200
    message:
      * Add utility method delta.get_changes_as_text to get the output of .show()
        as a string.
      * Add new errors.BzrRemoveChangedFilesError to raise when 'bzr remove'
        should refuse to delete stuff.
      * Add workingtree.canicalpath(filename) to normalize file names.
      * Changed the working of workingtree.remove(...) to check if any files
        are changed before it starts deleting anything. Will raise exception
        now if changed files are passed to be removed.
      * workingtree_implementations/test_remove.py
        - Checked all tests and add more cases.
    ------------------------------------------------------------
    revno: 2292.1.24
    merged: amanic at gmail.com-20070414124037-ewx6hp70tw4vdtdb
    parent: amanic at gmail.com-20070414115726-kykcsm9pztykst1w
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Sat 2007-04-14 14:40:37 +0200
    message:
      minor text cleanups
    ------------------------------------------------------------
    revno: 2292.1.23
    merged: amanic at gmail.com-20070414115726-kykcsm9pztykst1w
    parent: amanic at gmail.com-20070414005827-zf4n2y9scrjs2d2x
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Sat 2007-04-14 13:57:26 +0200
    message:
      Revert test_too_much.py and just do the minimum te get the tests to pass.
      Add some test to test_remove.py.
      Let test_remove.py make safe id's.
    ------------------------------------------------------------
    revno: 2292.1.22
    merged: amanic at gmail.com-20070414005827-zf4n2y9scrjs2d2x
    parent: amanic at gmail.com-20070413194540-jaf9n9wzhx4pd66c
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Sat 2007-04-14 02:58:27 +0200
    message:
      Implement TODO: Normalize names.
      Assume paths relative to the basedir.
      (not to the current dir, which I did previously)
    ------------------------------------------------------------
    revno: 2292.1.21
    merged: amanic at gmail.com-20070413194540-jaf9n9wzhx4pd66c
    parent: amanic at gmail.com-20070413184526-6xhpgyvqz2u18l2q
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-04-13 21:45:40 +0200
    message:
      undo tree.has_id fake-bugfix, which seems to not be needed anymore..
    ------------------------------------------------------------
    revno: 2292.1.20
    merged: amanic at gmail.com-20070413184526-6xhpgyvqz2u18l2q
    parent: amanic at gmail.com-20070413182326-qt44rjuzo249l7x8
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-04-13 20:45:26 +0200
    message:
      move test_workingtree.TestRemove to workingtree_implementations/test_remove
    ------------------------------------------------------------
    revno: 2292.1.19
    merged: amanic at gmail.com-20070413182326-qt44rjuzo249l7x8
    parent: amanic at gmail.com-20070413181313-btt9e9cx16987yrq
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-04-13 20:23:26 +0200
    message:
      convert from using self.runbzr to self.run_bzr_captured
    ------------------------------------------------------------
    revno: 2292.1.18
    merged: amanic at gmail.com-20070413181313-btt9e9cx16987yrq
    parent: amanic at gmail.com-20070412014719-3oo573tt381rpigu
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-04-13 20:13:13 +0200
    message:
      revert wrapping change
    ------------------------------------------------------------
    revno: 2292.1.17
    merged: amanic at gmail.com-20070412014719-3oo573tt381rpigu
    parent: amanic at gmail.com-20070412013832-1avmdh8pba8yp4b1
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 03:47:19 +0200
    message:
      Remove redundant __init__ and object variable.
    ------------------------------------------------------------
    revno: 2292.1.16
    merged: amanic at gmail.com-20070412013832-1avmdh8pba8yp4b1
    parent: amanic at gmail.com-20070412011853-z30g2o1ypj7rqcyr
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 03:38:32 +0200
    message:
      Update NEWS
    ------------------------------------------------------------
    revno: 2292.1.15
    merged: amanic at gmail.com-20070412011853-z30g2o1ypj7rqcyr
    parent: amanic at gmail.com-20070412011017-ofzhcgj7o7bnh6dd
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 03:18:53 +0200
    message:
      Fix test too much again.
    ------------------------------------------------------------
    revno: 2292.1.14
    merged: amanic at gmail.com-20070412011017-ofzhcgj7o7bnh6dd
    parent: amanic at gmail.com-20070412004748-0nsa6sughf0xu744
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 03:10:17 +0200
    message:
      * blackbox/test_remove
        - uninvert my logic
        - add a little more testing
    ------------------------------------------------------------
    revno: 2292.1.13
    merged: amanic at gmail.com-20070412004748-0nsa6sughf0xu744
    parent: amanic at gmail.com-20070411225348-7145vlsq3f390osg
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 02:47:48 +0200
    message:
      * merge the unversion command back into the remove command,
        merging the commands and tests.
      * make all tests pass again.
    ------------------------------------------------------------
    revno: 2292.1.12
    merged: amanic at gmail.com-20070411225348-7145vlsq3f390osg
    parent: amanic at gmail.com-20070411220954-azjn3zpjystqowcv
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 00:53:48 +0200
    message:
      * test_workingtree/TestRemove
        - Change global and local constants to class variables.
    ------------------------------------------------------------
    revno: 2292.1.11
    merged: amanic at gmail.com-20070411220954-azjn3zpjystqowcv
    parent: amanic at gmail.com-20070411220247-plcjs3dvidvjx2vj
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 00:09:54 +0200
    message:
      * workingtree.remove
        - Don't figure out what changed if we don't care.
    ------------------------------------------------------------
    revno: 2292.1.10
    merged: amanic at gmail.com-20070411220247-plcjs3dvidvjx2vj
    parent: amanic at gmail.com-20070411212706-7jzaph3pv0856qks
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Thu 2007-04-12 00:02:47 +0200
    message:
      * workingtree.remove
        - Add and test a new force option in order to force the deletion of changed files
          and non-empty directories.
        - Some doc fixups.
        - Remove some implemented todo's
        - Remove ## TODO: Remove nested loops; better scalability
          as I can't see any nested loop.s
        - small whitespace cleanups in tests.
    ------------------------------------------------------------
    revno: 2292.1.9
    merged: amanic at gmail.com-20070411212706-7jzaph3pv0856qks
    parent: amanic at gmail.com-20070411204847-quynsdasuvjhju64
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Wed 2007-04-11 23:27:06 +0200
    message:
      Handle the removing of nonempty directories.
    ------------------------------------------------------------
    revno: 2292.1.8
    merged: amanic at gmail.com-20070411204847-quynsdasuvjhju64
    parent: amanic at gmail.com-20070409215050-9k4jxh76grq7jxz8
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Wed 2007-04-11 22:48:47 +0200
    message:
      When removing and deleting files, 
      only check for changes on specific files.
    ------------------------------------------------------------
    revno: 2292.1.7
    merged: amanic at gmail.com-20070409215050-9k4jxh76grq7jxz8
    parent: amanic at gmail.com-20070331204906-iai2v10wk3l3lt12
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-04-09 23:50:50 +0200
    message:
      First pass at only deleting files on 'bzr remove' when
      they haven't changed.
      
      Needed to modify _find_children_across_trees (tree.py) because
      tree.has_id did not work as excpeted.
    ------------------------------------------------------------
    revno: 2292.1.6
    merged: amanic at gmail.com-20070331204906-iai2v10wk3l3lt12
    parent: amanic at gmail.com-20070323182956-5vestftm6gx3o9mz
    parent: pqm at pqm.ubuntu.com-20070329064515-7bfc20fbcf9cf1a7
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Sat 2007-03-31 22:49:06 +0200
    message:
      merge bzr.dev
    ------------------------------------------------------------
    revno: 2292.1.5
    merged: amanic at gmail.com-20070323182956-5vestftm6gx3o9mz
    parent: amanic at gmail.com-20070219054605-o69te1fuwcirv01a
    parent: pqm at pqm.ubuntu.com-20070322230820-f8735ba918f51539
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-03-23 20:29:56 +0200
    message:
      merge with bzr.dev
    ------------------------------------------------------------
    revno: 2292.1.4
    merged: amanic at gmail.com-20070219054605-o69te1fuwcirv01a
    parent: amanic at gmail.com-20070216072443-g38i13amiri6j2ir
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Mon 2007-02-19 07:46:05 +0200
    message:
      Make output for bzr remove more verbose.
    ------------------------------------------------------------
    revno: 2292.1.3
    merged: amanic at gmail.com-20070216072443-g38i13amiri6j2ir
    parent: amanic at gmail.com-20070216071540-wq20se0crnnki9c9
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-02-16 09:24:43 +0200
    message:
      Factored out common code from test_remove.py into test_unversion.py
    ------------------------------------------------------------
    revno: 2292.1.2
    merged: amanic at gmail.com-20070216071540-wq20se0crnnki9c9
    parent: amanic at gmail.com-20070216061611-sjscmgi4v5rozq6h
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-02-16 09:15:40 +0200
    message:
      Minor test cleanups, making test_remove.py and test_unversion.py more similar.
    ------------------------------------------------------------
    revno: 2292.1.1
    merged: amanic at gmail.com-20070216061611-sjscmgi4v5rozq6h
    parent: pqm at pqm.ubuntu.com-20070215181416-864dbe690a0f3da8
    committer: Marius Kruger <amanic at gmail.com>
    branch nick: bzr.rm_delete_working_file
    timestamp: Fri 2007-02-16 08:16:11 +0200
    message:
      "bzr remove" and "bzr rm" will now remove the working file.
      This has been done for consistency with svn and the unix rm command.
      
      The old remove behaviour has been retained in the new command
      "bzr unversion", which will just stop versioning the file,
      but not delete it.
      (Addressing Bug #82602)
      
      Exisitng tests have been reworked and new tests were added to test these
      changes properly.
=== added file 'bzrlib/tests/workingtree_implementations/test_remove.py'
--- a/bzrlib/tests/workingtree_implementations/test_remove.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/workingtree_implementations/test_remove.py	2007-04-18 20:12:15 +0000
@@ -0,0 +1,205 @@
+# Copyright (C) 2006, 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Tests for interface conformance of 'WorkingTree.remove'"""
+
+import re
+from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
+from bzrlib import errors, osutils
+
+class TestRemove(TestCaseWithWorkingTree):
+    """Tests WorkingTree.remove"""
+
+    files=['a', 'b/', 'b/c', 'd/']
+    a = ['a']
+    b = ['b']
+    b_c = ['b', 'b/c']
+
+    def getTree(self):
+        self.makeAndChdirToTestDir()
+        tree = self.make_branch_and_tree('.')
+        self.build_tree(TestRemove.files)
+        return tree
+
+    def test_remove_keep(self):
+        """Check that files are unversioned but not deleted."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        self.assertInWorkingTree(TestRemove.files)
+
+        tree.remove(TestRemove.files)
+        self.assertNotInWorkingTree(TestRemove.files)
+        self.failUnlessExists(TestRemove.files)
+
+    def test_remove_unchanged_files(self):
+        """Check that unchanged files are removed and deleted."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        tree.commit("files must not have changes")
+        self.assertInWorkingTree(TestRemove.files)
+
+        tree.remove(TestRemove.files, keep_files=False)
+
+        self.assertNotInWorkingTree(TestRemove.files)
+        self.failIfExists(TestRemove.files)
+
+    def test_remove_added_files(self):
+        """Removal of newly added files must fail."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        self.assertInWorkingTree(TestRemove.files)
+        try:
+            tree.remove(TestRemove.files, keep_files=False)
+            self.fail('Should throw BzrRemoveChangedFilesError')
+        except errors.BzrRemoveChangedFilesError, e:
+            self.assertTrue(re.match('Can\'t remove changed or unknown files:'
+                '.*added:.*a.*b.*b/c.*d.*',
+                str(e), re.DOTALL))
+        self.assertInWorkingTree(TestRemove.files)
+        self.failUnlessExists(TestRemove.files)
+
+    def test_remove_changed_file(self):
+        """Removal of a changed files must fail."""
+        tree = self.getTree()
+        tree.add(TestRemove.a)
+        tree.commit("make sure a is versioned")
+        f = file('a', 'wb')
+        f.write("some other new content!")
+        f.close()
+        self.assertInWorkingTree(TestRemove.a)
+        try:
+            tree.remove(TestRemove.a, keep_files=False)
+            self.fail('Should throw BzrRemoveChangedFilesError')
+        except errors.BzrRemoveChangedFilesError, e:
+            self.assertTrue(re.match('Can\'t remove changed or unknown files:'
+                '.*modified:.*a.*',
+                str(e), re.DOTALL))
+        self.assertInWorkingTree(TestRemove.a)
+        self.failUnlessExists(TestRemove.a)
+
+    def test_remove_deleted_files(self):
+        """Check that files are removed if they don't exist any more."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        tree.commit("make sure files are versioned")
+        for f in ['b/c', 'b', 'a', 'd']:
+            osutils.delete_any(f)
+        self.assertInWorkingTree(TestRemove.files)
+        self.failIfExists(TestRemove.files)
+
+        tree.remove(TestRemove.files, keep_files=False)
+
+        self.assertNotInWorkingTree(TestRemove.files)
+        self.failIfExists(TestRemove.files)
+
+    def test_force_remove_changed_files(self):
+        """Check that changed files are removed and deleted when forced."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        self.assertInWorkingTree(TestRemove.files)
+
+        tree.remove(TestRemove.files, keep_files=False, force=True)
+
+        self.assertNotInWorkingTree(TestRemove.files)
+        self.failIfExists(TestRemove.files)
+
+    def test_remove_unknown_files(self):
+        """Try to delete unknown files."""
+        tree = self.getTree()
+        try:
+            tree.remove(TestRemove.files, keep_files=False)
+            self.fail('Should throw BzrRemoveChangedFilesError')
+        except errors.BzrRemoveChangedFilesError, e:
+            self.assertTrue(re.match('Can\'t remove changed or unknown files:'
+                '.*unknown:.*b/c.*b.*a.*d.*',
+                str(e), re.DOTALL))
+
+    def test_remove_nonexisting_files(self):
+        """Try to delete non-existing files."""
+        tree = self.getTree()
+        tree.remove([''], keep_files=False)
+        try:
+            tree.remove(['xyz', 'abc/def'], keep_files=False)
+            self.fail('Should throw BzrRemoveChangedFilesError')
+        except errors.BzrRemoveChangedFilesError, e:
+            self.assertTrue(re.match('Can\'t remove changed or unknown files:'
+                '.*unknown:.*xyz.*abc/def.*',
+                str(e), re.DOTALL))
+
+    def test_remove_nonempty_directory(self):
+        """Unchanged non-empty directories should be deleted."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        tree.commit("make sure b is versioned")
+        self.assertInWorkingTree(TestRemove.files)
+        self.failUnlessExists(TestRemove.files)
+        tree.remove(TestRemove.b, keep_files=False)
+        self.assertNotInWorkingTree(TestRemove.b)
+        self.failIfExists(TestRemove.b)
+
+    def test_remove_nonempty_directory_with_unknowns(self):
+        """Unchanged non-empty directories should be deleted."""
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        tree.commit("make sure b is versioned")
+        self.assertInWorkingTree(TestRemove.files)
+        self.failUnlessExists(TestRemove.files)
+        f = file('b/my_unknown_file', 'wb')
+        f.write("some content!")
+        f.close()
+        try:
+            tree.remove(TestRemove.b, keep_files=False)
+            self.fail('Should throw BzrRemoveChangedFilesError')
+        except errors.BzrRemoveChangedFilesError, e:
+            self.assertTrue(re.match('Can\'t remove changed or unknown files:'
+                '.*unknown:.*b/my_unknown_file.*',
+                str(e), re.DOTALL))
+        self.assertInWorkingTree(TestRemove.b)
+        self.failUnlessExists(TestRemove.b)
+
+    def test_force_remove_nonempty_directory(self):
+        tree = self.getTree()
+        tree.add(TestRemove.files)
+        tree.commit("make sure b is versioned")
+        self.assertInWorkingTree(TestRemove.files)
+        self.failUnlessExists(TestRemove.files)
+        tree.remove(TestRemove.b, keep_files=False, force=True)
+        self.assertNotInWorkingTree(TestRemove.b_c)
+        self.failIfExists(TestRemove.b_c)
+
+    def test_remove_directory_with_changed_file(self):
+        """Refuse to delete directories with changed files."""
+        tree = self.getTree()
+        tree.add(TestRemove.b_c)
+        tree.commit("make sure b and c are versioned")
+        f = file('b/c', 'wb')
+        f.write("some other new content!")
+        f.close()
+        self.assertInWorkingTree(TestRemove.b_c)
+        try:
+            tree.remove(TestRemove.b, keep_files=False)
+            self.fail('Should throw BzrRemoveChangedFilesError')
+        except errors.BzrRemoveChangedFilesError, e:
+            self.assertTrue(re.match('Can\'t remove changed or unknown files:'
+                '.*modified:.*b/c.*',
+                str(e), re.DOTALL))
+        self.assertInWorkingTree(TestRemove.b_c)
+        self.failUnlessExists(TestRemove.b_c)
+
+        #see if we can force it now..
+        tree.remove(TestRemove.b, keep_files=False, force=True)
+        self.assertNotInWorkingTree(TestRemove.b_c)
+        self.failIfExists(TestRemove.b_c)

=== modified file 'NEWS'
--- a/NEWS	2007-04-24 22:44:26 +0000
+++ b/NEWS	2007-04-25 06:50:22 +0000
@@ -1,5 +1,18 @@
 IN DEVELOPMENT
 
+  NOTES WHEN UPGRADING:
+
+    * ``bzr remove`` and ``bzr rm`` will now remove the working file, if
+      it could be recovered again.
+      This has been done for consistency with svn and the unix rm command.
+      The old ``remove`` behaviour has been retained in the new option
+      ``bzr remove --keep``, which will just stop versioning the file,
+      but not delete it.
+      ``bzr remove --force`` have been added which will always delete the
+      files.
+      ``bzr remove`` is also more verbose.
+      (Marius Kruger, #82602)
+
   IMPROVEMENTS:
 
     * Merge directives can now be supplied as input to `merge` and `pull`,
@@ -2088,7 +2101,6 @@
 
     * Simplify handling of DivergedBranches in cmd_pull().
       (Michael Ellerman)
-		   
    
     * Branch.controlfile* logic has moved to lockablefiles.LockableFiles, which
       is exposed as Branch().control_files. Also this has been altered with the

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-04-24 02:43:56 +0000
+++ b/bzrlib/builtins.py	2007-04-25 06:50:22 +0000
@@ -1074,10 +1074,10 @@
 
 
 class cmd_remove(Command):
-    """Make a file unversioned.
+    """Remove files or directories.
 
-    This makes bzr stop tracking changes to a versioned file.  It does
-    not delete the working copy.
+    This makes bzr stop tracking changes to the specified files and
+    delete them if they can easily be recovered using revert.
 
     You can specify one or more files, and/or --new.  If you specify --new,
     only 'added' files will be removed.  If you specify both, then new files
@@ -1085,23 +1085,38 @@
     also new, they will also be removed.
     """
     takes_args = ['file*']
-    takes_options = ['verbose', Option('new', help='remove newly-added files')]
+    takes_options = ['verbose',
+        Option('new', help='remove newly-added files'),
+        RegistryOption.from_kwargs('file-deletion-strategy',
+            'The file deletion mode to be used',
+            title='Deletion Strategy', value_switches=True, enum_switch=False,
+            safe='Only delete files if they can be'
+                 ' safely recovered (default).',
+            keep="Don't delete any files.",
+            force='Delete all the specified files, even if they can not be '
+                'recovered and even if they are non-empty directories.')]
     aliases = ['rm']
     encoding_type = 'replace'
-    
-    def run(self, file_list, verbose=False, new=False):
+
+    def run(self, file_list, verbose=False, new=False,
+        file_deletion_strategy='safe'):
         tree, file_list = tree_files(file_list)
-        if new is False:
-            if file_list is None:
-                raise errors.BzrCommandError('Specify one or more files to'
-                                             ' remove, or use --new.')
-        else:
+
+        if file_list is not None:
+            file_list = [f for f in file_list if f != '']
+        elif not new:
+            raise errors.BzrCommandError('Specify one or more files to'
+            ' remove, or use --new.')
+
+        if new:
             added = tree.changes_from(tree.basis_tree(),
                 specific_files=file_list).added
             file_list = sorted([f[0] for f in added], reverse=True)
             if len(file_list) == 0:
                 raise errors.BzrCommandError('No matching files.')
-        tree.remove(file_list, verbose=verbose, to_file=self.outf)
+        tree.remove(file_list, verbose=verbose, to_file=self.outf,
+            keep_files=file_deletion_strategy=='keep',
+            force=file_deletion_strategy=='force')
 
 
 class cmd_file_id(Command):

=== modified file 'bzrlib/delta.py'
--- a/bzrlib/delta.py	2007-03-07 23:15:10 +0000
+++ b/bzrlib/delta.py	2007-04-16 01:00:20 +0000
@@ -195,6 +195,12 @@
             print >>to_file, 'unknown:'
             show_list(self.unversioned)
 
+    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
+             short_status=False):
+        import StringIO
+        output = StringIO.StringIO()
+        self.show(output, show_ids, show_unchanged, short_status)
+        return output.getvalue()
 
 @deprecated_function(zero_nine)
 def compare_trees(old_tree, new_tree, want_unchanged=False,

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2007-04-23 06:55:58 +0000
+++ b/bzrlib/errors.py	2007-04-25 06:50:22 +0000
@@ -1633,6 +1633,18 @@
     def __init__(self, from_path, to_path, extra=None):
         BzrMoveFailedError.__init__(self, from_path, to_path, extra)
 
+class BzrRemoveChangedFilesError(BzrError):
+    """Used when user is trying to remove changed files."""
+
+    _fmt = ("Can't remove changed or unknown files:\n%(changes_as_text)s"
+        "Use --keep to not delete them, or --force to delete them regardless.")
+
+    def __init__(self, tree_delta):
+        BzrError.__init__(self)
+        self.changes_as_text = tree_delta.get_changes_as_text()
+        #self.paths_as_string = '\n'.join(changed_files)
+        #self.paths_as_string = '\n'.join([quotefn(p) for p in changed_files])
+
 
 class BzrBadParameterNotString(BzrBadParameter):
 

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2007-04-25 02:24:56 +0000
+++ b/bzrlib/tests/__init__.py	2007-04-25 06:50:22 +0000
@@ -36,7 +36,6 @@
 from pprint import pformat
 import random
 import re
-import shlex
 import stat
 from subprocess import Popen, PIPE
 import sys
@@ -54,6 +53,7 @@
     progress,
     ui,
     urlutils,
+    workingtree,
     )
 import bzrlib.branch
 import bzrlib.commands
@@ -1294,7 +1294,8 @@
         if err:
             self.log('errors:\n%r', err)
         if retcode is not None:
-            self.assertEquals(retcode, result)
+            self.assertEquals(retcode, result,
+                              message='Unexpected return code')
         return out, err
 
     def run_bzr(self, *args, **kwargs):
@@ -1315,8 +1316,15 @@
         encoding = kwargs.pop('encoding', None)
         stdin = kwargs.pop('stdin', None)
         working_dir = kwargs.pop('working_dir', None)
-        return self.run_bzr_captured(args, retcode=retcode, encoding=encoding,
-                                     stdin=stdin, working_dir=working_dir)
+        error_regexes = kwargs.pop('error_regexes', [])
+
+        out, err = self.run_bzr_captured(args, retcode=retcode,
+            encoding=encoding, stdin=stdin, working_dir=working_dir)
+
+        for regex in error_regexes:
+            self.assertContainsRe(err, regex)
+        return out, err
+
 
     def run_bzr_decode(self, *args, **kwargs):
         if 'encoding' in kwargs:
@@ -1349,9 +1357,7 @@
                                'commit', '--strict', '-m', 'my commit comment')
         """
         kwargs.setdefault('retcode', 3)
-        out, err = self.run_bzr(*args, **kwargs)
-        for regex in error_regexes:
-            self.assertContainsRe(err, regex)
+        out, err = self.run_bzr(error_regexes=error_regexes, *args, **kwargs)
         return out, err
 
     def run_bzr_subprocess(self, *args, **kwargs):
@@ -1953,12 +1959,41 @@
         self.assertEqualDiff(content, s)
 
     def failUnlessExists(self, path):
-        """Fail unless path, which may be abs or relative, exists."""
-        self.failUnless(osutils.lexists(path),path+" does not exist")
+        """Fail unless path or paths, which may be abs or relative, exist."""
+        if not isinstance(path, basestring):
+            for p in path:
+                self.failUnlessExists(p)
+        else:
+            self.failUnless(osutils.lexists(path),path+" does not exist")
 
     def failIfExists(self, path):
-        """Fail if path, which may be abs or relative, exists."""
-        self.failIf(osutils.lexists(path),path+" exists")
+        """Fail if path or paths, which may be abs or relative, exist."""
+        if not isinstance(path, basestring):
+            for p in path:
+                self.failIfExists(p)
+        else:
+            self.failIf(osutils.lexists(path),path+" exists")
+
+    def assertInWorkingTree(self,path,root_path='.',tree=None):
+        """Assert whether path or paths are in the WorkingTree"""
+        if tree is None:
+            tree = workingtree.WorkingTree.open(root_path)
+        if not isinstance(path, basestring):
+            for p in path:
+                self.assertInWorkingTree(p,tree=tree)
+        else:
+            self.assertIsNot(tree.path2id(path), None,
+                path+' not in working tree.')
+
+    def assertNotInWorkingTree(self,path,root_path='.',tree=None):
+        """Assert whether path or paths are not in the WorkingTree"""
+        if tree is None:
+            tree = workingtree.WorkingTree.open(root_path)
+        if not isinstance(path, basestring):
+            for p in path:
+                self.assertNotInWorkingTree(p,tree=tree)
+        else:
+            self.assertIs(tree.path2id(path), None, path+' in working tree.')
 
 
 class TestCaseWithTransport(TestCaseInTempDir):

=== modified file 'bzrlib/tests/blackbox/test_mv.py'
--- a/bzrlib/tests/blackbox/test_mv.py	2007-03-07 01:14:11 +0000
+++ b/bzrlib/tests/blackbox/test_mv.py	2007-03-23 18:29:56 +0000
@@ -31,15 +31,6 @@
 
 class TestMove(TestCaseWithTransport):
 
-    def assertInWorkingTree(self,path):
-        tree = workingtree.WorkingTree.open('.')
-        self.assertIsNot(tree.path2id(path), None,
-            path+' not in working tree.')
-
-    def assertNotInWorkingTree(self,path):
-        tree = workingtree.WorkingTree.open('.')
-        self.assertIs(tree.path2id(path), None, path+' in working tree.')
-
     def assertMoved(self,from_path,to_path):
         """Assert that to_path is existing and versioned but from_path not. """
         self.failIfExists(from_path)

=== modified file 'bzrlib/tests/blackbox/test_remove.py'
--- a/bzrlib/tests/blackbox/test_remove.py	2006-10-11 23:08:27 +0000
+++ b/bzrlib/tests/blackbox/test_remove.py	2007-04-20 07:15:26 +0000
@@ -15,48 +15,204 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
-import os
+import os, re, shlex
 
 from bzrlib.tests.blackbox import ExternalBase
 from bzrlib.workingtree import WorkingTree
+from bzrlib import osutils
+
+_id='-id'
+a='a'
+b='b/'
+c='b/c'
+d='d/'
+files=(a, b, c, d)
 
 
 class TestRemove(ExternalBase):
 
-    def test_remove_deleted(self):
-        self.runbzr("init")
-        self.build_tree(['a'])
-        self.runbzr(['add', 'a'])
-        self.runbzr(['commit', '-m', 'added a'])
-        os.unlink('a')
-        self.runbzr(['remove', 'a'])
-
-    def test_remove_new(self):
-        self.build_tree(['filefile',
-                         'dir/',
-                         'dir/filefilefile'])
-        wt = self.make_branch_and_tree('.')
-        wt.add(['filefile', 'dir', 'dir/filefilefile'], 
-               ['filefile-id', 'dir-id', 'filefilefile-id'])
-        self.assertEqual(wt.path2id('filefile'), 'filefile-id')
-        self.assertEqual(wt.path2id('dir/filefilefile'), 'filefilefile-id')
-        self.assertEqual(wt.path2id('dir'), 'dir-id')
-        self.runbzr('remove --new')
-        wt = WorkingTree.open('.')
-        self.assertIs(wt.path2id('filefile'), None)
-        self.assertIs(wt.path2id('dir/filefilefile'), None)
-        self.assertIs(wt.path2id('dir'), None)
-        wt.add(['filefile', 'dir', 'dir/filefilefile'], 
-               ['filefile-id', 'dir-id', 'filefilefile-id'])
-        self.assertEqual(wt.path2id('filefile'), 'filefile-id')
-        self.assertEqual(wt.path2id('dir/filefilefile'), 'filefilefile-id')
-        self.assertEqual(wt.path2id('dir'), 'dir-id')
-        self.runbzr('remove --new dir')
-        wt = WorkingTree.open('.')
-        self.assertEqual(wt.path2id('filefile'), 'filefile-id')
-        self.assertIs(wt.path2id('dir/filefilefile'), None)
-        self.assertIs(wt.path2id('dir'), None)
-        self.runbzr('remove --new .')
-        wt = WorkingTree.open('.')
-        self.assertIs(wt.path2id('filefile'), None)
-        self.runbzr('remove --new .', retcode=3)
+    def run_bzr_captured(self, argv, retcode=0, encoding=None, stdin=None,
+                         working_dir=None):
+
+        # magically convert commands like 'remove abc' to ['remove', 'abc']
+        if (isinstance(argv, tuple) and len(argv) == 1 and
+            isinstance(argv[0], basestring)):
+            argv = shlex.split(argv[0])
+        return ExternalBase.run_bzr_captured(self, argv, retcode, encoding,
+            stdin, working_dir)
+
+    def _make_add_and_assert_tree(self, files):
+        tree = self.make_branch_and_tree('.')
+        self.build_tree(files)
+        for f in files:
+            id=str(f).replace('/', '_') + _id
+            tree.add(f, id)
+            self.assertEqual(tree.path2id(f), id)
+            self.failUnlessExists(f)
+            self.assertInWorkingTree(f)
+        return tree
+
+    def assertFilesDeleted(self, files):
+        for f in files:
+            id=f+_id
+            self.assertNotInWorkingTree(f)
+            self.failIfExists(f)
+
+    def assertFilesUnversioned(self, files):
+        for f in files:
+            self.assertNotInWorkingTree(f)
+            self.failUnlessExists(f)
+
+    def changeFile(self, file_name):
+        f = file(file_name, 'ab')
+        f.write("\nsome other new content!")
+        f.close()
+
+    def run_bzr_remove_changed_files(self, error_regexes, files_to_remove):
+        error_regexes.extend(["Can't remove changed or unknown files:",
+            'Use --keep to not delete them,'
+            ' or --force to delete them regardless.'
+            ])
+        self.run_bzr_error(error_regexes,
+            'remove ' + ' '.join(files_to_remove))
+        #see if we can force it now
+        self.run_bzr('remove --force ' + ' '.join(files_to_remove))
+
+    def test_remove_no_files_specified(self):
+        tree = self._make_add_and_assert_tree([])
+        self.run_bzr_error(["bzr: ERROR: Specify one or more files to remove, "
+            "or use --new."], 'remove')
+
+        self.run_bzr_error(["bzr: ERROR: No matching files."], 'remove --new')
+
+        self.run_bzr_error(["bzr: ERROR: No matching files."],
+            'remove --new .')
+
+    def test_rm_one_file(self):
+        tree = self._make_add_and_assert_tree([a])
+        self.run_bzr("commit -m 'added a'")
+        self.run_bzr('rm a', error_regexes=["deleted a"])
+        self.assertFilesDeleted([a])
+
+    def test_remove_one_file(self):
+        tree = self._make_add_and_assert_tree([a])
+        self.run_bzr("commit -m 'added a'")
+        self.run_bzr('remove a', error_regexes=["deleted a"])
+        self.assertFilesDeleted([a])
+
+    def test_remove_keep_one_file(self):
+        tree = self._make_add_and_assert_tree([a])
+        self.run_bzr('remove --keep a', error_regexes=["removed a"])
+        self.assertFilesUnversioned([a])
+
+    def test_remove_one_deleted_file(self):
+        tree = self._make_add_and_assert_tree([a])
+        self.run_bzr("commit -m 'added a'")
+        os.unlink(a)
+        self.assertInWorkingTree(a)
+        self.run_bzr('remove a')
+        self.assertNotInWorkingTree(a)
+
+    def test_remove_invalid_files(self):
+        self.build_tree(files)
+        tree = self.make_branch_and_tree('.')
+        self.run_bzr_remove_changed_files(['unknown:[.\s]*xyz[.\s]*abc/def'],
+            ['.', 'xyz', 'abc/def'])
+
+    def test_remove_unversioned_files(self):
+        self.build_tree(files)
+        tree = self.make_branch_and_tree('.')
+        self.run_bzr_remove_changed_files(
+            ['unknown:[.\s]*d/[.\s]*b/c[.\s]*b/[.\s]*a'], files)
+
+    def test_remove_changed_files(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr("commit -m 'added files'")
+        self.changeFile(a)
+        self.changeFile(c)
+        self.run_bzr_remove_changed_files(['modified:[.\s]*a[.\s]*b/c'], files)
+
+    def test_remove_changed_files_from_child_dir(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr("commit -m 'added files'")
+        self.changeFile(a)
+        self.changeFile(c)
+        os.chdir('b')
+        self.run_bzr_remove_changed_files(['modified:[.\s]*a[.\s]*b/c'],
+            ['../a', 'c', '.', '../d'])
+        os.chdir('..')
+        self.assertNotInWorkingTree(files)
+        self.failIfExists(files)
+
+    def test_remove_keep_unversioned_files(self):
+        self.build_tree(files)
+        tree = self.make_branch_and_tree('.')
+        self.run_bzr('remove --keep a', error_regexes=["a is not versioned."])
+        self.assertFilesUnversioned(files)
+
+    def test_remove_force_unversioned_files(self):
+        self.build_tree(files)
+        tree = self.make_branch_and_tree('.')
+        self.run_bzr('remove --force ' + ' '.join(files),
+                     error_regexes=["deleted a", "deleted b",
+                                    "deleted b/c", "deleted d"])
+        self.assertFilesDeleted(files)
+
+    def test_remove_deleted_files(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr("commit -m 'added files'")
+        my_files=[f for f in files]
+        my_files.sort(reverse=True)
+        for f in my_files:
+            osutils.delete_any(f)
+        self.assertInWorkingTree(files)
+        self.failIfExists(files)
+        self.run_bzr('remove ' + ' '.join(files))
+        self.assertNotInWorkingTree(a)
+        self.failIfExists(files)
+
+    def test_remove_non_existing_files(self):
+        tree = self._make_add_and_assert_tree([])
+        self.run_bzr_remove_changed_files(['unknown:[.\s]*b'], ['b'])
+
+    def test_remove_keep_non_existing_files(self):
+        tree = self._make_add_and_assert_tree([])
+        self.run_bzr('remove --keep b', error_regexes=["b is not versioned."])
+
+    def test_remove_files(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr("commit -m 'added files'")
+        self.run_bzr('remove a b b/c d',
+                     error_regexes=["deleted a", "deleted b", "deleted b/c",
+                     "deleted d"])
+        self.assertFilesDeleted(files)
+
+    def test_remove_keep_files(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr("commit -m 'added files'")
+        self.run_bzr('remove --keep a b b/c d',
+                     error_regexes=["removed a", "removed b", "removed b/c",
+                     "removed d"])
+        self.assertFilesUnversioned(files)
+
+    def test_remove_with_new(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr('remove --new --keep',
+                     error_regexes=["removed a", "removed b", "removed b/c"])
+        self.assertFilesUnversioned(files)
+
+    def test_remove_with_new_in_dir1(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr('remove --new --keep b b/c',
+                     error_regexes=["removed b", "removed b/c"])
+        tree = WorkingTree.open('.')
+        self.assertInWorkingTree(a)
+        self.assertEqual(tree.path2id(a), a + _id)
+        self.assertFilesUnversioned([b,c])
+
+    def test_remove_with_new_in_dir2(self):
+        tree = self._make_add_and_assert_tree(files)
+        self.run_bzr('remove --new --keep .',
+                     error_regexes=["removed a", "removed b", "removed b/c"])
+        tree = WorkingTree.open('.')
+        self.assertFilesUnversioned(files)

=== modified file 'bzrlib/tests/blackbox/test_selftest.py'
--- a/bzrlib/tests/blackbox/test_selftest.py	2007-04-13 00:30:33 +0000
+++ b/bzrlib/tests/blackbox/test_selftest.py	2007-04-25 06:50:22 +0000
@@ -103,6 +103,7 @@
         self.encoding = encoding
         self.stdin = stdin
         self.working_dir = working_dir
+        return '', ''
 
     def test_args(self):
         """Test that run_bzr passes args correctly to run_bzr_captured"""

=== modified file 'bzrlib/tests/blackbox/test_too_much.py'
--- a/bzrlib/tests/blackbox/test_too_much.py	2007-04-23 01:41:05 +0000
+++ b/bzrlib/tests/blackbox/test_too_much.py	2007-04-25 06:50:22 +0000
@@ -522,13 +522,13 @@
             self.assertEquals(self.capture("relpath d2/link1"), "d2/link1\n")
             runbzr(['commit', '-m', '4: retarget of two links'])
     
-            runbzr('remove d2/link1')
+            runbzr('remove --keep d2/link1')
             self.assertEquals(self.capture('unknowns'), 'd2/link1\n')
             runbzr(['commit', '-m', '5: remove d2/link1'])
             # try with the rm alias
             runbzr('add d2/link1')
             runbzr(['commit', '-m', '6: add d2/link1'])
-            runbzr('rm d2/link1')
+            runbzr('rm --keep d2/link1')
             self.assertEquals(self.capture('unknowns'), 'd2/link1\n')
             runbzr(['commit', '-m', '7: remove d2/link1'])
     

=== modified file 'bzrlib/tests/workingtree_implementations/__init__.py'
--- a/bzrlib/tests/workingtree_implementations/__init__.py	2007-04-11 22:14:59 +0000
+++ b/bzrlib/tests/workingtree_implementations/__init__.py	2007-04-18 20:12:15 +0000
@@ -74,6 +74,7 @@
         'bzrlib.tests.workingtree_implementations.test_put_file',
         'bzrlib.tests.workingtree_implementations.test_readonly',
         'bzrlib.tests.workingtree_implementations.test_read_working_inventory',
+        'bzrlib.tests.workingtree_implementations.test_remove',
         'bzrlib.tests.workingtree_implementations.test_rename_one',
         'bzrlib.tests.workingtree_implementations.test_revision_tree',
         'bzrlib.tests.workingtree_implementations.test_set_root_id',

=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py	2007-04-17 12:20:24 +0000
+++ b/bzrlib/workingtree.py	2007-04-25 06:50:22 +0000
@@ -367,7 +367,7 @@
 
     def abspath(self, filename):
         return pathjoin(self.basedir, filename)
-    
+
     def basis_tree(self):
         """Return RevisionTree for the current last revision.
         
@@ -1767,43 +1767,97 @@
         return result
 
     @needs_tree_write_lock
-    def remove(self, files, verbose=False, to_file=None):
-        """Remove nominated files from the working inventory..
-
-        This does not remove their text.  This does not run on XXX on what? RBC
-
-        TODO: Refuse to remove modified files unless --force is given?
-
-        TODO: Do something useful with directories.
-
-        TODO: Should this remove the text or not?  Tough call; not
-        removing may be useful and the user can just use use rm, and
-        is the opposite of add.  Removing it is consistent with most
-        other tools.  Maybe an option.
+    def remove(self, files, verbose=False, to_file=None, keep_files=True,
+        force=False):
+        """Remove nominated files from the working inventor.
+
+        :files: File paths relative to the basedir.
+        :keep_files: If true, the files will also be kept.
+        :force: Delete files and directories, even if they are changed and
+            even if the directories are not empty.
         """
         ## TODO: Normalize names
-        ## TODO: Remove nested loops; better scalability
+
         if isinstance(files, basestring):
             files = [files]
 
         inv = self.inventory
 
+        new_files=set()
+        unknown_files_in_directory=set()
+
+        def recurse_directory_to_add_files(directory):
+            # recurse directory and add all files
+            # so we can check if they have changed.
+            for contained_dir_info in self.walkdirs(directory):
+                for file_info in contained_dir_info[1]:
+                    if file_info[2] == 'file':
+                        relpath = self.relpath(file_info[0])
+                        if file_info[4]: #is it versioned?
+                            new_files.add(relpath)
+                        else:
+                            unknown_files_in_directory.add(
+                                (relpath, None, file_info[2]))
+
+        for filename in files:
+            # Get file name into canonical form.
+            filename = self.relpath(self.abspath(filename))
+            if len(filename) > 0:
+                new_files.add(filename)
+                if osutils.isdir(filename) and len(os.listdir(filename)) > 0:
+                    recurse_directory_to_add_files(filename)
+        files = [f for f in new_files]
+
+        # Sort needed to first handle directory content before the directory
+        files.sort(reverse=True)
+        if not keep_files and not force:
+            tree_delta = self.changes_from(self.basis_tree(),
+                specific_files=files)
+            for unknown_file in unknown_files_in_directory:
+                tree_delta.unversioned.extend((unknown_file,))
+            if bool(tree_delta.modified
+                    or tree_delta.added
+                    or tree_delta.renamed
+                    or tree_delta.kind_changed
+                    or tree_delta.unversioned):
+                raise errors.BzrRemoveChangedFilesError(tree_delta)
+
         # do this before any modifications
         for f in files:
             fid = inv.path2id(f)
+            message=None
             if not fid:
-                note("%s is not versioned."%f)
+                message="%s is not versioned." % (f,)
             else:
                 if verbose:
-                    # having remove it, it must be either ignored or unknown
+                    # having removed it, it must be either ignored or unknown
                     if self.is_ignored(f):
                         new_status = 'I'
                     else:
                         new_status = '?'
                     textui.show_status(new_status, inv[fid].kind, f,
                                        to_file=to_file)
+                # unversion file
                 del inv[fid]
-
+                message="removed %s" % (f,)
+
+            if not keep_files:
+                abs_path = self.abspath(f)
+                if osutils.lexists(abs_path):
+                    if (osutils.isdir(abs_path) and
+                        len(os.listdir(abs_path)) > 0):
+                        message="%s is not empty directory "\
+                            "and won't be deleted." % (f,)
+                    else:
+                        osutils.delete_any(abs_path)
+                        message="deleted %s" % (f,)
+                elif message is not None:
+                    # only care if we haven't done anything yet.
+                    message="%s does not exist." % (f,)
+
+            # print only one message (if any) per file.
+            if message is not None:
+                note(message)
         self._write_inventory(inv)
 
     @needs_tree_write_lock
@@ -2106,11 +2160,16 @@
     def walkdirs(self, prefix=""):
         """Walk the directories of this tree.
 
+        returns a generator which yields items in the form:
+                ((curren_directory_path, fileid),
+                 [(file1_path, file1_name, file1_kind, (lstat), file1_id,
+                   file1_kind), ... ])
+
         This API returns a generator, which is only valid during the current
         tree transaction - within a single lock_read or lock_write duration.
 
-        If the tree is not locked, it may cause an error to be raised, depending
-        on the tree implementation.
+        If the tree is not locked, it may cause an error to be raised,
+        depending on the tree implementation.
         """
         disk_top = self.abspath(prefix)
         if disk_top.endswith('/'):
@@ -2208,6 +2267,14 @@
                     disk_finished = True
 
     def _walkdirs(self, prefix=""):
+        """Walk the directories of this tree.
+
+           :prefix: is used as the directrory to start with.
+           returns a generator which yields items in the form:
+                ((curren_directory_path, fileid),
+                 [(file1_path, file1_name, file1_kind, None, file1_id,
+                   file1_kind), ... ])
+        """
         _directory = 'directory'
         # get the root in the inventory
         inv = self.inventory




More information about the bazaar-commits mailing list