Rev 5385: (vila) `bzr remove` now just backs up changed files instead of in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Aug 20 12:10:12 BST 2010


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

------------------------------------------------------------
revno: 5385 [merge]
revision-id: pqm at pqm.ubuntu.com-20100820111010-xwracendg19hytgt
parent: pqm at pqm.ubuntu.com-20100820080653-klig2pem60bl1hz6
parent: v.ladeuil+lp at free.fr-20100820095225-wijo4emfgvtmu2ku
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2010-08-20 12:10:10 +0100
message:
  (vila) `bzr remove` now just backs up changed files instead of
  	annoying you (Marius Kruger)
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/bzrdir.py               bzrdir.py-20060131065624-156dfea39c4387cb
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/tests/blackbox/test_remove.py test_remove.py-20060530011439-fika5rm84lon0goe-1
  bzrlib/tests/per_workingtree/test_remove.py test_remove.py-20070413183901-rvnp85rtc0q0sclp-1
  bzrlib/tests/test_bzrdir.py    test_bzrdir.py-20060131065654-deba40eef51cf220
  bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
=== modified file 'NEWS'
--- a/NEWS	2010-08-20 06:49:00 +0000
+++ b/NEWS	2010-08-20 09:39:20 +0000
@@ -94,6 +94,12 @@
 Improvements
 ************
 
+* ``bzr remove`` now just backs up changed files instead of exiting,
+  forcing you to choose to either keep or delete them. Bazaar will now delete
+  the files if they can easily be recovered using revert, otherwise they
+  will be backed up (adding an extention of the form .~#~).
+  (Marius Kruger, #400554)
+
 * Inventory entries now consume less memory (on 32-bit Ubuntu file entries
   have dropped from 68 bytes to 40, and directory entries from 120 bytes
   to 48).  (Andrew Bennetts)

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2010-08-20 06:49:00 +0000
+++ b/bzrlib/builtins.py	2010-08-20 09:39:20 +0000
@@ -1477,10 +1477,11 @@
 class cmd_remove(Command):
     __doc__ = """Remove files or directories.
 
-    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.
+    This makes Bazaar stop tracking changes to the specified files. Bazaar will
+    delete them if they can easily be recovered using revert otherwise they
+    will be backed up (adding an extention of the form .~#~). If no options or
+    parameters are given Bazaar will scan for files that are being tracked by
+    Bazaar but missing in your tree and stop tracking them for you.
     """
     takes_args = ['file*']
     takes_options = ['verbose',
@@ -1488,8 +1489,7 @@
         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).',
+            safe='Backup changed files (default).',
             keep='Delete from bzr but leave the working copy.',
             force='Delete all the specified files, even if they can not be '
                 'recovered and even if they are non-empty directories.')]

=== modified file 'bzrlib/bzrdir.py'
--- a/bzrlib/bzrdir.py	2010-08-05 04:21:03 +0000
+++ b/bzrlib/bzrdir.py	2010-08-15 04:52:51 +0000
@@ -615,20 +615,22 @@
         """
         raise NotImplementedError(self.create_workingtree)
 
+    def generate_backup_name(self, base):
+        """Generate a non-existing backup file name based on base."""
+        counter = 1
+        name = "%s.~%d~" % (base, counter)
+        while self.root_transport.has(name):
+            counter += 1
+            name = "%s.~%d~" % (base, counter)
+        return name
+
     def backup_bzrdir(self):
         """Backup this bzr control directory.
 
         :return: Tuple with old path name and new path name
         """
-        def name_gen(base='backup.bzr'):
-            counter = 1
-            name = "%s.~%d~" % (base, counter)
-            while self.root_transport.has(name):
-                counter += 1
-                name = "%s.~%d~" % (base, counter)
-            return name
 
-        backup_dir=name_gen()
+        backup_dir=self.generate_backup_name('backup.bzr')
         pb = ui.ui_factory.nested_progress_bar()
         try:
             # FIXME: bug 300001 -- the backup fails if the backup directory

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2010-08-06 00:05:45 +0000
+++ b/bzrlib/errors.py	2010-08-20 09:52:25 +0000
@@ -1984,6 +1984,8 @@
         "Use --keep to not delete them, or --force to delete them regardless.")
 
     def __init__(self, tree_delta):
+        symbol_versioning.warn(symbol_versioning.deprecated_in((2, 3, 0)) %
+            "BzrRemoveChangedFilesError", DeprecationWarning, stacklevel=2)
         BzrError.__init__(self)
         self.changes_as_text = tree_delta.get_changes_as_text()
         #self.paths_as_string = '\n'.join(changed_files)

=== modified file 'bzrlib/tests/blackbox/test_remove.py'
--- a/bzrlib/tests/blackbox/test_remove.py	2010-06-23 08:19:28 +0000
+++ b/bzrlib/tests/blackbox/test_remove.py	2010-07-09 23:09:39 +0000
@@ -61,18 +61,9 @@
         f.write("\nsome other new content!")
         f.close()
 
-    def run_bzr_remove_changed_files(self, error_regexes, files_to_remove,
-                                     working_dir=None):
-        error_regexes.extend(["Can't safely remove modified or unknown files:",
-            'Use --keep to not delete them,'
-            ' or --force to delete them regardless.'
-            ])
-        self.run_bzr_error(error_regexes,
-                           ['remove'] + list(files_to_remove),
-                           working_dir=working_dir)
-        #see if we can force it now
-        self.run_bzr(['remove', '--force'] + list(files_to_remove),
-                     working_dir=working_dir)
+    def run_bzr_remove_changed_files(self, files_to_remove, working_dir=None):
+        self.run_bzr(['remove'] + list(files_to_remove),
+           working_dir=working_dir)
 
     def test_remove_new_no_files_specified(self):
         tree = self.make_branch_and_tree('.')
@@ -177,20 +168,19 @@
     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)
+        self.run_bzr_remove_changed_files(files)
 
     def test_remove_changed_files(self):
         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)
+        self.run_bzr_remove_changed_files(files)
 
     def test_remove_changed_ignored_files(self):
         tree = self._make_tree_and_add(['a'])
         self.run_bzr(['ignore', 'a'])
-        self.run_bzr_remove_changed_files(['added:[.\s]*a'], ['a'])
+        self.run_bzr_remove_changed_files(['a'])
 
     def test_remove_changed_files_from_child_dir(self):
         if sys.platform == 'win32':
@@ -200,7 +190,6 @@
         self.changeFile(a)
         self.changeFile(c)
         self.run_bzr_remove_changed_files(
-            ['modified:[.\s]*a[.\s]*b/c'],
             ['../a', 'c', '.', '../d'], working_dir='b')
         self.assertNotInWorkingTree(files)
         self.failIfExists(files)
@@ -216,7 +205,7 @@
         tree = self.make_branch_and_tree('.')
         self.run_bzr(['remove', '--force'] + list(files),
                      error_regexes=["deleted a", "deleted b",
-                                    "deleted b/c", "deleted d"])
+                                    "removed b/c", "deleted d"])
         self.assertFilesDeleted(files)
 
     def test_remove_deleted_files(self):

=== modified file 'bzrlib/tests/per_workingtree/test_remove.py'
--- a/bzrlib/tests/per_workingtree/test_remove.py	2010-04-30 10:28:36 +0000
+++ b/bzrlib/tests/per_workingtree/test_remove.py	2010-08-20 09:39:20 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, 2007, 2008 Canonical Ltd
+# Copyright (C) 2007-2010 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
@@ -24,6 +24,7 @@
 
     files = ['a', 'b/', 'b/c', 'd/']
     rfiles = ['b/c', 'b', 'a', 'd']
+    backup_files = ['a.~1~', 'b.~1~/', 'b.~1~/c.~1~', 'd.~1~/']
 
     def get_tree(self, files):
         tree = self.make_branch_and_tree('.')
@@ -72,28 +73,23 @@
         tree._validate()
 
     def test_remove_added_files(self):
-        """Removal of newly added files must fail."""
+        """Removal of newly added files must back them up."""
         tree = self.get_tree(TestRemove.files)
         tree.add(TestRemove.files)
         self.assertInWorkingTree(TestRemove.files)
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            TestRemove.files, keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)added:.*a.*b/.*b/c.*d/')
-        self.assertInWorkingTree(TestRemove.files)
-        self.failUnlessExists(TestRemove.files)
+        tree.remove(TestRemove.files, keep_files=False)
+        self.assertNotInWorkingTree(TestRemove.files)
+        self.failUnlessExists(TestRemove.backup_files)
         tree._validate()
 
     def test_remove_changed_file(self):
-        """Removal of a changed files must fail."""
+        """Removal of changed files must back it up."""
         tree = self.get_committed_tree(['a'])
         self.build_tree_contents([('a', "some other new content!")])
         self.assertInWorkingTree('a')
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            'a', keep_files=False)
-        self.assertContainsRe(err.changes_as_text, '(?s)modified:.*a')
-        self.assertInWorkingTree('a')
-        self.failUnlessExists('a')
+        tree.remove('a', keep_files=False)
+        self.assertNotInWorkingTree(TestRemove.files)
+        self.failUnlessExists('a.~1~')
         tree._validate()
 
     def test_remove_deleted_files(self):
@@ -122,7 +118,7 @@
         tree._validate()
 
     def test_remove_renamed_changed_files(self):
-        """Check that files are not removed if they are renamed and changed."""
+        """Check that files that are renamed and changed are backed up."""
         tree = self.get_committed_tree(TestRemove.files)
 
         for f in TestRemove.rfiles:
@@ -133,12 +129,10 @@
         self.assertInWorkingTree(rfilesx)
         self.failUnlessExists(rfilesx)
 
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            rfilesx, keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)modified:.*ax.*bx/cx')
-        self.assertInWorkingTree(rfilesx)
-        self.failUnlessExists(rfilesx)
+        tree.remove(rfilesx, keep_files=False)
+        self.assertNotInWorkingTree(rfilesx)
+        self.failUnlessExists(['bx.~1~/cx.~1~', 'bx.~1~', 'ax.~1~'])
+        self.failIfExists('dx.~1~') # unchanged file
         tree._validate()
 
     def test_force_remove_changed_files(self):
@@ -149,15 +143,15 @@
 
         tree.remove(TestRemove.files, keep_files=False, force=True)
         self.assertRemovedAndDeleted(TestRemove.files)
+        self.failIfExists(['a.~1~', 'b.~1~/', 'b.~1~/c', 'd.~1~/'])
         tree._validate()
 
     def test_remove_unknown_files(self):
-        """Try to delete unknown files."""
+        """Unknown files shuld be backed up"""
         tree = self.get_tree(TestRemove.files)
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            TestRemove.files, keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)unknown:.*d/.*b/c.*b/.*a.*')
+        tree.remove(TestRemove.files, keep_files=False)
+        self.assertRemovedAndDeleted(TestRemove.files)
+        self.failUnlessExists(TestRemove.backup_files)
         tree._validate()
 
     def test_remove_nonexisting_files(self):
@@ -202,45 +196,40 @@
         tree._validate()
 
     def test_remove_changed_ignored_files(self):
-        """Changed ignored files should not be deleted."""
+        """Changed ignored files should be backed up."""
         files = ['an_ignored_file']
         tree = self.get_tree(files)
         tree.add(files)
         ignores.add_runtime_ignores(["*ignored*"])
         self.assertInWorkingTree(files)
         self.assertNotEquals(None, tree.is_ignored(files[0]))
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            files, keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)added:.*' + files[0])
-        self.assertInWorkingTree(files)
+
+        tree.remove(files, keep_files=False)
+        self.assertNotInWorkingTree(files)
+        self.failUnlessExists('an_ignored_file.~1~')
         tree._validate()
 
     def test_dont_remove_directory_with_unknowns(self):
-        """Directories with unknowns should not be deleted."""
+        """Directories with unknowns should be backed up."""
         directories = ['a/', 'b/', 'c/', 'c/c/']
         tree = self.get_committed_tree(directories)
 
         self.build_tree(['a/unknown_file'])
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            'a', keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)unknown:.*a/unknown_file')
+        tree.remove('a', keep_files=False)
+        self.failUnlessExists('a.~1~/unknown_file')
 
         self.build_tree(['b/unknown_directory'])
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            'b', keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)unknown:.*b/unknown_directory')
+        tree.remove('b', keep_files=False)
+        self.failUnlessExists('b.~1~/unknown_directory')
 
         self.build_tree(['c/c/unknown_file'])
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            'c/c', keep_files=False)
-        self.assertContainsRe(err.changes_as_text,
-            '(?s)unknown:.*c/c/unknown_file')
-
-        self.assertInWorkingTree(directories)
-        self.failUnlessExists(directories)
+        tree.remove('c/c', keep_files=False)
+        self.failUnlessExists('c/c.~1~/unknown_file')
+
+        tree.remove('c', keep_files=False)
+        self.failUnlessExists('c.~1~/')
+
+        self.assertNotInWorkingTree(directories)
         tree._validate()
 
     def test_force_remove_directory_with_unknowns(self):
@@ -262,16 +251,20 @@
         tree._validate()
 
     def test_remove_directory_with_changed_file(self):
-        """Refuse to delete directories with changed files."""
-        files = ['b/', 'b/c']
-        tree = self.get_committed_tree(files)
-        self.build_tree_contents([('b/c', "some other new content!")])
-
-        err = self.assertRaises(errors.BzrRemoveChangedFilesError, tree.remove,
-            'b', keep_files=False)
-        self.assertContainsRe(err.changes_as_text, '(?s)modified:.*b/c')
-        self.assertInWorkingTree(files)
-        self.failUnlessExists(files)
+        """Backup directories with changed files."""
+        files = ['b/', 'b/c']
+        tree = self.get_committed_tree(files)
+        self.build_tree_contents([('b/c', "some other new content!")])
+
+        tree.remove('b', keep_files=False)
+        self.failUnlessExists('b.~1~/c.~1~')
+        self.assertNotInWorkingTree(files)
+
+    def test_remove_force_directory_with_changed_file(self):
+        """Delete directories with changed files when forced."""
+        files = ['b/', 'b/c']
+        tree = self.get_committed_tree(files)
+        self.build_tree_contents([('b/c', "some other new content!")])
 
         # see if we can force it now..
         tree.remove('b', keep_files=False, force=True)

=== modified file 'bzrlib/tests/test_bzrdir.py'
--- a/bzrlib/tests/test_bzrdir.py	2010-08-13 07:43:51 +0000
+++ b/bzrlib/tests/test_bzrdir.py	2010-08-20 09:39:20 +0000
@@ -1411,3 +1411,19 @@
         param_repr = param_reprs[0]
         self.assertStartsWith(param_repr, '<RepoInitHookParams for ')
 
+
+class TestGenerateBackupName(TestCaseWithMemoryTransport):
+
+    def setUp(self):
+        super(TestGenerateBackupName, self).setUp()
+        self._transport = get_transport(self.get_url())
+        bzrdir.BzrDir.create(self.get_url(),
+            possible_transports=[self._transport])
+        self._bzrdir = bzrdir.BzrDir.open_from_transport(self._transport)
+
+    def test_new(self):
+        self.assertEqual("a.~1~", self._bzrdir.generate_backup_name("a"))
+
+    def test_exiting(self):
+        self._transport.put_bytes("a.~1~", "some content")
+        self.assertEqual("a.~2~", self._bzrdir.generate_backup_name("a"))

=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py	2010-08-17 06:45:33 +0000
+++ b/bzrlib/workingtree.py	2010-08-20 09:39:20 +0000
@@ -2024,34 +2024,36 @@
 
         inv_delta = []
 
-        new_files=set()
+        all_files = set() # specified and nested files 
         unknown_nested_files=set()
         if to_file is None:
             to_file = sys.stdout
 
+        files_to_backup = []
+
         def recurse_directory_to_add_files(directory):
             # Recurse directory and add all files
             # so we can check if they have changed.
             for parent_info, file_infos in self.walkdirs(directory):
                 for relpath, basename, kind, lstat, fileid, kind in file_infos:
                     # Is it versioned or ignored?
-                    if self.path2id(relpath) or self.is_ignored(relpath):
+                    if self.path2id(relpath):
                         # Add nested content for deletion.
-                        new_files.add(relpath)
+                        all_files.add(relpath)
                     else:
-                        # Files which are not versioned and not ignored
+                        # Files which are not versioned
                         # should be treated as unknown.
-                        unknown_nested_files.add((relpath, None, kind))
+                        files_to_backup.append(relpath)
 
         for filename in files:
             # Get file name into canonical form.
             abspath = self.abspath(filename)
             filename = self.relpath(abspath)
             if len(filename) > 0:
-                new_files.add(filename)
+                all_files.add(filename)
                 recurse_directory_to_add_files(filename)
 
-        files = list(new_files)
+        files = list(all_files)
 
         if len(files) == 0:
             return # nothing to do
@@ -2061,34 +2063,23 @@
 
         # Bail out if we are going to delete files we shouldn't
         if not keep_files and not force:
-            has_changed_files = len(unknown_nested_files) > 0
-            if not has_changed_files:
-                for (file_id, path, content_change, versioned, parent_id, name,
-                     kind, executable) in self.iter_changes(self.basis_tree(),
-                         include_unchanged=True, require_versioned=False,
-                         want_unversioned=True, specific_files=files):
-                    if versioned == (False, False):
-                        # The record is unknown ...
-                        if not self.is_ignored(path[1]):
-                            # ... but not ignored
-                            has_changed_files = True
-                            break
-                    elif (content_change and (kind[1] is not None) and
-                            osutils.is_inside_any(files, path[1])):
-                        # Versioned and changed, but not deleted, and still
-                        # in one of the dirs to be deleted.
-                        has_changed_files = True
-                        break
+            for (file_id, path, content_change, versioned, parent_id, name,
+                 kind, executable) in self.iter_changes(self.basis_tree(),
+                     include_unchanged=True, require_versioned=False,
+                     want_unversioned=True, specific_files=files):
+                if versioned[0] == False:
+                    # The record is unknown or newly added
+                    files_to_backup.append(path[1])
+                elif (content_change and (kind[1] is not None) and
+                        osutils.is_inside_any(files, path[1])):
+                    # Versioned and changed, but not deleted, and still
+                    # in one of the dirs to be deleted.
+                    files_to_backup.append(path[1])
 
-            if has_changed_files:
-                # Make delta show ALL applicable changes in error message.
-                tree_delta = self.changes_from(self.basis_tree(),
-                    require_versioned=False, want_unversioned=True,
-                    specific_files=files)
-                for unknown_file in unknown_nested_files:
-                    if unknown_file not in tree_delta.unversioned:
-                        tree_delta.unversioned.extend((unknown_file,))
-                raise errors.BzrRemoveChangedFilesError(tree_delta)
+        def backup(file_to_backup):
+            backup_name = self.bzrdir.generate_backup_name(file_to_backup)
+            osutils.rename(abs_path, self.abspath(backup_name))
+            return "removed %s (but kept a copy: %s)" % (file_to_backup, backup_name)
 
         # Build inv_delta and delete files where applicable,
         # do this before any modifications to inventory.
@@ -2118,12 +2109,15 @@
                         len(os.listdir(abs_path)) > 0):
                         if force:
                             osutils.rmtree(abs_path)
+                            message = "deleted %s" % (f,)
                         else:
-                            message = "%s is not an empty directory "\
-                                "and won't be deleted." % (f,)
+                            message = backup(f)
                     else:
-                        osutils.delete_any(abs_path)
-                        message = "deleted %s" % (f,)
+                        if f in files_to_backup:
+                            message = backup(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,)




More information about the bazaar-commits mailing list