1# Copyright (C) 2007-2012, 2016 Canonical Ltd 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""Tests for interface conformance of 'WorkingTree.remove'""" 18 19from breezy.tests.per_workingtree import TestCaseWithWorkingTree 20from breezy import ignores, osutils 21 22 23class TestRemove(TestCaseWithWorkingTree): 24 """Tests WorkingTree.remove""" 25 26 files = ['a', 'b/', 'b/c', 'd/'] 27 rfiles = ['b/c', 'b', 'a', 'd'] 28 backup_files = ['a.~1~', 'b.~1~/', 'b.~1~/c.~1~', 'd.~1~/'] 29 backup_files_no_version_dirs = ['a.~1~', 'b.~1~/', 'b.~1~/c.~1~'] 30 31 def get_tree(self, files): 32 tree = self.make_branch_and_tree('.') 33 self.build_tree(files) 34 self.assertPathExists(files) 35 return tree 36 37 def get_committed_tree(self, files, message="Committing"): 38 tree = self.get_tree(files) 39 tree.add(files) 40 tree.commit(message) 41 if not tree.has_versioned_directories(): 42 self.assertInWorkingTree( 43 [f for f in files if not f.endswith("/")]) 44 self.assertPathExists(files) 45 else: 46 self.assertInWorkingTree(files) 47 return tree 48 49 def assertRemovedAndDeleted(self, files): 50 self.assertNotInWorkingTree(files) 51 self.assertPathDoesNotExist(files) 52 53 def assertRemovedAndNotDeleted(self, files): 54 self.assertNotInWorkingTree(files) 55 self.assertPathExists(files) 56 57 def test_remove_keep(self): 58 """Check that files and directories are unversioned but not deleted.""" 59 tree = self.get_tree(TestRemove.files) 60 tree.add(TestRemove.files) 61 62 tree.remove(TestRemove.files) 63 self.assertRemovedAndNotDeleted(TestRemove.files) 64 65 def test_remove_keep_subtree(self): 66 """Check that a directory is unversioned but not deleted.""" 67 tree = self.make_branch_and_tree('.') 68 subtree = self.make_branch_and_tree('subtree') 69 subtree.commit('') 70 tree.add('subtree') 71 72 tree.remove('subtree') 73 self.assertRemovedAndNotDeleted('subtree') 74 75 def test_remove_unchanged_files(self): 76 """Check that unchanged files are removed and deleted.""" 77 tree = self.get_committed_tree(TestRemove.files) 78 tree.remove(TestRemove.files, keep_files=False) 79 self.assertRemovedAndDeleted(TestRemove.files) 80 tree._validate() 81 82 def test_remove_added_files(self): 83 """Removal of newly added files must back them up.""" 84 tree = self.get_tree(TestRemove.files) 85 tree.add(TestRemove.files) 86 tree.remove(TestRemove.files, keep_files=False) 87 self.assertNotInWorkingTree(TestRemove.files) 88 if tree.has_versioned_directories(): 89 self.assertPathExists(TestRemove.backup_files) 90 else: 91 self.assertPathExists(TestRemove.backup_files_no_version_dirs) 92 tree._validate() 93 94 def test_remove_changed_file(self): 95 """Removal of changed files must back it up.""" 96 tree = self.get_committed_tree(['a']) 97 self.build_tree_contents([('a', b"some other new content!")]) 98 self.assertInWorkingTree('a') 99 tree.remove('a', keep_files=False) 100 self.assertNotInWorkingTree(TestRemove.files) 101 self.assertPathExists('a.~1~') 102 tree._validate() 103 104 def test_remove_deleted_files(self): 105 """Check that files are removed if they don't exist any more.""" 106 tree = self.get_committed_tree(TestRemove.files) 107 for f in TestRemove.rfiles: 108 osutils.delete_any(f) 109 self.assertPathDoesNotExist(TestRemove.files) 110 tree.remove(TestRemove.files, keep_files=False) 111 self.assertRemovedAndDeleted(TestRemove.files) 112 tree._validate() 113 114 def test_remove_renamed_files(self): 115 """Check that files are removed even if they are renamed.""" 116 tree = self.get_committed_tree(TestRemove.files) 117 118 for f in TestRemove.rfiles: 119 tree.rename_one(f, f + 'x') 120 rfilesx = ['bx/cx', 'bx', 'ax', 'dx'] 121 self.assertPathExists(rfilesx) 122 123 tree.remove(rfilesx, keep_files=False) 124 self.assertRemovedAndDeleted(rfilesx) 125 tree._validate() 126 127 def test_remove_renamed_changed_files(self): 128 """Check that files that are renamed and changed are backed up.""" 129 tree = self.get_committed_tree(TestRemove.files) 130 131 for f in TestRemove.rfiles: 132 tree.rename_one(f, f + 'x') 133 rfilesx = ['bx/cx', 'bx', 'ax', 'dx'] 134 self.build_tree_contents([('ax', b'changed and renamed!'), 135 ('bx/cx', b'changed and renamed!')]) 136 self.assertPathExists(rfilesx) 137 138 tree.remove(rfilesx, keep_files=False) 139 self.assertNotInWorkingTree(rfilesx) 140 self.assertPathExists(['bx.~1~/cx.~1~', 'bx.~1~', 'ax.~1~']) 141 if (tree.supports_rename_tracking() or 142 not tree.has_versioned_directories()): 143 self.assertPathDoesNotExist('dx.~1~') # unchanged file 144 else: 145 self.assertPathExists('dx.~1~') # renamed, so appears changed 146 tree._validate() 147 148 def test_force_remove_changed_files(self): 149 """Check that changed files are removed and deleted when forced.""" 150 tree = self.get_tree(TestRemove.files) 151 tree.add(TestRemove.files) 152 153 tree.remove(TestRemove.files, keep_files=False, force=True) 154 self.assertRemovedAndDeleted(TestRemove.files) 155 self.assertPathDoesNotExist(['a.~1~', 'b.~1~/', 'b.~1~/c', 'd.~1~/']) 156 tree._validate() 157 158 def test_remove_unknown_files(self): 159 """Unknown files shuld be backed up""" 160 tree = self.get_tree(TestRemove.files) 161 tree.remove(TestRemove.files, keep_files=False) 162 self.assertRemovedAndDeleted(TestRemove.files) 163 if tree.has_versioned_directories(): 164 self.assertPathExists(TestRemove.backup_files) 165 else: 166 self.assertPathExists(TestRemove.backup_files_no_version_dirs) 167 tree._validate() 168 169 def test_remove_nonexisting_files(self): 170 """Try to delete non-existing files.""" 171 tree = self.get_tree(TestRemove.files) 172 tree.remove([''], keep_files=False) 173 tree.remove(['xyz', 'abc/def'], keep_files=False) 174 tree._validate() 175 176 def test_remove_unchanged_directory(self): 177 """Unchanged directories should be deleted.""" 178 files = ['b/', 'b/c', 'b/sub_directory/', 'b/sub_directory/with_file'] 179 tree = self.get_committed_tree(files) 180 tree.remove('b', keep_files=False) 181 self.assertRemovedAndDeleted('b') 182 tree._validate() 183 184 def test_remove_absent_directory(self): 185 """Removing a absent directory succeeds without corruption (#150438).""" 186 paths = ['a/', 'a/b'] 187 tree = self.get_committed_tree(paths) 188 tree.controldir.root_transport.delete_tree('a') 189 tree.remove(['a']) 190 self.assertRemovedAndDeleted('b') 191 tree._validate() 192 193 def test_remove_unknown_ignored_files(self): 194 """Unknown ignored files should be deleted.""" 195 tree = self.get_committed_tree(['b/']) 196 ignores.add_runtime_ignores(["*ignored*"]) 197 198 self.build_tree(['unknown_ignored_file']) 199 self.assertNotEqual(None, tree.is_ignored('unknown_ignored_file')) 200 tree.remove('unknown_ignored_file', keep_files=False) 201 self.assertRemovedAndDeleted('unknown_ignored_file') 202 203 self.build_tree(['b/unknown_ignored_file', 'b/unknown_ignored_dir/']) 204 self.assertNotEqual(None, tree.is_ignored('b/unknown_ignored_file')) 205 self.assertNotEqual(None, tree.is_ignored('b/unknown_ignored_dir')) 206 tree.remove('b', keep_files=False) 207 self.assertRemovedAndDeleted('b') 208 tree._validate() 209 210 def test_remove_changed_ignored_files(self): 211 """Changed ignored files should be backed up.""" 212 files = ['an_ignored_file'] 213 tree = self.get_tree(files) 214 tree.add(files) 215 ignores.add_runtime_ignores(["*ignored*"]) 216 self.assertNotEqual(None, tree.is_ignored(files[0])) 217 218 tree.remove(files, keep_files=False) 219 self.assertNotInWorkingTree(files) 220 self.assertPathExists('an_ignored_file.~1~') 221 tree._validate() 222 223 def test_dont_remove_directory_with_unknowns(self): 224 """Directories with unknowns should be backed up.""" 225 directories = ['a/', 'b/', 'c/', 'c/c/', 'c/blah'] 226 tree = self.get_committed_tree(directories) 227 228 self.build_tree(['a/unknown_file']) 229 tree.remove('a', keep_files=False) 230 self.assertPathExists('a.~1~/unknown_file') 231 232 self.build_tree(['b/unknown_directory']) 233 tree.remove('b', keep_files=False) 234 self.assertPathExists('b.~1~/unknown_directory') 235 236 self.build_tree(['c/c/unknown_file']) 237 tree.remove('c/c', keep_files=False) 238 self.assertPathExists('c/c.~1~/unknown_file') 239 240 tree.remove('c', keep_files=False) 241 self.assertPathExists('c.~1~/') 242 243 self.assertNotInWorkingTree(directories) 244 tree._validate() 245 246 def test_force_remove_directory_with_unknowns(self): 247 """Unchanged non-empty directories should be deleted when forced.""" 248 files = ['b/', 'b/c'] 249 tree = self.get_committed_tree(files) 250 251 other_files = ['b/unknown_file', 'b/sub_directory/', 252 'b/sub_directory/with_file', 'b/sub_directory/sub_directory/'] 253 self.build_tree(other_files) 254 255 self.assertInWorkingTree(files) 256 self.assertPathExists(files) 257 258 tree.remove('b', keep_files=False, force=True) 259 260 self.assertRemovedAndDeleted(files) 261 self.assertRemovedAndDeleted(other_files) 262 tree._validate() 263 264 def test_remove_directory_with_changed_file(self): 265 """Backup directories with changed files.""" 266 files = ['b/', 'b/c'] 267 tree = self.get_committed_tree(files) 268 self.build_tree_contents([('b/c', b"some other new content!")]) 269 270 tree.remove('b', keep_files=False) 271 self.assertPathExists('b.~1~/c.~1~') 272 self.assertNotInWorkingTree(files) 273 274 def test_remove_force_directory_with_changed_file(self): 275 """Delete directories with changed files when forced.""" 276 files = ['b/', 'b/c'] 277 tree = self.get_committed_tree(files) 278 self.build_tree_contents([('b/c', b"some other new content!")]) 279 280 # see if we can force it now.. 281 tree.remove('b', keep_files=False, force=True) 282 self.assertRemovedAndDeleted(files) 283 tree._validate() 284 285 def test_remove_directory_with_changed_emigrated_file(self): 286 # As per bug #129880 287 tree = self.make_branch_and_tree('.') 288 self.build_tree_contents( 289 [('somedir/',), (b'somedir/file', b'contents')]) 290 tree.add(['somedir', 'somedir/file']) 291 tree.commit(message="first") 292 self.build_tree_contents([('somedir/file', b'changed')]) 293 tree.rename_one('somedir/file', 'moved-file') 294 tree.remove('somedir', keep_files=False) 295 self.assertNotInWorkingTree('somedir') 296 self.assertPathDoesNotExist('somedir') 297 self.assertInWorkingTree('moved-file') 298 self.assertPathExists('moved-file') 299 300 def test_remove_directory_with_renames(self): 301 """Delete directory with renames in or out.""" 302 303 files = ['a/', 'a/file', 'a/directory/', 'a/directory/stuff', 'b/'] 304 files_to_move = ['a/file', 'a/directory/'] 305 306 tree = self.get_committed_tree(files) 307 # move stuff from a=>b 308 tree.move(['a/file', 'a/directory'], to_dir='b') 309 310 moved_files = ['b/file', 'b/directory/'] 311 self.assertRemovedAndDeleted(files_to_move) 312 self.assertInWorkingTree(moved_files) 313 self.assertPathExists(moved_files) 314 315 # check if it works with renames out 316 tree.remove('a', keep_files=False) 317 self.assertRemovedAndDeleted(['a/']) 318 319 # check if it works with renames in 320 tree.remove('b', keep_files=False) 321 self.assertRemovedAndDeleted(['b/']) 322 tree._validate() 323 324 def test_non_cwd(self): 325 tree = self.make_branch_and_tree('tree') 326 self.build_tree(['tree/dir/', 'tree/dir/file']) 327 tree.add(['dir', 'dir/file']) 328 tree.commit('add file') 329 tree.remove('dir/', keep_files=False) 330 self.assertPathDoesNotExist('tree/dir/file') 331 self.assertNotInWorkingTree('tree/dir/file', 'tree') 332 tree._validate() 333 334 def test_remove_uncommitted_removed_file(self): 335 # As per bug #152811 336 tree = self.get_committed_tree(['a']) 337 tree.remove('a', keep_files=False) 338 tree.remove('a', keep_files=False) 339 self.assertPathDoesNotExist('a') 340 tree._validate() 341 342 def test_remove_file_and_containing_dir(self): 343 tree = self.get_committed_tree(['config/', 'config/file']) 344 tree.remove('config/file', keep_files=False) 345 tree.remove('config', keep_files=False) 346 self.assertPathDoesNotExist('config/file') 347 self.assertPathDoesNotExist('config') 348 tree._validate() 349 350 def test_remove_dir_before_bzr(self): 351 # As per bug #272648. Note that a file must be present in the directory 352 # or the bug doesn't manifest itself. 353 tree = self.get_committed_tree(['.aaa/', '.aaa/file']) 354 tree.remove('.aaa/', keep_files=False) 355 self.assertPathDoesNotExist('.aaa/file') 356 self.assertPathDoesNotExist('.aaa') 357 tree._validate() 358