585
635
bar1_abspath = self.wt.abspath('bar')
586
636
self.assertEqual([bar1_abspath], stat_paths)
638
def test_iter_changes(self):
639
self.wt.set_root_id('eert_toor')
640
transform, root = self.get_transform()
641
transform.new_file('old', root, 'blah', 'id-1', True)
643
transform, root = self.get_transform()
645
self.assertEqual([], list(transform._iter_changes()))
646
old = transform.trans_id_tree_file_id('id-1')
647
transform.unversion_file(old)
648
self.assertEqual([('id-1', ('old', None), False, (True, False),
649
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
650
(True, True))], list(transform._iter_changes()))
651
transform.new_directory('new', root, 'id-1')
652
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
653
('eert_toor', 'eert_toor'), ('old', 'new'),
654
('file', 'directory'),
655
(True, False))], list(transform._iter_changes()))
659
def test_iter_changes_new(self):
660
self.wt.set_root_id('eert_toor')
661
transform, root = self.get_transform()
662
transform.new_file('old', root, 'blah')
664
transform, root = self.get_transform()
666
old = transform.trans_id_tree_path('old')
667
transform.version_file('id-1', old)
668
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
669
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
670
(False, False))], list(transform._iter_changes()))
674
def test_iter_changes_modifications(self):
675
self.wt.set_root_id('eert_toor')
676
transform, root = self.get_transform()
677
transform.new_file('old', root, 'blah', 'id-1')
678
transform.new_file('new', root, 'blah')
679
transform.new_directory('subdir', root, 'subdir-id')
681
transform, root = self.get_transform()
683
old = transform.trans_id_tree_path('old')
684
subdir = transform.trans_id_tree_file_id('subdir-id')
685
new = transform.trans_id_tree_path('new')
686
self.assertEqual([], list(transform._iter_changes()))
689
transform.delete_contents(old)
690
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
691
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
692
(False, False))], list(transform._iter_changes()))
695
transform.create_file('blah', old)
696
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
697
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
698
(False, False))], list(transform._iter_changes()))
699
transform.cancel_deletion(old)
700
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
701
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
702
(False, False))], list(transform._iter_changes()))
703
transform.cancel_creation(old)
705
# move file_id to a different file
706
self.assertEqual([], list(transform._iter_changes()))
707
transform.unversion_file(old)
708
transform.version_file('id-1', new)
709
transform.adjust_path('old', root, new)
710
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
711
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
712
(False, False))], list(transform._iter_changes()))
713
transform.cancel_versioning(new)
714
transform._removed_id = set()
717
self.assertEqual([], list(transform._iter_changes()))
718
transform.set_executability(True, old)
719
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
720
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
721
(False, True))], list(transform._iter_changes()))
722
transform.set_executability(None, old)
725
self.assertEqual([], list(transform._iter_changes()))
726
transform.adjust_path('new', root, old)
727
transform._new_parent = {}
728
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
729
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
730
(False, False))], list(transform._iter_changes()))
731
transform._new_name = {}
734
self.assertEqual([], list(transform._iter_changes()))
735
transform.adjust_path('new', subdir, old)
736
transform._new_name = {}
737
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
738
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
739
('file', 'file'), (False, False))],
740
list(transform._iter_changes()))
741
transform._new_path = {}
746
def test_iter_changes_modified_bleed(self):
747
self.wt.set_root_id('eert_toor')
748
"""Modified flag should not bleed from one change to another"""
749
# unfortunately, we have no guarantee that file1 (which is modified)
750
# will be applied before file2. And if it's applied after file2, it
751
# obviously can't bleed into file2's change output. But for now, it
753
transform, root = self.get_transform()
754
transform.new_file('file1', root, 'blah', 'id-1')
755
transform.new_file('file2', root, 'blah', 'id-2')
757
transform, root = self.get_transform()
759
transform.delete_contents(transform.trans_id_file_id('id-1'))
760
transform.set_executability(True,
761
transform.trans_id_file_id('id-2'))
762
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
763
('eert_toor', 'eert_toor'), ('file1', u'file1'),
764
('file', None), (False, False)),
765
('id-2', (u'file2', u'file2'), False, (True, True),
766
('eert_toor', 'eert_toor'), ('file2', u'file2'),
767
('file', 'file'), (False, True))],
768
list(transform._iter_changes()))
772
def test_iter_changes_move_missing(self):
773
"""Test moving ids with no files around"""
774
self.wt.set_root_id('toor_eert')
775
# Need two steps because versioning a non-existant file is a conflict.
776
transform, root = self.get_transform()
777
transform.new_directory('floater', root, 'floater-id')
779
transform, root = self.get_transform()
780
transform.delete_contents(transform.trans_id_tree_path('floater'))
782
transform, root = self.get_transform()
783
floater = transform.trans_id_tree_path('floater')
785
transform.adjust_path('flitter', root, floater)
786
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
787
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
788
(None, None), (False, False))], list(transform._iter_changes()))
792
def test_iter_changes_pointless(self):
793
"""Ensure that no-ops are not treated as modifications"""
794
self.wt.set_root_id('eert_toor')
795
transform, root = self.get_transform()
796
transform.new_file('old', root, 'blah', 'id-1')
797
transform.new_directory('subdir', root, 'subdir-id')
799
transform, root = self.get_transform()
801
old = transform.trans_id_tree_path('old')
802
subdir = transform.trans_id_tree_file_id('subdir-id')
803
self.assertEqual([], list(transform._iter_changes()))
804
transform.delete_contents(subdir)
805
transform.create_directory(subdir)
806
transform.set_executability(False, old)
807
transform.unversion_file(old)
808
transform.version_file('id-1', old)
809
transform.adjust_path('old', root, old)
810
self.assertEqual([], list(transform._iter_changes()))
814
def test_rename_count(self):
815
transform, root = self.get_transform()
816
transform.new_file('name1', root, 'contents')
817
self.assertEqual(transform.rename_count, 0)
819
self.assertEqual(transform.rename_count, 1)
820
transform2, root = self.get_transform()
821
transform2.adjust_path('name2', root,
822
transform2.trans_id_tree_path('name1'))
823
self.assertEqual(transform2.rename_count, 0)
825
self.assertEqual(transform2.rename_count, 2)
827
def test_change_parent(self):
828
"""Ensure that after we change a parent, the results are still right.
830
Renames and parent changes on pending transforms can happen as part
831
of conflict resolution, and are explicitly permitted by the
834
This test ensures they work correctly with the rename-avoidance
837
transform, root = self.get_transform()
838
parent1 = transform.new_directory('parent1', root)
839
child1 = transform.new_file('child1', parent1, 'contents')
840
parent2 = transform.new_directory('parent2', root)
841
transform.adjust_path('child1', parent2, child1)
843
self.failIfExists(self.wt.abspath('parent1/child1'))
844
self.failUnlessExists(self.wt.abspath('parent2/child1'))
845
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
846
# no rename for child1 (counting only renames during apply)
847
self.failUnlessEqual(2, transform.rename_count)
849
def test_cancel_parent(self):
850
"""Cancelling a parent doesn't cause deletion of a non-empty directory
852
This is like the test_change_parent, except that we cancel the parent
853
before adjusting the path. The transform must detect that the
854
directory is non-empty, and move children to safe locations.
856
transform, root = self.get_transform()
857
parent1 = transform.new_directory('parent1', root)
858
child1 = transform.new_file('child1', parent1, 'contents')
859
child2 = transform.new_file('child2', parent1, 'contents')
861
transform.cancel_creation(parent1)
863
self.fail('Failed to move child1 before deleting parent1')
864
transform.cancel_creation(child2)
865
transform.create_directory(parent1)
867
transform.cancel_creation(parent1)
868
# If the transform incorrectly believes that child2 is still in
869
# parent1's limbo directory, it will try to rename it and fail
870
# because was already moved by the first cancel_creation.
872
self.fail('Transform still thinks child2 is a child of parent1')
873
parent2 = transform.new_directory('parent2', root)
874
transform.adjust_path('child1', parent2, child1)
876
self.failIfExists(self.wt.abspath('parent1'))
877
self.failUnlessExists(self.wt.abspath('parent2/child1'))
878
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
879
self.failUnlessEqual(2, transform.rename_count)
881
def test_adjust_and_cancel(self):
882
"""Make sure adjust_path keeps track of limbo children properly"""
883
transform, root = self.get_transform()
884
parent1 = transform.new_directory('parent1', root)
885
child1 = transform.new_file('child1', parent1, 'contents')
886
parent2 = transform.new_directory('parent2', root)
887
transform.adjust_path('child1', parent2, child1)
888
transform.cancel_creation(child1)
890
transform.cancel_creation(parent1)
891
# if the transform thinks child1 is still in parent1's limbo
892
# directory, it will attempt to move it and fail.
894
self.fail('Transform still thinks child1 is a child of parent1')
897
def test_noname_contents(self):
898
"""TreeTransform should permit deferring naming files."""
899
transform, root = self.get_transform()
900
parent = transform.trans_id_file_id('parent-id')
902
transform.create_directory(parent)
904
self.fail("Can't handle contents with no name")
907
def test_noname_contents_nested(self):
908
"""TreeTransform should permit deferring naming files."""
909
transform, root = self.get_transform()
910
parent = transform.trans_id_file_id('parent-id')
912
transform.create_directory(parent)
914
self.fail("Can't handle contents with no name")
915
child = transform.new_directory('child', parent)
916
transform.adjust_path('parent', root, parent)
918
self.failUnlessExists(self.wt.abspath('parent/child'))
919
self.assertEqual(1, transform.rename_count)
921
def test_reuse_name(self):
922
"""Avoid reusing the same limbo name for different files"""
923
transform, root = self.get_transform()
924
parent = transform.new_directory('parent', root)
925
child1 = transform.new_directory('child', parent)
927
child2 = transform.new_directory('child', parent)
929
self.fail('Tranform tried to use the same limbo name twice')
930
transform.adjust_path('child2', parent, child2)
932
# limbo/new-1 => parent, limbo/new-3 => parent/child2
933
# child2 is put into top-level limbo because child1 has already
934
# claimed the direct limbo path when child2 is created. There is no
935
# advantage in renaming files once they're in top-level limbo, except
937
self.assertEqual(2, transform.rename_count)
939
def test_reuse_when_first_moved(self):
940
"""Don't avoid direct paths when it is safe to use them"""
941
transform, root = self.get_transform()
942
parent = transform.new_directory('parent', root)
943
child1 = transform.new_directory('child', parent)
944
transform.adjust_path('child1', parent, child1)
945
child2 = transform.new_directory('child', parent)
947
# limbo/new-1 => parent
948
self.assertEqual(1, transform.rename_count)
950
def test_reuse_after_cancel(self):
951
"""Don't avoid direct paths when it is safe to use them"""
952
transform, root = self.get_transform()
953
parent2 = transform.new_directory('parent2', root)
954
child1 = transform.new_directory('child1', parent2)
955
transform.cancel_creation(parent2)
956
transform.create_directory(parent2)
957
child2 = transform.new_directory('child1', parent2)
958
transform.adjust_path('child2', parent2, child1)
960
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
961
self.assertEqual(2, transform.rename_count)
963
def test_finalize_order(self):
964
"""Finalize must be done in child-to-parent order"""
965
transform, root = self.get_transform()
966
parent = transform.new_directory('parent', root)
967
child = transform.new_directory('child', parent)
971
self.fail('Tried to remove parent before child1')
973
def test_cancel_with_cancelled_child_should_succeed(self):
974
transform, root = self.get_transform()
975
parent = transform.new_directory('parent', root)
976
child = transform.new_directory('child', parent)
977
transform.cancel_creation(child)
978
transform.cancel_creation(parent)
981
def test_change_entry(self):
982
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
983
self.callDeprecated([txt], change_entry, None, None, None, None, None,
589
987
class TransformGroup(object):
590
def __init__(self, dirname):
988
def __init__(self, dirname, root_id):
591
989
self.name = dirname
592
990
os.mkdir(dirname)
593
991
self.wt = BzrDir.create_standalone_workingtree(dirname)
992
self.wt.set_root_id(root_id)
594
993
self.b = self.wt.branch
595
994
self.tt = TreeTransform(self.wt)
596
995
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
598
998
def conflict_text(tree, merge):
599
999
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
600
1000
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
937
1370
self.assertEqual(name, 'name.~1~')
938
1371
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
939
1372
self.assertEqual(name, 'name.~4~')
1375
class TestFileMover(tests.TestCaseWithTransport):
1377
def test_file_mover(self):
1378
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1379
mover = _FileMover()
1380
mover.rename('a', 'q')
1381
self.failUnlessExists('q')
1382
self.failIfExists('a')
1383
self.failUnlessExists('q/b')
1384
self.failUnlessExists('c')
1385
self.failUnlessExists('c/d')
1387
def test_pre_delete_rollback(self):
1388
self.build_tree(['a/'])
1389
mover = _FileMover()
1390
mover.pre_delete('a', 'q')
1391
self.failUnlessExists('q')
1392
self.failIfExists('a')
1394
self.failIfExists('q')
1395
self.failUnlessExists('a')
1397
def test_apply_deletions(self):
1398
self.build_tree(['a/', 'b/'])
1399
mover = _FileMover()
1400
mover.pre_delete('a', 'q')
1401
mover.pre_delete('b', 'r')
1402
self.failUnlessExists('q')
1403
self.failUnlessExists('r')
1404
self.failIfExists('a')
1405
self.failIfExists('b')
1406
mover.apply_deletions()
1407
self.failIfExists('q')
1408
self.failIfExists('r')
1409
self.failIfExists('a')
1410
self.failIfExists('b')
1412
def test_file_mover_rollback(self):
1413
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1414
mover = _FileMover()
1415
mover.rename('c/d', 'c/f')
1416
mover.rename('c/e', 'c/d')
1418
mover.rename('a', 'c')
1421
self.failUnlessExists('a')
1422
self.failUnlessExists('c/d')
1425
class Bogus(Exception):
1429
class TestTransformRollback(tests.TestCaseWithTransport):
1431
class ExceptionFileMover(_FileMover):
1433
def __init__(self, bad_source=None, bad_target=None):
1434
_FileMover.__init__(self)
1435
self.bad_source = bad_source
1436
self.bad_target = bad_target
1438
def rename(self, source, target):
1439
if (self.bad_source is not None and
1440
source.endswith(self.bad_source)):
1442
elif (self.bad_target is not None and
1443
target.endswith(self.bad_target)):
1446
_FileMover.rename(self, source, target)
1448
def test_rollback_rename(self):
1449
tree = self.make_branch_and_tree('.')
1450
self.build_tree(['a/', 'a/b'])
1451
tt = TreeTransform(tree)
1452
self.addCleanup(tt.finalize)
1453
a_id = tt.trans_id_tree_path('a')
1454
tt.adjust_path('c', tt.root, a_id)
1455
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1456
self.assertRaises(Bogus, tt.apply,
1457
_mover=self.ExceptionFileMover(bad_source='a'))
1458
self.failUnlessExists('a')
1459
self.failUnlessExists('a/b')
1461
self.failUnlessExists('c')
1462
self.failUnlessExists('c/d')
1464
def test_rollback_rename_into_place(self):
1465
tree = self.make_branch_and_tree('.')
1466
self.build_tree(['a/', 'a/b'])
1467
tt = TreeTransform(tree)
1468
self.addCleanup(tt.finalize)
1469
a_id = tt.trans_id_tree_path('a')
1470
tt.adjust_path('c', tt.root, a_id)
1471
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1472
self.assertRaises(Bogus, tt.apply,
1473
_mover=self.ExceptionFileMover(bad_target='c/d'))
1474
self.failUnlessExists('a')
1475
self.failUnlessExists('a/b')
1477
self.failUnlessExists('c')
1478
self.failUnlessExists('c/d')
1480
def test_rollback_deletion(self):
1481
tree = self.make_branch_and_tree('.')
1482
self.build_tree(['a/', 'a/b'])
1483
tt = TreeTransform(tree)
1484
self.addCleanup(tt.finalize)
1485
a_id = tt.trans_id_tree_path('a')
1486
tt.delete_contents(a_id)
1487
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1488
self.assertRaises(Bogus, tt.apply,
1489
_mover=self.ExceptionFileMover(bad_target='d'))
1490
self.failUnlessExists('a')
1491
self.failUnlessExists('a/b')
1493
def test_resolve_no_parent(self):
1494
wt = self.make_branch_and_tree('.')
1495
tt = TreeTransform(wt)
1496
self.addCleanup(tt.finalize)
1497
parent = tt.trans_id_file_id('parent-id')
1498
tt.new_file('file', parent, 'Contents')
1499
resolve_conflicts(tt)