544
508
wt = transform._tree
545
509
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
547
sac = transform.new_file('set_after_creation', root,
548
'Set after creation', 'sac')
511
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
549
512
transform.set_executability(True, sac)
550
uws = transform.new_file('unset_without_set', root, 'Unset badly',
513
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
552
514
self.assertRaises(KeyError, transform.set_executability, None, uws)
553
515
transform.apply()
554
516
self.assertTrue(wt.is_executable('soc'))
555
517
self.assertTrue(wt.is_executable('sac'))
557
def test_preserve_mode(self):
558
"""File mode is preserved when replacing content"""
559
if sys.platform == 'win32':
560
raise TestSkipped('chmod has no effect on win32')
561
transform, root = self.get_transform()
562
transform.new_file('file1', root, 'contents', 'file1-id', True)
564
self.assertTrue(self.wt.is_executable('file1-id'))
565
transform, root = self.get_transform()
566
file1_id = transform.trans_id_tree_file_id('file1-id')
567
transform.delete_contents(file1_id)
568
transform.create_file('contents2', file1_id)
570
self.assertTrue(self.wt.is_executable('file1-id'))
572
def test__set_mode_stats_correctly(self):
573
"""_set_mode stats to determine file mode."""
574
if sys.platform == 'win32':
575
raise TestSkipped('chmod has no effect on win32')
579
def instrumented_stat(path):
580
stat_paths.append(path)
581
return real_stat(path)
583
transform, root = self.get_transform()
585
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
586
file_id='bar-id-1', executable=False)
589
transform, root = self.get_transform()
590
bar1_id = transform.trans_id_tree_path('bar')
591
bar2_id = transform.trans_id_tree_path('bar2')
593
os.stat = instrumented_stat
594
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
599
bar1_abspath = self.wt.abspath('bar')
600
self.assertEqual([bar1_abspath], stat_paths)
602
def test_iter_changes(self):
603
self.wt.set_root_id('eert_toor')
604
transform, root = self.get_transform()
605
transform.new_file('old', root, 'blah', 'id-1', True)
607
transform, root = self.get_transform()
609
self.assertEqual([], list(transform._iter_changes()))
610
old = transform.trans_id_tree_file_id('id-1')
611
transform.unversion_file(old)
612
self.assertEqual([('id-1', ('old', None), False, (True, False),
613
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
614
(True, True))], list(transform._iter_changes()))
615
transform.new_directory('new', root, 'id-1')
616
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
617
('eert_toor', 'eert_toor'), ('old', 'new'),
618
('file', 'directory'),
619
(True, False))], list(transform._iter_changes()))
623
def test_iter_changes_new(self):
624
self.wt.set_root_id('eert_toor')
625
transform, root = self.get_transform()
626
transform.new_file('old', root, 'blah')
628
transform, root = self.get_transform()
630
old = transform.trans_id_tree_path('old')
631
transform.version_file('id-1', old)
632
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
633
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
634
(False, False))], list(transform._iter_changes()))
638
def test_iter_changes_modifications(self):
639
self.wt.set_root_id('eert_toor')
640
transform, root = self.get_transform()
641
transform.new_file('old', root, 'blah', 'id-1')
642
transform.new_file('new', root, 'blah')
643
transform.new_directory('subdir', root, 'subdir-id')
645
transform, root = self.get_transform()
647
old = transform.trans_id_tree_path('old')
648
subdir = transform.trans_id_tree_file_id('subdir-id')
649
new = transform.trans_id_tree_path('new')
650
self.assertEqual([], list(transform._iter_changes()))
653
transform.delete_contents(old)
654
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
655
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
656
(False, False))], list(transform._iter_changes()))
659
transform.create_file('blah', old)
660
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
661
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
662
(False, False))], list(transform._iter_changes()))
663
transform.cancel_deletion(old)
664
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
665
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
666
(False, False))], list(transform._iter_changes()))
667
transform.cancel_creation(old)
669
# move file_id to a different file
670
self.assertEqual([], list(transform._iter_changes()))
671
transform.unversion_file(old)
672
transform.version_file('id-1', new)
673
transform.adjust_path('old', root, new)
674
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
675
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
676
(False, False))], list(transform._iter_changes()))
677
transform.cancel_versioning(new)
678
transform._removed_id = set()
681
self.assertEqual([], list(transform._iter_changes()))
682
transform.set_executability(True, old)
683
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
684
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
685
(False, True))], list(transform._iter_changes()))
686
transform.set_executability(None, old)
689
self.assertEqual([], list(transform._iter_changes()))
690
transform.adjust_path('new', root, old)
691
transform._new_parent = {}
692
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
693
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
694
(False, False))], list(transform._iter_changes()))
695
transform._new_name = {}
698
self.assertEqual([], list(transform._iter_changes()))
699
transform.adjust_path('new', subdir, old)
700
transform._new_name = {}
701
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
702
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
703
('file', 'file'), (False, False))],
704
list(transform._iter_changes()))
705
transform._new_path = {}
710
def test_iter_changes_modified_bleed(self):
711
self.wt.set_root_id('eert_toor')
712
"""Modified flag should not bleed from one change to another"""
713
# unfortunately, we have no guarantee that file1 (which is modified)
714
# will be applied before file2. And if it's applied after file2, it
715
# obviously can't bleed into file2's change output. But for now, it
717
transform, root = self.get_transform()
718
transform.new_file('file1', root, 'blah', 'id-1')
719
transform.new_file('file2', root, 'blah', 'id-2')
721
transform, root = self.get_transform()
723
transform.delete_contents(transform.trans_id_file_id('id-1'))
724
transform.set_executability(True,
725
transform.trans_id_file_id('id-2'))
726
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
727
('eert_toor', 'eert_toor'), ('file1', u'file1'),
728
('file', None), (False, False)),
729
('id-2', (u'file2', u'file2'), False, (True, True),
730
('eert_toor', 'eert_toor'), ('file2', u'file2'),
731
('file', 'file'), (False, True))],
732
list(transform._iter_changes()))
736
def test_iter_changes_move_missing(self):
737
"""Test moving ids with no files around"""
738
self.wt.set_root_id('toor_eert')
739
# Need two steps because versioning a non-existant file is a conflict.
740
transform, root = self.get_transform()
741
transform.new_directory('floater', root, 'floater-id')
743
transform, root = self.get_transform()
744
transform.delete_contents(transform.trans_id_tree_path('floater'))
746
transform, root = self.get_transform()
747
floater = transform.trans_id_tree_path('floater')
749
transform.adjust_path('flitter', root, floater)
750
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
751
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
752
(None, None), (False, False))], list(transform._iter_changes()))
756
def test_iter_changes_pointless(self):
757
"""Ensure that no-ops are not treated as modifications"""
758
self.wt.set_root_id('eert_toor')
759
transform, root = self.get_transform()
760
transform.new_file('old', root, 'blah', 'id-1')
761
transform.new_directory('subdir', root, 'subdir-id')
763
transform, root = self.get_transform()
765
old = transform.trans_id_tree_path('old')
766
subdir = transform.trans_id_tree_file_id('subdir-id')
767
self.assertEqual([], list(transform._iter_changes()))
768
transform.delete_contents(subdir)
769
transform.create_directory(subdir)
770
transform.set_executability(False, old)
771
transform.unversion_file(old)
772
transform.version_file('id-1', old)
773
transform.adjust_path('old', root, old)
774
self.assertEqual([], list(transform._iter_changes()))
778
def test_rename_count(self):
779
transform, root = self.get_transform()
780
transform.new_file('name1', root, 'contents')
781
self.assertEqual(transform.rename_count, 0)
783
self.assertEqual(transform.rename_count, 1)
784
transform2, root = self.get_transform()
785
transform2.adjust_path('name2', root,
786
transform2.trans_id_tree_path('name1'))
787
self.assertEqual(transform2.rename_count, 0)
789
self.assertEqual(transform2.rename_count, 2)
791
def test_change_parent(self):
792
"""Ensure that after we change a parent, the results are still right.
794
Renames and parent changes on pending transforms can happen as part
795
of conflict resolution, and are explicitly permitted by the
798
This test ensures they work correctly with the rename-avoidance
801
transform, root = self.get_transform()
802
parent1 = transform.new_directory('parent1', root)
803
child1 = transform.new_file('child1', parent1, 'contents')
804
parent2 = transform.new_directory('parent2', root)
805
transform.adjust_path('child1', parent2, child1)
807
self.failIfExists(self.wt.abspath('parent1/child1'))
808
self.failUnlessExists(self.wt.abspath('parent2/child1'))
809
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
810
# no rename for child1 (counting only renames during apply)
811
self.failUnlessEqual(2, transform.rename_count)
813
def test_cancel_parent(self):
814
"""Cancelling a parent doesn't cause deletion of a non-empty directory
816
This is like the test_change_parent, except that we cancel the parent
817
before adjusting the path. The transform must detect that the
818
directory is non-empty, and move children to safe locations.
820
transform, root = self.get_transform()
821
parent1 = transform.new_directory('parent1', root)
822
child1 = transform.new_file('child1', parent1, 'contents')
823
child2 = transform.new_file('child2', parent1, 'contents')
825
transform.cancel_creation(parent1)
827
self.fail('Failed to move child1 before deleting parent1')
828
transform.cancel_creation(child2)
829
transform.create_directory(parent1)
831
transform.cancel_creation(parent1)
832
# If the transform incorrectly believes that child2 is still in
833
# parent1's limbo directory, it will try to rename it and fail
834
# because was already moved by the first cancel_creation.
836
self.fail('Transform still thinks child2 is a child of parent1')
837
parent2 = transform.new_directory('parent2', root)
838
transform.adjust_path('child1', parent2, child1)
840
self.failIfExists(self.wt.abspath('parent1'))
841
self.failUnlessExists(self.wt.abspath('parent2/child1'))
842
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
843
self.failUnlessEqual(2, transform.rename_count)
845
def test_adjust_and_cancel(self):
846
"""Make sure adjust_path keeps track of limbo children properly"""
847
transform, root = self.get_transform()
848
parent1 = transform.new_directory('parent1', root)
849
child1 = transform.new_file('child1', parent1, 'contents')
850
parent2 = transform.new_directory('parent2', root)
851
transform.adjust_path('child1', parent2, child1)
852
transform.cancel_creation(child1)
854
transform.cancel_creation(parent1)
855
# if the transform thinks child1 is still in parent1's limbo
856
# directory, it will attempt to move it and fail.
858
self.fail('Transform still thinks child1 is a child of parent1')
861
def test_noname_contents(self):
862
"""TreeTransform should permit deferring naming files."""
863
transform, root = self.get_transform()
864
parent = transform.trans_id_file_id('parent-id')
866
transform.create_directory(parent)
868
self.fail("Can't handle contents with no name")
871
def test_noname_contents_nested(self):
872
"""TreeTransform should permit deferring naming files."""
873
transform, root = self.get_transform()
874
parent = transform.trans_id_file_id('parent-id')
876
transform.create_directory(parent)
878
self.fail("Can't handle contents with no name")
879
child = transform.new_directory('child', parent)
880
transform.adjust_path('parent', root, parent)
882
self.failUnlessExists(self.wt.abspath('parent/child'))
883
self.assertEqual(1, transform.rename_count)
885
def test_reuse_name(self):
886
"""Avoid reusing the same limbo name for different files"""
887
transform, root = self.get_transform()
888
parent = transform.new_directory('parent', root)
889
child1 = transform.new_directory('child', parent)
891
child2 = transform.new_directory('child', parent)
893
self.fail('Tranform tried to use the same limbo name twice')
894
transform.adjust_path('child2', parent, child2)
896
# limbo/new-1 => parent, limbo/new-3 => parent/child2
897
# child2 is put into top-level limbo because child1 has already
898
# claimed the direct limbo path when child2 is created. There is no
899
# advantage in renaming files once they're in top-level limbo, except
901
self.assertEqual(2, transform.rename_count)
903
def test_reuse_when_first_moved(self):
904
"""Don't avoid direct paths when it is safe to use them"""
905
transform, root = self.get_transform()
906
parent = transform.new_directory('parent', root)
907
child1 = transform.new_directory('child', parent)
908
transform.adjust_path('child1', parent, child1)
909
child2 = transform.new_directory('child', parent)
911
# limbo/new-1 => parent
912
self.assertEqual(1, transform.rename_count)
914
def test_reuse_after_cancel(self):
915
"""Don't avoid direct paths when it is safe to use them"""
916
transform, root = self.get_transform()
917
parent2 = transform.new_directory('parent2', root)
918
child1 = transform.new_directory('child1', parent2)
919
transform.cancel_creation(parent2)
920
transform.create_directory(parent2)
921
child2 = transform.new_directory('child1', parent2)
922
transform.adjust_path('child2', parent2, child1)
924
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
925
self.assertEqual(2, transform.rename_count)
927
def test_finalize_order(self):
928
"""Finalize must be done in child-to-parent order"""
929
transform, root = self.get_transform()
930
parent = transform.new_directory('parent', root)
931
child = transform.new_directory('child', parent)
935
self.fail('Tried to remove parent before child1')
937
def test_cancel_with_cancelled_child_should_succeed(self):
938
transform, root = self.get_transform()
939
parent = transform.new_directory('parent', root)
940
child = transform.new_directory('child', parent)
941
transform.cancel_creation(child)
942
transform.cancel_creation(parent)
946
520
class TransformGroup(object):
947
def __init__(self, dirname, root_id):
521
def __init__(self, dirname):
948
522
self.name = dirname
949
523
os.mkdir(dirname)
950
524
self.wt = BzrDir.create_standalone_workingtree(dirname)
951
self.wt.set_root_id(root_id)
952
525
self.b = self.wt.branch
953
526
self.tt = TreeTransform(self.wt)
954
527
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
957
529
def conflict_text(tree, merge):
958
530
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
959
531
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1158
724
a.add(['foo', 'foo/bar', 'foo/baz'])
1159
725
a.commit('initial commit')
1160
726
b = BzrDir.create_standalone_workingtree('b')
1161
basis = a.basis_tree()
1163
self.addCleanup(basis.unlock)
1164
build_tree(basis, b)
727
build_tree(a.basis_tree(), b)
1165
728
self.assertIs(os.path.isdir('b/foo'), True)
1166
729
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1167
730
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1169
def test_build_with_references(self):
1170
tree = self.make_branch_and_tree('source',
1171
format='dirstate-with-subtree')
1172
subtree = self.make_branch_and_tree('source/subtree',
1173
format='dirstate-with-subtree')
1174
tree.add_reference(subtree)
1175
tree.commit('a revision')
1176
tree.branch.create_checkout('target')
1177
self.failUnlessExists('target')
1178
self.failUnlessExists('target/subtree')
1180
def test_file_conflict_handling(self):
1181
"""Ensure that when building trees, conflict handling is done"""
1182
source = self.make_branch_and_tree('source')
1183
target = self.make_branch_and_tree('target')
1184
self.build_tree(['source/file', 'target/file'])
1185
source.add('file', 'new-file')
1186
source.commit('added file')
1187
build_tree(source.basis_tree(), target)
1188
self.assertEqual([DuplicateEntry('Moved existing file to',
1189
'file.moved', 'file', None, 'new-file')],
1191
target2 = self.make_branch_and_tree('target2')
1192
target_file = file('target2/file', 'wb')
1194
source_file = file('source/file', 'rb')
1196
target_file.write(source_file.read())
1201
build_tree(source.basis_tree(), target2)
1202
self.assertEqual([], target2.conflicts())
1204
def test_symlink_conflict_handling(self):
1205
"""Ensure that when building trees, conflict handling is done"""
1206
if not has_symlinks():
1207
raise TestSkipped('Test requires symlink support')
1208
source = self.make_branch_and_tree('source')
1209
os.symlink('foo', 'source/symlink')
1210
source.add('symlink', 'new-symlink')
1211
source.commit('added file')
1212
target = self.make_branch_and_tree('target')
1213
os.symlink('bar', 'target/symlink')
1214
build_tree(source.basis_tree(), target)
1215
self.assertEqual([DuplicateEntry('Moved existing file to',
1216
'symlink.moved', 'symlink', None, 'new-symlink')],
1218
target = self.make_branch_and_tree('target2')
1219
os.symlink('foo', 'target2/symlink')
1220
build_tree(source.basis_tree(), target)
1221
self.assertEqual([], target.conflicts())
1223
def test_directory_conflict_handling(self):
1224
"""Ensure that when building trees, conflict handling is done"""
1225
source = self.make_branch_and_tree('source')
1226
target = self.make_branch_and_tree('target')
1227
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1228
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1229
source.commit('added file')
1230
build_tree(source.basis_tree(), target)
1231
self.assertEqual([], target.conflicts())
1232
self.failUnlessExists('target/dir1/file')
1234
# Ensure contents are merged
1235
target = self.make_branch_and_tree('target2')
1236
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1237
build_tree(source.basis_tree(), target)
1238
self.assertEqual([], target.conflicts())
1239
self.failUnlessExists('target2/dir1/file2')
1240
self.failUnlessExists('target2/dir1/file')
1242
# Ensure new contents are suppressed for existing branches
1243
target = self.make_branch_and_tree('target3')
1244
self.make_branch('target3/dir1')
1245
self.build_tree(['target3/dir1/file2'])
1246
build_tree(source.basis_tree(), target)
1247
self.failIfExists('target3/dir1/file')
1248
self.failUnlessExists('target3/dir1/file2')
1249
self.failUnlessExists('target3/dir1.diverted/file')
1250
self.assertEqual([DuplicateEntry('Diverted to',
1251
'dir1.diverted', 'dir1', 'new-dir1', None)],
1254
target = self.make_branch_and_tree('target4')
1255
self.build_tree(['target4/dir1/'])
1256
self.make_branch('target4/dir1/file')
1257
build_tree(source.basis_tree(), target)
1258
self.failUnlessExists('target4/dir1/file')
1259
self.assertEqual('directory', file_kind('target4/dir1/file'))
1260
self.failUnlessExists('target4/dir1/file.diverted')
1261
self.assertEqual([DuplicateEntry('Diverted to',
1262
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1265
def test_mixed_conflict_handling(self):
1266
"""Ensure that when building trees, conflict handling is done"""
1267
source = self.make_branch_and_tree('source')
1268
target = self.make_branch_and_tree('target')
1269
self.build_tree(['source/name', 'target/name/'])
1270
source.add('name', 'new-name')
1271
source.commit('added file')
1272
build_tree(source.basis_tree(), target)
1273
self.assertEqual([DuplicateEntry('Moved existing file to',
1274
'name.moved', 'name', None, 'new-name')], target.conflicts())
1276
def test_raises_in_populated(self):
1277
source = self.make_branch_and_tree('source')
1278
self.build_tree(['source/name'])
1280
source.commit('added name')
1281
target = self.make_branch_and_tree('target')
1282
self.build_tree(['target/name'])
1284
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1285
build_tree, source.basis_tree(), target)
1287
def test_build_tree_rename_count(self):
1288
source = self.make_branch_and_tree('source')
1289
self.build_tree(['source/file1', 'source/dir1/'])
1290
source.add(['file1', 'dir1'])
1291
source.commit('add1')
1292
target1 = self.make_branch_and_tree('target1')
1293
transform_result = build_tree(source.basis_tree(), target1)
1294
self.assertEqual(2, transform_result.rename_count)
1296
self.build_tree(['source/dir1/file2'])
1297
source.add(['dir1/file2'])
1298
source.commit('add3')
1299
target2 = self.make_branch_and_tree('target2')
1300
transform_result = build_tree(source.basis_tree(), target2)
1301
# children of non-root directories should not be renamed
1302
self.assertEqual(2, transform_result.rename_count)
1305
732
class MockTransform(object):
1307
734
def has_named_child(self, by_parent, parent_id, name):