Rev 3637: (robertc) Improve bzr rm to detect missing files and have an alias to in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Aug 15 06:40:17 BST 2008


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

------------------------------------------------------------
revno: 3637
revision-id: pqm at pqm.ubuntu.com-20080815054008-1xrwm20d1wv9fyt7
parent: pqm at pqm.ubuntu.com-20080815025810-eguaiqf0kwwqo4yp
parent: robertc at robertcollins.net-20080815041247-xycggdgkzkkeide4
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2008-08-15 06:40:08 +0100
message:
  (robertc) Improve bzr rm to detect missing files and have an alias to
  	del. (Robert Collins)
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/tests/blackbox/test_remove.py test_remove.py-20060530011439-fika5rm84lon0goe-1
    ------------------------------------------------------------
    revno: 3619.5.4
    revision-id: robertc at robertcollins.net-20080815041247-xycggdgkzkkeide4
    parent: robertc at robertcollins.net-20080815024542-leg2xenbvo883tcc
    parent: pqm at pqm.ubuntu.com-20080815025810-eguaiqf0kwwqo4yp
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: integration
    timestamp: Fri 2008-08-15 14:12:47 +1000
    message:
      Resolve NEWS conflicts.
    added:
      doc/en/developer-guide/testing.txt testing.txt-20080812140359-i70zzh6v2z7grqex-1
    modified:
      Makefile                       Makefile-20050805140406-d96e3498bb61c5bb
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/__init__.py             __init__.py-20050309040759-33e65acf91bbcd5d
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/diff.py                 diff.py-20050309040759-26944fbbf2ebbf36
      bzrlib/help_topics/en/rules.txt rules.txt-20080516063844-ghr5l6pvvrhiycun-1
      bzrlib/index.py                index.py-20070712131115-lolkarso50vjr64s-1
      bzrlib/osutils.py              osutils.py-20050309040759-eeaff12fbf77ac86
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
      bzrlib/repofmt/pack_repo.py    pack_repo.py-20070813041115-gjv5ma7ktfqwsjgn-1
      bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
      bzrlib/smart/medium.py         medium.py-20061103051856-rgu2huy59fkz902q-1
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/blackbox/test_init.py test_init.py-20060309032856-a292116204d86eb7
      bzrlib/tests/blackbox/test_shared_repository.py test_shared_repository.py-20060317053531-ed30c0d79325e483
      bzrlib/tests/commands/test_init.py test_init.py-20070514074921-audbcdd8o56dpame-1
      bzrlib/tests/commands/test_init_repository.py test_init_repository-20070525163812-87xw0678ky573l27-1
      bzrlib/tests/http_utils.py     HTTPTestUtil.py-20050914180604-247d3aafb7a43343
      bzrlib/tests/interrepository_implementations/test_fetch.py test_fetch.py-20080425213627-j60cjh782ufm83ry-1
      bzrlib/tests/intertree_implementations/test_compare.py test_compare.py-20060724101752-09ysswo1a92uqyoz-2
      bzrlib/tests/test_bzrdir.py    test_bzrdir.py-20060131065654-deba40eef51cf220
      bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
      bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
      bzrlib/tests/test_plugins.py   plugins.py-20050622075746-32002b55e5e943e9
      bzrlib/tests/test_source.py    test_source.py-20051207061333-a58dea6abecc030d
      bzrlib/tests/test_transform.py test_transaction.py-20060105172520-b3ffb3946550e6c4
      bzrlib/tests/tree_implementations/test_iter_search_rules.py test_iter_search_rul-20080528065532-1ml1ttb12az20cxf-1
      bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
      bzrlib/transport/http/__init__.py http_transport.py-20050711212304-506c5fd1059ace96
      bzrlib/transport/local.py      local_transport.py-20050711165921-9b1f142bfe480c24
      bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
      doc/developers/HACKING.txt     HACKING-20050805200004-2a5dc975d870f78c
      doc/en/user-guide/configuring_bazaar.txt configuring_bazaar.t-20071128000722-ncxiua259xwbdbg7-1
      setup.py                       setup.py-20050314065409-02f8a0a6e3f9bc70
      tools/win32/bzr.iss.cog        bzr.iss.cog-20060622100836-b3yup582rt3y0nvm-5
    ------------------------------------------------------------
    revno: 3619.5.3
    revision-id: robertc at robertcollins.net-20080815024542-leg2xenbvo883tcc
    parent: robertc at robertcollins.net-20080813040249-dijrxmf7fzsolt6p
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: rm
    timestamp: Fri 2008-08-15 12:45:42 +1000
    message:
      Review feedback.
    modified:
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/tests/blackbox/test_remove.py test_remove.py-20060530011439-fika5rm84lon0goe-1
    ------------------------------------------------------------
    revno: 3619.5.2
    revision-id: robertc at robertcollins.net-20080813040249-dijrxmf7fzsolt6p
    parent: robertc at robertcollins.net-20080813035223-i9sxgq2rp5477oc5
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: rm
    timestamp: Wed 2008-08-13 14:02:49 +1000
    message:
       * ``bzr rm`` is now aliased to ``bzr del`` for the convenience of svn
         users. (Robert Collins, #205416)
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
    ------------------------------------------------------------
    revno: 3619.5.1
    revision-id: robertc at robertcollins.net-20080813035223-i9sxgq2rp5477oc5
    parent: robertc at robertcollins.net-20080813032119-09pl9q0t9gxng5t6
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: rm
    timestamp: Wed 2008-08-13 13:52:23 +1000
    message:
       * ``bzr rm`` will now scan for files that are missing and remove just
         them automatically, much as ``bzr add`` scans for new files that
         are not ignored and adds them automatically. (Robert Collins)
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/tests/blackbox/test_remove.py test_remove.py-20060530011439-fika5rm84lon0goe-1
=== modified file 'NEWS'
--- a/NEWS	2008-08-14 20:42:22 +0000
+++ b/NEWS	2008-08-15 04:12:47 +0000
@@ -12,6 +12,10 @@
     * ``bzr export`` can now export a subdirectory of a project.
       (Robert Collins)
 
+    * ``bzr rm`` will now scan for files that are missing and remove just
+      them automatically, much as ``bzr add`` scans for new files that
+      are not ignored and adds them automatically. (Robert Collins)
+
   IMPROVEMENTS:
 
     * ``bzr init`` and ``bzr init-repo`` will now print out the same as
@@ -20,6 +24,9 @@
 
   BUG FIXES:
 
+    * ``bzr rm`` is now aliased to ``bzr del`` for the convenience of svn
+      users. (Robert Collins, #205416)
+
     * ``WorkingTree4`` trees will now correctly report missing-and-new
       paths in the output of ``iter_changes``. (Robert Collins)
 

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2008-08-14 17:08:56 +0000
+++ b/bzrlib/builtins.py	2008-08-15 04:12:47 +0000
@@ -1066,17 +1066,14 @@
 class cmd_remove(Command):
     """Remove files or directories.
 
-    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
-    in the specified directories will be removed.  If the directories are
-    also new, they will also be removed.
+    This makes bzr stop tracking changes to the specified files. bzr will delete
+    them if they can easily be recovered using revert. If no options or
+    parameters are given bzr will scan for files that are being tracked by bzr
+    but missing in your tree and stop tracking them for you.
     """
     takes_args = ['file*']
     takes_options = ['verbose',
-        Option('new', help='Remove newly-added files.'),
+        Option('new', help='Only remove files that have never been committed.'),
         RegistryOption.from_kwargs('file-deletion-strategy',
             'The file deletion mode to be used.',
             title='Deletion Strategy', value_switches=True, enum_switch=False,
@@ -1085,7 +1082,7 @@
             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']
+    aliases = ['rm', 'del']
     encoding_type = 'replace'
 
     def run(self, file_list, verbose=False, new=False,
@@ -1094,19 +1091,32 @@
 
         if file_list is not None:
             file_list = [f for f in file_list]
-        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,
-            keep_files=file_deletion_strategy=='keep',
-            force=file_deletion_strategy=='force')
+        tree.lock_write()
+        try:
+            # Heuristics should probably all move into tree.remove_smart or
+            # some such?
+            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.')
+            elif file_list is None:
+                # missing files show up in iter_changes(basis) as
+                # versioned-with-no-kind.
+                missing = []
+                for change in tree.iter_changes(tree.basis_tree()):
+                    # Find paths in the working tree that have no kind:
+                    if change[1][1] is not None and change[6][1] is None:
+                        missing.append(change[1][1])
+                file_list = sorted(missing, reverse=True)
+                file_deletion_strategy = 'keep'
+            tree.remove(file_list, verbose=verbose, to_file=self.outf,
+                keep_files=file_deletion_strategy=='keep',
+                force=file_deletion_strategy=='force')
+        finally:
+            tree.unlock()
 
 
 class cmd_file_id(Command):

=== modified file 'bzrlib/tests/blackbox/test_remove.py'
--- a/bzrlib/tests/blackbox/test_remove.py	2007-11-28 17:02:05 +0000
+++ b/bzrlib/tests/blackbox/test_remove.py	2008-08-15 02:45:42 +0000
@@ -18,7 +18,7 @@
 import os
 import sys
 
-from bzrlib.tests import TestSkipped
+from bzrlib.tests import SymlinkFeature, TestSkipped
 from bzrlib.tests.blackbox import ExternalBase
 from bzrlib.workingtree import WorkingTree
 from bzrlib import osutils
@@ -33,15 +33,16 @@
 
 class TestRemove(ExternalBase):
 
-    def _make_add_and_assert_tree(self, files):
+    def _make_tree_and_add(self, paths):
         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)
+        tree.lock_write()
+        try:
+            self.build_tree(paths)
+            for path in paths:
+                file_id=str(path).replace('/', '_') + _id
+                tree.add(path, file_id)
+        finally:
+            tree.unlock()
         return tree
 
     def assertFilesDeleted(self, files):
@@ -70,35 +71,95 @@
         #see if we can force it now
         self.run_bzr(['remove', '--force'] + list(files_to_remove))
 
+    def test_remove_new_no_files_specified(self):
+        tree = self.make_branch_and_tree('.')
+        self.run_bzr_error(["bzr: ERROR: No matching files."], 'remove --new')
+        self.run_bzr_error(["bzr: ERROR: No matching files."], 'remove --new .')
+
     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 .')
+        tree = self._make_tree_and_add(['foo'])
+        out, err = self.run_bzr(['rm'])
+        self.assertEqual('', err)
+        self.assertEqual('', out)
+        self.assertInWorkingTree('foo', tree=tree)
+        self.failUnlessExists('foo')
+
+    def test_remove_no_files_specified_missing_dir_and_contents(self):
+        tree = self._make_tree_and_add(
+            ['foo', 'dir/', 'dir/missing/', 'dir/missing/child'])
+        self.get_transport('.').delete_tree('dir/missing')
+        out, err = self.run_bzr(['rm'])
+        self.assertEqual('', out)
+        self.assertEqual(
+            'removed dir/missing/child\n'
+            'removed dir/missing\n',
+            err)
+        # non-missing paths not touched:
+        self.assertInWorkingTree('foo', tree=tree)
+        self.failUnlessExists('foo')
+        self.assertInWorkingTree('dir', tree=tree)
+        self.failUnlessExists('dir')
+        # missing files unversioned
+        self.assertNotInWorkingTree('dir/missing', tree=tree)
+        self.assertNotInWorkingTree('dir/missing/child', tree=tree)
+
+    def test_remove_no_files_specified_already_deleted(self):
+        tree = self._make_tree_and_add(['foo', 'bar'])
+        tree.commit('save foo and bar')
+        os.unlink('bar')
+        self.run_bzr(['rm'])
+        self.assertEqual(None, tree.path2id('bar'))
+        # Running rm with a deleted file does not error.
+        out, err = self.run_bzr(['rm'])
+        self.assertEqual('', out)
+        self.assertEqual('', err)
+
+    def test_remove_no_files_specified_missing_file(self):
+        tree = self._make_tree_and_add(['foo', 'bar'])
+        os.unlink('bar')
+        out, err = self.run_bzr(['rm'])
+        self.assertEqual('', out)
+        self.assertEqual('removed bar\n', err)
+        # non-missing files not touched:
+        self.assertInWorkingTree('foo', tree=tree)
+        self.failUnlessExists('foo')
+        # missing files unversioned
+        self.assertNotInWorkingTree('bar', tree=tree)
+
+    def test_remove_no_files_specified_missing_link(self):
+        self.requireFeature(SymlinkFeature)
+        tree = self._make_tree_and_add(['foo'])
+        os.symlink('foo', 'linkname')
+        tree.add(['linkname'])
+        os.unlink('linkname')
+        out, err = self.run_bzr(['rm'])
+        self.assertEqual('', out)
+        self.assertEqual('removed linkname\n', err)
+        # non-missing files not touched:
+        self.assertInWorkingTree('foo', tree=tree)
+        self.failUnlessExists('foo')
+        # missing files unversioned
+        self.assertNotInWorkingTree('linkname', tree=tree)
 
     def test_rm_one_file(self):
-        tree = self._make_add_and_assert_tree([a])
+        tree = self._make_tree_and_add([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])
+        tree = self._make_tree_and_add([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])
+        tree = self._make_tree_and_add([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])
+        tree = self._make_tree_and_add([a])
         self.run_bzr("commit -m 'added a'")
         os.unlink(a)
         self.assertInWorkingTree(a)
@@ -117,21 +178,21 @@
             ['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)
+        tree = self._make_tree_and_add(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_ignored_files(self):
-        tree = self._make_add_and_assert_tree(['a'])
+        tree = self._make_tree_and_add(['a'])
         self.run_bzr(['ignore', 'a'])
         self.run_bzr_remove_changed_files(['added:[.\s]*a'], ['a'])
 
     def test_remove_changed_files_from_child_dir(self):
         if sys.platform == 'win32':
             raise TestSkipped("Windows unable to remove '.' directory")
-        tree = self._make_add_and_assert_tree(files)
+        tree = self._make_tree_and_add(files)
         self.run_bzr("commit -m 'added files'")
         self.changeFile(a)
         self.changeFile(c)
@@ -157,7 +218,7 @@
         self.assertFilesDeleted(files)
 
     def test_remove_deleted_files(self):
-        tree = self._make_add_and_assert_tree(files)
+        tree = self._make_tree_and_add(files)
         self.run_bzr("commit -m 'added files'")
         my_files=[f for f in files]
         my_files.sort(reverse=True)
@@ -170,15 +231,15 @@
         self.failIfExists(files)
 
     def test_remove_non_existing_files(self):
-        tree = self._make_add_and_assert_tree([])
+        tree = self._make_tree_and_add([])
         self.run_bzr(['remove', 'b'])
 
     def test_remove_keep_non_existing_files(self):
-        tree = self._make_add_and_assert_tree([])
+        tree = self._make_tree_and_add([])
         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)
+        tree = self._make_tree_and_add(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",
@@ -186,7 +247,7 @@
         self.assertFilesDeleted(files)
 
     def test_remove_keep_files(self):
-        tree = self._make_add_and_assert_tree(files)
+        tree = self._make_tree_and_add(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",
@@ -194,13 +255,13 @@
         self.assertFilesUnversioned(files)
 
     def test_remove_with_new(self):
-        tree = self._make_add_and_assert_tree(files)
+        tree = self._make_tree_and_add(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)
+        tree = self._make_tree_and_add(files)
         self.run_bzr('remove --new --keep b b/c',
                      error_regexes=["removed b", "removed b/c"])
         tree = WorkingTree.open('.')
@@ -209,7 +270,7 @@
         self.assertFilesUnversioned([b,c])
 
     def test_remove_with_new_in_dir2(self):
-        tree = self._make_add_and_assert_tree(files)
+        tree = self._make_tree_and_add(files)
         self.run_bzr('remove --new --keep .',
                      error_regexes=["removed a", "removed b", "removed b/c"])
         tree = WorkingTree.open('.')




More information about the bazaar-commits mailing list