31
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
32
ReusingTransform, CantMoveRoot,
33
33
PathsNotVersionedError, ExistingLimbo,
34
ImmortalLimbo, LockError)
35
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
34
ExistingPendingDeletion, ImmortalLimbo,
35
ImmortalPendingDeletion, LockError)
36
from bzrlib.osutils import file_kind, pathjoin
36
37
from bzrlib.merge import Merge3Merger
37
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
38
from bzrlib.tests import (
39
CaseInsensitiveFilesystemFeature,
38
45
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
39
46
resolve_conflicts, cook_conflicts,
40
find_interesting, build_tree, get_backup_name)
47
find_interesting, build_tree, get_backup_name,
48
change_entry, _FileMover, resolve_checkout)
43
51
class TestTreeTransform(tests.TestCaseWithTransport):
63
71
self.assertRaises(LockError, self.wt.unlock)
64
72
os.rmdir(pathjoin(limbo_name, 'hehe'))
65
73
os.rmdir(limbo_name)
74
os.rmdir(deletion_path)
66
75
transform, root = self.get_transform()
78
def test_existing_pending_deletion(self):
79
transform, root = self.get_transform()
80
deletion_path = self._limbodir = urlutils.local_path_from_url(
81
transform._tree._control_files.controlfilename('pending-deletion'))
82
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
83
self.assertRaises(ImmortalPendingDeletion, transform.apply)
84
self.assertRaises(LockError, self.wt.unlock)
85
self.assertRaises(ExistingPendingDeletion, self.get_transform)
69
87
def test_build(self):
70
transform, root = self.get_transform()
88
transform, root = self.get_transform()
89
self.wt.lock_tree_write()
90
self.addCleanup(self.wt.unlock)
71
91
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
72
92
imaginary_id = transform.trans_id_tree_path('imaginary')
73
93
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
216
238
transform3.adjust_path('tip', root_id, tip_id)
217
239
transform3.apply()
241
def test_conflict_on_case_insensitive(self):
242
tree = self.make_branch_and_tree('tree')
243
# Don't try this at home, kids!
244
# Force the tree to report that it is case sensitive, for conflict
246
tree.case_sensitive = True
247
transform = TreeTransform(tree)
248
self.addCleanup(transform.finalize)
249
transform.new_file('file', transform.root, 'content')
250
transform.new_file('FiLe', transform.root, 'content')
251
result = transform.find_conflicts()
252
self.assertEqual([], result)
253
# Force the tree to report that it is case insensitive, for conflict
255
tree.case_sensitive = False
256
result = transform.find_conflicts()
257
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
259
def test_conflict_on_case_insensitive_existing(self):
260
tree = self.make_branch_and_tree('tree')
261
self.build_tree(['tree/FiLe'])
262
# Don't try this at home, kids!
263
# Force the tree to report that it is case sensitive, for conflict
265
tree.case_sensitive = True
266
transform = TreeTransform(tree)
267
self.addCleanup(transform.finalize)
268
transform.new_file('file', transform.root, 'content')
269
result = transform.find_conflicts()
270
self.assertEqual([], result)
271
# Force the tree to report that it is case insensitive, for conflict
273
tree.case_sensitive = False
274
result = transform.find_conflicts()
275
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
277
def test_resolve_case_insensitive_conflict(self):
278
tree = self.make_branch_and_tree('tree')
279
# Don't try this at home, kids!
280
# Force the tree to report that it is case insensitive, for conflict
282
tree.case_sensitive = False
283
transform = TreeTransform(tree)
284
self.addCleanup(transform.finalize)
285
transform.new_file('file', transform.root, 'content')
286
transform.new_file('FiLe', transform.root, 'content')
287
resolve_conflicts(transform)
289
self.failUnlessExists('tree/file')
290
self.failUnlessExists('tree/FiLe.moved')
292
def test_resolve_checkout_case_conflict(self):
293
tree = self.make_branch_and_tree('tree')
294
# Don't try this at home, kids!
295
# Force the tree to report that it is case insensitive, for conflict
297
tree.case_sensitive = False
298
transform = TreeTransform(tree)
299
self.addCleanup(transform.finalize)
300
transform.new_file('file', transform.root, 'content')
301
transform.new_file('FiLe', transform.root, 'content')
302
resolve_conflicts(transform,
303
pass_func=lambda t, c: resolve_checkout(t, c, []))
305
self.failUnlessExists('tree/file')
306
self.failUnlessExists('tree/FiLe.moved')
308
def test_apply_case_conflict(self):
309
"""Ensure that a transform with case conflicts can always be applied"""
310
tree = self.make_branch_and_tree('tree')
311
transform = TreeTransform(tree)
312
self.addCleanup(transform.finalize)
313
transform.new_file('file', transform.root, 'content')
314
transform.new_file('FiLe', transform.root, 'content')
315
dir = transform.new_directory('dir', transform.root)
316
transform.new_file('dirfile', dir, 'content')
317
transform.new_file('dirFiLe', dir, 'content')
318
resolve_conflicts(transform)
320
self.failUnlessExists('tree/file')
321
if not os.path.exists('tree/FiLe.moved'):
322
self.failUnlessExists('tree/FiLe')
323
self.failUnlessExists('tree/dir/dirfile')
324
if not os.path.exists('tree/dir/dirFiLe.moved'):
325
self.failUnlessExists('tree/dir/dirFiLe')
327
def test_case_insensitive_limbo(self):
328
tree = self.make_branch_and_tree('tree')
329
# Don't try this at home, kids!
330
# Force the tree to report that it is case insensitive
331
tree.case_sensitive = False
332
transform = TreeTransform(tree)
333
self.addCleanup(transform.finalize)
334
dir = transform.new_directory('dir', transform.root)
335
first = transform.new_file('file', dir, 'content')
336
second = transform.new_file('FiLe', dir, 'content')
337
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
338
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
219
340
def test_add_del(self):
220
341
start, root = self.get_transform()
221
342
start.new_directory('a', root, 'a')
942
1084
transform.cancel_creation(parent)
943
1085
transform.finalize()
1087
def test_change_entry(self):
1088
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1089
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1092
def test_case_insensitive_clash(self):
1093
self.requireFeature(CaseInsensitiveFilesystemFeature)
1095
wt = self.make_branch_and_tree('.')
1096
tt = TreeTransform(wt) # TreeTransform obtains write lock
1098
tt.new_file('foo', tt.root, 'bar')
1099
tt.new_file('Foo', tt.root, 'spam')
1100
# Lie to tt that we've already resolved all conflicts.
1101
tt.apply(no_conflicts=True)
1105
err = self.assertRaises(errors.FileExists, tt_helper)
1106
self.assertContainsRe(str(err),
1107
"^File exists: .+/foo")
1109
def test_two_directories_clash(self):
1111
wt = self.make_branch_and_tree('.')
1112
tt = TreeTransform(wt) # TreeTransform obtains write lock
1114
foo_1 = tt.new_directory('foo', tt.root)
1115
tt.new_directory('bar', foo_1)
1116
foo_2 = tt.new_directory('foo', tt.root)
1117
tt.new_directory('baz', foo_2)
1118
# Lie to tt that we've already resolved all conflicts.
1119
tt.apply(no_conflicts=True)
1123
err = self.assertRaises(errors.FileExists, tt_helper)
1124
self.assertContainsRe(str(err),
1125
"^File exists: .+/foo")
1127
def test_two_directories_clash_finalize(self):
1129
wt = self.make_branch_and_tree('.')
1130
tt = TreeTransform(wt) # TreeTransform obtains write lock
1132
foo_1 = tt.new_directory('foo', tt.root)
1133
tt.new_directory('bar', foo_1)
1134
foo_2 = tt.new_directory('foo', tt.root)
1135
tt.new_directory('baz', foo_2)
1136
# Lie to tt that we've already resolved all conflicts.
1137
tt.apply(no_conflicts=True)
1141
err = self.assertRaises(errors.FileExists, tt_helper)
1142
self.assertContainsRe(str(err),
1143
"^File exists: .+/foo")
946
1146
class TransformGroup(object):
947
1148
def __init__(self, dirname, root_id):
948
1149
self.name = dirname
949
1150
os.mkdir(dirname)
1301
1499
# children of non-root directories should not be renamed
1302
1500
self.assertEqual(2, transform_result.rename_count)
1502
def test_build_tree_accelerator_tree(self):
1503
source = self.make_branch_and_tree('source')
1504
self.build_tree_contents([('source/file1', 'A')])
1505
self.build_tree_contents([('source/file2', 'B')])
1506
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1507
source.commit('commit files')
1508
self.build_tree_contents([('source/file2', 'C')])
1510
real_source_get_file = source.get_file
1511
def get_file(file_id, path=None):
1512
calls.append(file_id)
1513
return real_source_get_file(file_id, path)
1514
source.get_file = get_file
1516
self.addCleanup(source.unlock)
1517
target = self.make_branch_and_tree('target')
1518
revision_tree = source.basis_tree()
1519
revision_tree.lock_read()
1520
self.addCleanup(revision_tree.unlock)
1521
build_tree(revision_tree, target, source)
1522
self.assertEqual(['file1-id'], calls)
1524
self.addCleanup(target.unlock)
1525
self.assertEqual([], list(target._iter_changes(revision_tree)))
1527
def test_build_tree_accelerator_tree_missing_file(self):
1528
source = self.make_branch_and_tree('source')
1529
self.build_tree_contents([('source/file1', 'A')])
1530
self.build_tree_contents([('source/file2', 'B')])
1531
source.add(['file1', 'file2'])
1532
source.commit('commit files')
1533
os.unlink('source/file1')
1534
source.remove(['file2'])
1535
target = self.make_branch_and_tree('target')
1536
revision_tree = source.basis_tree()
1537
revision_tree.lock_read()
1538
self.addCleanup(revision_tree.unlock)
1539
build_tree(revision_tree, target, source)
1541
self.addCleanup(target.unlock)
1542
self.assertEqual([], list(target._iter_changes(revision_tree)))
1544
def test_build_tree_accelerator_wrong_kind(self):
1545
source = self.make_branch_and_tree('source')
1546
self.build_tree_contents([('source/file1', '')])
1547
self.build_tree_contents([('source/file2', '')])
1548
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1549
source.commit('commit files')
1550
os.unlink('source/file2')
1551
self.build_tree_contents([('source/file2/', 'C')])
1552
os.unlink('source/file1')
1553
os.symlink('file2', 'source/file1')
1555
real_source_get_file = source.get_file
1556
def get_file(file_id, path=None):
1557
calls.append(file_id)
1558
return real_source_get_file(file_id, path)
1559
source.get_file = get_file
1561
self.addCleanup(source.unlock)
1562
target = self.make_branch_and_tree('target')
1563
revision_tree = source.basis_tree()
1564
revision_tree.lock_read()
1565
self.addCleanup(revision_tree.unlock)
1566
build_tree(revision_tree, target, source)
1567
self.assertEqual([], calls)
1569
self.addCleanup(target.unlock)
1570
self.assertEqual([], list(target._iter_changes(revision_tree)))
1572
def test_build_tree_accelerator_tree_moved(self):
1573
source = self.make_branch_and_tree('source')
1574
self.build_tree_contents([('source/file1', 'A')])
1575
source.add(['file1'], ['file1-id'])
1576
source.commit('commit files')
1577
source.rename_one('file1', 'file2')
1579
self.addCleanup(source.unlock)
1580
target = self.make_branch_and_tree('target')
1581
revision_tree = source.basis_tree()
1582
revision_tree.lock_read()
1583
self.addCleanup(revision_tree.unlock)
1584
build_tree(revision_tree, target, source)
1586
self.addCleanup(target.unlock)
1587
self.assertEqual([], list(target._iter_changes(revision_tree)))
1305
1590
class MockTransform(object):
1332
1618
self.assertEqual(name, 'name.~1~')
1333
1619
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1334
1620
self.assertEqual(name, 'name.~4~')
1623
class TestFileMover(tests.TestCaseWithTransport):
1625
def test_file_mover(self):
1626
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1627
mover = _FileMover()
1628
mover.rename('a', 'q')
1629
self.failUnlessExists('q')
1630
self.failIfExists('a')
1631
self.failUnlessExists('q/b')
1632
self.failUnlessExists('c')
1633
self.failUnlessExists('c/d')
1635
def test_pre_delete_rollback(self):
1636
self.build_tree(['a/'])
1637
mover = _FileMover()
1638
mover.pre_delete('a', 'q')
1639
self.failUnlessExists('q')
1640
self.failIfExists('a')
1642
self.failIfExists('q')
1643
self.failUnlessExists('a')
1645
def test_apply_deletions(self):
1646
self.build_tree(['a/', 'b/'])
1647
mover = _FileMover()
1648
mover.pre_delete('a', 'q')
1649
mover.pre_delete('b', 'r')
1650
self.failUnlessExists('q')
1651
self.failUnlessExists('r')
1652
self.failIfExists('a')
1653
self.failIfExists('b')
1654
mover.apply_deletions()
1655
self.failIfExists('q')
1656
self.failIfExists('r')
1657
self.failIfExists('a')
1658
self.failIfExists('b')
1660
def test_file_mover_rollback(self):
1661
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1662
mover = _FileMover()
1663
mover.rename('c/d', 'c/f')
1664
mover.rename('c/e', 'c/d')
1666
mover.rename('a', 'c')
1667
except errors.FileExists, e:
1669
self.failUnlessExists('a')
1670
self.failUnlessExists('c/d')
1673
class Bogus(Exception):
1677
class TestTransformRollback(tests.TestCaseWithTransport):
1679
class ExceptionFileMover(_FileMover):
1681
def __init__(self, bad_source=None, bad_target=None):
1682
_FileMover.__init__(self)
1683
self.bad_source = bad_source
1684
self.bad_target = bad_target
1686
def rename(self, source, target):
1687
if (self.bad_source is not None and
1688
source.endswith(self.bad_source)):
1690
elif (self.bad_target is not None and
1691
target.endswith(self.bad_target)):
1694
_FileMover.rename(self, source, target)
1696
def test_rollback_rename(self):
1697
tree = self.make_branch_and_tree('.')
1698
self.build_tree(['a/', 'a/b'])
1699
tt = TreeTransform(tree)
1700
self.addCleanup(tt.finalize)
1701
a_id = tt.trans_id_tree_path('a')
1702
tt.adjust_path('c', tt.root, a_id)
1703
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1704
self.assertRaises(Bogus, tt.apply,
1705
_mover=self.ExceptionFileMover(bad_source='a'))
1706
self.failUnlessExists('a')
1707
self.failUnlessExists('a/b')
1709
self.failUnlessExists('c')
1710
self.failUnlessExists('c/d')
1712
def test_rollback_rename_into_place(self):
1713
tree = self.make_branch_and_tree('.')
1714
self.build_tree(['a/', 'a/b'])
1715
tt = TreeTransform(tree)
1716
self.addCleanup(tt.finalize)
1717
a_id = tt.trans_id_tree_path('a')
1718
tt.adjust_path('c', tt.root, a_id)
1719
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1720
self.assertRaises(Bogus, tt.apply,
1721
_mover=self.ExceptionFileMover(bad_target='c/d'))
1722
self.failUnlessExists('a')
1723
self.failUnlessExists('a/b')
1725
self.failUnlessExists('c')
1726
self.failUnlessExists('c/d')
1728
def test_rollback_deletion(self):
1729
tree = self.make_branch_and_tree('.')
1730
self.build_tree(['a/', 'a/b'])
1731
tt = TreeTransform(tree)
1732
self.addCleanup(tt.finalize)
1733
a_id = tt.trans_id_tree_path('a')
1734
tt.delete_contents(a_id)
1735
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1736
self.assertRaises(Bogus, tt.apply,
1737
_mover=self.ExceptionFileMover(bad_target='d'))
1738
self.failUnlessExists('a')
1739
self.failUnlessExists('a/b')
1741
def test_resolve_no_parent(self):
1742
wt = self.make_branch_and_tree('.')
1743
tt = TreeTransform(wt)
1744
self.addCleanup(tt.finalize)
1745
parent = tt.trans_id_file_id('parent-id')
1746
tt.new_file('file', parent, 'Contents')
1747
resolve_conflicts(tt)