1
1
# Copyright (C) 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
25
from bzrlib.bzrdir import BzrDir
20
26
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
UnversionedParent, ParentLoop)
27
UnversionedParent, ParentLoop, DeletingParent,)
22
28
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
ReusingTransform, CantMoveRoot, NotVersionedError,
24
ExistingLimbo, ImmortalLimbo, LockError)
29
ReusingTransform, CantMoveRoot,
30
PathsNotVersionedError, ExistingLimbo,
31
ImmortalLimbo, LockError)
25
32
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
26
33
from bzrlib.merge import Merge3Merger
27
34
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
28
35
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
29
36
resolve_conflicts, cook_conflicts,
30
37
find_interesting, build_tree, get_backup_name)
38
from bzrlib.workingtree import gen_root_id
32
41
class TestTreeTransform(TestCaseInTempDir):
34
44
super(TestTreeTransform, self).setUp()
35
45
self.wt = BzrDir.create_standalone_workingtree('.')
410
427
'dorothy.moved', 'dorothy', None,
412
429
self.assertEqual(cooked_conflicts[1], duplicate_id)
413
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
430
missing_parent = MissingParent('Created directory', 'munchkincity',
432
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
414
433
self.assertEqual(cooked_conflicts[2], missing_parent)
415
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
434
unversioned_parent = UnversionedParent('Versioned directory',
437
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
417
439
self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
440
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
419
441
'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
self.assertEqual(cooked_conflicts[4], parent_loop)
421
self.assertEqual(len(cooked_conflicts), 5)
442
self.assertEqual(cooked_conflicts[4], deleted_parent)
443
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
444
self.assertEqual(cooked_conflicts[6], parent_loop)
445
self.assertEqual(len(cooked_conflicts), 7)
424
448
def test_string_conflicts(self):
434
458
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
435
459
'Unversioned existing file '
436
460
'dorothy.moved.')
437
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
439
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
440
'oz. Versioned directory.')
441
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
461
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
462
' munchkincity. Created directory.')
463
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
464
' versioned, but has versioned'
465
' children. Versioned directory.')
466
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
467
" is not empty. Not deleting.")
468
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
469
' versioned, but has versioned'
470
' children. Versioned directory.')
471
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
442
472
' oz/emeraldcity. Cancelled move.')
444
474
def test_moving_versioned_directories(self):
488
518
self.assertEqual(find_interesting(wt, wt, ['vfile']),
489
519
set(['myfile-id']))
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
520
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
523
def test_set_executability_order(self):
524
"""Ensure that executability behaves the same, no matter what order.
526
- create file and set executability simultaneously
527
- create file and set executability afterward
528
- unsetting the executability of a file whose executability has not been
529
declared should throw an exception (this may happen when a
530
merge attempts to create a file with a duplicate ID)
532
transform, root = self.get_transform()
534
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
536
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
537
transform.set_executability(True, sac)
538
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
539
self.assertRaises(KeyError, transform.set_executability, None, uws)
541
self.assertTrue(wt.is_executable('soc'))
542
self.assertTrue(wt.is_executable('sac'))
544
def test_preserve_mode(self):
545
"""File mode is preserved when replacing content"""
546
if sys.platform == 'win32':
547
raise TestSkipped('chmod has no effect on win32')
548
transform, root = self.get_transform()
549
transform.new_file('file1', root, 'contents', 'file1-id', True)
551
self.assertTrue(self.wt.is_executable('file1-id'))
552
transform, root = self.get_transform()
553
file1_id = transform.trans_id_tree_file_id('file1-id')
554
transform.delete_contents(file1_id)
555
transform.create_file('contents2', file1_id)
557
self.assertTrue(self.wt.is_executable('file1-id'))
559
def test__set_mode_stats_correctly(self):
560
"""_set_mode stats to determine file mode."""
561
if sys.platform == 'win32':
562
raise TestSkipped('chmod has no effect on win32')
566
def instrumented_stat(path):
567
stat_paths.append(path)
568
return real_stat(path)
570
transform, root = self.get_transform()
572
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
573
file_id='bar-id-1', executable=False)
576
transform, root = self.get_transform()
577
bar1_id = transform.trans_id_tree_path('bar')
578
bar2_id = transform.trans_id_tree_path('bar2')
580
os.stat = instrumented_stat
581
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
586
bar1_abspath = self.wt.abspath('bar')
587
self.assertEqual([bar1_abspath], stat_paths)
494
590
class TransformGroup(object):
495
def __init__(self, dirname):
591
def __init__(self, dirname, root_id):
496
592
self.name = dirname
497
593
os.mkdir(dirname)
498
594
self.wt = BzrDir.create_standalone_workingtree(dirname)
595
self.wt.set_root_id(root_id)
499
596
self.b = self.wt.branch
500
597
self.tt = TreeTransform(self.wt)
501
598
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
702
805
self.assertIs(os.path.isdir('b/foo'), True)
703
806
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
807
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
809
def test_file_conflict_handling(self):
810
"""Ensure that when building trees, conflict handling is done"""
811
source = self.make_branch_and_tree('source')
812
target = self.make_branch_and_tree('target')
813
self.build_tree(['source/file', 'target/file'])
814
source.add('file', 'new-file')
815
source.commit('added file')
816
build_tree(source.basis_tree(), target)
817
self.assertEqual([DuplicateEntry('Moved existing file to',
818
'file.moved', 'file', None, 'new-file')],
820
target2 = self.make_branch_and_tree('target2')
821
target_file = file('target2/file', 'wb')
823
source_file = file('source/file', 'rb')
825
target_file.write(source_file.read())
830
build_tree(source.basis_tree(), target2)
831
self.assertEqual([], target2.conflicts())
833
def test_symlink_conflict_handling(self):
834
"""Ensure that when building trees, conflict handling is done"""
835
if not has_symlinks():
836
raise TestSkipped('Test requires symlink support')
837
source = self.make_branch_and_tree('source')
838
os.symlink('foo', 'source/symlink')
839
source.add('symlink', 'new-symlink')
840
source.commit('added file')
841
target = self.make_branch_and_tree('target')
842
os.symlink('bar', 'target/symlink')
843
build_tree(source.basis_tree(), target)
844
self.assertEqual([DuplicateEntry('Moved existing file to',
845
'symlink.moved', 'symlink', None, 'new-symlink')],
847
target = self.make_branch_and_tree('target2')
848
os.symlink('foo', 'target2/symlink')
849
build_tree(source.basis_tree(), target)
850
self.assertEqual([], target.conflicts())
852
def test_directory_conflict_handling(self):
853
"""Ensure that when building trees, conflict handling is done"""
854
source = self.make_branch_and_tree('source')
855
target = self.make_branch_and_tree('target')
856
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
857
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
858
source.commit('added file')
859
build_tree(source.basis_tree(), target)
860
self.assertEqual([], target.conflicts())
861
self.failUnlessExists('target/dir1/file')
863
# Ensure contents are merged
864
target = self.make_branch_and_tree('target2')
865
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
866
build_tree(source.basis_tree(), target)
867
self.assertEqual([], target.conflicts())
868
self.failUnlessExists('target2/dir1/file2')
869
self.failUnlessExists('target2/dir1/file')
871
# Ensure new contents are suppressed for existing branches
872
target = self.make_branch_and_tree('target3')
873
self.make_branch('target3/dir1')
874
self.build_tree(['target3/dir1/file2'])
875
build_tree(source.basis_tree(), target)
876
self.failIfExists('target3/dir1/file')
877
self.failUnlessExists('target3/dir1/file2')
878
self.failUnlessExists('target3/dir1.diverted/file')
879
self.assertEqual([DuplicateEntry('Diverted to',
880
'dir1.diverted', 'dir1', 'new-dir1', None)],
883
target = self.make_branch_and_tree('target4')
884
self.build_tree(['target4/dir1/'])
885
self.make_branch('target4/dir1/file')
886
build_tree(source.basis_tree(), target)
887
self.failUnlessExists('target4/dir1/file')
888
self.assertEqual('directory', file_kind('target4/dir1/file'))
889
self.failUnlessExists('target4/dir1/file.diverted')
890
self.assertEqual([DuplicateEntry('Diverted to',
891
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
894
def test_mixed_conflict_handling(self):
895
"""Ensure that when building trees, conflict handling is done"""
896
source = self.make_branch_and_tree('source')
897
target = self.make_branch_and_tree('target')
898
self.build_tree(['source/name', 'target/name/'])
899
source.add('name', 'new-name')
900
source.commit('added file')
901
build_tree(source.basis_tree(), target)
902
self.assertEqual([DuplicateEntry('Moved existing file to',
903
'name.moved', 'name', None, 'new-name')], target.conflicts())
905
def test_raises_in_populated(self):
906
source = self.make_branch_and_tree('source')
907
self.build_tree(['source/name'])
909
source.commit('added name')
910
target = self.make_branch_and_tree('target')
911
self.build_tree(['target/name'])
913
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
706
917
class MockTransform(object):
708
919
def has_named_child(self, by_parent, parent_id, name):