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