326
364
'c', but not 'b'.
328
366
this_tree = self.make_branch_and_tree('this')
329
self.build_tree_contents([('this/file', "a\n")])
367
self.build_tree_contents([('this/file', b"a\n")])
330
368
this_tree.add('file')
331
369
this_tree.commit('rev1')
332
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
333
self.build_tree_contents([('other/file', "a\nb\n")])
334
other_tree.commit('rev2b', rev_id='rev2b')
335
self.build_tree_contents([('other/file', "c\na\nb\n")])
336
other_tree.commit('rev3b', rev_id='rev3b')
370
other_tree = this_tree.controldir.sprout('other').open_workingtree()
371
self.build_tree_contents([('other/file', b"a\nb\n")])
372
other_tree.commit('rev2b', rev_id=b'rev2b')
373
self.build_tree_contents([('other/file', b"c\na\nb\n")])
374
other_tree.commit('rev3b', rev_id=b'rev3b')
337
375
this_tree.lock_write()
338
376
self.addCleanup(this_tree.unlock)
339
377
return this_tree, other_tree
341
379
def test_weave_cherrypick(self):
342
380
this_tree, other_tree = self.prepare_cherrypick()
343
merger = _mod_merge.Merger.from_revision_ids(None,
344
this_tree, 'rev3b', 'rev2b', other_tree.branch)
381
merger = _mod_merge.Merger.from_revision_ids(
382
this_tree, b'rev3b', b'rev2b', other_tree.branch)
345
383
merger.merge_type = _mod_merge.WeaveMerger
346
384
merger.do_merge()
347
self.assertFileEqual('c\na\n', 'this/file')
385
self.assertFileEqual(b'c\na\n', 'this/file')
349
387
def test_weave_cannot_reverse_cherrypick(self):
350
388
this_tree, other_tree = self.prepare_cherrypick()
351
merger = _mod_merge.Merger.from_revision_ids(None,
352
this_tree, 'rev2b', 'rev3b', other_tree.branch)
389
merger = _mod_merge.Merger.from_revision_ids(
390
this_tree, b'rev2b', b'rev3b', other_tree.branch)
353
391
merger.merge_type = _mod_merge.WeaveMerger
354
392
self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
356
394
def test_merge3_can_reverse_cherrypick(self):
357
395
this_tree, other_tree = self.prepare_cherrypick()
358
merger = _mod_merge.Merger.from_revision_ids(None,
359
this_tree, 'rev2b', 'rev3b', other_tree.branch)
396
merger = _mod_merge.Merger.from_revision_ids(
397
this_tree, b'rev2b', b'rev3b', other_tree.branch)
360
398
merger.merge_type = _mod_merge.Merge3Merger
361
399
merger.do_merge()
363
401
def test_merge3_will_detect_cherrypick(self):
364
402
this_tree = self.make_branch_and_tree('this')
365
self.build_tree_contents([('this/file', "a\n")])
403
self.build_tree_contents([('this/file', b"a\n")])
366
404
this_tree.add('file')
367
405
this_tree.commit('rev1')
368
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
369
self.build_tree_contents([('other/file', "a\nb\n")])
370
other_tree.commit('rev2b', rev_id='rev2b')
371
self.build_tree_contents([('other/file', "a\nb\nc\n")])
372
other_tree.commit('rev3b', rev_id='rev3b')
406
other_tree = this_tree.controldir.sprout('other').open_workingtree()
407
self.build_tree_contents([('other/file', b"a\nb\n")])
408
other_tree.commit('rev2b', rev_id=b'rev2b')
409
self.build_tree_contents([('other/file', b"a\nb\nc\n")])
410
other_tree.commit('rev3b', rev_id=b'rev3b')
373
411
this_tree.lock_write()
374
412
self.addCleanup(this_tree.unlock)
376
merger = _mod_merge.Merger.from_revision_ids(None,
377
this_tree, 'rev3b', 'rev2b', other_tree.branch)
414
merger = _mod_merge.Merger.from_revision_ids(
415
this_tree, b'rev3b', b'rev2b', other_tree.branch)
378
416
merger.merge_type = _mod_merge.Merge3Merger
379
417
merger.do_merge()
380
self.assertFileEqual('a\n'
384
'>>>>>>> MERGE-SOURCE\n',
418
self.assertFileEqual(b'a\n'
422
b'>>>>>>> MERGE-SOURCE\n',
425
def test_merge_reverse_revision_range(self):
426
tree = self.make_branch_and_tree(".")
428
self.addCleanup(tree.unlock)
429
self.build_tree(['a'])
431
first_rev = tree.commit("added a")
432
merger = _mod_merge.Merger.from_revision_ids(tree,
433
_mod_revision.NULL_REVISION,
435
merger.merge_type = _mod_merge.Merge3Merger
436
merger.interesting_files = 'a'
437
conflict_count = merger.do_merge()
438
self.assertEqual(0, conflict_count)
440
self.assertPathDoesNotExist("a")
442
self.assertPathExists("a")
387
444
def test_make_merger(self):
388
445
this_tree = self.make_branch_and_tree('this')
389
this_tree.commit('rev1', rev_id='rev1')
390
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
391
this_tree.commit('rev2', rev_id='rev2a')
392
other_tree.commit('rev2', rev_id='rev2b')
446
this_tree.commit('rev1', rev_id=b'rev1')
447
other_tree = this_tree.controldir.sprout('other').open_workingtree()
448
this_tree.commit('rev2', rev_id=b'rev2a')
449
other_tree.commit('rev2', rev_id=b'rev2b')
393
450
this_tree.lock_write()
394
451
self.addCleanup(this_tree.unlock)
395
merger = _mod_merge.Merger.from_revision_ids(None,
396
this_tree, 'rev2b', other_branch=other_tree.branch)
452
merger = _mod_merge.Merger.from_revision_ids(
453
this_tree, b'rev2b', other_branch=other_tree.branch)
397
454
merger.merge_type = _mod_merge.Merge3Merger
398
455
tree_merger = merger.make_merger()
399
456
self.assertIs(_mod_merge.Merge3Merger, tree_merger.__class__)
400
self.assertEqual('rev2b', tree_merger.other_tree.get_revision_id())
401
self.assertEqual('rev1', tree_merger.base_tree.get_revision_id())
457
self.assertEqual(b'rev2b',
458
tree_merger.other_tree.get_revision_id())
459
self.assertEqual(b'rev1',
460
tree_merger.base_tree.get_revision_id())
461
self.assertEqual(other_tree.branch, tree_merger.other_branch)
403
463
def test_make_preview_transform(self):
404
464
this_tree = self.make_branch_and_tree('this')
405
self.build_tree_contents([('this/file', '1\n')])
406
this_tree.add('file', 'file-id')
407
this_tree.commit('rev1', rev_id='rev1')
408
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
409
self.build_tree_contents([('this/file', '1\n2a\n')])
410
this_tree.commit('rev2', rev_id='rev2a')
411
self.build_tree_contents([('other/file', '2b\n1\n')])
412
other_tree.commit('rev2', rev_id='rev2b')
465
self.build_tree_contents([('this/file', b'1\n')])
466
this_tree.add('file', b'file-id')
467
this_tree.commit('rev1', rev_id=b'rev1')
468
other_tree = this_tree.controldir.sprout('other').open_workingtree()
469
self.build_tree_contents([('this/file', b'1\n2a\n')])
470
this_tree.commit('rev2', rev_id=b'rev2a')
471
self.build_tree_contents([('other/file', b'2b\n1\n')])
472
other_tree.commit('rev2', rev_id=b'rev2b')
413
473
this_tree.lock_write()
414
474
self.addCleanup(this_tree.unlock)
415
merger = _mod_merge.Merger.from_revision_ids(None,
416
this_tree, 'rev2b', other_branch=other_tree.branch)
475
merger = _mod_merge.Merger.from_revision_ids(
476
this_tree, b'rev2b', other_branch=other_tree.branch)
417
477
merger.merge_type = _mod_merge.Merge3Merger
418
478
tree_merger = merger.make_merger()
419
tt = tree_merger.make_preview_transform()
420
self.addCleanup(tt.finalize)
421
preview_tree = tt.get_preview_tree()
422
tree_file = this_tree.get_file('file-id')
424
self.assertEqual('1\n2a\n', tree_file.read())
427
preview_file = preview_tree.get_file('file-id')
429
self.assertEqual('2b\n1\n2a\n', preview_file.read())
479
with tree_merger.make_preview_transform() as tt:
480
preview_tree = tt.get_preview_tree()
481
with this_tree.get_file('file') as tree_file:
482
self.assertEqual(b'1\n2a\n', tree_file.read())
483
with preview_tree.get_file('file') as preview_file:
484
self.assertEqual(b'2b\n1\n2a\n', preview_file.read())
433
486
def test_do_merge(self):
434
487
this_tree = self.make_branch_and_tree('this')
435
self.build_tree_contents([('this/file', '1\n')])
436
this_tree.add('file', 'file-id')
437
this_tree.commit('rev1', rev_id='rev1')
438
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
439
self.build_tree_contents([('this/file', '1\n2a\n')])
440
this_tree.commit('rev2', rev_id='rev2a')
441
self.build_tree_contents([('other/file', '2b\n1\n')])
442
other_tree.commit('rev2', rev_id='rev2b')
488
self.build_tree_contents([('this/file', b'1\n')])
489
this_tree.add('file', b'file-id')
490
this_tree.commit('rev1', rev_id=b'rev1')
491
other_tree = this_tree.controldir.sprout('other').open_workingtree()
492
self.build_tree_contents([('this/file', b'1\n2a\n')])
493
this_tree.commit('rev2', rev_id=b'rev2a')
494
self.build_tree_contents([('other/file', b'2b\n1\n')])
495
other_tree.commit('rev2', rev_id=b'rev2b')
443
496
this_tree.lock_write()
444
497
self.addCleanup(this_tree.unlock)
445
merger = _mod_merge.Merger.from_revision_ids(None,
446
this_tree, 'rev2b', other_branch=other_tree.branch)
498
merger = _mod_merge.Merger.from_revision_ids(
499
this_tree, b'rev2b', other_branch=other_tree.branch)
447
500
merger.merge_type = _mod_merge.Merge3Merger
448
501
tree_merger = merger.make_merger()
449
502
tt = tree_merger.do_merge()
450
tree_file = this_tree.get_file('file-id')
452
self.assertEqual('2b\n1\n2a\n', tree_file.read())
503
with this_tree.get_file('file') as tree_file:
504
self.assertEqual(b'2b\n1\n2a\n', tree_file.read())
506
def test_merge_require_tree_root(self):
507
tree = self.make_branch_and_tree(".")
509
self.addCleanup(tree.unlock)
510
self.build_tree(['a'])
512
first_rev = tree.commit("added a")
513
old_root_id = tree.path2id('')
514
merger = _mod_merge.Merger.from_revision_ids(tree,
515
_mod_revision.NULL_REVISION,
517
merger.merge_type = _mod_merge.Merge3Merger
518
conflict_count = merger.do_merge()
519
self.assertEqual(0, conflict_count)
520
self.assertEqual({''}, set(tree.all_versioned_paths()))
521
tree.set_parent_ids([])
456
523
def test_merge_add_into_deleted_root(self):
457
524
# Yes, people actually do this. And report bugs if it breaks.
458
525
source = self.make_branch_and_tree('source', format='rich-root-pack')
459
526
self.build_tree(['source/foo/'])
460
source.add('foo', 'foo-id')
527
source.add('foo', b'foo-id')
461
528
source.commit('Add foo')
462
target = source.bzrdir.sprout('target').open_workingtree()
463
subtree = target.extract('foo-id')
529
target = source.controldir.sprout('target').open_workingtree()
530
subtree = target.extract('foo')
464
531
subtree.commit('Delete root')
465
532
self.build_tree(['source/bar'])
466
source.add('bar', 'bar-id')
533
source.add('bar', b'bar-id')
467
534
source.commit('Add bar')
468
535
subtree.merge_from_branch(source.branch)
506
574
def add_uncommitted_version(self, key, parents, text):
507
575
self.plan_merge_vf.add_lines(key, parents,
508
[c+'\n' for c in text])
576
[bytes([c]) + b'\n' for c in bytearray(text)])
510
578
def setup_plan_merge(self):
511
self.add_rev('root', 'A', [], 'abc')
512
self.add_rev('root', 'B', ['A'], 'acehg')
513
self.add_rev('root', 'C', ['A'], 'fabg')
514
return _PlanMerge('B', 'C', self.plan_merge_vf, ('root',))
579
self.add_rev(b'root', b'A', [], b'abc')
580
self.add_rev(b'root', b'B', [b'A'], b'acehg')
581
self.add_rev(b'root', b'C', [b'A'], b'fabg')
582
return _PlanMerge(b'B', b'C', self.plan_merge_vf, (b'root',))
516
584
def setup_plan_merge_uncommitted(self):
517
self.add_version(('root', 'A'), [], 'abc')
518
self.add_uncommitted_version(('root', 'B:'), [('root', 'A')], 'acehg')
519
self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
520
return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
585
self.add_version((b'root', b'A'), [], b'abc')
586
self.add_uncommitted_version(
587
(b'root', b'B:'), [(b'root', b'A')], b'acehg')
588
self.add_uncommitted_version(
589
(b'root', b'C:'), [(b'root', b'A')], b'fabg')
590
return _PlanMerge(b'B:', b'C:', self.plan_merge_vf, (b'root',))
522
592
def test_base_from_plan(self):
523
593
self.setup_plan_merge()
524
plan = self.plan_merge_vf.plan_merge('B', 'C')
594
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
525
595
pwm = versionedfile.PlanWeaveMerge(plan)
526
self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
596
self.assertEqual([b'a\n', b'b\n', b'c\n'], pwm.base_from_plan())
528
598
def test_unique_lines(self):
529
599
plan = self.setup_plan_merge()
530
600
self.assertEqual(plan._unique_lines(
531
plan._get_matching_blocks('B', 'C')),
601
plan._get_matching_blocks(b'B', b'C')),
532
602
([1, 2, 3], [0, 2]))
534
604
def test_plan_merge(self):
535
605
self.setup_plan_merge()
536
plan = self.plan_merge_vf.plan_merge('B', 'C')
606
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
537
607
self.assertEqual([
539
('unchanged', 'a\n'),
609
('unchanged', b'a\n'),
610
('killed-a', b'b\n'),
611
('killed-b', b'c\n'),
548
618
def test_plan_merge_cherrypick(self):
549
self.add_rev('root', 'A', [], 'abc')
550
self.add_rev('root', 'B', ['A'], 'abcde')
551
self.add_rev('root', 'C', ['A'], 'abcefg')
552
self.add_rev('root', 'D', ['A', 'B', 'C'], 'abcdegh')
553
my_plan = _PlanMerge('B', 'D', self.plan_merge_vf, ('root',))
619
self.add_rev(b'root', b'A', [], b'abc')
620
self.add_rev(b'root', b'B', [b'A'], b'abcde')
621
self.add_rev(b'root', b'C', [b'A'], b'abcefg')
622
self.add_rev(b'root', b'D', [b'A', b'B', b'C'], b'abcdegh')
623
my_plan = _PlanMerge(b'B', b'D', self.plan_merge_vf, (b'root',))
554
624
# We shortcut when one text supersedes the other in the per-file graph.
555
625
# We don't actually need to compare the texts at this point.
556
626
self.assertEqual([
564
list(my_plan.plan_merge()))
634
list(my_plan.plan_merge()))
566
636
def test_plan_merge_no_common_ancestor(self):
567
self.add_rev('root', 'A', [], 'abc')
568
self.add_rev('root', 'B', [], 'xyz')
569
my_plan = _PlanMerge('A', 'B', self.plan_merge_vf, ('root',))
637
self.add_rev(b'root', b'A', [], b'abc')
638
self.add_rev(b'root', b'B', [], b'xyz')
639
my_plan = _PlanMerge(b'A', b'B', self.plan_merge_vf, (b'root',))
570
640
self.assertEqual([
577
list(my_plan.plan_merge()))
647
list(my_plan.plan_merge()))
579
649
def test_plan_merge_tail_ancestors(self):
580
650
# The graph looks like this:
851
921
# XX unused ancestor, should not show up in the weave
855
925
# B C B & C both introduce a new line
859
929
# D E B & C are both merged, so both are common ancestors
860
930
# In the process of merging, both sides order the new
861
931
# lines differently
863
self.add_rev('root', 'XX', [], 'qrs')
864
self.add_rev('root', 'A', ['XX'], 'abcdef')
865
self.add_rev('root', 'B', ['A'], 'abcdgef')
866
self.add_rev('root', 'C', ['A'], 'abcdhef')
867
self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
868
self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
869
plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
933
self.add_rev(b'root', b'XX', [], b'qrs')
934
self.add_rev(b'root', b'A', [b'XX'], b'abcdef')
935
self.add_rev(b'root', b'B', [b'A'], b'abcdgef')
936
self.add_rev(b'root', b'C', [b'A'], b'abcdhef')
937
self.add_rev(b'root', b'D', [b'B', b'C'], b'abcdghef')
938
self.add_rev(b'root', b'E', [b'C', b'B'], b'abcdhgef')
939
plan = list(self.plan_merge_vf.plan_merge(b'D', b'E'))
870
940
self.assertEqual([
871
('unchanged', 'a\n'),
872
('unchanged', 'b\n'),
873
('unchanged', 'c\n'),
874
('unchanged', 'd\n'),
876
('unchanged', 'g\n'),
878
('unchanged', 'e\n'),
879
('unchanged', 'f\n'),
941
('unchanged', b'a\n'),
942
('unchanged', b'b\n'),
943
('unchanged', b'c\n'),
944
('unchanged', b'd\n'),
946
('unchanged', b'g\n'),
947
('killed-b', b'h\n'),
948
('unchanged', b'e\n'),
949
('unchanged', b'f\n'),
881
951
pwm = versionedfile.PlanWeaveMerge(plan)
882
self.assertEqualDiff('\n'.join('abcdghef') + '\n',
883
''.join(pwm.base_from_plan()))
952
self.assertEqualDiff(b'a\nb\nc\nd\ng\nh\ne\nf\n',
953
b''.join(pwm.base_from_plan()))
884
954
# Reversing the order reverses the merge plan, and final order of 'hg'
886
plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
956
plan = list(self.plan_merge_vf.plan_merge(b'E', b'D'))
887
957
self.assertEqual([
888
('unchanged', 'a\n'),
889
('unchanged', 'b\n'),
890
('unchanged', 'c\n'),
891
('unchanged', 'd\n'),
893
('unchanged', 'h\n'),
895
('unchanged', 'e\n'),
896
('unchanged', 'f\n'),
958
('unchanged', b'a\n'),
959
('unchanged', b'b\n'),
960
('unchanged', b'c\n'),
961
('unchanged', b'd\n'),
963
('unchanged', b'h\n'),
964
('killed-b', b'g\n'),
965
('unchanged', b'e\n'),
966
('unchanged', b'f\n'),
898
968
pwm = versionedfile.PlanWeaveMerge(plan)
899
self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
900
''.join(pwm.base_from_plan()))
969
self.assertEqualDiff(b'a\nb\nc\nd\nh\ng\ne\nf\n',
970
b''.join(pwm.base_from_plan()))
901
971
# This is where lca differs, in that it (fairly correctly) determines
902
972
# that there is a conflict because both sides resolved the merge
904
plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
974
plan = list(self.plan_merge_vf.plan_lca_merge(b'D', b'E'))
905
975
self.assertEqual([
906
('unchanged', 'a\n'),
907
('unchanged', 'b\n'),
908
('unchanged', 'c\n'),
909
('unchanged', 'd\n'),
910
('conflicted-b', 'h\n'),
911
('unchanged', 'g\n'),
912
('conflicted-a', 'h\n'),
913
('unchanged', 'e\n'),
914
('unchanged', 'f\n'),
976
('unchanged', b'a\n'),
977
('unchanged', b'b\n'),
978
('unchanged', b'c\n'),
979
('unchanged', b'd\n'),
980
('conflicted-b', b'h\n'),
981
('unchanged', b'g\n'),
982
('conflicted-a', b'h\n'),
983
('unchanged', b'e\n'),
984
('unchanged', b'f\n'),
916
986
pwm = versionedfile.PlanWeaveMerge(plan)
917
self.assertEqualDiff('\n'.join('abcdgef') + '\n',
918
''.join(pwm.base_from_plan()))
987
self.assertEqualDiff(b'a\nb\nc\nd\ng\ne\nf\n',
988
b''.join(pwm.base_from_plan()))
919
989
# Reversing it changes what line is doubled, but still gives a
920
990
# double-conflict
921
plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
991
plan = list(self.plan_merge_vf.plan_lca_merge(b'E', b'D'))
922
992
self.assertEqual([
923
('unchanged', 'a\n'),
924
('unchanged', 'b\n'),
925
('unchanged', 'c\n'),
926
('unchanged', 'd\n'),
927
('conflicted-b', 'g\n'),
928
('unchanged', 'h\n'),
929
('conflicted-a', 'g\n'),
930
('unchanged', 'e\n'),
931
('unchanged', 'f\n'),
993
('unchanged', b'a\n'),
994
('unchanged', b'b\n'),
995
('unchanged', b'c\n'),
996
('unchanged', b'd\n'),
997
('conflicted-b', b'g\n'),
998
('unchanged', b'h\n'),
999
('conflicted-a', b'g\n'),
1000
('unchanged', b'e\n'),
1001
('unchanged', b'f\n'),
933
1003
pwm = versionedfile.PlanWeaveMerge(plan)
934
self.assertEqualDiff('\n'.join('abcdhef') + '\n',
935
''.join(pwm.base_from_plan()))
1004
self.assertEqualDiff(b'a\nb\nc\nd\nh\ne\nf\n',
1005
b''.join(pwm.base_from_plan()))
937
1007
def assertRemoveExternalReferences(self, filtered_parent_map,
938
1008
child_map, tails, parent_map):
996
1066
self.assertPruneTails({1: []}, [5],
997
1067
{1: [], 2: [3, 4], 3: [5], 4: [5], 5: []})
998
1068
# Prune a partial chain
999
self.assertPruneTails({1: [6], 6:[]}, [5],
1069
self.assertPruneTails({1: [6], 6: []}, [5],
1000
1070
{1: [2, 6], 2: [3, 4], 3: [5], 4: [5], 5: [],
1002
1072
# Prune a chain with multiple tips, that pulls out intermediates
1003
self.assertPruneTails({1:[3], 3:[]}, [4, 5],
1004
{1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
1005
self.assertPruneTails({1:[3], 3:[]}, [5, 4],
1006
{1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
1073
self.assertPruneTails({1: [3], 3: []}, [4, 5],
1074
{1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []})
1075
self.assertPruneTails({1: [3], 3: []}, [5, 4],
1076
{1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []})
1008
1078
def test_subtract_plans(self):
1010
('unchanged', 'a\n'),
1012
('killed-a', 'c\n'),
1015
('killed-b', 'f\n'),
1016
('killed-b', 'g\n'),
1080
('unchanged', b'a\n'),
1082
('killed-a', b'c\n'),
1085
('killed-b', b'f\n'),
1086
('killed-b', b'g\n'),
1019
('unchanged', 'a\n'),
1021
('killed-a', 'c\n'),
1024
('killed-b', 'f\n'),
1025
('killed-b', 'i\n'),
1089
('unchanged', b'a\n'),
1091
('killed-a', b'c\n'),
1094
('killed-b', b'f\n'),
1095
('killed-b', b'i\n'),
1027
1097
subtracted_plan = [
1028
('unchanged', 'a\n'),
1030
('killed-a', 'c\n'),
1032
('unchanged', 'f\n'),
1033
('killed-b', 'i\n'),
1098
('unchanged', b'a\n'),
1100
('killed-a', b'c\n'),
1102
('unchanged', b'f\n'),
1103
('killed-b', b'i\n'),
1035
1105
self.assertEqual(subtracted_plan,
1036
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1106
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1038
1108
def setup_merge_with_base(self):
1039
self.add_rev('root', 'COMMON', [], 'abc')
1040
self.add_rev('root', 'THIS', ['COMMON'], 'abcd')
1041
self.add_rev('root', 'BASE', ['COMMON'], 'eabc')
1042
self.add_rev('root', 'OTHER', ['BASE'], 'eafb')
1109
self.add_rev(b'root', b'COMMON', [], b'abc')
1110
self.add_rev(b'root', b'THIS', [b'COMMON'], b'abcd')
1111
self.add_rev(b'root', b'BASE', [b'COMMON'], b'eabc')
1112
self.add_rev(b'root', b'OTHER', [b'BASE'], b'eafb')
1044
1114
def test_plan_merge_with_base(self):
1045
1115
self.setup_merge_with_base()
1046
plan = self.plan_merge_vf.plan_merge('THIS', 'OTHER', 'BASE')
1047
self.assertEqual([('unchanged', 'a\n'),
1049
('unchanged', 'b\n'),
1050
('killed-b', 'c\n'),
1116
plan = self.plan_merge_vf.plan_merge(b'THIS', b'OTHER', b'BASE')
1117
self.assertEqual([('unchanged', b'a\n'),
1119
('unchanged', b'b\n'),
1120
('killed-b', b'c\n'),
1054
1124
def test_plan_lca_merge(self):
1055
1125
self.setup_plan_merge()
1056
plan = self.plan_merge_vf.plan_lca_merge('B', 'C')
1126
plan = self.plan_merge_vf.plan_lca_merge(b'B', b'C')
1057
1127
self.assertEqual([
1059
('unchanged', 'a\n'),
1060
('killed-b', 'c\n'),
1063
('killed-a', 'b\n'),
1064
('unchanged', 'g\n')],
1129
('unchanged', b'a\n'),
1130
('killed-b', b'c\n'),
1133
('killed-a', b'b\n'),
1134
('unchanged', b'g\n')],
1067
1137
def test_plan_lca_merge_uncommitted_files(self):
1068
1138
self.setup_plan_merge_uncommitted()
1069
plan = self.plan_merge_vf.plan_lca_merge('B:', 'C:')
1139
plan = self.plan_merge_vf.plan_lca_merge(b'B:', b'C:')
1070
1140
self.assertEqual([
1072
('unchanged', 'a\n'),
1073
('killed-b', 'c\n'),
1076
('killed-a', 'b\n'),
1077
('unchanged', 'g\n')],
1142
('unchanged', b'a\n'),
1143
('killed-b', b'c\n'),
1146
('killed-a', b'b\n'),
1147
('unchanged', b'g\n')],
1080
1150
def test_plan_lca_merge_with_base(self):
1081
1151
self.setup_merge_with_base()
1082
plan = self.plan_merge_vf.plan_lca_merge('THIS', 'OTHER', 'BASE')
1083
self.assertEqual([('unchanged', 'a\n'),
1085
('unchanged', 'b\n'),
1086
('killed-b', 'c\n'),
1152
plan = self.plan_merge_vf.plan_lca_merge(b'THIS', b'OTHER', b'BASE')
1153
self.assertEqual([('unchanged', b'a\n'),
1155
('unchanged', b'b\n'),
1156
('killed-b', b'c\n'),
1090
1160
def test_plan_lca_merge_with_criss_cross(self):
1091
self.add_version(('root', 'ROOT'), [], 'abc')
1161
self.add_version((b'root', b'ROOT'), [], b'abc')
1092
1162
# each side makes a change
1093
self.add_version(('root', 'REV1'), [('root', 'ROOT')], 'abcd')
1094
self.add_version(('root', 'REV2'), [('root', 'ROOT')], 'abce')
1163
self.add_version((b'root', b'REV1'), [(b'root', b'ROOT')], b'abcd')
1164
self.add_version((b'root', b'REV2'), [(b'root', b'ROOT')], b'abce')
1095
1165
# both sides merge, discarding others' changes
1096
self.add_version(('root', 'LCA1'),
1097
[('root', 'REV1'), ('root', 'REV2')], 'abcd')
1098
self.add_version(('root', 'LCA2'),
1099
[('root', 'REV1'), ('root', 'REV2')], 'fabce')
1100
plan = self.plan_merge_vf.plan_lca_merge('LCA1', 'LCA2')
1101
self.assertEqual([('new-b', 'f\n'),
1102
('unchanged', 'a\n'),
1103
('unchanged', 'b\n'),
1104
('unchanged', 'c\n'),
1105
('conflicted-a', 'd\n'),
1106
('conflicted-b', 'e\n'),
1166
self.add_version((b'root', b'LCA1'),
1167
[(b'root', b'REV1'), (b'root', b'REV2')], b'abcd')
1168
self.add_version((b'root', b'LCA2'),
1169
[(b'root', b'REV1'), (b'root', b'REV2')], b'fabce')
1170
plan = self.plan_merge_vf.plan_lca_merge(b'LCA1', b'LCA2')
1171
self.assertEqual([('new-b', b'f\n'),
1172
('unchanged', b'a\n'),
1173
('unchanged', b'b\n'),
1174
('unchanged', b'c\n'),
1175
('conflicted-a', b'd\n'),
1176
('conflicted-b', b'e\n'),
1109
1179
def test_plan_lca_merge_with_null(self):
1110
self.add_version(('root', 'A'), [], 'ab')
1111
self.add_version(('root', 'B'), [], 'bc')
1112
plan = self.plan_merge_vf.plan_lca_merge('A', 'B')
1113
self.assertEqual([('new-a', 'a\n'),
1114
('unchanged', 'b\n'),
1180
self.add_version((b'root', b'A'), [], b'ab')
1181
self.add_version((b'root', b'B'), [], b'bc')
1182
plan = self.plan_merge_vf.plan_lca_merge(b'A', b'B')
1183
self.assertEqual([('new-a', b'a\n'),
1184
('unchanged', b'b\n'),
1118
1188
def test_plan_merge_with_delete_and_change(self):
1119
self.add_rev('root', 'C', [], 'a')
1120
self.add_rev('root', 'A', ['C'], 'b')
1121
self.add_rev('root', 'B', ['C'], '')
1122
plan = self.plan_merge_vf.plan_merge('A', 'B')
1123
self.assertEqual([('killed-both', 'a\n'),
1189
self.add_rev(b'root', b'C', [], b'a')
1190
self.add_rev(b'root', b'A', [b'C'], b'b')
1191
self.add_rev(b'root', b'B', [b'C'], b'')
1192
plan = self.plan_merge_vf.plan_merge(b'A', b'B')
1193
self.assertEqual([('killed-both', b'a\n'),
1127
1197
def test_plan_merge_with_move_and_change(self):
1128
self.add_rev('root', 'C', [], 'abcd')
1129
self.add_rev('root', 'A', ['C'], 'acbd')
1130
self.add_rev('root', 'B', ['C'], 'aBcd')
1131
plan = self.plan_merge_vf.plan_merge('A', 'B')
1132
self.assertEqual([('unchanged', 'a\n'),
1134
('killed-b', 'b\n'),
1136
('killed-a', 'c\n'),
1137
('unchanged', 'd\n'),
1198
self.add_rev(b'root', b'C', [], b'abcd')
1199
self.add_rev(b'root', b'A', [b'C'], b'acbd')
1200
self.add_rev(b'root', b'B', [b'C'], b'aBcd')
1201
plan = self.plan_merge_vf.plan_merge(b'A', b'B')
1202
self.assertEqual([('unchanged', b'a\n'),
1204
('killed-b', b'b\n'),
1206
('killed-a', b'c\n'),
1207
('unchanged', b'd\n'),
1141
1211
class LoggingMerger(object):
1262
1330
builder = self.setup_criss_cross_graph()
1263
builder.build_snapshot('F-id', ['A-id'], [])
1264
builder.build_snapshot('H-id', ['E-id', 'F-id'], [])
1265
builder.build_snapshot('G-id', ['D-id', 'F-id'], [])
1266
merger = self.make_Merger(builder, 'H-id')
1267
self.assertEqual(['B-id', 'C-id', 'F-id'],
1331
builder.build_snapshot([b'A-id'], [], revision_id=b'F-id')
1332
builder.build_snapshot([b'E-id', b'F-id'], [], revision_id=b'H-id')
1333
builder.build_snapshot([b'D-id', b'F-id'], [], revision_id=b'G-id')
1334
merger = self.make_Merger(builder, b'H-id')
1335
self.assertEqual([b'B-id', b'C-id', b'F-id'],
1268
1336
[t.get_revision_id() for t in merger._lca_trees])
1338
def test_find_base_new_root_criss_cross(self):
1344
builder = self.get_builder()
1345
builder.build_snapshot(None,
1346
[('add', ('', None, 'directory', None))],
1347
revision_id=b'A-id')
1348
builder.build_snapshot([],
1349
[('add', ('', None, 'directory', None))],
1350
revision_id=b'B-id')
1351
builder.build_snapshot([b'A-id', b'B-id'], [], revision_id=b'D-id')
1352
builder.build_snapshot([b'A-id', b'B-id'], [], revision_id=b'C-id')
1353
merger = self.make_Merger(builder, b'D-id')
1354
self.assertEqual(b'A-id', merger.base_rev_id)
1355
self.assertTrue(merger._is_criss_cross)
1356
self.assertEqual([b'A-id', b'B-id'], [t.get_revision_id()
1357
for t in merger._lca_trees])
1270
1359
def test_no_criss_cross_passed_to_merge_type(self):
1271
1360
class LCATreesMerger(LoggingMerger):
1272
1361
supports_lca_trees = True
1274
merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1363
merger = self.make_Merger(self.setup_simple_graph(), b'C-id')
1275
1364
merger.merge_type = LCATreesMerger
1276
1365
merge_obj = merger.make_merger()
1277
1366
self.assertIsInstance(merge_obj, LCATreesMerger)
1278
1367
self.assertFalse('lca_trees' in merge_obj.kwargs)
1280
1369
def test_criss_cross_passed_to_merge_type(self):
1281
merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
1370
merger = self.make_Merger(self.setup_criss_cross_graph(), b'E-id')
1282
1371
merger.merge_type = _mod_merge.Merge3Merger
1283
1372
merge_obj = merger.make_merger()
1284
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1285
for t in merger._lca_trees])
1373
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1374
for t in merger._lca_trees])
1287
1376
def test_criss_cross_not_supported_merge_type(self):
1288
merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
1377
merger = self.make_Merger(self.setup_criss_cross_graph(), b'E-id')
1289
1378
# We explicitly do not define supports_lca_trees
1290
1379
merger.merge_type = LoggingMerger
1291
1380
merge_obj = merger.make_merger()
1306
1395
class TestMergerEntriesLCA(TestMergerBase):
1308
1397
def make_merge_obj(self, builder, other_revision_id,
1309
interesting_files=None, interesting_ids=None):
1398
interesting_files=None):
1310
1399
merger = self.make_Merger(builder, other_revision_id,
1311
interesting_files=interesting_files,
1312
interesting_ids=interesting_ids)
1400
interesting_files=interesting_files)
1313
1401
return merger.make_merger()
1315
1403
def test_simple(self):
1316
1404
builder = self.get_builder()
1317
builder.build_snapshot('A-id', None,
1318
[('add', (u'', 'a-root-id', 'directory', None)),
1319
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1320
builder.build_snapshot('C-id', ['A-id'],
1321
[('modify', ('a-id', 'a\nb\nC\nc\n'))])
1322
builder.build_snapshot('B-id', ['A-id'],
1323
[('modify', ('a-id', 'a\nB\nb\nc\n'))])
1324
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1325
[('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
1326
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1327
[('modify', ('a-id', 'a\nB\nb\nC\nc\n'))])
1328
merge_obj = self.make_merge_obj(builder, 'E-id')
1405
builder.build_snapshot(None,
1406
[('add', (u'', b'a-root-id', 'directory', None)),
1407
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1408
revision_id=b'A-id')
1409
builder.build_snapshot([b'A-id'],
1410
[('modify', ('a', b'a\nb\nC\nc\n'))],
1411
revision_id=b'C-id')
1412
builder.build_snapshot([b'A-id'],
1413
[('modify', ('a', b'a\nB\nb\nc\n'))],
1414
revision_id=b'B-id')
1415
builder.build_snapshot([b'C-id', b'B-id'],
1416
[('modify', ('a', b'a\nB\nb\nC\nc\nE\n'))],
1417
revision_id=b'E-id')
1418
builder.build_snapshot([b'B-id', b'C-id'],
1419
[('modify', ('a', b'a\nB\nb\nC\nc\n'))],
1420
revision_id=b'D-id', )
1421
merge_obj = self.make_merge_obj(builder, b'E-id')
1330
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1331
for t in merge_obj._lca_trees])
1332
self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
1423
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1424
for t in merge_obj._lca_trees])
1425
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1333
1426
entries = list(merge_obj._entries_lca())
1335
1428
# (file_id, changed, parents, names, executable)
1336
1429
# BASE, lca1, lca2, OTHER, THIS
1337
root_id = 'a-root-id'
1338
self.assertEqual([('a-id', True,
1430
root_id = b'a-root-id'
1431
self.assertEqual([(b'a-id', True,
1432
((u'a', [u'a', u'a']), u'a', u'a'),
1339
1433
((root_id, [root_id, root_id]), root_id, root_id),
1340
1434
((u'a', [u'a', u'a']), u'a', u'a'),
1341
1435
((False, [False, False]), False, False)),
1344
1438
def test_not_in_base(self):
1345
1439
# LCAs all have the same last-modified revision for the file, as do
1354
1448
# G modifies 'bar'
1356
1450
builder = self.get_builder()
1357
builder.build_snapshot('A-id', None,
1358
[('add', (u'', 'a-root-id', 'directory', None))])
1359
builder.build_snapshot('B-id', ['A-id'],
1360
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
1361
builder.build_snapshot('C-id', ['A-id'],
1362
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
1363
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1364
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
1365
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1366
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
1367
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1368
[('modify', (u'bar-id', 'd\ne\nf\nG\n'))])
1369
builder.build_snapshot('F-id', ['D-id', 'E-id'], [])
1370
merge_obj = self.make_merge_obj(builder, 'G-id')
1451
builder.build_snapshot(None,
1452
[('add', (u'', b'a-root-id', 'directory', None))],
1453
revision_id=b'A-id')
1454
builder.build_snapshot([b'A-id'],
1455
[('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
1456
revision_id=b'B-id')
1457
builder.build_snapshot([b'A-id'],
1458
[('add', (u'bar', b'bar-id', 'file', b'd\ne\nf\n'))],
1459
revision_id=b'C-id')
1460
builder.build_snapshot([b'B-id', b'C-id'],
1461
[('add', (u'bar', b'bar-id', 'file', b'd\ne\nf\n'))],
1462
revision_id=b'D-id')
1463
builder.build_snapshot([b'C-id', b'B-id'],
1464
[('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
1465
revision_id=b'E-id')
1466
builder.build_snapshot([b'E-id', b'D-id'],
1467
[('modify', (u'bar', b'd\ne\nf\nG\n'))],
1468
revision_id=b'G-id')
1469
builder.build_snapshot([b'D-id', b'E-id'], [], revision_id=b'F-id')
1470
merge_obj = self.make_merge_obj(builder, b'G-id')
1372
self.assertEqual(['D-id', 'E-id'], [t.get_revision_id()
1373
for t in merge_obj._lca_trees])
1374
self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
1472
self.assertEqual([b'D-id', b'E-id'], [t.get_revision_id()
1473
for t in merge_obj._lca_trees])
1474
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1375
1475
entries = list(merge_obj._entries_lca())
1376
root_id = 'a-root-id'
1377
self.assertEqual([('bar-id', True,
1476
root_id = b'a-root-id'
1477
self.assertEqual([(b'bar-id', True,
1478
((None, [u'bar', u'bar']), u'bar', u'bar'),
1378
1479
((None, [root_id, root_id]), root_id, root_id),
1379
1480
((None, [u'bar', u'bar']), u'bar', u'bar'),
1380
1481
((None, [False, False]), False, False)),
1383
1484
def test_not_in_this(self):
1384
1485
builder = self.get_builder()
1385
builder.build_snapshot('A-id', None,
1386
[('add', (u'', 'a-root-id', 'directory', None)),
1387
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1388
builder.build_snapshot('B-id', ['A-id'],
1389
[('modify', ('a-id', 'a\nB\nb\nc\n'))])
1390
builder.build_snapshot('C-id', ['A-id'],
1391
[('modify', ('a-id', 'a\nb\nC\nc\n'))])
1392
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1393
[('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
1394
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1395
[('unversion', 'a-id')])
1396
merge_obj = self.make_merge_obj(builder, 'E-id')
1486
builder.build_snapshot(None,
1487
[('add', (u'', b'a-root-id', 'directory', None)),
1488
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1489
revision_id=b'A-id')
1490
builder.build_snapshot([b'A-id'],
1491
[('modify', ('a', b'a\nB\nb\nc\n'))],
1492
revision_id=b'B-id')
1493
builder.build_snapshot([b'A-id'],
1494
[('modify', ('a', b'a\nb\nC\nc\n'))],
1495
revision_id=b'C-id')
1496
builder.build_snapshot([b'C-id', b'B-id'],
1497
[('modify', ('a', b'a\nB\nb\nC\nc\nE\n'))],
1498
revision_id=b'E-id')
1499
builder.build_snapshot([b'B-id', b'C-id'],
1500
[('unversion', 'a')],
1501
revision_id=b'D-id')
1502
merge_obj = self.make_merge_obj(builder, b'E-id')
1398
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1399
for t in merge_obj._lca_trees])
1400
self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
1504
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1505
for t in merge_obj._lca_trees])
1506
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1402
1508
entries = list(merge_obj._entries_lca())
1403
root_id = 'a-root-id'
1404
self.assertEqual([('a-id', True,
1509
root_id = b'a-root-id'
1510
self.assertEqual([(b'a-id', True,
1511
((u'a', [u'a', u'a']), u'a', None),
1405
1512
((root_id, [root_id, root_id]), root_id, None),
1406
1513
((u'a', [u'a', u'a']), u'a', None),
1407
1514
((False, [False, False]), False, None)),
1410
1517
def test_file_not_in_one_lca(self):
1411
1518
# A # just root
1415
1522
# D E # D and E both have the file, unchanged from C
1416
1523
builder = self.get_builder()
1417
builder.build_snapshot('A-id', None,
1418
[('add', (u'', 'a-root-id', 'directory', None))])
1419
builder.build_snapshot('B-id', ['A-id'], [])
1420
builder.build_snapshot('C-id', ['A-id'],
1421
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1422
builder.build_snapshot('E-id', ['C-id', 'B-id'], []) # Inherited from C
1423
builder.build_snapshot('D-id', ['B-id', 'C-id'], # Merged from C
1424
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1425
merge_obj = self.make_merge_obj(builder, 'E-id')
1524
builder.build_snapshot(None,
1525
[('add', (u'', b'a-root-id', 'directory', None))],
1526
revision_id=b'A-id')
1527
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1528
builder.build_snapshot([b'A-id'],
1529
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1530
revision_id=b'C-id')
1531
builder.build_snapshot([b'C-id', b'B-id'],
1532
[], revision_id=b'E-id') # Inherited from C
1533
builder.build_snapshot([b'B-id', b'C-id'], # Merged from C
1534
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1535
revision_id=b'D-id')
1536
merge_obj = self.make_merge_obj(builder, b'E-id')
1427
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1428
for t in merge_obj._lca_trees])
1429
self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
1538
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1539
for t in merge_obj._lca_trees])
1540
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1431
1542
entries = list(merge_obj._entries_lca())
1432
1543
self.assertEqual([], entries)
1434
1545
def test_not_in_other(self):
1435
1546
builder = self.get_builder()
1436
builder.build_snapshot('A-id', None,
1437
[('add', (u'', 'a-root-id', 'directory', None)),
1438
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1439
builder.build_snapshot('B-id', ['A-id'], [])
1440
builder.build_snapshot('C-id', ['A-id'], [])
1441
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1442
[('unversion', 'a-id')])
1443
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1444
merge_obj = self.make_merge_obj(builder, 'E-id')
1547
builder.build_snapshot(None,
1548
[('add', (u'', b'a-root-id', 'directory', None)),
1549
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1550
revision_id=b'A-id')
1551
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1552
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1553
builder.build_snapshot(
1555
[('unversion', 'a')], revision_id=b'E-id')
1556
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1557
merge_obj = self.make_merge_obj(builder, b'E-id')
1446
1559
entries = list(merge_obj._entries_lca())
1447
root_id = 'a-root-id'
1448
self.assertEqual([('a-id', True,
1560
root_id = b'a-root-id'
1561
self.assertEqual([(b'a-id', True,
1562
((u'a', [u'a', u'a']), None, u'a'),
1449
1563
((root_id, [root_id, root_id]), None, root_id),
1450
1564
((u'a', [u'a', u'a']), None, u'a'),
1451
1565
((False, [False, False]), None, False)),
1454
1568
def test_not_in_other_or_lca(self):
1455
1569
# A base, introduces 'foo'
1529
1648
# A => C, add file, thus C supersedes B
1530
1649
# w/ C=BASE, D=THIS, E=OTHER we have 'happy convergence'
1531
1650
builder = self.get_builder()
1532
builder.build_snapshot('A-id', None,
1533
[('add', (u'', 'a-root-id', 'directory', None))])
1534
builder.build_snapshot('B-id', ['A-id'], [])
1535
builder.build_snapshot('C-id', ['A-id'],
1536
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1537
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1538
[('unversion', 'a-id')])
1539
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1540
merge_obj = self.make_merge_obj(builder, 'E-id')
1651
builder.build_snapshot(None,
1652
[('add', (u'', b'a-root-id', 'directory', None))],
1653
revision_id=b'A-id')
1654
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1655
builder.build_snapshot([b'A-id'],
1656
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1657
revision_id=b'C-id')
1658
builder.build_snapshot([b'C-id', b'B-id'],
1659
[('unversion', 'a')],
1660
revision_id=b'E-id')
1661
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1662
merge_obj = self.make_merge_obj(builder, b'E-id')
1542
1664
entries = list(merge_obj._entries_lca())
1543
1665
self.assertEqual([], entries)
1545
1667
def test_only_in_other(self):
1546
1668
builder = self.get_builder()
1547
builder.build_snapshot('A-id', None,
1548
[('add', (u'', 'a-root-id', 'directory', None))])
1549
builder.build_snapshot('B-id', ['A-id'], [])
1550
builder.build_snapshot('C-id', ['A-id'], [])
1551
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1552
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1553
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1554
merge_obj = self.make_merge_obj(builder, 'E-id')
1669
builder.build_snapshot(None,
1670
[('add', (u'', b'a-root-id', 'directory', None))],
1671
revision_id=b'A-id')
1672
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1673
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1674
builder.build_snapshot([b'C-id', b'B-id'],
1675
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1676
revision_id=b'E-id')
1677
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1678
merge_obj = self.make_merge_obj(builder, b'E-id')
1556
1680
entries = list(merge_obj._entries_lca())
1557
root_id = 'a-root-id'
1558
self.assertEqual([('a-id', True,
1681
root_id = b'a-root-id'
1682
self.assertEqual([(b'a-id', True,
1683
((None, [None, None]), u'a', None),
1559
1684
((None, [None, None]), root_id, None),
1560
1685
((None, [None, None]), u'a', None),
1561
1686
((None, [None, None]), False, None)),
1564
1689
def test_one_lca_supersedes(self):
1565
1690
# One LCA supersedes the other LCAs last modified value, but the
1655
1789
# be pruned from the LCAs, even though it was newly introduced by E
1656
1790
# (superseding B).
1657
1791
builder = self.get_builder()
1658
builder.build_snapshot('A-id', None,
1659
[('add', (u'', 'a-root-id', 'directory', None)),
1660
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1661
builder.build_snapshot('C-id', ['A-id'], [])
1662
builder.build_snapshot('B-id', ['A-id'],
1663
[('rename', ('foo', 'bar'))])
1664
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1665
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1666
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1667
[('rename', ('foo', 'bar'))])
1668
builder.build_snapshot('F-id', ['D-id', 'E-id'],
1669
[('rename', ('bar', 'bing'))]) # should end up conflicting
1670
merge_obj = self.make_merge_obj(builder, 'G-id')
1792
builder.build_snapshot(None,
1793
[('add', (u'', b'a-root-id', 'directory', None)),
1794
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1795
revision_id=b'A-id')
1796
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1797
builder.build_snapshot([b'A-id'],
1798
[('rename', ('foo', 'bar'))],
1799
revision_id=b'B-id')
1800
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1801
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1802
builder.build_snapshot([b'E-id', b'D-id'],
1803
[('rename', ('foo', 'bar'))],
1804
revision_id=b'G-id')
1805
builder.build_snapshot([b'D-id', b'E-id'],
1806
[('rename', ('bar', 'bing'))],
1807
revision_id=b'F-id') # should end up conflicting
1808
merge_obj = self.make_merge_obj(builder, b'G-id')
1672
1810
entries = list(merge_obj._entries_lca())
1673
root_id = 'a-root-id'
1811
root_id = b'a-root-id'
1674
1812
self.expectFailure("We prune values from BASE even when relevant.",
1677
((root_id, [root_id, root_id]), root_id, root_id),
1678
((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
1679
((False, [False, False]), False, False)),
1815
((root_id, [root_id, root_id]), root_id, root_id),
1816
((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
1817
((False, [False, False]), False, False)),
1682
1820
def test_both_sides_revert(self):
1683
1821
# Both sides of a criss-cross revert the text to the lca
1755
1902
# We need to conflict.
1757
1904
builder = self.get_builder()
1758
builder.build_snapshot('A-id', None,
1759
[('add', (u'', 'a-root-id', 'directory', None)),
1760
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1761
builder.build_snapshot('B-id', ['A-id'],
1762
[('modify', ('foo-id', 'B content\n'))])
1763
builder.build_snapshot('C-id', ['A-id'],
1764
[('modify', ('foo-id', 'C content\n'))])
1765
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1766
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1767
[('modify', ('foo-id', 'C content\n'))]) # Same as E
1768
builder.build_snapshot('F-id', ['D-id'],
1769
[('modify', ('foo-id', 'F content\n'))])
1770
merge_obj = self.make_merge_obj(builder, 'E-id')
1905
builder.build_snapshot(None,
1906
[('add', (u'', b'a-root-id', 'directory', None)),
1907
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1908
revision_id=b'A-id')
1909
builder.build_snapshot([b'A-id'],
1910
[('modify', ('foo', b'B content\n'))],
1911
revision_id=b'B-id')
1912
builder.build_snapshot([b'A-id'],
1913
[('modify', ('foo', b'C content\n'))],
1914
revision_id=b'C-id')
1915
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1916
builder.build_snapshot([b'B-id', b'C-id'],
1917
[('modify', ('foo', b'C content\n'))],
1918
revision_id=b'D-id') # Same as E
1919
builder.build_snapshot([b'D-id'],
1920
[('modify', ('foo', b'F content\n'))],
1921
revision_id=b'F-id')
1922
merge_obj = self.make_merge_obj(builder, b'E-id')
1772
1924
entries = list(merge_obj._entries_lca())
1773
1925
self.expectFailure("We don't detect that LCA resolution was the"
1774
1926
" same on both sides",
1775
self.assertEqual, [], entries)
1927
self.assertEqual, [], entries)
1777
1929
def test_only_path_changed(self):
1778
1930
builder = self.get_builder()
1779
builder.build_snapshot('A-id', None,
1780
[('add', (u'', 'a-root-id', 'directory', None)),
1781
('add', (u'a', 'a-id', 'file', 'content\n'))])
1782
builder.build_snapshot('B-id', ['A-id'], [])
1783
builder.build_snapshot('C-id', ['A-id'], [])
1784
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1785
[('rename', (u'a', u'b'))])
1786
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1787
merge_obj = self.make_merge_obj(builder, 'E-id')
1931
builder.build_snapshot(None,
1932
[('add', (u'', b'a-root-id', 'directory', None)),
1933
('add', (u'a', b'a-id', 'file', b'content\n'))],
1934
revision_id=b'A-id')
1935
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1936
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1937
builder.build_snapshot([b'C-id', b'B-id'],
1938
[('rename', (u'a', u'b'))],
1939
revision_id=b'E-id')
1940
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1941
merge_obj = self.make_merge_obj(builder, b'E-id')
1788
1942
entries = list(merge_obj._entries_lca())
1789
root_id = 'a-root-id'
1943
root_id = b'a-root-id'
1790
1944
# The content was not changed, only the path
1791
self.assertEqual([('a-id', False,
1945
self.assertEqual([(b'a-id', False,
1946
((u'a', [u'a', u'a']), u'b', u'a'),
1792
1947
((root_id, [root_id, root_id]), root_id, root_id),
1793
1948
((u'a', [u'a', u'a']), u'b', u'a'),
1794
1949
((False, [False, False]), False, False)),
1797
1952
def test_kind_changed(self):
1798
1953
# Identical content, except 'D' changes a-id into a directory
1799
1954
builder = self.get_builder()
1800
builder.build_snapshot('A-id', None,
1801
[('add', (u'', 'a-root-id', 'directory', None)),
1802
('add', (u'a', 'a-id', 'file', 'content\n'))])
1803
builder.build_snapshot('B-id', ['A-id'], [])
1804
builder.build_snapshot('C-id', ['A-id'], [])
1805
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1806
[('unversion', 'a-id'),
1807
('add', (u'a', 'a-id', 'directory', None))])
1808
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1809
merge_obj = self.make_merge_obj(builder, 'E-id')
1955
builder.build_snapshot(None,
1956
[('add', (u'', b'a-root-id', 'directory', None)),
1957
('add', (u'a', b'a-id', 'file', b'content\n'))],
1958
revision_id=b'A-id')
1959
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1960
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1961
builder.build_snapshot([b'C-id', b'B-id'],
1962
[('unversion', 'a'),
1964
('add', (u'a', b'a-id', 'directory', None))],
1965
revision_id=b'E-id')
1966
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1967
merge_obj = self.make_merge_obj(builder, b'E-id')
1810
1968
entries = list(merge_obj._entries_lca())
1811
root_id = 'a-root-id'
1969
root_id = b'a-root-id'
1812
1970
# Only the kind was changed (content)
1813
self.assertEqual([('a-id', True,
1971
self.assertEqual([(b'a-id', True,
1972
((u'a', [u'a', u'a']), u'a', u'a'),
1814
1973
((root_id, [root_id, root_id]), root_id, root_id),
1815
1974
((u'a', [u'a', u'a']), u'a', u'a'),
1816
1975
((False, [False, False]), False, False)),
1819
1978
def test_this_changed_kind(self):
1820
1979
# Identical content, but THIS changes a file to a directory
1821
1980
builder = self.get_builder()
1822
builder.build_snapshot('A-id', None,
1823
[('add', (u'', 'a-root-id', 'directory', None)),
1824
('add', (u'a', 'a-id', 'file', 'content\n'))])
1825
builder.build_snapshot('B-id', ['A-id'], [])
1826
builder.build_snapshot('C-id', ['A-id'], [])
1827
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1828
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1829
[('unversion', 'a-id'),
1830
('add', (u'a', 'a-id', 'directory', None))])
1831
merge_obj = self.make_merge_obj(builder, 'E-id')
1981
builder.build_snapshot(None,
1982
[('add', (u'', b'a-root-id', 'directory', None)),
1983
('add', (u'a', b'a-id', 'file', b'content\n'))],
1984
revision_id=b'A-id')
1985
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1986
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1987
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1988
builder.build_snapshot([b'B-id', b'C-id'],
1989
[('unversion', 'a'),
1991
('add', (u'a', b'a-id', 'directory', None))],
1992
revision_id=b'D-id')
1993
merge_obj = self.make_merge_obj(builder, b'E-id')
1832
1994
entries = list(merge_obj._entries_lca())
1833
1995
# Only the kind was changed (content)
1834
1996
self.assertEqual([], entries)
1836
1998
def test_interesting_files(self):
1837
1999
# Two files modified, but we should filter one of them
1838
2000
builder = self.get_builder()
1839
builder.build_snapshot('A-id', None,
1840
[('add', (u'', 'a-root-id', 'directory', None)),
1841
('add', (u'a', 'a-id', 'file', 'content\n')),
1842
('add', (u'b', 'b-id', 'file', 'content\n'))])
1843
builder.build_snapshot('B-id', ['A-id'], [])
1844
builder.build_snapshot('C-id', ['A-id'], [])
1845
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1846
[('modify', ('a-id', 'new-content\n')),
1847
('modify', ('b-id', 'new-content\n'))])
1848
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1849
merge_obj = self.make_merge_obj(builder, 'E-id',
2001
builder.build_snapshot(None,
2002
[('add', (u'', b'a-root-id', 'directory', None)),
2003
('add', (u'a', b'a-id', 'file', b'content\n')),
2004
('add', (u'b', b'b-id', 'file', b'content\n'))],
2005
revision_id=b'A-id')
2006
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2007
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2008
builder.build_snapshot([b'C-id', b'B-id'],
2009
[('modify', ('a', b'new-content\n')),
2010
('modify', ('b', b'new-content\n'))],
2011
revision_id=b'E-id')
2012
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2013
merge_obj = self.make_merge_obj(builder, b'E-id',
1850
2014
interesting_files=['b'])
1851
2015
entries = list(merge_obj._entries_lca())
1852
root_id = 'a-root-id'
1853
self.assertEqual([('b-id', True,
2016
root_id = b'a-root-id'
2017
self.assertEqual([(b'b-id', True,
2018
((u'b', [u'b', u'b']), u'b', u'b'),
1854
2019
((root_id, [root_id, root_id]), root_id, root_id),
1855
2020
((u'b', [u'b', u'b']), u'b', u'b'),
1856
2021
((False, [False, False]), False, False)),
1859
2024
def test_interesting_file_in_this(self):
1860
2025
# This renamed the file, but it should still match the entry in other
1861
2026
builder = self.get_builder()
1862
builder.build_snapshot('A-id', None,
1863
[('add', (u'', 'a-root-id', 'directory', None)),
1864
('add', (u'a', 'a-id', 'file', 'content\n')),
1865
('add', (u'b', 'b-id', 'file', 'content\n'))])
1866
builder.build_snapshot('B-id', ['A-id'], [])
1867
builder.build_snapshot('C-id', ['A-id'], [])
1868
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1869
[('modify', ('a-id', 'new-content\n')),
1870
('modify', ('b-id', 'new-content\n'))])
1871
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1872
[('rename', ('b', 'c'))])
1873
merge_obj = self.make_merge_obj(builder, 'E-id',
2027
builder.build_snapshot(None,
2028
[('add', (u'', b'a-root-id', 'directory', None)),
2029
('add', (u'a', b'a-id', 'file', b'content\n')),
2030
('add', (u'b', b'b-id', 'file', b'content\n'))],
2031
revision_id=b'A-id')
2032
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2033
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2034
builder.build_snapshot([b'C-id', b'B-id'],
2035
[('modify', ('a', b'new-content\n')),
2036
('modify', ('b', b'new-content\n'))],
2037
revision_id=b'E-id')
2038
builder.build_snapshot([b'B-id', b'C-id'],
2039
[('rename', ('b', 'c'))],
2040
revision_id=b'D-id')
2041
merge_obj = self.make_merge_obj(builder, b'E-id',
1874
2042
interesting_files=['c'])
1875
2043
entries = list(merge_obj._entries_lca())
1876
root_id = 'a-root-id'
1877
self.assertEqual([('b-id', True,
2044
root_id = b'a-root-id'
2045
self.assertEqual([(b'b-id', True,
2046
((u'b', [u'b', u'b']), u'b', u'c'),
1878
2047
((root_id, [root_id, root_id]), root_id, root_id),
1879
2048
((u'b', [u'b', u'b']), u'b', u'c'),
1880
2049
((False, [False, False]), False, False)),
1883
2052
def test_interesting_file_in_base(self):
1884
2053
# This renamed the file, but it should still match the entry in BASE
1885
2054
builder = self.get_builder()
1886
builder.build_snapshot('A-id', None,
1887
[('add', (u'', 'a-root-id', 'directory', None)),
1888
('add', (u'a', 'a-id', 'file', 'content\n')),
1889
('add', (u'c', 'c-id', 'file', 'content\n'))])
1890
builder.build_snapshot('B-id', ['A-id'],
1891
[('rename', ('c', 'b'))])
1892
builder.build_snapshot('C-id', ['A-id'],
1893
[('rename', ('c', 'b'))])
1894
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1895
[('modify', ('a-id', 'new-content\n')),
1896
('modify', ('c-id', 'new-content\n'))])
1897
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1898
merge_obj = self.make_merge_obj(builder, 'E-id',
2055
builder.build_snapshot(None,
2056
[('add', (u'', b'a-root-id', 'directory', None)),
2057
('add', (u'a', b'a-id', 'file', b'content\n')),
2058
('add', (u'c', b'c-id', 'file', b'content\n'))],
2059
revision_id=b'A-id')
2060
builder.build_snapshot([b'A-id'],
2061
[('rename', ('c', 'b'))],
2062
revision_id=b'B-id')
2063
builder.build_snapshot([b'A-id'],
2064
[('rename', ('c', 'b'))],
2065
revision_id=b'C-id')
2066
builder.build_snapshot([b'C-id', b'B-id'],
2067
[('modify', ('a', b'new-content\n')),
2068
('modify', ('b', b'new-content\n'))],
2069
revision_id=b'E-id')
2070
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2071
merge_obj = self.make_merge_obj(builder, b'E-id',
1899
2072
interesting_files=['c'])
1900
2073
entries = list(merge_obj._entries_lca())
1901
root_id = 'a-root-id'
1902
self.assertEqual([('c-id', True,
2074
root_id = b'a-root-id'
2075
self.assertEqual([(b'c-id', True,
2076
((u'c', [u'b', u'b']), u'b', u'b'),
1903
2077
((root_id, [root_id, root_id]), root_id, root_id),
1904
2078
((u'c', [u'b', u'b']), u'b', u'b'),
1905
2079
((False, [False, False]), False, False)),
1908
2082
def test_interesting_file_in_lca(self):
1909
2083
# This renamed the file, but it should still match the entry in LCA
1910
2084
builder = self.get_builder()
1911
builder.build_snapshot('A-id', None,
1912
[('add', (u'', 'a-root-id', 'directory', None)),
1913
('add', (u'a', 'a-id', 'file', 'content\n')),
1914
('add', (u'b', 'b-id', 'file', 'content\n'))])
1915
builder.build_snapshot('B-id', ['A-id'],
1916
[('rename', ('b', 'c'))])
1917
builder.build_snapshot('C-id', ['A-id'], [])
1918
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1919
[('modify', ('a-id', 'new-content\n')),
1920
('modify', ('b-id', 'new-content\n'))])
1921
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1922
[('rename', ('c', 'b'))])
1923
merge_obj = self.make_merge_obj(builder, 'E-id',
2085
builder.build_snapshot(None,
2086
[('add', (u'', b'a-root-id', 'directory', None)),
2087
('add', (u'a', b'a-id', 'file', b'content\n')),
2088
('add', (u'b', b'b-id', 'file', b'content\n'))],
2089
revision_id=b'A-id')
2090
builder.build_snapshot([b'A-id'],
2091
[('rename', ('b', 'c'))], revision_id=b'B-id')
2092
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2093
builder.build_snapshot([b'C-id', b'B-id'],
2094
[('modify', ('a', b'new-content\n')),
2095
('modify', ('b', b'new-content\n'))],
2096
revision_id=b'E-id')
2097
builder.build_snapshot([b'B-id', b'C-id'],
2098
[('rename', ('c', 'b'))], revision_id=b'D-id')
2099
merge_obj = self.make_merge_obj(builder, b'E-id',
1924
2100
interesting_files=['c'])
1925
2101
entries = list(merge_obj._entries_lca())
1926
root_id = 'a-root-id'
1927
self.assertEqual([('b-id', True,
2102
root_id = b'a-root-id'
2103
self.assertEqual([(b'b-id', True,
2104
((u'b', [u'c', u'b']), u'b', u'b'),
1928
2105
((root_id, [root_id, root_id]), root_id, root_id),
1929
2106
((u'b', [u'c', u'b']), u'b', u'b'),
1930
2107
((False, [False, False]), False, False)),
1933
def test_interesting_ids(self):
2110
def test_interesting_files(self):
1934
2111
# Two files modified, but we should filter one of them
1935
2112
builder = self.get_builder()
1936
builder.build_snapshot('A-id', None,
1937
[('add', (u'', 'a-root-id', 'directory', None)),
1938
('add', (u'a', 'a-id', 'file', 'content\n')),
1939
('add', (u'b', 'b-id', 'file', 'content\n'))])
1940
builder.build_snapshot('B-id', ['A-id'], [])
1941
builder.build_snapshot('C-id', ['A-id'], [])
1942
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1943
[('modify', ('a-id', 'new-content\n')),
1944
('modify', ('b-id', 'new-content\n'))])
1945
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1946
merge_obj = self.make_merge_obj(builder, 'E-id',
1947
interesting_ids=['b-id'])
2113
builder.build_snapshot(None,
2114
[('add', (u'', b'a-root-id', 'directory', None)),
2115
('add', (u'a', b'a-id', 'file', b'content\n')),
2116
('add', (u'b', b'b-id', 'file', b'content\n'))],
2117
revision_id=b'A-id')
2118
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2119
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2120
builder.build_snapshot([b'C-id', b'B-id'],
2121
[('modify', ('a', b'new-content\n')),
2122
('modify', ('b', b'new-content\n'))], revision_id=b'E-id')
2123
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2124
merge_obj = self.make_merge_obj(builder, b'E-id',
2125
interesting_files=['b'])
1948
2126
entries = list(merge_obj._entries_lca())
1949
root_id = 'a-root-id'
1950
self.assertEqual([('b-id', True,
2127
root_id = b'a-root-id'
2128
self.assertEqual([(b'b-id', True,
2129
((u'b', [u'b', u'b']), u'b', u'b'),
1951
2130
((root_id, [root_id, root_id]), root_id, root_id),
1952
2131
((u'b', [u'b', u'b']), u'b', u'b'),
1953
2132
((False, [False, False]), False, False)),
1958
2136
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
2223
2408
wt.lock_write()
2224
2409
self.addCleanup(wt.unlock)
2225
2410
os.symlink('bar', 'path/foo')
2226
wt.add(['foo'], ['foo-id'])
2227
wt.commit('A add symlink', rev_id='A-id')
2411
wt.add(['foo'], [b'foo-id'])
2412
wt.commit('A add symlink', rev_id=b'A-id')
2228
2413
wt.rename_one('foo', 'barry')
2229
wt.commit('B foo => barry', rev_id='B-id')
2230
wt.set_last_revision('A-id')
2231
wt.branch.set_last_revision_info(1, 'A-id')
2414
wt.commit('B foo => barry', rev_id=b'B-id')
2415
wt.set_last_revision(b'A-id')
2416
wt.branch.set_last_revision_info(1, b'A-id')
2233
wt.commit('C', rev_id='C-id')
2234
wt.merge_from_branch(wt.branch, 'B-id')
2235
self.assertEqual('barry', wt.id2path('foo-id'))
2236
self.assertEqual('bar', wt.get_symlink_target('foo-id'))
2237
wt.commit('E merges C & B', rev_id='E-id')
2418
wt.commit('C', rev_id=b'C-id')
2419
wt.merge_from_branch(wt.branch, b'B-id')
2420
self.assertEqual('barry', wt.id2path(b'foo-id'))
2421
self.assertEqual('bar', wt.get_symlink_target('barry'))
2422
wt.commit('E merges C & B', rev_id=b'E-id')
2238
2423
wt.rename_one('barry', 'blah')
2239
wt.commit('F barry => blah', rev_id='F-id')
2240
wt.set_last_revision('B-id')
2241
wt.branch.set_last_revision_info(2, 'B-id')
2424
wt.commit('F barry => blah', rev_id=b'F-id')
2425
wt.set_last_revision(b'B-id')
2426
wt.branch.set_last_revision_info(2, b'B-id')
2243
wt.merge_from_branch(wt.branch, 'C-id')
2244
wt.commit('D merges B & C', rev_id='D-id')
2245
self.assertEqual('barry', wt.id2path('foo-id'))
2428
wt.merge_from_branch(wt.branch, b'C-id')
2429
wt.commit('D merges B & C', rev_id=b'D-id')
2430
self.assertEqual('barry', wt.id2path(b'foo-id'))
2246
2431
# Check the output of the Merger object directly
2247
merger = _mod_merge.Merger.from_revision_ids(None,
2432
merger = _mod_merge.Merger.from_revision_ids(wt, b'F-id')
2249
2433
merger.merge_type = _mod_merge.Merge3Merger
2250
2434
merge_obj = merger.make_merger()
2251
2435
root_id = wt.path2id('')
2252
2436
entries = list(merge_obj._entries_lca())
2253
2437
# No content change, just a path change
2254
self.assertEqual([('foo-id', False,
2438
self.assertEqual([(b'foo-id', False,
2439
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2255
2440
((root_id, [root_id, root_id]), root_id, root_id),
2256
2441
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2257
2442
((False, [False, False]), False, False)),
2259
conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
2444
conflicts = wt.merge_from_branch(wt.branch, to_revision=b'F-id')
2260
2445
self.assertEqual(0, conflicts)
2261
self.assertEqual('blah', wt.id2path('foo-id'))
2446
self.assertEqual('blah', wt.id2path(b'foo-id'))
2263
2448
def test_symlink_no_content_change(self):
2264
self.requireFeature(tests.SymlinkFeature)
2449
self.requireFeature(features.SymlinkFeature)
2265
2450
# A Create symlink foo => bar
2267
2452
# B C B relinks foo => baz
2326
2510
wt = self.make_branch_and_tree('path')
2327
2511
wt.lock_write()
2328
2512
self.addCleanup(wt.unlock)
2329
wt.commit('base', rev_id='A-id')
2513
wt.commit('base', rev_id=b'A-id')
2330
2514
os.symlink('bar', 'path/foo')
2331
wt.add(['foo'], ['foo-id'])
2332
wt.commit('add symlink foo => bar', rev_id='B-id')
2333
wt.set_last_revision('A-id')
2334
wt.branch.set_last_revision_info(1, 'A-id')
2515
wt.add(['foo'], [b'foo-id'])
2516
wt.commit('add symlink foo => bar', rev_id=b'B-id')
2517
wt.set_last_revision(b'A-id')
2518
wt.branch.set_last_revision_info(1, b'A-id')
2336
wt.commit('C', rev_id='C-id')
2337
wt.merge_from_branch(wt.branch, 'B-id')
2338
self.assertEqual('bar', wt.get_symlink_target('foo-id'))
2520
wt.commit('C', rev_id=b'C-id')
2521
wt.merge_from_branch(wt.branch, b'B-id')
2522
self.assertEqual('bar', wt.get_symlink_target('foo'))
2339
2523
os.remove('path/foo')
2340
2524
# We have to change the link in E, or it won't try to do a comparison
2341
2525
os.symlink('bing', 'path/foo')
2342
wt.commit('E merges C & B, overrides to bing', rev_id='E-id')
2343
wt.set_last_revision('B-id')
2344
wt.branch.set_last_revision_info(2, 'B-id')
2526
wt.commit('E merges C & B, overrides to bing', rev_id=b'E-id')
2527
wt.set_last_revision(b'B-id')
2528
wt.branch.set_last_revision_info(2, b'B-id')
2346
wt.merge_from_branch(wt.branch, 'C-id')
2530
wt.merge_from_branch(wt.branch, b'C-id')
2347
2531
os.remove('path/foo')
2348
self.build_tree_contents([('path/foo', 'file content\n')])
2532
self.build_tree_contents([('path/foo', b'file content\n')])
2349
2533
# XXX: workaround, WT doesn't detect kind changes unless you do
2350
2534
# iter_changes()
2351
2535
list(wt.iter_changes(wt.basis_tree()))
2352
wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2536
wt.commit('D merges B & C, makes it a file', rev_id=b'D-id')
2354
merger = _mod_merge.Merger.from_revision_ids(None,
2538
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2356
2539
merger.merge_type = _mod_merge.Merge3Merger
2357
2540
merge_obj = merger.make_merger()
2358
2541
entries = list(merge_obj._entries_lca())
2359
2542
root_id = wt.path2id('')
2360
self.assertEqual([('foo-id', True,
2543
self.assertEqual([(b'foo-id', True,
2544
((None, [u'foo', None]), u'foo', u'foo'),
2361
2545
((None, [root_id, None]), root_id, root_id),
2362
2546
((None, [u'foo', None]), u'foo', u'foo'),
2363
2547
((None, [False, None]), False, False)),
2366
2550
def test_symlink_all_wt(self):
2367
2551
"""Check behavior if all trees are Working Trees."""
2368
self.requireFeature(tests.SymlinkFeature)
2552
self.requireFeature(features.SymlinkFeature)
2369
2553
# The big issue is that entry.symlink_target is None for WorkingTrees.
2370
2554
# So we need to make sure we handle that case correctly.
2382
2566
wt.lock_write()
2383
2567
self.addCleanup(wt.unlock)
2384
2568
os.symlink('bar', 'path/foo')
2385
wt.add(['foo'], ['foo-id'])
2386
wt.commit('add symlink', rev_id='A-id')
2569
wt.add(['foo'], [b'foo-id'])
2570
wt.commit('add symlink', rev_id=b'A-id')
2387
2571
os.remove('path/foo')
2388
2572
os.symlink('baz', 'path/foo')
2389
wt.commit('foo => baz', rev_id='B-id')
2390
wt.set_last_revision('A-id')
2391
wt.branch.set_last_revision_info(1, 'A-id')
2573
wt.commit('foo => baz', rev_id=b'B-id')
2574
wt.set_last_revision(b'A-id')
2575
wt.branch.set_last_revision_info(1, b'A-id')
2393
wt.commit('C', rev_id='C-id')
2394
wt.merge_from_branch(wt.branch, 'B-id')
2395
self.assertEqual('baz', wt.get_symlink_target('foo-id'))
2396
wt.commit('E merges C & B', rev_id='E-id')
2577
wt.commit('C', rev_id=b'C-id')
2578
wt.merge_from_branch(wt.branch, b'B-id')
2579
self.assertEqual('baz', wt.get_symlink_target('foo'))
2580
wt.commit('E merges C & B', rev_id=b'E-id')
2397
2581
os.remove('path/foo')
2398
2582
os.symlink('bing', 'path/foo')
2399
wt.commit('F foo => bing', rev_id='F-id')
2400
wt.set_last_revision('B-id')
2401
wt.branch.set_last_revision_info(2, 'B-id')
2583
wt.commit('F foo => bing', rev_id=b'F-id')
2584
wt.set_last_revision(b'B-id')
2585
wt.branch.set_last_revision_info(2, b'B-id')
2403
wt.merge_from_branch(wt.branch, 'C-id')
2404
wt.commit('D merges B & C', rev_id='D-id')
2405
wt_base = wt.bzrdir.sprout('base', 'A-id').open_workingtree()
2587
wt.merge_from_branch(wt.branch, b'C-id')
2588
wt.commit('D merges B & C', rev_id=b'D-id')
2589
wt_base = wt.controldir.sprout('base', b'A-id').open_workingtree()
2406
2590
wt_base.lock_read()
2407
2591
self.addCleanup(wt_base.unlock)
2408
wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2592
wt_lca1 = wt.controldir.sprout('b-tree', b'B-id').open_workingtree()
2409
2593
wt_lca1.lock_read()
2410
2594
self.addCleanup(wt_lca1.unlock)
2411
wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2595
wt_lca2 = wt.controldir.sprout('c-tree', b'C-id').open_workingtree()
2412
2596
wt_lca2.lock_read()
2413
2597
self.addCleanup(wt_lca2.unlock)
2414
wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
2598
wt_other = wt.controldir.sprout('other', b'F-id').open_workingtree()
2415
2599
wt_other.lock_read()
2416
2600
self.addCleanup(wt_other.unlock)
2417
2601
merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
2418
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2602
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2419
2603
entries = list(merge_obj._entries_lca())
2420
2604
root_id = wt.path2id('')
2421
self.assertEqual([('foo-id', True,
2605
self.assertEqual([(b'foo-id', True,
2606
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2422
2607
((root_id, [root_id, root_id]), root_id, root_id),
2423
2608
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2424
2609
((False, [False, False]), False, False)),
2427
2612
def test_other_reverted_path_to_base(self):
2428
2613
# A Path at 'foo'
2436
2621
# F Path at 'foo'
2437
2622
builder = self.get_builder()
2438
builder.build_snapshot('A-id', None,
2439
[('add', (u'', 'a-root-id', 'directory', None)),
2440
('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
2441
builder.build_snapshot('C-id', ['A-id'], [])
2442
builder.build_snapshot('B-id', ['A-id'],
2443
[('rename', ('foo', 'bar'))])
2444
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2445
[('rename', ('foo', 'bar'))]) # merge the rename
2446
builder.build_snapshot('F-id', ['E-id'],
2447
[('rename', ('bar', 'foo'))]) # Rename back to BASE
2448
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2449
wt, conflicts = self.do_merge(builder, 'F-id')
2623
builder.build_snapshot(None,
2624
[('add', (u'', b'a-root-id', 'directory', None)),
2625
('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
2626
revision_id=b'A-id')
2627
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2628
builder.build_snapshot([b'A-id'],
2629
[('rename', ('foo', 'bar'))], revision_id=b'B-id')
2630
builder.build_snapshot([b'C-id', b'B-id'],
2631
[('rename', ('foo', 'bar'))], revision_id=b'E-id') # merge the rename
2632
builder.build_snapshot([b'E-id'],
2633
[('rename', ('bar', 'foo'))], revision_id=b'F-id') # Rename back to BASE
2634
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2635
wt, conflicts = self.do_merge(builder, b'F-id')
2450
2636
self.assertEqual(0, conflicts)
2451
self.assertEqual('foo', wt.id2path('foo-id'))
2637
self.assertEqual('foo', wt.id2path(b'foo-id'))
2453
2639
def test_other_reverted_content_to_base(self):
2454
2640
builder = self.get_builder()
2455
builder.build_snapshot('A-id', None,
2456
[('add', (u'', 'a-root-id', 'directory', None)),
2457
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2458
builder.build_snapshot('C-id', ['A-id'], [])
2459
builder.build_snapshot('B-id', ['A-id'],
2460
[('modify', ('foo-id', 'B content\n'))])
2461
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2462
[('modify', ('foo-id', 'B content\n'))]) # merge the content
2463
builder.build_snapshot('F-id', ['E-id'],
2464
[('modify', ('foo-id', 'base content\n'))]) # Revert back to BASE
2465
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2466
wt, conflicts = self.do_merge(builder, 'F-id')
2641
builder.build_snapshot(None,
2642
[('add', (u'', b'a-root-id', 'directory', None)),
2643
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2644
revision_id=b'A-id')
2645
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2646
builder.build_snapshot([b'A-id'],
2647
[('modify', ('foo', b'B content\n'))],
2648
revision_id=b'B-id')
2649
builder.build_snapshot([b'C-id', b'B-id'],
2650
[('modify', ('foo', b'B content\n'))],
2651
revision_id=b'E-id') # merge the content
2652
builder.build_snapshot([b'E-id'],
2653
[('modify', ('foo', b'base content\n'))],
2654
revision_id=b'F-id') # Revert back to BASE
2655
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2656
wt, conflicts = self.do_merge(builder, b'F-id')
2467
2657
self.assertEqual(0, conflicts)
2468
2658
# TODO: We need to use the per-file graph to properly select a BASE
2469
2659
# before this will work. Or at least use the LCA trees to find
2470
2660
# the appropriate content base. (which is B, not A).
2471
self.assertEqual('base content\n', wt.get_file_text('foo-id'))
2661
self.assertEqual(b'base content\n', wt.get_file_text('foo'))
2473
2663
def test_other_modified_content(self):
2474
2664
builder = self.get_builder()
2475
builder.build_snapshot('A-id', None,
2476
[('add', (u'', 'a-root-id', 'directory', None)),
2477
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2478
builder.build_snapshot('C-id', ['A-id'], [])
2479
builder.build_snapshot('B-id', ['A-id'],
2480
[('modify', ('foo-id', 'B content\n'))])
2481
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2482
[('modify', ('foo-id', 'B content\n'))]) # merge the content
2483
builder.build_snapshot('F-id', ['E-id'],
2484
[('modify', ('foo-id', 'F content\n'))]) # Override B content
2485
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2486
wt, conflicts = self.do_merge(builder, 'F-id')
2665
builder.build_snapshot(None,
2666
[('add', (u'', b'a-root-id', 'directory', None)),
2667
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2668
revision_id=b'A-id')
2669
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2670
builder.build_snapshot([b'A-id'],
2671
[('modify', ('foo', b'B content\n'))],
2672
revision_id=b'B-id')
2673
builder.build_snapshot([b'C-id', b'B-id'],
2674
[('modify', ('foo', b'B content\n'))],
2675
revision_id=b'E-id') # merge the content
2676
builder.build_snapshot([b'E-id'],
2677
[('modify', ('foo', b'F content\n'))],
2678
revision_id=b'F-id') # Override B content
2679
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2680
wt, conflicts = self.do_merge(builder, b'F-id')
2487
2681
self.assertEqual(0, conflicts)
2488
self.assertEqual('F content\n', wt.get_file_text('foo-id'))
2682
self.assertEqual(b'F content\n', wt.get_file_text('foo'))
2490
2684
def test_all_wt(self):
2491
2685
"""Check behavior if all trees are Working Trees."""
2499
2693
# D E E updates content, renames 'b' => 'c'
2500
2694
builder = self.get_builder()
2501
builder.build_snapshot('A-id', None,
2502
[('add', (u'', 'a-root-id', 'directory', None)),
2503
('add', (u'a', 'a-id', 'file', 'base content\n')),
2504
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2505
builder.build_snapshot('B-id', ['A-id'],
2506
[('modify', ('foo-id', 'B content\n'))])
2507
builder.build_snapshot('C-id', ['A-id'],
2508
[('rename', ('a', 'b'))])
2509
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2510
[('rename', ('b', 'c')),
2511
('modify', ('foo-id', 'E content\n'))])
2512
builder.build_snapshot('D-id', ['B-id', 'C-id'],
2513
[('rename', ('a', 'b'))]) # merged change
2695
builder.build_snapshot(None,
2696
[('add', (u'', b'a-root-id', 'directory', None)),
2697
('add', (u'a', b'a-id', 'file', b'base content\n')),
2698
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2699
revision_id=b'A-id')
2700
builder.build_snapshot([b'A-id'],
2701
[('modify', ('foo', b'B content\n'))],
2702
revision_id=b'B-id')
2703
builder.build_snapshot([b'A-id'],
2704
[('rename', ('a', 'b'))],
2705
revision_id=b'C-id')
2706
builder.build_snapshot([b'C-id', b'B-id'],
2707
[('rename', ('b', 'c')),
2708
('modify', ('foo', b'E content\n'))],
2709
revision_id=b'E-id')
2710
builder.build_snapshot([b'B-id', b'C-id'],
2711
[('rename', ('a', 'b'))], revision_id=b'D-id') # merged change
2514
2712
wt_this = self.get_wt_from_builder(builder)
2515
wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
2713
wt_base = wt_this.controldir.sprout('base', b'A-id').open_workingtree()
2516
2714
wt_base.lock_read()
2517
2715
self.addCleanup(wt_base.unlock)
2518
wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2716
wt_lca1 = wt_this.controldir.sprout(
2717
'b-tree', b'B-id').open_workingtree()
2519
2718
wt_lca1.lock_read()
2520
2719
self.addCleanup(wt_lca1.unlock)
2521
wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2720
wt_lca2 = wt_this.controldir.sprout(
2721
'c-tree', b'C-id').open_workingtree()
2522
2722
wt_lca2.lock_read()
2523
2723
self.addCleanup(wt_lca2.unlock)
2524
wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
2724
wt_other = wt_this.controldir.sprout(
2725
'other', b'E-id').open_workingtree()
2525
2726
wt_other.lock_read()
2526
2727
self.addCleanup(wt_other.unlock)
2527
2728
merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
2528
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2729
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2529
2730
entries = list(merge_obj._entries_lca())
2530
root_id = 'a-root-id'
2531
self.assertEqual([('a-id', False,
2532
((root_id, [root_id, root_id]), root_id, root_id),
2533
((u'a', [u'a', u'b']), u'c', u'b'),
2534
((False, [False, False]), False, False)),
2536
((root_id, [root_id, root_id]), root_id, root_id),
2537
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2538
((False, [False, False]), False, False)),
2731
root_id = b'a-root-id'
2732
self.assertEqual([(b'a-id', False,
2733
((u'a', [u'a', u'b']), u'c', u'b'),
2734
((root_id, [root_id, root_id]), root_id, root_id),
2735
((u'a', [u'a', u'b']), u'c', u'b'),
2736
((False, [False, False]), False, False)),
2738
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2739
((root_id, [root_id, root_id]), root_id, root_id),
2740
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2741
((False, [False, False]), False, False)),
2541
2744
def test_nested_tree_unmodified(self):
2542
2745
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2543
2746
# 'tree-reference'
2544
2747
wt = self.make_branch_and_tree('tree',
2545
format='dirstate-with-subtree')
2748
format='development-subtree')
2546
2749
wt.lock_write()
2547
2750
self.addCleanup(wt.unlock)
2548
2751
sub_tree = self.make_branch_and_tree('tree/sub-tree',
2549
format='dirstate-with-subtree')
2550
wt.set_root_id('a-root-id')
2551
sub_tree.set_root_id('sub-tree-root')
2552
self.build_tree_contents([('tree/sub-tree/file', 'text1')])
2752
format='development-subtree')
2753
wt.set_root_id(b'a-root-id')
2754
sub_tree.set_root_id(b'sub-tree-root')
2755
self.build_tree_contents([('tree/sub-tree/file', b'text1')])
2553
2756
sub_tree.add('file')
2554
sub_tree.commit('foo', rev_id='sub-A-id')
2757
sub_tree.commit('foo', rev_id=b'sub-A-id')
2555
2758
wt.add_reference(sub_tree)
2556
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2759
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2557
2760
# Now create a criss-cross merge in the parent, without modifying the
2559
wt.commit('B', rev_id='B-id', recursive=None)
2560
wt.set_last_revision('A-id')
2561
wt.branch.set_last_revision_info(1, 'A-id')
2562
wt.commit('C', rev_id='C-id', recursive=None)
2563
wt.merge_from_branch(wt.branch, to_revision='B-id')
2564
wt.commit('E', rev_id='E-id', recursive=None)
2565
wt.set_parent_ids(['B-id', 'C-id'])
2566
wt.branch.set_last_revision_info(2, 'B-id')
2567
wt.commit('D', rev_id='D-id', recursive=None)
2762
wt.commit('B', rev_id=b'B-id', recursive=None)
2763
wt.set_last_revision(b'A-id')
2764
wt.branch.set_last_revision_info(1, b'A-id')
2765
wt.commit('C', rev_id=b'C-id', recursive=None)
2766
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2767
wt.commit('E', rev_id=b'E-id', recursive=None)
2768
wt.set_parent_ids([b'B-id', b'C-id'])
2769
wt.branch.set_last_revision_info(2, b'B-id')
2770
wt.commit('D', rev_id=b'D-id', recursive=None)
2569
merger = _mod_merge.Merger.from_revision_ids(None,
2772
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2571
2773
merger.merge_type = _mod_merge.Merge3Merger
2572
2774
merge_obj = merger.make_merger()
2573
2775
entries = list(merge_obj._entries_lca())
2616
2817
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2617
2818
# 'tree-reference'
2618
2819
wt = self.make_branch_and_tree('tree',
2619
format='dirstate-with-subtree')
2820
format='development-subtree')
2620
2821
wt.lock_write()
2621
2822
self.addCleanup(wt.unlock)
2622
2823
sub_tree = self.make_branch_and_tree('tree/sub',
2623
format='dirstate-with-subtree')
2624
wt.set_root_id('a-root-id')
2625
sub_tree.set_root_id('sub-tree-root')
2626
self.build_tree_contents([('tree/sub/file', 'text1')])
2824
format='development-subtree')
2825
wt.set_root_id(b'a-root-id')
2826
sub_tree.set_root_id(b'sub-tree-root')
2827
self.build_tree_contents([('tree/sub/file', b'text1')])
2627
2828
sub_tree.add('file')
2628
sub_tree.commit('foo', rev_id='sub-A-id')
2829
sub_tree.commit('foo', rev_id=b'sub-A-id')
2629
2830
wt.add_reference(sub_tree)
2630
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2831
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2631
2832
# Now create a criss-cross merge in the parent, without modifying the
2633
wt.commit('B', rev_id='B-id', recursive=None)
2634
wt.set_last_revision('A-id')
2635
wt.branch.set_last_revision_info(1, 'A-id')
2636
wt.commit('C', rev_id='C-id', recursive=None)
2637
wt.merge_from_branch(wt.branch, to_revision='B-id')
2834
wt.commit('B', rev_id=b'B-id', recursive=None)
2835
wt.set_last_revision(b'A-id')
2836
wt.branch.set_last_revision_info(1, b'A-id')
2837
wt.commit('C', rev_id=b'C-id', recursive=None)
2838
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2638
2839
wt.rename_one('sub', 'alt_sub')
2639
wt.commit('E', rev_id='E-id', recursive=None)
2640
wt.set_last_revision('B-id')
2840
wt.commit('E', rev_id=b'E-id', recursive=None)
2841
wt.set_last_revision(b'B-id')
2642
wt.set_parent_ids(['B-id', 'C-id'])
2643
wt.branch.set_last_revision_info(2, 'B-id')
2644
wt.commit('D', rev_id='D-id', recursive=None)
2843
wt.set_parent_ids([b'B-id', b'C-id'])
2844
wt.branch.set_last_revision_info(2, b'B-id')
2845
wt.commit('D', rev_id=b'D-id', recursive=None)
2646
merger = _mod_merge.Merger.from_revision_ids(None,
2847
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2648
2848
merger.merge_type = _mod_merge.Merge3Merger
2649
2849
merge_obj = merger.make_merger()
2650
2850
entries = list(merge_obj._entries_lca())
2651
root_id = 'a-root-id'
2652
self.assertEqual([('sub-tree-root', False,
2851
root_id = b'a-root-id'
2852
self.assertEqual([(b'sub-tree-root', False,
2853
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2653
2854
((root_id, [root_id, root_id]), root_id, root_id),
2654
2855
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2655
2856
((False, [False, False]), False, False)),
2658
2859
def test_nested_tree_subtree_renamed_and_modified(self):
2659
2860
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2660
2861
# 'tree-reference'
2661
2862
wt = self.make_branch_and_tree('tree',
2662
format='dirstate-with-subtree')
2863
format='development-subtree')
2663
2864
wt.lock_write()
2664
2865
self.addCleanup(wt.unlock)
2665
2866
sub_tree = self.make_branch_and_tree('tree/sub',
2666
format='dirstate-with-subtree')
2667
wt.set_root_id('a-root-id')
2668
sub_tree.set_root_id('sub-tree-root')
2669
self.build_tree_contents([('tree/sub/file', 'text1')])
2867
format='development-subtree')
2868
wt.set_root_id(b'a-root-id')
2869
sub_tree.set_root_id(b'sub-tree-root')
2870
self.build_tree_contents([('tree/sub/file', b'text1')])
2670
2871
sub_tree.add('file')
2671
sub_tree.commit('foo', rev_id='sub-A-id')
2872
sub_tree.commit('foo', rev_id=b'sub-A-id')
2672
2873
wt.add_reference(sub_tree)
2673
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2874
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2674
2875
# Now create a criss-cross merge in the parent, without modifying the
2676
wt.commit('B', rev_id='B-id', recursive=None)
2677
wt.set_last_revision('A-id')
2678
wt.branch.set_last_revision_info(1, 'A-id')
2679
wt.commit('C', rev_id='C-id', recursive=None)
2680
wt.merge_from_branch(wt.branch, to_revision='B-id')
2681
self.build_tree_contents([('tree/sub/file', 'text2')])
2682
sub_tree.commit('modify contents', rev_id='sub-B-id')
2877
wt.commit('B', rev_id=b'B-id', recursive=None)
2878
wt.set_last_revision(b'A-id')
2879
wt.branch.set_last_revision_info(1, b'A-id')
2880
wt.commit('C', rev_id=b'C-id', recursive=None)
2881
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2882
self.build_tree_contents([('tree/sub/file', b'text2')])
2883
sub_tree.commit('modify contents', rev_id=b'sub-B-id')
2683
2884
wt.rename_one('sub', 'alt_sub')
2684
wt.commit('E', rev_id='E-id', recursive=None)
2685
wt.set_last_revision('B-id')
2885
wt.commit('E', rev_id=b'E-id', recursive=None)
2886
wt.set_last_revision(b'B-id')
2687
wt.set_parent_ids(['B-id', 'C-id'])
2688
wt.branch.set_last_revision_info(2, 'B-id')
2689
wt.commit('D', rev_id='D-id', recursive=None)
2888
wt.set_parent_ids([b'B-id', b'C-id'])
2889
wt.branch.set_last_revision_info(2, b'B-id')
2890
wt.commit('D', rev_id=b'D-id', recursive=None)
2691
merger = _mod_merge.Merger.from_revision_ids(None,
2892
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2693
2893
merger.merge_type = _mod_merge.Merge3Merger
2694
2894
merge_obj = merger.make_merger()
2695
2895
entries = list(merge_obj._entries_lca())
2696
root_id = 'a-root-id'
2697
self.assertEqual([('sub-tree-root', False,
2896
root_id = b'a-root-id'
2897
self.assertEqual([(b'sub-tree-root', False,
2898
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2698
2899
((root_id, [root_id, root_id]), root_id, root_id),
2699
2900
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2700
2901
((False, [False, False]), False, False)),
2704
2905
class TestLCAMultiWay(tests.TestCase):
2706
2907
def assertLCAMultiWay(self, expected, base, lcas, other, this,
2707
2908
allow_overriding_lca=True):
2708
2909
self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
2709
(base, lcas), other, this,
2710
allow_overriding_lca=allow_overriding_lca))
2910
(base, lcas), other, this,
2911
allow_overriding_lca=allow_overriding_lca))
2712
2913
def test_other_equal_equal_lcas(self):
2713
2914
"""Test when OTHER=LCA and all LCAs are identical."""
2714
2915
self.assertLCAMultiWay('this',
2715
'bval', ['bval', 'bval'], 'bval', 'bval')
2716
self.assertLCAMultiWay('this',
2717
'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
2718
self.assertLCAMultiWay('this',
2719
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
2720
self.assertLCAMultiWay('this',
2721
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
2722
self.assertLCAMultiWay('this',
2723
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
2916
'bval', ['bval', 'bval'], 'bval', 'bval')
2917
self.assertLCAMultiWay('this',
2918
'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
2919
self.assertLCAMultiWay('this',
2920
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
2921
self.assertLCAMultiWay('this',
2922
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
2923
self.assertLCAMultiWay('this',
2924
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
2725
2926
def test_other_equal_this(self):
2726
2927
"""Test when other and this are identical."""
2727
2928
self.assertLCAMultiWay('this',
2728
'bval', ['bval', 'bval'], 'oval', 'oval')
2729
self.assertLCAMultiWay('this',
2730
'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
2731
self.assertLCAMultiWay('this',
2732
'bval', ['cval', 'dval'], 'oval', 'oval')
2733
self.assertLCAMultiWay('this',
2734
'bval', [None, 'lcaval'], 'oval', 'oval')
2735
self.assertLCAMultiWay('this',
2736
None, [None, 'lcaval'], 'oval', 'oval')
2737
self.assertLCAMultiWay('this',
2738
None, ['lcaval', 'lcaval'], 'oval', 'oval')
2739
self.assertLCAMultiWay('this',
2740
None, ['cval', 'dval'], 'oval', 'oval')
2741
self.assertLCAMultiWay('this',
2742
None, ['cval', 'dval'], None, None)
2743
self.assertLCAMultiWay('this',
2744
None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
2929
'bval', ['bval', 'bval'], 'oval', 'oval')
2930
self.assertLCAMultiWay('this',
2931
'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
2932
self.assertLCAMultiWay('this',
2933
'bval', ['cval', 'dval'], 'oval', 'oval')
2934
self.assertLCAMultiWay('this',
2935
'bval', [None, 'lcaval'], 'oval', 'oval')
2936
self.assertLCAMultiWay('this',
2937
None, [None, 'lcaval'], 'oval', 'oval')
2938
self.assertLCAMultiWay('this',
2939
None, ['lcaval', 'lcaval'], 'oval', 'oval')
2940
self.assertLCAMultiWay('this',
2941
None, ['cval', 'dval'], 'oval', 'oval')
2942
self.assertLCAMultiWay('this',
2943
None, ['cval', 'dval'], None, None)
2944
self.assertLCAMultiWay('this',
2945
None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
2746
2947
def test_no_lcas(self):
2747
2948
self.assertLCAMultiWay('this',
2748
'bval', [], 'bval', 'tval')
2949
'bval', [], 'bval', 'tval')
2749
2950
self.assertLCAMultiWay('other',
2750
'bval', [], 'oval', 'bval')
2951
'bval', [], 'oval', 'bval')
2751
2952
self.assertLCAMultiWay('conflict',
2752
'bval', [], 'oval', 'tval')
2953
'bval', [], 'oval', 'tval')
2753
2954
self.assertLCAMultiWay('this',
2754
'bval', [], 'oval', 'oval')
2955
'bval', [], 'oval', 'oval')
2756
2957
def test_lca_supersedes_other_lca(self):
2757
2958
"""If one lca == base, the other lca takes precedence"""
2758
2959
self.assertLCAMultiWay('this',
2759
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2960
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2760
2961
self.assertLCAMultiWay('this',
2761
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2962
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2762
2963
# This is actually considered a 'revert' because the 'lcaval' in LCAS
2763
2964
# supersedes the BASE val (in the other LCA) but then OTHER reverts it
2764
2965
# back to bval.
2765
2966
self.assertLCAMultiWay('other',
2766
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2967
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2767
2968
self.assertLCAMultiWay('conflict',
2768
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2969
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2770
2971
def test_other_and_this_pick_different_lca(self):
2771
2972
# OTHER and THIS resolve the lca conflict in different ways
2772
2973
self.assertLCAMultiWay('conflict',
2773
'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
2774
self.assertLCAMultiWay('conflict',
2775
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
2776
self.assertLCAMultiWay('conflict',
2777
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
2974
'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
2975
self.assertLCAMultiWay('conflict',
2976
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
2977
self.assertLCAMultiWay('conflict',
2978
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
2779
2980
def test_other_in_lca(self):
2780
2981
# OTHER takes a value of one of the LCAs, THIS takes a new value, which
2781
2982
# theoretically supersedes both LCA values and 'wins'
2782
self.assertLCAMultiWay('this',
2783
'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
2784
self.assertLCAMultiWay('this',
2785
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval')
2786
self.assertLCAMultiWay('conflict',
2787
'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval',
2788
allow_overriding_lca=False)
2789
self.assertLCAMultiWay('conflict',
2790
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval',
2791
allow_overriding_lca=False)
2983
self.assertLCAMultiWay(
2984
'this', 'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
2985
self.assertLCAMultiWay(
2986
'this', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val',
2988
self.assertLCAMultiWay('conflict',
2990
'lca2val'], 'lca1val', 'newval',
2991
allow_overriding_lca=False)
2992
self.assertLCAMultiWay('conflict',
2993
'bval', ['lca1val', 'lca2val',
2994
'lca3val'], 'lca1val', 'newval',
2995
allow_overriding_lca=False)
2792
2996
# THIS reverted back to BASE, but that is an explicit supersede of all
2794
self.assertLCAMultiWay('this',
2795
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval')
2796
self.assertLCAMultiWay('this',
2797
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
2798
self.assertLCAMultiWay('conflict',
2799
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval',
2800
allow_overriding_lca=False)
2801
self.assertLCAMultiWay('conflict',
2802
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval',
2803
allow_overriding_lca=False)
2998
self.assertLCAMultiWay(
2999
'this', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val',
3001
self.assertLCAMultiWay(
3002
'this', 'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
3003
self.assertLCAMultiWay('conflict',
3004
'bval', ['lca1val', 'lca2val',
3005
'lca3val'], 'lca1val', 'bval',
3006
allow_overriding_lca=False)
3007
self.assertLCAMultiWay('conflict',
3008
'bval', ['lca1val', 'lca2val',
3009
'bval'], 'lca1val', 'bval',
3010
allow_overriding_lca=False)
2805
3012
def test_this_in_lca(self):
2806
3013
# THIS takes a value of one of the LCAs, OTHER takes a new value, which
2807
3014
# theoretically supersedes both LCA values and 'wins'
2808
self.assertLCAMultiWay('other',
2809
'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
2810
self.assertLCAMultiWay('other',
2811
'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
2812
self.assertLCAMultiWay('conflict',
2813
'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val',
2814
allow_overriding_lca=False)
2815
self.assertLCAMultiWay('conflict',
2816
'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val',
2817
allow_overriding_lca=False)
3015
self.assertLCAMultiWay(
3016
'other', 'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
3017
self.assertLCAMultiWay(
3018
'other', 'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
3019
self.assertLCAMultiWay('conflict',
3021
'lca2val'], 'oval', 'lca1val',
3022
allow_overriding_lca=False)
3023
self.assertLCAMultiWay('conflict',
3025
'lca2val'], 'oval', 'lca2val',
3026
allow_overriding_lca=False)
2818
3027
# OTHER reverted back to BASE, but that is an explicit supersede of all
2820
self.assertLCAMultiWay('other',
2821
'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val')
2822
self.assertLCAMultiWay('conflict',
2823
'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val',
2824
allow_overriding_lca=False)
3029
self.assertLCAMultiWay(
3030
'other', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval',
3032
self.assertLCAMultiWay(
3033
'conflict', 'bval', ['lca1val', 'lca2val', 'lca3val'],
3034
'bval', 'lca3val', allow_overriding_lca=False)
2826
3036
def test_all_differ(self):
2827
self.assertLCAMultiWay('conflict',
2828
'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
2829
self.assertLCAMultiWay('conflict',
2830
'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
2831
self.assertLCAMultiWay('conflict',
2832
'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')
3037
self.assertLCAMultiWay(
3038
'conflict', 'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
3039
self.assertLCAMultiWay(
3040
'conflict', 'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval',
3042
self.assertLCAMultiWay(
3043
'conflict', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval',
2835
3047
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
2903
3116
def test_hook_called_for_text_conflicts(self):
2904
3117
builder = self.make_text_conflict()
2905
conflicts = builder.merge()
2906
3119
# The hook should call the merge_text() method
2907
3120
self.assertEqual(['merge_text'], self.calls)
2909
3122
def test_hook_not_called_for_kind_change(self):
2910
3123
builder = self.make_kind_change()
2911
conflicts = builder.merge()
2912
3125
# The hook should not call the merge_text() method
2913
3126
self.assertEqual([], self.calls)
2915
3128
def test_hook_not_called_for_other_files(self):
2916
3129
builder = self.make_text_conflict('foobar')
2917
conflicts = builder.merge()
2918
3131
# The hook should not call the merge_text() method
2919
3132
self.assertEqual([], self.calls)
3135
class TestMergeIntoBase(tests.TestCaseWithTransport):
3137
def setup_simple_branch(self, relpath, shape=None, root_id=None):
3138
"""One commit, containing tree specified by optional shape.
3140
Default is empty tree (just root entry).
3143
root_id = b'%s-root-id' % (relpath.encode('ascii'),)
3144
wt = self.make_branch_and_tree(relpath)
3145
wt.set_root_id(root_id)
3146
if shape is not None:
3147
adjusted_shape = [relpath + '/' + elem for elem in shape]
3148
self.build_tree(adjusted_shape)
3150
(b'%s-%s-id' % (relpath.encode('utf-8'),
3151
basename(elem.rstrip('/')).encode('ascii')))
3153
wt.add(shape, ids=ids)
3154
rev_id = b'r1-%s' % (relpath.encode('utf-8'),)
3155
wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
3156
self.assertEqual(root_id, wt.path2id(''))
3159
def setup_two_branches(self, custom_root_ids=True):
3160
"""Setup 2 branches, one will be a library, the other a project."""
3164
root_id = inventory.ROOT_ID
3165
project_wt = self.setup_simple_branch(
3166
'project', ['README', 'dir/', 'dir/file.c'],
3168
lib_wt = self.setup_simple_branch(
3169
'lib1', ['README', 'Makefile', 'foo.c'], root_id)
3171
return project_wt, lib_wt
3173
def do_merge_into(self, location, merge_as):
3174
"""Helper for using MergeIntoMerger.
3176
:param location: location of directory to merge from, either the
3177
location of a branch or of a path inside a branch.
3178
:param merge_as: the path in a tree to add the new directory as.
3179
:returns: the conflicts from 'do_merge'.
3181
with contextlib.ExitStack() as stack:
3182
# Open and lock the various tree and branch objects
3183
wt, subdir_relpath = WorkingTree.open_containing(merge_as)
3184
stack.enter_context(wt.lock_write())
3185
branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
3187
stack.enter_context(branch_to_merge.lock_read())
3188
other_tree = branch_to_merge.basis_tree()
3189
stack.enter_context(other_tree.lock_read())
3191
merger = _mod_merge.MergeIntoMerger(
3192
this_tree=wt, other_tree=other_tree, other_branch=branch_to_merge,
3193
target_subdir=subdir_relpath, source_subpath=subdir_to_merge)
3194
merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
3195
conflicts = merger.do_merge()
3196
merger.set_pending()
3199
def assertTreeEntriesEqual(self, expected_entries, tree):
3200
"""Assert that 'tree' contains the expected inventory entries.
3202
:param expected_entries: sequence of (path, file-id) pairs.
3204
files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
3205
self.assertEqual(expected_entries, files)
3208
class TestMergeInto(TestMergeIntoBase):
3210
def test_newdir_with_unique_roots(self):
3211
"""Merge a branch with a unique root into a new directory."""
3212
project_wt, lib_wt = self.setup_two_branches()
3213
self.do_merge_into('lib1', 'project/lib1')
3214
project_wt.lock_read()
3215
self.addCleanup(project_wt.unlock)
3216
# The r1-lib1 revision should be merged into this one
3217
self.assertEqual([b'r1-project', b'r1-lib1'],
3218
project_wt.get_parent_ids())
3219
self.assertTreeEntriesEqual(
3220
[('', b'project-root-id'),
3221
('README', b'project-README-id'),
3222
('dir', b'project-dir-id'),
3223
('lib1', b'lib1-root-id'),
3224
('dir/file.c', b'project-file.c-id'),
3225
('lib1/Makefile', b'lib1-Makefile-id'),
3226
('lib1/README', b'lib1-README-id'),
3227
('lib1/foo.c', b'lib1-foo.c-id'),
3230
def test_subdir(self):
3231
"""Merge a branch into a subdirectory of an existing directory."""
3232
project_wt, lib_wt = self.setup_two_branches()
3233
self.do_merge_into('lib1', 'project/dir/lib1')
3234
project_wt.lock_read()
3235
self.addCleanup(project_wt.unlock)
3236
# The r1-lib1 revision should be merged into this one
3237
self.assertEqual([b'r1-project', b'r1-lib1'],
3238
project_wt.get_parent_ids())
3239
self.assertTreeEntriesEqual(
3240
[('', b'project-root-id'),
3241
('README', b'project-README-id'),
3242
('dir', b'project-dir-id'),
3243
('dir/file.c', b'project-file.c-id'),
3244
('dir/lib1', b'lib1-root-id'),
3245
('dir/lib1/Makefile', b'lib1-Makefile-id'),
3246
('dir/lib1/README', b'lib1-README-id'),
3247
('dir/lib1/foo.c', b'lib1-foo.c-id'),
3250
def test_newdir_with_repeat_roots(self):
3251
"""If the file-id of the dir to be merged already exists a new ID will
3252
be allocated to let the merge happen.
3254
project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
3255
root_id = project_wt.path2id('')
3256
self.do_merge_into('lib1', 'project/lib1')
3257
project_wt.lock_read()
3258
self.addCleanup(project_wt.unlock)
3259
# The r1-lib1 revision should be merged into this one
3260
self.assertEqual([b'r1-project', b'r1-lib1'],
3261
project_wt.get_parent_ids())
3262
new_lib1_id = project_wt.path2id('lib1')
3263
self.assertNotEqual(None, new_lib1_id)
3264
self.assertTreeEntriesEqual(
3266
('README', b'project-README-id'),
3267
('dir', b'project-dir-id'),
3268
('lib1', new_lib1_id),
3269
('dir/file.c', b'project-file.c-id'),
3270
('lib1/Makefile', b'lib1-Makefile-id'),
3271
('lib1/README', b'lib1-README-id'),
3272
('lib1/foo.c', b'lib1-foo.c-id'),
3275
def test_name_conflict(self):
3276
"""When the target directory name already exists a conflict is
3277
generated and the original directory is renamed to foo.moved.
3279
dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
3280
self.setup_simple_branch('src', ['README'])
3281
conflicts = self.do_merge_into('src', 'dest/dir')
3282
self.assertEqual(1, conflicts)
3284
self.addCleanup(dest_wt.unlock)
3285
# The r1-lib1 revision should be merged into this one
3286
self.assertEqual([b'r1-dest', b'r1-src'], dest_wt.get_parent_ids())
3287
self.assertTreeEntriesEqual(
3288
[('', b'dest-root-id'),
3289
('dir', b'src-root-id'),
3290
('dir.moved', b'dest-dir-id'),
3291
('dir/README', b'src-README-id'),
3292
('dir.moved/file.txt', b'dest-file.txt-id'),
3295
def test_file_id_conflict(self):
3296
"""A conflict is generated if the merge-into adds a file (or other
3297
inventory entry) with a file-id that already exists in the target tree.
3299
self.setup_simple_branch('dest', ['file.txt'])
3300
# Make a second tree with a file-id that will clash with file.txt in
3302
src_wt = self.make_branch_and_tree('src')
3303
self.build_tree(['src/README'])
3304
src_wt.add(['README'], ids=[b'dest-file.txt-id'])
3305
src_wt.commit("Rev 1 of src.", rev_id=b'r1-src')
3306
conflicts = self.do_merge_into('src', 'dest/dir')
3307
# This is an edge case that shouldn't happen to users very often. So
3308
# we don't care really about the exact presentation of the conflict,
3309
# just that there is one.
3310
self.assertEqual(1, conflicts)
3312
def test_only_subdir(self):
3313
"""When the location points to just part of a tree, merge just that
3316
dest_wt = self.setup_simple_branch('dest')
3317
self.setup_simple_branch('src', ['hello.txt', 'dir/', 'dir/foo.c'])
3318
self.do_merge_into('src/dir', 'dest/dir')
3320
self.addCleanup(dest_wt.unlock)
3321
# The r1-lib1 revision should NOT be merged into this one (this is a
3323
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3324
self.assertTreeEntriesEqual(
3325
[('', b'dest-root-id'),
3326
('dir', b'src-dir-id'),
3327
('dir/foo.c', b'src-foo.c-id'),
3330
def test_only_file(self):
3331
"""An edge case: merge just one file, not a whole dir."""
3332
dest_wt = self.setup_simple_branch('dest')
3333
self.setup_simple_branch('two-file', ['file1.txt', 'file2.txt'])
3334
self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
3336
self.addCleanup(dest_wt.unlock)
3337
# The r1-lib1 revision should NOT be merged into this one
3338
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3339
self.assertTreeEntriesEqual(
3340
[('', b'dest-root-id'), ('file1.txt', b'two-file-file1.txt-id')],
3343
def test_no_such_source_path(self):
3344
"""PathNotInTree is raised if the specified path in the source tree
3347
dest_wt = self.setup_simple_branch('dest')
3348
self.setup_simple_branch('src', ['dir/'])
3349
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3350
'src/no-such-dir', 'dest/foo')
3352
self.addCleanup(dest_wt.unlock)
3353
# The dest tree is unmodified.
3354
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3355
self.assertTreeEntriesEqual([('', b'dest-root-id')], dest_wt)
3357
def test_no_such_target_path(self):
3358
"""PathNotInTree is also raised if the specified path in the target
3359
tree does not exist.
3361
dest_wt = self.setup_simple_branch('dest')
3362
self.setup_simple_branch('src', ['file.txt'])
3363
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3364
'src', 'dest/no-such-dir/foo')
3366
self.addCleanup(dest_wt.unlock)
3367
# The dest tree is unmodified.
3368
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3369
self.assertTreeEntriesEqual([('', b'dest-root-id')], dest_wt)
3372
class TestMergeHooks(TestCaseWithTransport):
3375
super(TestMergeHooks, self).setUp()
3376
self.tree_a = self.make_branch_and_tree('tree_a')
3377
self.build_tree_contents([('tree_a/file', b'content_1')])
3378
self.tree_a.add('file', b'file-id')
3379
self.tree_a.commit('added file')
3381
self.tree_b = self.tree_a.controldir.sprout(
3382
'tree_b').open_workingtree()
3383
self.build_tree_contents([('tree_b/file', b'content_2')])
3384
self.tree_b.commit('modify file')
3386
def test_pre_merge_hook_inject_different_tree(self):
3387
tree_c = self.tree_b.controldir.sprout('tree_c').open_workingtree()
3388
self.build_tree_contents([('tree_c/file', b'content_3')])
3389
tree_c.commit("more content")
3392
def factory(merger):
3393
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3394
merger.other_tree = tree_c
3395
calls.append(merger)
3396
_mod_merge.Merger.hooks.install_named_hook('pre_merge',
3397
factory, 'test factory')
3398
self.tree_a.merge_from_branch(self.tree_b.branch)
3400
self.assertFileEqual(b"content_3", 'tree_a/file')
3401
self.assertLength(1, calls)
3403
def test_post_merge_hook_called(self):
3406
def factory(merger):
3407
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3408
calls.append(merger)
3409
_mod_merge.Merger.hooks.install_named_hook('post_merge',
3410
factory, 'test factory')
3412
self.tree_a.merge_from_branch(self.tree_b.branch)
3414
self.assertFileEqual(b"content_2", 'tree_a/file')
3415
self.assertLength(1, calls)