301
320
def _get_check(self, name):
302
321
return getattr(self, 'check_%s' % name)
323
def do_nothing(self):
324
return (None, None, [])
326
def do_create_file(self):
327
return ('file', 'file-id',
328
[('add', ('file', 'file-id', 'file', 'trunk content\n'))])
330
def do_create_file_a(self):
331
return ('file', 'file-a-id',
332
[('add', ('file', 'file-a-id', 'file', 'file a content\n'))])
334
def check_file_content_a(self):
335
self.assertFileEqual('file a content\n', 'branch/file')
337
def do_create_file_b(self):
338
return ('file', 'file-b-id',
339
[('add', ('file', 'file-b-id', 'file', 'file b content\n'))])
341
def check_file_content_b(self):
342
self.assertFileEqual('file b content\n', 'branch/file')
344
def do_create_dir(self):
345
return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
347
def do_modify_file(self):
348
return ('file', 'file-id',
349
[('modify', ('file-id', 'trunk content\nmore content\n'))])
351
def check_file_has_more_content(self):
352
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
354
def do_delete_file(self):
355
return ('file', 'file-id', [('unversion', 'file-id')])
357
def check_file_doesnt_exist(self):
358
self.failIfExists('branch/file')
360
def do_rename_file(self):
361
return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
363
def check_file_renamed(self):
364
self.failIfExists('branch/file')
365
self.failUnlessExists('branch/new-file')
367
def do_rename_file2(self):
368
return ('new-file2', 'file-id', [('rename', ('file', 'new-file2'))])
370
def check_file_renamed2(self):
371
self.failIfExists('branch/file')
372
self.failUnlessExists('branch/new-file2')
374
def do_rename_dir(self):
375
return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
377
def check_dir_renamed(self):
378
self.failIfExists('branch/dir')
379
self.failUnlessExists('branch/new-dir')
381
def do_rename_dir2(self):
382
return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
384
def check_dir_renamed2(self):
385
self.failIfExists('branch/dir')
386
self.failUnlessExists('branch/new-dir2')
388
def do_delete_dir(self):
389
return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
391
def check_dir_doesnt_exist(self):
392
self.failIfExists('branch/dir')
304
394
def _merge_other_into_this(self):
305
395
b = self.builder.get_branch()
306
396
wt = b.bzrdir.sprout('branch').open_workingtree()
328
418
wt = self._merge_other_into_this()
329
419
self.assertConflict(wt)
330
420
self.check_resolved(wt, 'take_this')
331
check_this = self._get_check(self._this['check'])
421
check_this = self._get_check(self._check_this)
334
424
def test_resolve_taking_other(self):
335
425
wt = self._merge_other_into_this()
336
426
self.assertConflict(wt)
337
427
self.check_resolved(wt, 'take_other')
338
check_other = self._get_check(self._other['check'])
428
check_other = self._get_check(self._check_other)
342
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
344
_conflict_type = conflicts.TextConflict
346
# Set by the scenarios
347
# path and file-id for the file involved in the conflict
351
scenarios = mirror_scenarios(
353
# File modified on both sides
354
(dict(_base_actions='create_file',
355
_path='file', _file_id='file-id'),
357
dict(actions='modify_file_A', check='file_has_content_A')),
359
dict(actions='modify_file_B', check='file_has_content_B')),),
360
# File modified on both sides in dir
361
(dict(_base_actions='create_file_in_dir',
362
_path='dir/file', _file_id='file-id'),
363
('filed_modified_A_in_dir',
364
dict(actions='modify_file_A',
365
check='file_in_dir_has_content_A')),
367
dict(actions='modify_file_B',
368
check='file_in_dir_has_content_B')),),
371
def do_create_file(self, path='file'):
372
return [('add', (path, 'file-id', 'file', 'trunk content\n'))]
374
def do_modify_file_A(self):
375
return [('modify', ('file-id', 'trunk content\nfeature A\n'))]
377
def do_modify_file_B(self):
378
return [('modify', ('file-id', 'trunk content\nfeature B\n'))]
380
def check_file_has_content_A(self, path='file'):
381
self.assertFileEqual('trunk content\nfeature A\n',
382
osutils.pathjoin('branch', path))
384
def check_file_has_content_B(self, path='file'):
385
self.assertFileEqual('trunk content\nfeature B\n',
386
osutils.pathjoin('branch', path))
388
def do_create_file_in_dir(self):
389
return [('add', ('dir', 'dir-id', 'directory', '')),
390
] + self.do_create_file('dir/file')
392
def check_file_in_dir_has_content_A(self):
393
self.check_file_has_content_A('dir/file')
395
def check_file_in_dir_has_content_B(self):
396
self.check_file_has_content_B('dir/file')
398
def _get_resolve_path_arg(self, wt, action):
401
def assertTextConflict(self, wt, c):
402
self.assertEqual(self._file_id, c.file_id)
403
self.assertEqual(self._path, c.path)
404
_assert_conflict = assertTextConflict
407
432
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
409
_conflict_type = conflicts.ContentsConflict
411
# Set by the scenarios
412
# path and file-id for the file involved in the conflict
416
scenarios = mirror_scenarios(
418
# File modified/deleted
419
(dict(_base_actions='create_file',
420
_path='file', _file_id='file-id'),
422
dict(actions='modify_file', check='file_has_more_content')),
424
dict(actions='delete_file', check='file_doesnt_exist')),),
425
# File modified/deleted in dir
426
(dict(_base_actions='create_file_in_dir',
427
_path='dir/file', _file_id='file-id'),
428
('file_modified_in_dir',
429
dict(actions='modify_file_in_dir',
430
check='file_in_dir_has_more_content')),
431
('file_deleted_in_dir',
432
dict(actions='delete_file',
433
check='file_in_dir_doesnt_exist')),),
436
def do_create_file(self):
437
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
439
def do_modify_file(self):
440
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
442
def check_file_has_more_content(self):
443
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
445
def do_delete_file(self):
446
return [('unversion', 'file-id')]
448
def check_file_doesnt_exist(self):
449
self.assertPathDoesNotExist('branch/file')
451
def do_create_file_in_dir(self):
452
return [('add', ('dir', 'dir-id', 'directory', '')),
453
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
455
def do_modify_file_in_dir(self):
456
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
458
def check_file_in_dir_has_more_content(self):
459
self.assertFileEqual('trunk content\nmore content\n', 'branch/dir/file')
461
def check_file_in_dir_doesnt_exist(self):
462
self.assertPathDoesNotExist('branch/dir/file')
464
def _get_resolve_path_arg(self, wt, action):
434
_conflict_type = conflicts.ContentsConflict,
436
def scenarios(klass):
438
(('file_modified', dict(actions='modify_file',
439
check='file_has_more_content')),
440
('file_deleted', dict(actions='delete_file',
441
check='file_doesnt_exist')),
442
dict(_actions_base='create_file', _item_path='file')),
444
return klass.mirror_scenarios(base_scenarios)
467
446
def assertContentsConflict(self, wt, c):
468
self.assertEqual(self._file_id, c.file_id)
469
self.assertEqual(self._path, c.path)
447
self.assertEqual(self._other_id, c.file_id)
448
self.assertEqual(self._other_path, c.path)
470
449
_assert_conflict = assertContentsConflict
473
453
class TestResolvePathConflict(TestParametrizedResolveConflicts):
475
_conflict_type = conflicts.PathConflict
477
def do_nothing(self):
480
# Each side dict additionally defines:
481
# - path path involved (can be '<deleted>')
483
scenarios = mirror_scenarios(
485
# File renamed/deleted
486
(dict(_base_actions='create_file'),
488
dict(actions='rename_file', check='file_renamed',
489
path='new-file', file_id='file-id')),
491
dict(actions='delete_file', check='file_doesnt_exist',
492
# PathConflicts deletion handling requires a special
494
path='<deleted>', file_id='file-id')),),
495
# File renamed/deleted in dir
496
(dict(_base_actions='create_file_in_dir'),
497
('file_renamed_in_dir',
498
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
499
path='dir/new-file', file_id='file-id')),
501
dict(actions='delete_file', check='file_in_dir_doesnt_exist',
502
# PathConflicts deletion handling requires a special
504
path='<deleted>', file_id='file-id')),),
505
# File renamed/renamed differently
506
(dict(_base_actions='create_file'),
508
dict(actions='rename_file', check='file_renamed',
509
path='new-file', file_id='file-id')),
455
_conflict_type = conflicts.PathConflict,
458
def scenarios(klass):
459
for_file = dict(_actions_base='create_file',
460
_item_path='new-file', _item_id='file-id',)
461
for_dir = dict(_actions_base='create_dir',
462
_item_path='new-dir', _item_id='dir-id',)
465
dict(actions='rename_file', check='file_renamed')),
467
dict(actions='delete_file', check='file_doesnt_exist')),
470
dict(actions='rename_file', check='file_renamed')),
510
471
('file_renamed2',
511
dict(actions='rename_file2', check='file_renamed2',
512
path='new-file2', file_id='file-id')),),
513
# Dir renamed/deleted
514
(dict(_base_actions='create_dir'),
516
dict(actions='rename_dir', check='dir_renamed',
517
path='new-dir', file_id='dir-id')),
472
dict(actions='rename_file2', check='file_renamed2')),
475
dict(actions='rename_dir', check='dir_renamed')),
519
dict(actions='delete_dir', check='dir_doesnt_exist',
520
# PathConflicts deletion handling requires a special
522
path='<deleted>', file_id='dir-id')),),
523
# Dir renamed/renamed differently
524
(dict(_base_actions='create_dir'),
526
dict(actions='rename_dir', check='dir_renamed',
527
path='new-dir', file_id='dir-id')),
477
dict(actions='delete_dir', check='dir_doesnt_exist')),
480
dict(actions='rename_dir', check='dir_renamed')),
529
dict(actions='rename_dir2', check='dir_renamed2',
530
path='new-dir2', file_id='dir-id')),),
533
def do_create_file(self):
534
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
536
def do_create_dir(self):
537
return [('add', ('dir', 'dir-id', 'directory', ''))]
539
def do_rename_file(self):
540
return [('rename', ('file', 'new-file'))]
542
def check_file_renamed(self):
543
self.assertPathDoesNotExist('branch/file')
544
self.assertPathExists('branch/new-file')
546
def do_rename_file2(self):
547
return [('rename', ('file', 'new-file2'))]
549
def check_file_renamed2(self):
550
self.assertPathDoesNotExist('branch/file')
551
self.assertPathExists('branch/new-file2')
553
def do_rename_dir(self):
554
return [('rename', ('dir', 'new-dir'))]
556
def check_dir_renamed(self):
557
self.assertPathDoesNotExist('branch/dir')
558
self.assertPathExists('branch/new-dir')
560
def do_rename_dir2(self):
561
return [('rename', ('dir', 'new-dir2'))]
563
def check_dir_renamed2(self):
564
self.assertPathDoesNotExist('branch/dir')
565
self.assertPathExists('branch/new-dir2')
482
dict(actions='rename_dir2', check='dir_renamed2')),
485
return klass.mirror_scenarios(base_scenarios)
567
487
def do_delete_file(self):
568
return [('unversion', 'file-id')]
570
def check_file_doesnt_exist(self):
571
self.assertPathDoesNotExist('branch/file')
573
def do_delete_dir(self):
574
return [('unversion', 'dir-id')]
576
def check_dir_doesnt_exist(self):
577
self.assertPathDoesNotExist('branch/dir')
579
def do_create_file_in_dir(self):
580
return [('add', ('dir', 'dir-id', 'directory', '')),
581
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
583
def do_rename_file_in_dir(self):
584
return [('rename', ('dir/file', 'dir/new-file'))]
586
def check_file_in_dir_renamed(self):
587
self.assertPathDoesNotExist('branch/dir/file')
588
self.assertPathExists('branch/dir/new-file')
590
def check_file_in_dir_doesnt_exist(self):
591
self.assertPathDoesNotExist('branch/dir/file')
593
def _get_resolve_path_arg(self, wt, action):
594
tpath = self._this['path']
595
opath = self._other['path']
596
if tpath == '<deleted>':
488
sup = super(TestResolvePathConflict, self).do_delete_file()
489
# PathConflicts handle deletion differently and requires a special
491
return ('<deleted>',) + sup[1:]
602
493
def assertPathConflict(self, wt, c):
603
tpath = self._this['path']
604
tfile_id = self._this['file_id']
605
opath = self._other['path']
606
ofile_id = self._other['file_id']
607
self.assertEqual(tfile_id, ofile_id) # Sanity check
608
self.assertEqual(tfile_id, c.file_id)
609
self.assertEqual(tpath, c.path)
610
self.assertEqual(opath, c.conflict_path)
494
self.assertEqual(self._item_id, c.file_id)
495
self.assertEqual(self._this_path, c.path)
496
self.assertEqual(self._other_path, c.conflict_path)
611
497
_assert_conflict = assertPathConflict
807
667
def test_keep_them_all(self):
808
668
self.run_script("""
809
669
$ bzr resolve dir
810
2>2 conflict(s) resolved, 0 remaining
811
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
670
$ bzr commit --strict -m 'No more conflicts nor unknown files'
814
673
def test_adopt_child(self):
815
674
self.run_script("""
816
$ bzr mv -q dir/file2 file2
817
$ bzr rm -q dir --force
675
$ bzr mv dir/file2 file2
818
677
$ bzr resolve dir
819
2>2 conflict(s) resolved, 0 remaining
820
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
678
$ bzr commit --strict -m 'No more conflicts nor unknown files'
823
681
def test_kill_them_all(self):
824
682
self.run_script("""
825
$ bzr rm -q dir --force
826
684
$ bzr resolve dir
827
2>2 conflict(s) resolved, 0 remaining
828
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
685
$ bzr commit --strict -m 'No more conflicts nor unknown files'
831
688
def test_resolve_taking_this(self):
832
689
self.run_script("""
833
690
$ bzr resolve --take-this dir
834
2>2 conflict(s) resolved, 0 remaining
835
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
691
$ bzr commit --strict -m 'No more conflicts nor unknown files'
838
694
def test_resolve_taking_other(self):
839
695
self.run_script("""
840
696
$ bzr resolve --take-other dir
843
2>2 conflict(s) resolved, 0 remaining
844
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
697
$ bzr commit --strict -m 'No more conflicts nor unknown files'
848
701
class TestResolveParentLoop(TestParametrizedResolveConflicts):
850
_conflict_type = conflicts.ParentLoop
855
# Each side dict additionally defines:
856
# - dir_id: the directory being moved
857
# - target_id: The target directory
858
# - xfail: whether the test is expected to fail if the action is
859
# involved as 'other'
860
scenarios = mirror_scenarios(
862
# Dirs moved into each other
863
(dict(_base_actions='create_dir1_dir2'),
865
dict(actions='move_dir1_into_dir2', check='dir1_moved',
866
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
868
dict(actions='move_dir2_into_dir1', check='dir2_moved',
869
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
870
# Subdirs moved into each other
871
(dict(_base_actions='create_dir1_4'),
873
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
874
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
876
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
877
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
703
_conflict_type = conflicts.ParentLoop,
705
def scenarios(klass):
707
(('dir1_into_dir2', dict(actions='move_dir1_into_dir2',
708
check='dir1_moved')),
709
('dir2_into_dir1', dict(actions='move_dir2_into_dir1',
710
check='dir2_moved')),
711
dict(_actions_base='create_dir1_dir2')),
712
(('dir1_into_dir4', dict(actions='move_dir1_into_dir4',
713
check='dir1_2_moved')),
714
('dir3_into_dir2', dict(actions='move_dir3_into_dir2',
715
check='dir3_4_moved')),
716
dict(_actions_base='create_dir1_4')),
718
return klass.mirror_scenarios(base_scenarios)
880
720
def do_create_dir1_dir2(self):
881
return [('add', ('dir1', 'dir1-id', 'directory', '')),
882
('add', ('dir2', 'dir2-id', 'directory', '')),]
722
[('add', ('dir1', 'dir1-id', 'directory', '')),
723
('add', ('dir2', 'dir2-id', 'directory', '')),
884
726
def do_move_dir1_into_dir2(self):
885
return [('rename', ('dir1', 'dir2/dir1'))]
727
# The arguments are the file-id to move and the targeted file-id dir.
728
return ('dir1-id', 'dir2-id', [('rename', ('dir1', 'dir2/dir1'))])
887
730
def check_dir1_moved(self):
888
self.assertPathDoesNotExist('branch/dir1')
889
self.assertPathExists('branch/dir2/dir1')
731
self.failIfExists('branch/dir1')
732
self.failUnlessExists('branch/dir2/dir1')
891
734
def do_move_dir2_into_dir1(self):
892
return [('rename', ('dir2', 'dir1/dir2'))]
735
# The arguments are the file-id to move and the targeted file-id dir.
736
return ('dir2-id', 'dir1-id', [('rename', ('dir2', 'dir1/dir2'))])
894
738
def check_dir2_moved(self):
895
self.assertPathDoesNotExist('branch/dir2')
896
self.assertPathExists('branch/dir1/dir2')
739
self.failIfExists('branch/dir2')
740
self.failUnlessExists('branch/dir1/dir2')
898
742
def do_create_dir1_4(self):
899
return [('add', ('dir1', 'dir1-id', 'directory', '')),
900
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
901
('add', ('dir3', 'dir3-id', 'directory', '')),
902
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
744
[('add', ('dir1', 'dir1-id', 'directory', '')),
745
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
746
('add', ('dir3', 'dir3-id', 'directory', '')),
747
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),
904
750
def do_move_dir1_into_dir4(self):
905
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
751
# The arguments are the file-id to move and the targeted file-id dir.
752
return ('dir1-id', 'dir4-id',
753
[('rename', ('dir1', 'dir3/dir4/dir1'))])
907
755
def check_dir1_2_moved(self):
908
self.assertPathDoesNotExist('branch/dir1')
909
self.assertPathExists('branch/dir3/dir4/dir1')
910
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
756
self.failIfExists('branch/dir1')
757
self.failUnlessExists('branch/dir3/dir4/dir1')
758
self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
912
760
def do_move_dir3_into_dir2(self):
913
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
761
# The arguments are the file-id to move and the targeted file-id dir.
762
return ('dir3-id', 'dir2-id',
763
[('rename', ('dir3', 'dir1/dir2/dir3'))])
915
765
def check_dir3_4_moved(self):
916
self.assertPathDoesNotExist('branch/dir3')
917
self.assertPathExists('branch/dir1/dir2/dir3')
918
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
766
self.failIfExists('branch/dir3')
767
self.failUnlessExists('branch/dir1/dir2/dir3')
768
self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
920
770
def _get_resolve_path_arg(self, wt, action):
921
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
771
# ParentLoop is unsual as it says:
772
# moving <conflict_path> into <path>. Cancelled move.
922
773
# But since <path> doesn't exist in the working tree, we need to use
923
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
924
return wt.id2path(self._other['dir_id'])
774
# <conflict_path> instead
775
path = wt.id2path(self._other_id)
926
778
def assertParentLoop(self, wt, c):
927
self.assertEqual(self._other['dir_id'], c.file_id)
928
self.assertEqual(self._other['target_id'], c.conflict_file_id)
779
if 'taking_other(' in self.id() and 'dir4' in self.id():
780
raise tests.KnownFailure(
781
"ParentLoop doesn't carry enough info to resolve")
782
# The relevant file-ids are other_args swapped (which is the main
783
# reason why they should be renamed other_args instead of Other_path
784
# and other_id). In the conflict object, they represent:
785
# c.file_id: the directory being moved
786
# c.conflict_id_id: The target directory
787
self.assertEqual(self._other_path, c.file_id)
788
self.assertEqual(self._other_id, c.conflict_file_id)
929
789
# The conflict paths are irrelevant (they are deterministic but not
930
790
# worth checking since they don't provide the needed information
932
if self._other['xfail']:
933
# It's a bit hackish to raise from here relying on being called for
934
# both tests but this avoid overriding test_resolve_taking_other
935
raise tests.KnownFailure(
936
"ParentLoop doesn't carry enough info to resolve --take-other")
937
792
_assert_conflict = assertParentLoop
795
class OldTestResolveParentLoop(TestResolveConflicts):
802
$ bzr commit -m 'Create trunk'
805
$ bzr commit -m 'Moved dir2 into dir1'
807
$ bzr branch . -r 1 ../branch
810
$ bzr commit -m 'Moved dir1 into dir2'
813
2>Conflict moving dir2 into dir2/dir1. Cancelled move.
814
2>1 conflicts encountered.
817
def test_take_this(self):
820
$ bzr commit --strict -m 'No more conflicts nor unknown files'
823
def test_take_other(self):
825
$ bzr mv dir2/dir1 dir1
828
$ bzr commit --strict -m 'No more conflicts nor unknown files'
831
def test_resolve_taking_this(self):
833
$ bzr resolve --take-this dir2
834
$ bzr commit --strict -m 'No more conflicts nor unknown files'
836
self.failUnlessExists('dir2')
838
def test_resolve_taking_other(self):
840
$ bzr resolve --take-other dir2
841
$ bzr commit --strict -m 'No more conflicts nor unknown files'
843
self.failUnlessExists('dir1')
940
846
class TestResolveNonDirectoryParent(TestResolveConflicts):
948
$ bzr commit -m 'Create trunk' -q
852
$ bzr commit -m 'Create trunk'
949
853
$ echo "Boing" >foo/bar
951
$ bzr commit -q -m 'Add foo/bar'
952
$ bzr branch -q . -r 1 ../branch
855
$ bzr commit -m 'Add foo/bar'
857
$ bzr branch . -r 1 ../branch
955
860
$ echo "Boo!" >foo
956
$ bzr commit -q -m 'foo is now a file'
861
$ bzr commit -m 'foo is now a file'
957
863
$ bzr merge ../trunk
959
865
2>RK foo => foo.new/