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
27
from bzrlib.bzrdir import BzrDir
20
28
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
UnversionedParent, ParentLoop)
29
UnversionedParent, ParentLoop, DeletingParent,)
22
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
ReusingTransform, CantMoveRoot, NotVersionedError,
24
ExistingLimbo, ImmortalLimbo, LockError)
31
ReusingTransform, CantMoveRoot,
32
PathsNotVersionedError, ExistingLimbo,
33
ImmortalLimbo, LockError)
25
34
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
26
35
from bzrlib.merge import Merge3Merger
27
36
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
410
428
'dorothy.moved', 'dorothy', None,
412
430
self.assertEqual(cooked_conflicts[1], duplicate_id)
413
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
431
missing_parent = MissingParent('Created directory', 'munchkincity',
433
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
414
434
self.assertEqual(cooked_conflicts[2], missing_parent)
415
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
435
unversioned_parent = UnversionedParent('Versioned directory',
438
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
417
440
self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
441
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
419
442
'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
self.assertEqual(cooked_conflicts[4], parent_loop)
421
self.assertEqual(len(cooked_conflicts), 5)
443
self.assertEqual(cooked_conflicts[4], deleted_parent)
444
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
445
self.assertEqual(cooked_conflicts[6], parent_loop)
446
self.assertEqual(len(cooked_conflicts), 7)
424
449
def test_string_conflicts(self):
434
459
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
435
460
'Unversioned existing file '
436
461
'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'
462
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
463
' munchkincity. Created directory.')
464
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
465
' versioned, but has versioned'
466
' children. Versioned directory.')
467
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
468
" is not empty. Not deleting.")
469
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
470
' versioned, but has versioned'
471
' children. Versioned directory.')
472
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
442
473
' oz/emeraldcity. Cancelled move.')
444
475
def test_moving_versioned_directories(self):
488
519
self.assertEqual(find_interesting(wt, wt, ['vfile']),
489
520
set(['myfile-id']))
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
521
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
524
def test_set_executability_order(self):
525
"""Ensure that executability behaves the same, no matter what order.
527
- create file and set executability simultaneously
528
- create file and set executability afterward
529
- unsetting the executability of a file whose executability has not been
530
declared should throw an exception (this may happen when a
531
merge attempts to create a file with a duplicate ID)
533
transform, root = self.get_transform()
535
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
537
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
538
transform.set_executability(True, sac)
539
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
540
self.assertRaises(KeyError, transform.set_executability, None, uws)
542
self.assertTrue(wt.is_executable('soc'))
543
self.assertTrue(wt.is_executable('sac'))
545
def test_preserve_mode(self):
546
"""File mode is preserved when replacing content"""
547
if sys.platform == 'win32':
548
raise TestSkipped('chmod has no effect on win32')
549
transform, root = self.get_transform()
550
transform.new_file('file1', root, 'contents', 'file1-id', True)
552
self.assertTrue(self.wt.is_executable('file1-id'))
553
transform, root = self.get_transform()
554
file1_id = transform.trans_id_tree_file_id('file1-id')
555
transform.delete_contents(file1_id)
556
transform.create_file('contents2', file1_id)
558
self.assertTrue(self.wt.is_executable('file1-id'))
560
def test__set_mode_stats_correctly(self):
561
"""_set_mode stats to determine file mode."""
562
if sys.platform == 'win32':
563
raise TestSkipped('chmod has no effect on win32')
567
def instrumented_stat(path):
568
stat_paths.append(path)
569
return real_stat(path)
571
transform, root = self.get_transform()
573
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
574
file_id='bar-id-1', executable=False)
577
transform, root = self.get_transform()
578
bar1_id = transform.trans_id_tree_path('bar')
579
bar2_id = transform.trans_id_tree_path('bar2')
581
os.stat = instrumented_stat
582
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
587
bar1_abspath = self.wt.abspath('bar')
588
self.assertEqual([bar1_abspath], stat_paths)
494
591
class TransformGroup(object):
495
def __init__(self, dirname):
592
def __init__(self, dirname, root_id):
496
593
self.name = dirname
497
594
os.mkdir(dirname)
498
595
self.wt = BzrDir.create_standalone_workingtree(dirname)
596
self.wt.set_root_id(root_id)
499
597
self.b = self.wt.branch
500
598
self.tt = TreeTransform(self.wt)
501
599
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
702
806
self.assertIs(os.path.isdir('b/foo'), True)
703
807
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
808
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
810
def test_file_conflict_handling(self):
811
"""Ensure that when building trees, conflict handling is done"""
812
source = self.make_branch_and_tree('source')
813
target = self.make_branch_and_tree('target')
814
self.build_tree(['source/file', 'target/file'])
815
source.add('file', 'new-file')
816
source.commit('added file')
817
build_tree(source.basis_tree(), target)
818
self.assertEqual([DuplicateEntry('Moved existing file to',
819
'file.moved', 'file', None, 'new-file')],
821
target2 = self.make_branch_and_tree('target2')
822
target_file = file('target2/file', 'wb')
824
source_file = file('source/file', 'rb')
826
target_file.write(source_file.read())
831
build_tree(source.basis_tree(), target2)
832
self.assertEqual([], target2.conflicts())
834
def test_symlink_conflict_handling(self):
835
"""Ensure that when building trees, conflict handling is done"""
836
if not has_symlinks():
837
raise TestSkipped('Test requires symlink support')
838
source = self.make_branch_and_tree('source')
839
os.symlink('foo', 'source/symlink')
840
source.add('symlink', 'new-symlink')
841
source.commit('added file')
842
target = self.make_branch_and_tree('target')
843
os.symlink('bar', 'target/symlink')
844
build_tree(source.basis_tree(), target)
845
self.assertEqual([DuplicateEntry('Moved existing file to',
846
'symlink.moved', 'symlink', None, 'new-symlink')],
848
target = self.make_branch_and_tree('target2')
849
os.symlink('foo', 'target2/symlink')
850
build_tree(source.basis_tree(), target)
851
self.assertEqual([], target.conflicts())
853
def test_directory_conflict_handling(self):
854
"""Ensure that when building trees, conflict handling is done"""
855
source = self.make_branch_and_tree('source')
856
target = self.make_branch_and_tree('target')
857
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
858
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
859
source.commit('added file')
860
build_tree(source.basis_tree(), target)
861
self.assertEqual([], target.conflicts())
862
self.failUnlessExists('target/dir1/file')
864
# Ensure contents are merged
865
target = self.make_branch_and_tree('target2')
866
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
867
build_tree(source.basis_tree(), target)
868
self.assertEqual([], target.conflicts())
869
self.failUnlessExists('target2/dir1/file2')
870
self.failUnlessExists('target2/dir1/file')
872
# Ensure new contents are suppressed for existing branches
873
target = self.make_branch_and_tree('target3')
874
self.make_branch('target3/dir1')
875
self.build_tree(['target3/dir1/file2'])
876
build_tree(source.basis_tree(), target)
877
self.failIfExists('target3/dir1/file')
878
self.failUnlessExists('target3/dir1/file2')
879
self.failUnlessExists('target3/dir1.diverted/file')
880
self.assertEqual([DuplicateEntry('Diverted to',
881
'dir1.diverted', 'dir1', 'new-dir1', None)],
884
target = self.make_branch_and_tree('target4')
885
self.build_tree(['target4/dir1/'])
886
self.make_branch('target4/dir1/file')
887
build_tree(source.basis_tree(), target)
888
self.failUnlessExists('target4/dir1/file')
889
self.assertEqual('directory', file_kind('target4/dir1/file'))
890
self.failUnlessExists('target4/dir1/file.diverted')
891
self.assertEqual([DuplicateEntry('Diverted to',
892
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
895
def test_mixed_conflict_handling(self):
896
"""Ensure that when building trees, conflict handling is done"""
897
source = self.make_branch_and_tree('source')
898
target = self.make_branch_and_tree('target')
899
self.build_tree(['source/name', 'target/name/'])
900
source.add('name', 'new-name')
901
source.commit('added file')
902
build_tree(source.basis_tree(), target)
903
self.assertEqual([DuplicateEntry('Moved existing file to',
904
'name.moved', 'name', None, 'new-name')], target.conflicts())
906
def test_raises_in_populated(self):
907
source = self.make_branch_and_tree('source')
908
self.build_tree(['source/name'])
910
source.commit('added name')
911
target = self.make_branch_and_tree('target')
912
self.build_tree(['target/name'])
914
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
915
build_tree, source.basis_tree(), target)
706
918
class MockTransform(object):
708
920
def has_named_child(self, by_parent, parent_id, name):