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
410
426
'dorothy.moved', 'dorothy', None,
412
428
self.assertEqual(cooked_conflicts[1], duplicate_id)
413
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
429
missing_parent = MissingParent('Created directory', 'munchkincity',
431
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
414
432
self.assertEqual(cooked_conflicts[2], missing_parent)
415
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
433
unversioned_parent = UnversionedParent('Versioned directory',
436
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
417
438
self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
439
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
419
440
'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
self.assertEqual(cooked_conflicts[4], parent_loop)
421
self.assertEqual(len(cooked_conflicts), 5)
441
self.assertEqual(cooked_conflicts[4], deleted_parent)
442
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
443
self.assertEqual(cooked_conflicts[6], parent_loop)
444
self.assertEqual(len(cooked_conflicts), 7)
424
447
def test_string_conflicts(self):
434
457
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
435
458
'Unversioned existing file '
436
459
'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'
460
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
461
' munchkincity. Created directory.')
462
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
463
' versioned, but has versioned'
464
' children. Versioned directory.')
465
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
466
" is not empty. Not deleting.")
467
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
468
' versioned, but has versioned'
469
' children. Versioned directory.')
470
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
442
471
' oz/emeraldcity. Cancelled move.')
444
473
def test_moving_versioned_directories(self):
488
517
self.assertEqual(find_interesting(wt, wt, ['vfile']),
489
518
set(['myfile-id']))
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
519
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
522
def test_set_executability_order(self):
523
"""Ensure that executability behaves the same, no matter what order.
525
- create file and set executability simultaneously
526
- create file and set executability afterward
527
- unsetting the executability of a file whose executability has not been
528
declared should throw an exception (this may happen when a
529
merge attempts to create a file with a duplicate ID)
531
transform, root = self.get_transform()
533
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
535
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
536
transform.set_executability(True, sac)
537
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
538
self.assertRaises(KeyError, transform.set_executability, None, uws)
540
self.assertTrue(wt.is_executable('soc'))
541
self.assertTrue(wt.is_executable('sac'))
543
def test_preserve_mode(self):
544
"""File mode is preserved when replacing content"""
545
if sys.platform == 'win32':
546
raise TestSkipped('chmod has no effect on win32')
547
transform, root = self.get_transform()
548
transform.new_file('file1', root, 'contents', 'file1-id', True)
550
self.assertTrue(self.wt.is_executable('file1-id'))
551
transform, root = self.get_transform()
552
file1_id = transform.trans_id_tree_file_id('file1-id')
553
transform.delete_contents(file1_id)
554
transform.create_file('contents2', file1_id)
556
self.assertTrue(self.wt.is_executable('file1-id'))
558
def test__set_mode_stats_correctly(self):
559
"""_set_mode stats to determine file mode."""
560
if sys.platform == 'win32':
561
raise TestSkipped('chmod has no effect on win32')
565
def instrumented_stat(path):
566
stat_paths.append(path)
567
return real_stat(path)
569
transform, root = self.get_transform()
571
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
572
file_id='bar-id-1', executable=False)
575
transform, root = self.get_transform()
576
bar1_id = transform.trans_id_tree_path('bar')
577
bar2_id = transform.trans_id_tree_path('bar2')
579
os.stat = instrumented_stat
580
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
585
bar1_abspath = self.wt.abspath('bar')
586
self.assertEqual([bar1_abspath], stat_paths)
494
589
class TransformGroup(object):
495
590
def __init__(self, dirname):
702
799
self.assertIs(os.path.isdir('b/foo'), True)
703
800
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
801
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
803
def test_file_conflict_handling(self):
804
"""Ensure that when building trees, conflict handling is done"""
805
source = self.make_branch_and_tree('source')
806
target = self.make_branch_and_tree('target')
807
self.build_tree(['source/file', 'target/file'])
808
source.add('file', 'new-file')
809
source.commit('added file')
810
build_tree(source.basis_tree(), target)
811
self.assertEqual([DuplicateEntry('Moved existing file to',
812
'file.moved', 'file', None, 'new-file')],
814
target2 = self.make_branch_and_tree('target2')
815
target_file = file('target2/file', 'wb')
817
source_file = file('source/file', 'rb')
819
target_file.write(source_file.read())
824
build_tree(source.basis_tree(), target2)
825
self.assertEqual([], target2.conflicts())
827
def test_symlink_conflict_handling(self):
828
"""Ensure that when building trees, conflict handling is done"""
829
if not has_symlinks():
830
raise TestSkipped('Test requires symlink support')
831
source = self.make_branch_and_tree('source')
832
os.symlink('foo', 'source/symlink')
833
source.add('symlink', 'new-symlink')
834
source.commit('added file')
835
target = self.make_branch_and_tree('target')
836
os.symlink('bar', 'target/symlink')
837
build_tree(source.basis_tree(), target)
838
self.assertEqual([DuplicateEntry('Moved existing file to',
839
'symlink.moved', 'symlink', None, 'new-symlink')],
841
target = self.make_branch_and_tree('target2')
842
os.symlink('foo', 'target2/symlink')
843
build_tree(source.basis_tree(), target)
844
self.assertEqual([], target.conflicts())
846
def test_directory_conflict_handling(self):
847
"""Ensure that when building trees, conflict handling is done"""
848
source = self.make_branch_and_tree('source')
849
target = self.make_branch_and_tree('target')
850
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
851
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
852
source.commit('added file')
853
build_tree(source.basis_tree(), target)
854
self.assertEqual([], target.conflicts())
855
self.failUnlessExists('target/dir1/file')
857
# Ensure contents are merged
858
target = self.make_branch_and_tree('target2')
859
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
860
build_tree(source.basis_tree(), target)
861
self.assertEqual([], target.conflicts())
862
self.failUnlessExists('target2/dir1/file2')
863
self.failUnlessExists('target2/dir1/file')
865
# Ensure new contents are suppressed for existing branches
866
target = self.make_branch_and_tree('target3')
867
self.make_branch('target3/dir1')
868
self.build_tree(['target3/dir1/file2'])
869
build_tree(source.basis_tree(), target)
870
self.failIfExists('target3/dir1/file')
871
self.failUnlessExists('target3/dir1/file2')
872
self.failUnlessExists('target3/dir1.diverted/file')
873
self.assertEqual([DuplicateEntry('Diverted to',
874
'dir1.diverted', 'dir1', 'new-dir1', None)],
877
target = self.make_branch_and_tree('target4')
878
self.build_tree(['target4/dir1/'])
879
self.make_branch('target4/dir1/file')
880
build_tree(source.basis_tree(), target)
881
self.failUnlessExists('target4/dir1/file')
882
self.assertEqual('directory', file_kind('target4/dir1/file'))
883
self.failUnlessExists('target4/dir1/file.diverted')
884
self.assertEqual([DuplicateEntry('Diverted to',
885
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
888
def test_mixed_conflict_handling(self):
889
"""Ensure that when building trees, conflict handling is done"""
890
source = self.make_branch_and_tree('source')
891
target = self.make_branch_and_tree('target')
892
self.build_tree(['source/name', 'target/name/'])
893
source.add('name', 'new-name')
894
source.commit('added file')
895
build_tree(source.basis_tree(), target)
896
self.assertEqual([DuplicateEntry('Moved existing file to',
897
'name.moved', 'name', None, 'new-name')], target.conflicts())
899
def test_raises_in_populated(self):
900
source = self.make_branch_and_tree('source')
901
self.build_tree(['source/name'])
903
source.commit('added name')
904
target = self.make_branch_and_tree('target')
905
self.build_tree(['target/name'])
907
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
706
911
class MockTransform(object):
708
913
def has_named_child(self, by_parent, parent_id, name):