326
365
'c', but not 'b'.
328
367
this_tree = self.make_branch_and_tree('this')
329
self.build_tree_contents([('this/file', "a\n")])
368
self.build_tree_contents([('this/file', b"a\n")])
330
369
this_tree.add('file')
331
370
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')
371
other_tree = this_tree.controldir.sprout('other').open_workingtree()
372
self.build_tree_contents([('other/file', b"a\nb\n")])
373
other_tree.commit('rev2b', rev_id=b'rev2b')
374
self.build_tree_contents([('other/file', b"c\na\nb\n")])
375
other_tree.commit('rev3b', rev_id=b'rev3b')
337
376
this_tree.lock_write()
338
377
self.addCleanup(this_tree.unlock)
339
378
return this_tree, other_tree
341
380
def test_weave_cherrypick(self):
342
381
this_tree, other_tree = self.prepare_cherrypick()
343
merger = _mod_merge.Merger.from_revision_ids(None,
344
this_tree, 'rev3b', 'rev2b', other_tree.branch)
382
merger = _mod_merge.Merger.from_revision_ids(
383
this_tree, b'rev3b', b'rev2b', other_tree.branch)
345
384
merger.merge_type = _mod_merge.WeaveMerger
346
385
merger.do_merge()
347
self.assertFileEqual('c\na\n', 'this/file')
386
self.assertFileEqual(b'c\na\n', 'this/file')
349
388
def test_weave_cannot_reverse_cherrypick(self):
350
389
this_tree, other_tree = self.prepare_cherrypick()
351
merger = _mod_merge.Merger.from_revision_ids(None,
352
this_tree, 'rev2b', 'rev3b', other_tree.branch)
390
merger = _mod_merge.Merger.from_revision_ids(
391
this_tree, b'rev2b', b'rev3b', other_tree.branch)
353
392
merger.merge_type = _mod_merge.WeaveMerger
354
393
self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
356
395
def test_merge3_can_reverse_cherrypick(self):
357
396
this_tree, other_tree = self.prepare_cherrypick()
358
merger = _mod_merge.Merger.from_revision_ids(None,
359
this_tree, 'rev2b', 'rev3b', other_tree.branch)
397
merger = _mod_merge.Merger.from_revision_ids(
398
this_tree, b'rev2b', b'rev3b', other_tree.branch)
360
399
merger.merge_type = _mod_merge.Merge3Merger
361
400
merger.do_merge()
363
402
def test_merge3_will_detect_cherrypick(self):
364
403
this_tree = self.make_branch_and_tree('this')
365
self.build_tree_contents([('this/file', "a\n")])
404
self.build_tree_contents([('this/file', b"a\n")])
366
405
this_tree.add('file')
367
406
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')
407
other_tree = this_tree.controldir.sprout('other').open_workingtree()
408
self.build_tree_contents([('other/file', b"a\nb\n")])
409
other_tree.commit('rev2b', rev_id=b'rev2b')
410
self.build_tree_contents([('other/file', b"a\nb\nc\n")])
411
other_tree.commit('rev3b', rev_id=b'rev3b')
373
412
this_tree.lock_write()
374
413
self.addCleanup(this_tree.unlock)
376
merger = _mod_merge.Merger.from_revision_ids(None,
377
this_tree, 'rev3b', 'rev2b', other_tree.branch)
415
merger = _mod_merge.Merger.from_revision_ids(
416
this_tree, b'rev3b', b'rev2b', other_tree.branch)
378
417
merger.merge_type = _mod_merge.Merge3Merger
379
418
merger.do_merge()
380
self.assertFileEqual('a\n'
384
'>>>>>>> MERGE-SOURCE\n',
419
self.assertFileEqual(b'a\n'
423
b'>>>>>>> MERGE-SOURCE\n',
426
def test_merge_reverse_revision_range(self):
427
tree = self.make_branch_and_tree(".")
429
self.addCleanup(tree.unlock)
430
self.build_tree(['a'])
432
first_rev = tree.commit("added a")
433
merger = _mod_merge.Merger.from_revision_ids(tree,
434
_mod_revision.NULL_REVISION,
436
merger.merge_type = _mod_merge.Merge3Merger
437
merger.interesting_files = 'a'
438
conflict_count = merger.do_merge()
439
self.assertEqual(0, conflict_count)
441
self.assertPathDoesNotExist("a")
443
self.assertPathExists("a")
387
445
def test_make_merger(self):
388
446
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')
447
this_tree.commit('rev1', rev_id=b'rev1')
448
other_tree = this_tree.controldir.sprout('other').open_workingtree()
449
this_tree.commit('rev2', rev_id=b'rev2a')
450
other_tree.commit('rev2', rev_id=b'rev2b')
393
451
this_tree.lock_write()
394
452
self.addCleanup(this_tree.unlock)
395
merger = _mod_merge.Merger.from_revision_ids(None,
396
this_tree, 'rev2b', other_branch=other_tree.branch)
453
merger = _mod_merge.Merger.from_revision_ids(
454
this_tree, b'rev2b', other_branch=other_tree.branch)
397
455
merger.merge_type = _mod_merge.Merge3Merger
398
456
tree_merger = merger.make_merger()
399
457
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())
458
self.assertEqual(b'rev2b',
459
tree_merger.other_tree.get_revision_id())
460
self.assertEqual(b'rev1',
461
tree_merger.base_tree.get_revision_id())
462
self.assertEqual(other_tree.branch, tree_merger.other_branch)
403
464
def test_make_preview_transform(self):
404
465
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')
466
self.build_tree_contents([('this/file', b'1\n')])
467
this_tree.add('file', b'file-id')
468
this_tree.commit('rev1', rev_id=b'rev1')
469
other_tree = this_tree.controldir.sprout('other').open_workingtree()
470
self.build_tree_contents([('this/file', b'1\n2a\n')])
471
this_tree.commit('rev2', rev_id=b'rev2a')
472
self.build_tree_contents([('other/file', b'2b\n1\n')])
473
other_tree.commit('rev2', rev_id=b'rev2b')
413
474
this_tree.lock_write()
414
475
self.addCleanup(this_tree.unlock)
415
merger = _mod_merge.Merger.from_revision_ids(None,
416
this_tree, 'rev2b', other_branch=other_tree.branch)
476
merger = _mod_merge.Merger.from_revision_ids(
477
this_tree, b'rev2b', other_branch=other_tree.branch)
417
478
merger.merge_type = _mod_merge.Merge3Merger
418
479
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())
480
with tree_merger.make_preview_transform() as tt:
481
preview_tree = tt.get_preview_tree()
482
with this_tree.get_file('file') as tree_file:
483
self.assertEqual(b'1\n2a\n', tree_file.read())
484
with preview_tree.get_file('file') as preview_file:
485
self.assertEqual(b'2b\n1\n2a\n', preview_file.read())
433
487
def test_do_merge(self):
434
488
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')
489
self.build_tree_contents([('this/file', b'1\n')])
490
this_tree.add('file', b'file-id')
491
this_tree.commit('rev1', rev_id=b'rev1')
492
other_tree = this_tree.controldir.sprout('other').open_workingtree()
493
self.build_tree_contents([('this/file', b'1\n2a\n')])
494
this_tree.commit('rev2', rev_id=b'rev2a')
495
self.build_tree_contents([('other/file', b'2b\n1\n')])
496
other_tree.commit('rev2', rev_id=b'rev2b')
443
497
this_tree.lock_write()
444
498
self.addCleanup(this_tree.unlock)
445
merger = _mod_merge.Merger.from_revision_ids(None,
446
this_tree, 'rev2b', other_branch=other_tree.branch)
499
merger = _mod_merge.Merger.from_revision_ids(
500
this_tree, b'rev2b', other_branch=other_tree.branch)
447
501
merger.merge_type = _mod_merge.Merge3Merger
448
502
tree_merger = merger.make_merger()
449
503
tt = tree_merger.do_merge()
450
tree_file = this_tree.get_file('file-id')
452
self.assertEqual('2b\n1\n2a\n', tree_file.read())
504
with this_tree.get_file('file') as tree_file:
505
self.assertEqual(b'2b\n1\n2a\n', tree_file.read())
507
def test_merge_require_tree_root(self):
508
tree = self.make_branch_and_tree(".")
510
self.addCleanup(tree.unlock)
511
self.build_tree(['a'])
513
first_rev = tree.commit("added a")
514
old_root_id = tree.path2id('')
515
merger = _mod_merge.Merger.from_revision_ids(tree,
516
_mod_revision.NULL_REVISION,
518
merger.merge_type = _mod_merge.Merge3Merger
519
conflict_count = merger.do_merge()
520
self.assertEqual(0, conflict_count)
521
self.assertEqual({''}, set(tree.all_versioned_paths()))
522
tree.set_parent_ids([])
456
524
def test_merge_add_into_deleted_root(self):
457
525
# Yes, people actually do this. And report bugs if it breaks.
458
526
source = self.make_branch_and_tree('source', format='rich-root-pack')
459
527
self.build_tree(['source/foo/'])
460
source.add('foo', 'foo-id')
528
source.add('foo', b'foo-id')
461
529
source.commit('Add foo')
462
target = source.bzrdir.sprout('target').open_workingtree()
463
subtree = target.extract('foo-id')
530
target = source.controldir.sprout('target').open_workingtree()
531
subtree = target.extract('foo')
464
532
subtree.commit('Delete root')
465
533
self.build_tree(['source/bar'])
466
source.add('bar', 'bar-id')
534
source.add('bar', b'bar-id')
467
535
source.commit('Add bar')
468
536
subtree.merge_from_branch(source.branch)
506
575
def add_uncommitted_version(self, key, parents, text):
507
576
self.plan_merge_vf.add_lines(key, parents,
508
[c+'\n' for c in text])
577
[int2byte(c) + b'\n' for c in bytearray(text)])
510
579
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',))
580
self.add_rev(b'root', b'A', [], b'abc')
581
self.add_rev(b'root', b'B', [b'A'], b'acehg')
582
self.add_rev(b'root', b'C', [b'A'], b'fabg')
583
return _PlanMerge(b'B', b'C', self.plan_merge_vf, (b'root',))
516
585
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',))
586
self.add_version((b'root', b'A'), [], b'abc')
587
self.add_uncommitted_version(
588
(b'root', b'B:'), [(b'root', b'A')], b'acehg')
589
self.add_uncommitted_version(
590
(b'root', b'C:'), [(b'root', b'A')], b'fabg')
591
return _PlanMerge(b'B:', b'C:', self.plan_merge_vf, (b'root',))
522
593
def test_base_from_plan(self):
523
594
self.setup_plan_merge()
524
plan = self.plan_merge_vf.plan_merge('B', 'C')
595
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
525
596
pwm = versionedfile.PlanWeaveMerge(plan)
526
self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
597
self.assertEqual([b'a\n', b'b\n', b'c\n'], pwm.base_from_plan())
528
599
def test_unique_lines(self):
529
600
plan = self.setup_plan_merge()
530
601
self.assertEqual(plan._unique_lines(
531
plan._get_matching_blocks('B', 'C')),
602
plan._get_matching_blocks(b'B', b'C')),
532
603
([1, 2, 3], [0, 2]))
534
605
def test_plan_merge(self):
535
606
self.setup_plan_merge()
536
plan = self.plan_merge_vf.plan_merge('B', 'C')
607
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
537
608
self.assertEqual([
539
('unchanged', 'a\n'),
610
('unchanged', b'a\n'),
611
('killed-a', b'b\n'),
612
('killed-b', b'c\n'),
548
619
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',))
620
self.add_rev(b'root', b'A', [], b'abc')
621
self.add_rev(b'root', b'B', [b'A'], b'abcde')
622
self.add_rev(b'root', b'C', [b'A'], b'abcefg')
623
self.add_rev(b'root', b'D', [b'A', b'B', b'C'], b'abcdegh')
624
my_plan = _PlanMerge(b'B', b'D', self.plan_merge_vf, (b'root',))
554
625
# We shortcut when one text supersedes the other in the per-file graph.
555
626
# We don't actually need to compare the texts at this point.
556
627
self.assertEqual([
564
list(my_plan.plan_merge()))
635
list(my_plan.plan_merge()))
566
637
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',))
638
self.add_rev(b'root', b'A', [], b'abc')
639
self.add_rev(b'root', b'B', [], b'xyz')
640
my_plan = _PlanMerge(b'A', b'B', self.plan_merge_vf, (b'root',))
570
641
self.assertEqual([
577
list(my_plan.plan_merge()))
648
list(my_plan.plan_merge()))
579
650
def test_plan_merge_tail_ancestors(self):
580
651
# The graph looks like this:
851
922
# XX unused ancestor, should not show up in the weave
855
926
# B C B & C both introduce a new line
859
930
# D E B & C are both merged, so both are common ancestors
860
931
# In the process of merging, both sides order the new
861
932
# 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'))
934
self.add_rev(b'root', b'XX', [], b'qrs')
935
self.add_rev(b'root', b'A', [b'XX'], b'abcdef')
936
self.add_rev(b'root', b'B', [b'A'], b'abcdgef')
937
self.add_rev(b'root', b'C', [b'A'], b'abcdhef')
938
self.add_rev(b'root', b'D', [b'B', b'C'], b'abcdghef')
939
self.add_rev(b'root', b'E', [b'C', b'B'], b'abcdhgef')
940
plan = list(self.plan_merge_vf.plan_merge(b'D', b'E'))
870
941
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'),
942
('unchanged', b'a\n'),
943
('unchanged', b'b\n'),
944
('unchanged', b'c\n'),
945
('unchanged', b'd\n'),
947
('unchanged', b'g\n'),
948
('killed-b', b'h\n'),
949
('unchanged', b'e\n'),
950
('unchanged', b'f\n'),
881
952
pwm = versionedfile.PlanWeaveMerge(plan)
882
self.assertEqualDiff('\n'.join('abcdghef') + '\n',
883
''.join(pwm.base_from_plan()))
953
self.assertEqualDiff(b'a\nb\nc\nd\ng\nh\ne\nf\n',
954
b''.join(pwm.base_from_plan()))
884
955
# Reversing the order reverses the merge plan, and final order of 'hg'
886
plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
957
plan = list(self.plan_merge_vf.plan_merge(b'E', b'D'))
887
958
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'),
959
('unchanged', b'a\n'),
960
('unchanged', b'b\n'),
961
('unchanged', b'c\n'),
962
('unchanged', b'd\n'),
964
('unchanged', b'h\n'),
965
('killed-b', b'g\n'),
966
('unchanged', b'e\n'),
967
('unchanged', b'f\n'),
898
969
pwm = versionedfile.PlanWeaveMerge(plan)
899
self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
900
''.join(pwm.base_from_plan()))
970
self.assertEqualDiff(b'a\nb\nc\nd\nh\ng\ne\nf\n',
971
b''.join(pwm.base_from_plan()))
901
972
# This is where lca differs, in that it (fairly correctly) determines
902
973
# that there is a conflict because both sides resolved the merge
904
plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
975
plan = list(self.plan_merge_vf.plan_lca_merge(b'D', b'E'))
905
976
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'),
977
('unchanged', b'a\n'),
978
('unchanged', b'b\n'),
979
('unchanged', b'c\n'),
980
('unchanged', b'd\n'),
981
('conflicted-b', b'h\n'),
982
('unchanged', b'g\n'),
983
('conflicted-a', b'h\n'),
984
('unchanged', b'e\n'),
985
('unchanged', b'f\n'),
916
987
pwm = versionedfile.PlanWeaveMerge(plan)
917
self.assertEqualDiff('\n'.join('abcdgef') + '\n',
918
''.join(pwm.base_from_plan()))
988
self.assertEqualDiff(b'a\nb\nc\nd\ng\ne\nf\n',
989
b''.join(pwm.base_from_plan()))
919
990
# Reversing it changes what line is doubled, but still gives a
920
991
# double-conflict
921
plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
992
plan = list(self.plan_merge_vf.plan_lca_merge(b'E', b'D'))
922
993
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'),
994
('unchanged', b'a\n'),
995
('unchanged', b'b\n'),
996
('unchanged', b'c\n'),
997
('unchanged', b'd\n'),
998
('conflicted-b', b'g\n'),
999
('unchanged', b'h\n'),
1000
('conflicted-a', b'g\n'),
1001
('unchanged', b'e\n'),
1002
('unchanged', b'f\n'),
933
1004
pwm = versionedfile.PlanWeaveMerge(plan)
934
self.assertEqualDiff('\n'.join('abcdhef') + '\n',
935
''.join(pwm.base_from_plan()))
1005
self.assertEqualDiff(b'a\nb\nc\nd\nh\ne\nf\n',
1006
b''.join(pwm.base_from_plan()))
937
1008
def assertRemoveExternalReferences(self, filtered_parent_map,
938
1009
child_map, tails, parent_map):
996
1067
self.assertPruneTails({1: []}, [5],
997
1068
{1: [], 2: [3, 4], 3: [5], 4: [5], 5: []})
998
1069
# Prune a partial chain
999
self.assertPruneTails({1: [6], 6:[]}, [5],
1070
self.assertPruneTails({1: [6], 6: []}, [5],
1000
1071
{1: [2, 6], 2: [3, 4], 3: [5], 4: [5], 5: [],
1002
1073
# 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:[]})
1074
self.assertPruneTails({1: [3], 3: []}, [4, 5],
1075
{1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []})
1076
self.assertPruneTails({1: [3], 3: []}, [5, 4],
1077
{1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []})
1008
1079
def test_subtract_plans(self):
1010
('unchanged', 'a\n'),
1012
('killed-a', 'c\n'),
1015
('killed-b', 'f\n'),
1016
('killed-b', 'g\n'),
1081
('unchanged', b'a\n'),
1083
('killed-a', b'c\n'),
1086
('killed-b', b'f\n'),
1087
('killed-b', b'g\n'),
1019
('unchanged', 'a\n'),
1021
('killed-a', 'c\n'),
1024
('killed-b', 'f\n'),
1025
('killed-b', 'i\n'),
1090
('unchanged', b'a\n'),
1092
('killed-a', b'c\n'),
1095
('killed-b', b'f\n'),
1096
('killed-b', b'i\n'),
1027
1098
subtracted_plan = [
1028
('unchanged', 'a\n'),
1030
('killed-a', 'c\n'),
1032
('unchanged', 'f\n'),
1033
('killed-b', 'i\n'),
1099
('unchanged', b'a\n'),
1101
('killed-a', b'c\n'),
1103
('unchanged', b'f\n'),
1104
('killed-b', b'i\n'),
1035
1106
self.assertEqual(subtracted_plan,
1036
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1107
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1038
1109
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')
1110
self.add_rev(b'root', b'COMMON', [], b'abc')
1111
self.add_rev(b'root', b'THIS', [b'COMMON'], b'abcd')
1112
self.add_rev(b'root', b'BASE', [b'COMMON'], b'eabc')
1113
self.add_rev(b'root', b'OTHER', [b'BASE'], b'eafb')
1044
1115
def test_plan_merge_with_base(self):
1045
1116
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'),
1117
plan = self.plan_merge_vf.plan_merge(b'THIS', b'OTHER', b'BASE')
1118
self.assertEqual([('unchanged', b'a\n'),
1120
('unchanged', b'b\n'),
1121
('killed-b', b'c\n'),
1054
1125
def test_plan_lca_merge(self):
1055
1126
self.setup_plan_merge()
1056
plan = self.plan_merge_vf.plan_lca_merge('B', 'C')
1127
plan = self.plan_merge_vf.plan_lca_merge(b'B', b'C')
1057
1128
self.assertEqual([
1059
('unchanged', 'a\n'),
1060
('killed-b', 'c\n'),
1063
('killed-a', 'b\n'),
1064
('unchanged', 'g\n')],
1130
('unchanged', b'a\n'),
1131
('killed-b', b'c\n'),
1134
('killed-a', b'b\n'),
1135
('unchanged', b'g\n')],
1067
1138
def test_plan_lca_merge_uncommitted_files(self):
1068
1139
self.setup_plan_merge_uncommitted()
1069
plan = self.plan_merge_vf.plan_lca_merge('B:', 'C:')
1140
plan = self.plan_merge_vf.plan_lca_merge(b'B:', b'C:')
1070
1141
self.assertEqual([
1072
('unchanged', 'a\n'),
1073
('killed-b', 'c\n'),
1076
('killed-a', 'b\n'),
1077
('unchanged', 'g\n')],
1143
('unchanged', b'a\n'),
1144
('killed-b', b'c\n'),
1147
('killed-a', b'b\n'),
1148
('unchanged', b'g\n')],
1080
1151
def test_plan_lca_merge_with_base(self):
1081
1152
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'),
1153
plan = self.plan_merge_vf.plan_lca_merge(b'THIS', b'OTHER', b'BASE')
1154
self.assertEqual([('unchanged', b'a\n'),
1156
('unchanged', b'b\n'),
1157
('killed-b', b'c\n'),
1090
1161
def test_plan_lca_merge_with_criss_cross(self):
1091
self.add_version(('root', 'ROOT'), [], 'abc')
1162
self.add_version((b'root', b'ROOT'), [], b'abc')
1092
1163
# each side makes a change
1093
self.add_version(('root', 'REV1'), [('root', 'ROOT')], 'abcd')
1094
self.add_version(('root', 'REV2'), [('root', 'ROOT')], 'abce')
1164
self.add_version((b'root', b'REV1'), [(b'root', b'ROOT')], b'abcd')
1165
self.add_version((b'root', b'REV2'), [(b'root', b'ROOT')], b'abce')
1095
1166
# 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'),
1167
self.add_version((b'root', b'LCA1'),
1168
[(b'root', b'REV1'), (b'root', b'REV2')], b'abcd')
1169
self.add_version((b'root', b'LCA2'),
1170
[(b'root', b'REV1'), (b'root', b'REV2')], b'fabce')
1171
plan = self.plan_merge_vf.plan_lca_merge(b'LCA1', b'LCA2')
1172
self.assertEqual([('new-b', b'f\n'),
1173
('unchanged', b'a\n'),
1174
('unchanged', b'b\n'),
1175
('unchanged', b'c\n'),
1176
('conflicted-a', b'd\n'),
1177
('conflicted-b', b'e\n'),
1109
1180
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'),
1181
self.add_version((b'root', b'A'), [], b'ab')
1182
self.add_version((b'root', b'B'), [], b'bc')
1183
plan = self.plan_merge_vf.plan_lca_merge(b'A', b'B')
1184
self.assertEqual([('new-a', b'a\n'),
1185
('unchanged', b'b\n'),
1118
1189
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'),
1190
self.add_rev(b'root', b'C', [], b'a')
1191
self.add_rev(b'root', b'A', [b'C'], b'b')
1192
self.add_rev(b'root', b'B', [b'C'], b'')
1193
plan = self.plan_merge_vf.plan_merge(b'A', b'B')
1194
self.assertEqual([('killed-both', b'a\n'),
1127
1198
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'),
1199
self.add_rev(b'root', b'C', [], b'abcd')
1200
self.add_rev(b'root', b'A', [b'C'], b'acbd')
1201
self.add_rev(b'root', b'B', [b'C'], b'aBcd')
1202
plan = self.plan_merge_vf.plan_merge(b'A', b'B')
1203
self.assertEqual([('unchanged', b'a\n'),
1205
('killed-b', b'b\n'),
1207
('killed-a', b'c\n'),
1208
('unchanged', b'd\n'),
1141
1212
class LoggingMerger(object):
1306
1396
class TestMergerEntriesLCA(TestMergerBase):
1308
1398
def make_merge_obj(self, builder, other_revision_id,
1309
interesting_files=None, interesting_ids=None):
1399
interesting_files=None):
1310
1400
merger = self.make_Merger(builder, other_revision_id,
1311
interesting_files=interesting_files,
1312
interesting_ids=interesting_ids)
1401
interesting_files=interesting_files)
1313
1402
return merger.make_merger()
1315
1404
def test_simple(self):
1316
1405
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')
1406
builder.build_snapshot(None,
1407
[('add', (u'', b'a-root-id', 'directory', None)),
1408
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1409
revision_id=b'A-id')
1410
builder.build_snapshot([b'A-id'],
1411
[('modify', ('a', b'a\nb\nC\nc\n'))],
1412
revision_id=b'C-id')
1413
builder.build_snapshot([b'A-id'],
1414
[('modify', ('a', b'a\nB\nb\nc\n'))],
1415
revision_id=b'B-id')
1416
builder.build_snapshot([b'C-id', b'B-id'],
1417
[('modify', ('a', b'a\nB\nb\nC\nc\nE\n'))],
1418
revision_id=b'E-id')
1419
builder.build_snapshot([b'B-id', b'C-id'],
1420
[('modify', ('a', b'a\nB\nb\nC\nc\n'))],
1421
revision_id=b'D-id', )
1422
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())
1424
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1425
for t in merge_obj._lca_trees])
1426
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1333
1427
entries = list(merge_obj._entries_lca())
1335
1429
# (file_id, changed, parents, names, executable)
1336
1430
# BASE, lca1, lca2, OTHER, THIS
1337
root_id = 'a-root-id'
1338
self.assertEqual([('a-id', True,
1431
root_id = b'a-root-id'
1432
self.assertEqual([(b'a-id', True,
1433
((u'a', [u'a', u'a']), u'a', u'a'),
1339
1434
((root_id, [root_id, root_id]), root_id, root_id),
1340
1435
((u'a', [u'a', u'a']), u'a', u'a'),
1341
((False, [False, False]), False, False)),
1436
((False, [False, False]), False, False),
1344
1440
def test_not_in_base(self):
1345
1441
# LCAs all have the same last-modified revision for the file, as do
1354
1450
# G modifies 'bar'
1356
1452
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')
1453
builder.build_snapshot(None,
1454
[('add', (u'', b'a-root-id', 'directory', None))],
1455
revision_id=b'A-id')
1456
builder.build_snapshot([b'A-id'],
1457
[('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
1458
revision_id=b'B-id')
1459
builder.build_snapshot([b'A-id'],
1460
[('add', (u'bar', b'bar-id', 'file', b'd\ne\nf\n'))],
1461
revision_id=b'C-id')
1462
builder.build_snapshot([b'B-id', b'C-id'],
1463
[('add', (u'bar', b'bar-id', 'file', b'd\ne\nf\n'))],
1464
revision_id=b'D-id')
1465
builder.build_snapshot([b'C-id', b'B-id'],
1466
[('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
1467
revision_id=b'E-id')
1468
builder.build_snapshot([b'E-id', b'D-id'],
1469
[('modify', (u'bar', b'd\ne\nf\nG\n'))],
1470
revision_id=b'G-id')
1471
builder.build_snapshot([b'D-id', b'E-id'], [], revision_id=b'F-id')
1472
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())
1474
self.assertEqual([b'D-id', b'E-id'], [t.get_revision_id()
1475
for t in merge_obj._lca_trees])
1476
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1375
1477
entries = list(merge_obj._entries_lca())
1376
root_id = 'a-root-id'
1377
self.assertEqual([('bar-id', True,
1478
root_id = b'a-root-id'
1479
self.assertEqual([(b'bar-id', True,
1480
((None, [u'bar', u'bar']), u'bar', u'bar'),
1378
1481
((None, [root_id, root_id]), root_id, root_id),
1379
1482
((None, [u'bar', u'bar']), u'bar', u'bar'),
1380
((None, [False, False]), False, False)),
1483
((None, [False, False]), False, False),
1383
1487
def test_not_in_this(self):
1384
1488
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')
1489
builder.build_snapshot(None,
1490
[('add', (u'', b'a-root-id', 'directory', None)),
1491
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1492
revision_id=b'A-id')
1493
builder.build_snapshot([b'A-id'],
1494
[('modify', ('a', b'a\nB\nb\nc\n'))],
1495
revision_id=b'B-id')
1496
builder.build_snapshot([b'A-id'],
1497
[('modify', ('a', b'a\nb\nC\nc\n'))],
1498
revision_id=b'C-id')
1499
builder.build_snapshot([b'C-id', b'B-id'],
1500
[('modify', ('a', b'a\nB\nb\nC\nc\nE\n'))],
1501
revision_id=b'E-id')
1502
builder.build_snapshot([b'B-id', b'C-id'],
1503
[('unversion', 'a')],
1504
revision_id=b'D-id')
1505
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())
1507
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1508
for t in merge_obj._lca_trees])
1509
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1402
1511
entries = list(merge_obj._entries_lca())
1403
root_id = 'a-root-id'
1404
self.assertEqual([('a-id', True,
1512
root_id = b'a-root-id'
1513
self.assertEqual([(b'a-id', True,
1514
((u'a', [u'a', u'a']), u'a', None),
1405
1515
((root_id, [root_id, root_id]), root_id, None),
1406
1516
((u'a', [u'a', u'a']), u'a', None),
1407
((False, [False, False]), False, None)),
1517
((False, [False, False]), False, None),
1410
1521
def test_file_not_in_one_lca(self):
1411
1522
# A # just root
1415
1526
# D E # D and E both have the file, unchanged from C
1416
1527
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')
1528
builder.build_snapshot(None,
1529
[('add', (u'', b'a-root-id', 'directory', None))],
1530
revision_id=b'A-id')
1531
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1532
builder.build_snapshot([b'A-id'],
1533
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1534
revision_id=b'C-id')
1535
builder.build_snapshot([b'C-id', b'B-id'],
1536
[], revision_id=b'E-id') # Inherited from C
1537
builder.build_snapshot([b'B-id', b'C-id'], # Merged from C
1538
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1539
revision_id=b'D-id')
1540
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())
1542
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1543
for t in merge_obj._lca_trees])
1544
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1431
1546
entries = list(merge_obj._entries_lca())
1432
1547
self.assertEqual([], entries)
1434
1549
def test_not_in_other(self):
1435
1550
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')
1551
builder.build_snapshot(None,
1552
[('add', (u'', b'a-root-id', 'directory', None)),
1553
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1554
revision_id=b'A-id')
1555
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1556
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1557
builder.build_snapshot(
1559
[('unversion', 'a')], revision_id=b'E-id')
1560
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1561
merge_obj = self.make_merge_obj(builder, b'E-id')
1446
1563
entries = list(merge_obj._entries_lca())
1447
root_id = 'a-root-id'
1448
self.assertEqual([('a-id', True,
1564
root_id = b'a-root-id'
1565
self.assertEqual([(b'a-id', True,
1566
((u'a', [u'a', u'a']), None, u'a'),
1449
1567
((root_id, [root_id, root_id]), None, root_id),
1450
1568
((u'a', [u'a', u'a']), None, u'a'),
1451
((False, [False, False]), None, False)),
1569
((False, [False, False]), None, False),
1454
1573
def test_not_in_other_or_lca(self):
1455
1574
# A base, introduces 'foo'
1529
1654
# A => C, add file, thus C supersedes B
1530
1655
# w/ C=BASE, D=THIS, E=OTHER we have 'happy convergence'
1531
1656
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')
1657
builder.build_snapshot(None,
1658
[('add', (u'', b'a-root-id', 'directory', None))],
1659
revision_id=b'A-id')
1660
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1661
builder.build_snapshot([b'A-id'],
1662
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1663
revision_id=b'C-id')
1664
builder.build_snapshot([b'C-id', b'B-id'],
1665
[('unversion', 'a')],
1666
revision_id=b'E-id')
1667
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1668
merge_obj = self.make_merge_obj(builder, b'E-id')
1542
1670
entries = list(merge_obj._entries_lca())
1543
1671
self.assertEqual([], entries)
1545
1673
def test_only_in_other(self):
1546
1674
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')
1675
builder.build_snapshot(None,
1676
[('add', (u'', b'a-root-id', 'directory', None))],
1677
revision_id=b'A-id')
1678
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1679
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1680
builder.build_snapshot([b'C-id', b'B-id'],
1681
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1682
revision_id=b'E-id')
1683
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1684
merge_obj = self.make_merge_obj(builder, b'E-id')
1556
1686
entries = list(merge_obj._entries_lca())
1557
root_id = 'a-root-id'
1558
self.assertEqual([('a-id', True,
1687
root_id = b'a-root-id'
1688
self.assertEqual([(b'a-id', True,
1689
((None, [None, None]), u'a', None),
1559
1690
((None, [None, None]), root_id, None),
1560
1691
((None, [None, None]), u'a', None),
1561
((None, [None, None]), False, None)),
1692
((None, [None, None]), False, None),
1564
1696
def test_one_lca_supersedes(self):
1565
1697
# One LCA supersedes the other LCAs last modified value, but the
1655
1796
# be pruned from the LCAs, even though it was newly introduced by E
1656
1797
# (superseding B).
1657
1798
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')
1799
builder.build_snapshot(None,
1800
[('add', (u'', b'a-root-id', 'directory', None)),
1801
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1802
revision_id=b'A-id')
1803
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1804
builder.build_snapshot([b'A-id'],
1805
[('rename', ('foo', 'bar'))],
1806
revision_id=b'B-id')
1807
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1808
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1809
builder.build_snapshot([b'E-id', b'D-id'],
1810
[('rename', ('foo', 'bar'))],
1811
revision_id=b'G-id')
1812
builder.build_snapshot([b'D-id', b'E-id'],
1813
[('rename', ('bar', 'bing'))],
1814
revision_id=b'F-id') # should end up conflicting
1815
merge_obj = self.make_merge_obj(builder, b'G-id')
1672
1817
entries = list(merge_obj._entries_lca())
1673
root_id = 'a-root-id'
1818
root_id = b'a-root-id'
1674
1819
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)),
1822
((root_id, [root_id, root_id]), root_id, root_id),
1823
((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
1824
((False, [False, False]), False, False),
1682
1828
def test_both_sides_revert(self):
1683
1829
# Both sides of a criss-cross revert the text to the lca
1719
1870
# We need to emit an entry for 'foo', because D & E differed on the
1720
1871
# merge resolution
1721
1872
builder = self.get_builder()
1722
builder.build_snapshot('A-id', None,
1723
[('add', (u'', 'a-root-id', 'directory', None)),
1724
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1725
builder.build_snapshot('B-id', ['A-id'],
1726
[('modify', ('foo-id', 'B content\n'))])
1727
builder.build_snapshot('C-id', ['A-id'],
1728
[('modify', ('foo-id', 'C content\n'))])
1729
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1730
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1731
builder.build_snapshot('F-id', ['D-id'],
1732
[('modify', ('foo-id', 'F content\n'))])
1733
merge_obj = self.make_merge_obj(builder, 'E-id')
1873
builder.build_snapshot(None,
1874
[('add', (u'', b'a-root-id', 'directory', None)),
1875
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1876
revision_id=b'A-id')
1877
builder.build_snapshot([b'A-id'],
1878
[('modify', ('foo', b'B content\n'))],
1879
revision_id=b'B-id')
1880
builder.build_snapshot([b'A-id'],
1881
[('modify', ('foo', b'C content\n'))],
1882
revision_id=b'C-id', )
1883
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1884
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1885
builder.build_snapshot([b'D-id'],
1886
[('modify', ('foo', b'F content\n'))],
1887
revision_id=b'F-id')
1888
merge_obj = self.make_merge_obj(builder, b'E-id')
1735
1890
entries = list(merge_obj._entries_lca())
1736
root_id = 'a-root-id'
1737
self.assertEqual([('foo-id', True,
1891
root_id = b'a-root-id'
1892
self.assertEqual([(b'foo-id', True,
1893
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
1738
1894
((root_id, [root_id, root_id]), root_id, root_id),
1739
1895
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
1740
((False, [False, False]), False, False)),
1896
((False, [False, False]), False, False),
1743
1900
def test_same_lca_resolution_one_side_updates_content(self):
1744
1901
# Both sides converge, but then one side updates the text.
1755
1912
# We need to conflict.
1757
1914
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')
1915
builder.build_snapshot(None,
1916
[('add', (u'', b'a-root-id', 'directory', None)),
1917
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1918
revision_id=b'A-id')
1919
builder.build_snapshot([b'A-id'],
1920
[('modify', ('foo', b'B content\n'))],
1921
revision_id=b'B-id')
1922
builder.build_snapshot([b'A-id'],
1923
[('modify', ('foo', b'C content\n'))],
1924
revision_id=b'C-id')
1925
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1926
builder.build_snapshot([b'B-id', b'C-id'],
1927
[('modify', ('foo', b'C content\n'))],
1928
revision_id=b'D-id') # Same as E
1929
builder.build_snapshot([b'D-id'],
1930
[('modify', ('foo', b'F content\n'))],
1931
revision_id=b'F-id')
1932
merge_obj = self.make_merge_obj(builder, b'E-id')
1772
1934
entries = list(merge_obj._entries_lca())
1773
1935
self.expectFailure("We don't detect that LCA resolution was the"
1774
1936
" same on both sides",
1775
self.assertEqual, [], entries)
1937
self.assertEqual, [], entries)
1777
1939
def test_only_path_changed(self):
1778
1940
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')
1941
builder.build_snapshot(None,
1942
[('add', (u'', b'a-root-id', 'directory', None)),
1943
('add', (u'a', b'a-id', 'file', b'content\n'))],
1944
revision_id=b'A-id')
1945
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1946
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1947
builder.build_snapshot([b'C-id', b'B-id'],
1948
[('rename', (u'a', u'b'))],
1949
revision_id=b'E-id')
1950
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1951
merge_obj = self.make_merge_obj(builder, b'E-id')
1788
1952
entries = list(merge_obj._entries_lca())
1789
root_id = 'a-root-id'
1953
root_id = b'a-root-id'
1790
1954
# The content was not changed, only the path
1791
self.assertEqual([('a-id', False,
1955
self.assertEqual([(b'a-id', False,
1956
((u'a', [u'a', u'a']), u'b', u'a'),
1792
1957
((root_id, [root_id, root_id]), root_id, root_id),
1793
1958
((u'a', [u'a', u'a']), u'b', u'a'),
1794
((False, [False, False]), False, False)),
1959
((False, [False, False]), False, False),
1797
1963
def test_kind_changed(self):
1798
1964
# Identical content, except 'D' changes a-id into a directory
1799
1965
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')
1966
builder.build_snapshot(None,
1967
[('add', (u'', b'a-root-id', 'directory', None)),
1968
('add', (u'a', b'a-id', 'file', b'content\n'))],
1969
revision_id=b'A-id')
1970
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1971
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1972
builder.build_snapshot([b'C-id', b'B-id'],
1973
[('unversion', 'a'),
1975
('add', (u'a', b'a-id', 'directory', None))],
1976
revision_id=b'E-id')
1977
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1978
merge_obj = self.make_merge_obj(builder, b'E-id')
1810
1979
entries = list(merge_obj._entries_lca())
1811
root_id = 'a-root-id'
1980
root_id = b'a-root-id'
1812
1981
# Only the kind was changed (content)
1813
self.assertEqual([('a-id', True,
1982
self.assertEqual([(b'a-id', True,
1983
((u'a', [u'a', u'a']), u'a', u'a'),
1814
1984
((root_id, [root_id, root_id]), root_id, root_id),
1815
1985
((u'a', [u'a', u'a']), u'a', u'a'),
1816
((False, [False, False]), False, False)),
1986
((False, [False, False]), False, False),
1819
1990
def test_this_changed_kind(self):
1820
1991
# Identical content, but THIS changes a file to a directory
1821
1992
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')
1993
builder.build_snapshot(None,
1994
[('add', (u'', b'a-root-id', 'directory', None)),
1995
('add', (u'a', b'a-id', 'file', b'content\n'))],
1996
revision_id=b'A-id')
1997
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1998
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1999
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
2000
builder.build_snapshot([b'B-id', b'C-id'],
2001
[('unversion', 'a'),
2003
('add', (u'a', b'a-id', 'directory', None))],
2004
revision_id=b'D-id')
2005
merge_obj = self.make_merge_obj(builder, b'E-id')
1832
2006
entries = list(merge_obj._entries_lca())
1833
2007
# Only the kind was changed (content)
1834
2008
self.assertEqual([], entries)
1836
2010
def test_interesting_files(self):
1837
2011
# Two files modified, but we should filter one of them
1838
2012
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',
2013
builder.build_snapshot(None,
2014
[('add', (u'', b'a-root-id', 'directory', None)),
2015
('add', (u'a', b'a-id', 'file', b'content\n')),
2016
('add', (u'b', b'b-id', 'file', b'content\n'))],
2017
revision_id=b'A-id')
2018
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2019
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2020
builder.build_snapshot([b'C-id', b'B-id'],
2021
[('modify', ('a', b'new-content\n')),
2022
('modify', ('b', b'new-content\n'))],
2023
revision_id=b'E-id')
2024
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2025
merge_obj = self.make_merge_obj(builder, b'E-id',
1850
2026
interesting_files=['b'])
1851
2027
entries = list(merge_obj._entries_lca())
1852
root_id = 'a-root-id'
1853
self.assertEqual([('b-id', True,
2028
root_id = b'a-root-id'
2029
self.assertEqual([(b'b-id', True,
2030
((u'b', [u'b', u'b']), u'b', u'b'),
1854
2031
((root_id, [root_id, root_id]), root_id, root_id),
1855
2032
((u'b', [u'b', u'b']), u'b', u'b'),
1856
((False, [False, False]), False, False)),
2033
((False, [False, False]), False, False),
1859
2037
def test_interesting_file_in_this(self):
1860
2038
# This renamed the file, but it should still match the entry in other
1861
2039
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',
2040
builder.build_snapshot(None,
2041
[('add', (u'', b'a-root-id', 'directory', None)),
2042
('add', (u'a', b'a-id', 'file', b'content\n')),
2043
('add', (u'b', b'b-id', 'file', b'content\n'))],
2044
revision_id=b'A-id')
2045
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2046
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2047
builder.build_snapshot([b'C-id', b'B-id'],
2048
[('modify', ('a', b'new-content\n')),
2049
('modify', ('b', b'new-content\n'))],
2050
revision_id=b'E-id')
2051
builder.build_snapshot([b'B-id', b'C-id'],
2052
[('rename', ('b', 'c'))],
2053
revision_id=b'D-id')
2054
merge_obj = self.make_merge_obj(builder, b'E-id',
1874
2055
interesting_files=['c'])
1875
2056
entries = list(merge_obj._entries_lca())
1876
root_id = 'a-root-id'
1877
self.assertEqual([('b-id', True,
2057
root_id = b'a-root-id'
2058
self.assertEqual([(b'b-id', True,
2059
((u'b', [u'b', u'b']), u'b', u'c'),
1878
2060
((root_id, [root_id, root_id]), root_id, root_id),
1879
2061
((u'b', [u'b', u'b']), u'b', u'c'),
1880
((False, [False, False]), False, False)),
2062
((False, [False, False]), False, False),
1883
2066
def test_interesting_file_in_base(self):
1884
2067
# This renamed the file, but it should still match the entry in BASE
1885
2068
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',
2069
builder.build_snapshot(None,
2070
[('add', (u'', b'a-root-id', 'directory', None)),
2071
('add', (u'a', b'a-id', 'file', b'content\n')),
2072
('add', (u'c', b'c-id', 'file', b'content\n'))],
2073
revision_id=b'A-id')
2074
builder.build_snapshot([b'A-id'],
2075
[('rename', ('c', 'b'))],
2076
revision_id=b'B-id')
2077
builder.build_snapshot([b'A-id'],
2078
[('rename', ('c', 'b'))],
2079
revision_id=b'C-id')
2080
builder.build_snapshot([b'C-id', b'B-id'],
2081
[('modify', ('a', b'new-content\n')),
2082
('modify', ('b', b'new-content\n'))],
2083
revision_id=b'E-id')
2084
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2085
merge_obj = self.make_merge_obj(builder, b'E-id',
1899
2086
interesting_files=['c'])
1900
2087
entries = list(merge_obj._entries_lca())
1901
root_id = 'a-root-id'
1902
self.assertEqual([('c-id', True,
2088
root_id = b'a-root-id'
2089
self.assertEqual([(b'c-id', True,
2090
((u'c', [u'b', u'b']), u'b', u'b'),
1903
2091
((root_id, [root_id, root_id]), root_id, root_id),
1904
2092
((u'c', [u'b', u'b']), u'b', u'b'),
1905
((False, [False, False]), False, False)),
2093
((False, [False, False]), False, False),
1908
2097
def test_interesting_file_in_lca(self):
1909
2098
# This renamed the file, but it should still match the entry in LCA
1910
2099
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',
2100
builder.build_snapshot(None,
2101
[('add', (u'', b'a-root-id', 'directory', None)),
2102
('add', (u'a', b'a-id', 'file', b'content\n')),
2103
('add', (u'b', b'b-id', 'file', b'content\n'))],
2104
revision_id=b'A-id')
2105
builder.build_snapshot([b'A-id'],
2106
[('rename', ('b', 'c'))], revision_id=b'B-id')
2107
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2108
builder.build_snapshot([b'C-id', b'B-id'],
2109
[('modify', ('a', b'new-content\n')),
2110
('modify', ('b', b'new-content\n'))],
2111
revision_id=b'E-id')
2112
builder.build_snapshot([b'B-id', b'C-id'],
2113
[('rename', ('c', 'b'))], revision_id=b'D-id')
2114
merge_obj = self.make_merge_obj(builder, b'E-id',
1924
2115
interesting_files=['c'])
1925
2116
entries = list(merge_obj._entries_lca())
1926
root_id = 'a-root-id'
1927
self.assertEqual([('b-id', True,
2117
root_id = b'a-root-id'
2118
self.assertEqual([(b'b-id', True,
2119
((u'b', [u'c', u'b']), u'b', u'b'),
1928
2120
((root_id, [root_id, root_id]), root_id, root_id),
1929
2121
((u'b', [u'c', u'b']), u'b', u'b'),
1930
((False, [False, False]), False, False)),
2122
((False, [False, False]), False, False),
1933
def test_interesting_ids(self):
2126
def test_interesting_files(self):
1934
2127
# Two files modified, but we should filter one of them
1935
2128
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'])
2129
builder.build_snapshot(None,
2130
[('add', (u'', b'a-root-id', 'directory', None)),
2131
('add', (u'a', b'a-id', 'file', b'content\n')),
2132
('add', (u'b', b'b-id', 'file', b'content\n'))],
2133
revision_id=b'A-id')
2134
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2135
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2136
builder.build_snapshot([b'C-id', b'B-id'],
2137
[('modify', ('a', b'new-content\n')),
2138
('modify', ('b', b'new-content\n'))], revision_id=b'E-id')
2139
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2140
merge_obj = self.make_merge_obj(builder, b'E-id',
2141
interesting_files=['b'])
1948
2142
entries = list(merge_obj._entries_lca())
1949
root_id = 'a-root-id'
1950
self.assertEqual([('b-id', True,
2143
root_id = b'a-root-id'
2144
self.assertEqual([(b'b-id', True,
2145
((u'b', [u'b', u'b']), u'b', u'b'),
1951
2146
((root_id, [root_id, root_id]), root_id, root_id),
1952
2147
((u'b', [u'b', u'b']), u'b', u'b'),
1953
((False, [False, False]), False, False)),
2148
((False, [False, False]), False, False),
1958
2153
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
2223
2425
wt.lock_write()
2224
2426
self.addCleanup(wt.unlock)
2225
2427
os.symlink('bar', 'path/foo')
2226
wt.add(['foo'], ['foo-id'])
2227
wt.commit('A add symlink', rev_id='A-id')
2428
wt.add(['foo'], [b'foo-id'])
2429
wt.commit('A add symlink', rev_id=b'A-id')
2228
2430
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')
2431
wt.commit('B foo => barry', rev_id=b'B-id')
2432
wt.set_last_revision(b'A-id')
2433
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')
2435
wt.commit('C', rev_id=b'C-id')
2436
wt.merge_from_branch(wt.branch, b'B-id')
2437
self.assertEqual('barry', wt.id2path(b'foo-id'))
2438
self.assertEqual('bar', wt.get_symlink_target('barry'))
2439
wt.commit('E merges C & B', rev_id=b'E-id')
2238
2440
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')
2441
wt.commit('F barry => blah', rev_id=b'F-id')
2442
wt.set_last_revision(b'B-id')
2443
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'))
2445
wt.merge_from_branch(wt.branch, b'C-id')
2446
wt.commit('D merges B & C', rev_id=b'D-id')
2447
self.assertEqual('barry', wt.id2path(b'foo-id'))
2246
2448
# Check the output of the Merger object directly
2247
merger = _mod_merge.Merger.from_revision_ids(None,
2449
merger = _mod_merge.Merger.from_revision_ids(wt, b'F-id')
2249
2450
merger.merge_type = _mod_merge.Merge3Merger
2250
2451
merge_obj = merger.make_merger()
2251
2452
root_id = wt.path2id('')
2252
2453
entries = list(merge_obj._entries_lca())
2253
2454
# No content change, just a path change
2254
self.assertEqual([('foo-id', False,
2455
self.assertEqual([(b'foo-id', False,
2456
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2255
2457
((root_id, [root_id, root_id]), root_id, root_id),
2256
2458
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2257
((False, [False, False]), False, False)),
2259
conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
2459
((False, [False, False]), False, False),
2462
conflicts = wt.merge_from_branch(wt.branch, to_revision=b'F-id')
2260
2463
self.assertEqual(0, conflicts)
2261
self.assertEqual('blah', wt.id2path('foo-id'))
2464
self.assertEqual('blah', wt.id2path(b'foo-id'))
2263
2466
def test_symlink_no_content_change(self):
2264
self.requireFeature(tests.SymlinkFeature)
2467
self.requireFeature(features.SymlinkFeature)
2265
2468
# A Create symlink foo => bar
2267
2470
# B C B relinks foo => baz
2326
2528
wt = self.make_branch_and_tree('path')
2327
2529
wt.lock_write()
2328
2530
self.addCleanup(wt.unlock)
2329
wt.commit('base', rev_id='A-id')
2531
wt.commit('base', rev_id=b'A-id')
2330
2532
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')
2533
wt.add(['foo'], [b'foo-id'])
2534
wt.commit('add symlink foo => bar', rev_id=b'B-id')
2535
wt.set_last_revision(b'A-id')
2536
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'))
2538
wt.commit('C', rev_id=b'C-id')
2539
wt.merge_from_branch(wt.branch, b'B-id')
2540
self.assertEqual('bar', wt.get_symlink_target('foo'))
2339
2541
os.remove('path/foo')
2340
2542
# We have to change the link in E, or it won't try to do a comparison
2341
2543
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')
2544
wt.commit('E merges C & B, overrides to bing', rev_id=b'E-id')
2545
wt.set_last_revision(b'B-id')
2546
wt.branch.set_last_revision_info(2, b'B-id')
2346
wt.merge_from_branch(wt.branch, 'C-id')
2548
wt.merge_from_branch(wt.branch, b'C-id')
2347
2549
os.remove('path/foo')
2348
self.build_tree_contents([('path/foo', 'file content\n')])
2550
self.build_tree_contents([('path/foo', b'file content\n')])
2349
2551
# XXX: workaround, WT doesn't detect kind changes unless you do
2350
2552
# iter_changes()
2351
2553
list(wt.iter_changes(wt.basis_tree()))
2352
wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2554
wt.commit('D merges B & C, makes it a file', rev_id=b'D-id')
2354
merger = _mod_merge.Merger.from_revision_ids(None,
2556
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2356
2557
merger.merge_type = _mod_merge.Merge3Merger
2357
2558
merge_obj = merger.make_merger()
2358
2559
entries = list(merge_obj._entries_lca())
2359
2560
root_id = wt.path2id('')
2360
self.assertEqual([('foo-id', True,
2561
self.assertEqual([(b'foo-id', True,
2562
((None, [u'foo', None]), u'foo', u'foo'),
2361
2563
((None, [root_id, None]), root_id, root_id),
2362
2564
((None, [u'foo', None]), u'foo', u'foo'),
2363
((None, [False, None]), False, False)),
2565
((None, [False, None]), False, False),
2366
2569
def test_symlink_all_wt(self):
2367
2570
"""Check behavior if all trees are Working Trees."""
2368
self.requireFeature(tests.SymlinkFeature)
2571
self.requireFeature(features.SymlinkFeature)
2369
2572
# The big issue is that entry.symlink_target is None for WorkingTrees.
2370
2573
# So we need to make sure we handle that case correctly.
2382
2585
wt.lock_write()
2383
2586
self.addCleanup(wt.unlock)
2384
2587
os.symlink('bar', 'path/foo')
2385
wt.add(['foo'], ['foo-id'])
2386
wt.commit('add symlink', rev_id='A-id')
2588
wt.add(['foo'], [b'foo-id'])
2589
wt.commit('add symlink', rev_id=b'A-id')
2387
2590
os.remove('path/foo')
2388
2591
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')
2592
wt.commit('foo => baz', rev_id=b'B-id')
2593
wt.set_last_revision(b'A-id')
2594
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')
2596
wt.commit('C', rev_id=b'C-id')
2597
wt.merge_from_branch(wt.branch, b'B-id')
2598
self.assertEqual('baz', wt.get_symlink_target('foo'))
2599
wt.commit('E merges C & B', rev_id=b'E-id')
2397
2600
os.remove('path/foo')
2398
2601
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')
2602
wt.commit('F foo => bing', rev_id=b'F-id')
2603
wt.set_last_revision(b'B-id')
2604
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()
2606
wt.merge_from_branch(wt.branch, b'C-id')
2607
wt.commit('D merges B & C', rev_id=b'D-id')
2608
wt_base = wt.controldir.sprout('base', b'A-id').open_workingtree()
2406
2609
wt_base.lock_read()
2407
2610
self.addCleanup(wt_base.unlock)
2408
wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2611
wt_lca1 = wt.controldir.sprout('b-tree', b'B-id').open_workingtree()
2409
2612
wt_lca1.lock_read()
2410
2613
self.addCleanup(wt_lca1.unlock)
2411
wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2614
wt_lca2 = wt.controldir.sprout('c-tree', b'C-id').open_workingtree()
2412
2615
wt_lca2.lock_read()
2413
2616
self.addCleanup(wt_lca2.unlock)
2414
wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
2617
wt_other = wt.controldir.sprout('other', b'F-id').open_workingtree()
2415
2618
wt_other.lock_read()
2416
2619
self.addCleanup(wt_other.unlock)
2417
2620
merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
2418
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2621
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2419
2622
entries = list(merge_obj._entries_lca())
2420
2623
root_id = wt.path2id('')
2421
self.assertEqual([('foo-id', True,
2624
self.assertEqual([(b'foo-id', True,
2625
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2422
2626
((root_id, [root_id, root_id]), root_id, root_id),
2423
2627
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2424
((False, [False, False]), False, False)),
2628
((False, [False, False]), False, False),
2427
2632
def test_other_reverted_path_to_base(self):
2428
2633
# A Path at 'foo'
2436
2641
# F Path at 'foo'
2437
2642
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')
2643
builder.build_snapshot(None,
2644
[('add', (u'', b'a-root-id', 'directory', None)),
2645
('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
2646
revision_id=b'A-id')
2647
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2648
builder.build_snapshot([b'A-id'],
2649
[('rename', ('foo', 'bar'))], revision_id=b'B-id')
2650
builder.build_snapshot([b'C-id', b'B-id'],
2651
[('rename', ('foo', 'bar'))], revision_id=b'E-id') # merge the rename
2652
builder.build_snapshot([b'E-id'],
2653
[('rename', ('bar', 'foo'))], revision_id=b'F-id') # Rename back to BASE
2654
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2655
wt, conflicts = self.do_merge(builder, b'F-id')
2450
2656
self.assertEqual(0, conflicts)
2451
self.assertEqual('foo', wt.id2path('foo-id'))
2657
self.assertEqual('foo', wt.id2path(b'foo-id'))
2453
2659
def test_other_reverted_content_to_base(self):
2454
2660
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')
2661
builder.build_snapshot(None,
2662
[('add', (u'', b'a-root-id', 'directory', None)),
2663
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2664
revision_id=b'A-id')
2665
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2666
builder.build_snapshot([b'A-id'],
2667
[('modify', ('foo', b'B content\n'))],
2668
revision_id=b'B-id')
2669
builder.build_snapshot([b'C-id', b'B-id'],
2670
[('modify', ('foo', b'B content\n'))],
2671
revision_id=b'E-id') # merge the content
2672
builder.build_snapshot([b'E-id'],
2673
[('modify', ('foo', b'base content\n'))],
2674
revision_id=b'F-id') # Revert back to BASE
2675
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2676
wt, conflicts = self.do_merge(builder, b'F-id')
2467
2677
self.assertEqual(0, conflicts)
2468
2678
# TODO: We need to use the per-file graph to properly select a BASE
2469
2679
# before this will work. Or at least use the LCA trees to find
2470
2680
# the appropriate content base. (which is B, not A).
2471
self.assertEqual('base content\n', wt.get_file_text('foo-id'))
2681
self.assertEqual(b'base content\n', wt.get_file_text('foo'))
2473
2683
def test_other_modified_content(self):
2474
2684
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')
2685
builder.build_snapshot(None,
2686
[('add', (u'', b'a-root-id', 'directory', None)),
2687
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2688
revision_id=b'A-id')
2689
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2690
builder.build_snapshot([b'A-id'],
2691
[('modify', ('foo', b'B content\n'))],
2692
revision_id=b'B-id')
2693
builder.build_snapshot([b'C-id', b'B-id'],
2694
[('modify', ('foo', b'B content\n'))],
2695
revision_id=b'E-id') # merge the content
2696
builder.build_snapshot([b'E-id'],
2697
[('modify', ('foo', b'F content\n'))],
2698
revision_id=b'F-id') # Override B content
2699
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2700
wt, conflicts = self.do_merge(builder, b'F-id')
2487
2701
self.assertEqual(0, conflicts)
2488
self.assertEqual('F content\n', wt.get_file_text('foo-id'))
2702
self.assertEqual(b'F content\n', wt.get_file_text('foo'))
2490
2704
def test_all_wt(self):
2491
2705
"""Check behavior if all trees are Working Trees."""
2499
2713
# D E E updates content, renames 'b' => 'c'
2500
2714
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
2715
builder.build_snapshot(None,
2716
[('add', (u'', b'a-root-id', 'directory', None)),
2717
('add', (u'a', b'a-id', 'file', b'base content\n')),
2718
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2719
revision_id=b'A-id')
2720
builder.build_snapshot([b'A-id'],
2721
[('modify', ('foo', b'B content\n'))],
2722
revision_id=b'B-id')
2723
builder.build_snapshot([b'A-id'],
2724
[('rename', ('a', 'b'))],
2725
revision_id=b'C-id')
2726
builder.build_snapshot([b'C-id', b'B-id'],
2727
[('rename', ('b', 'c')),
2728
('modify', ('foo', b'E content\n'))],
2729
revision_id=b'E-id')
2730
builder.build_snapshot([b'B-id', b'C-id'],
2731
[('rename', ('a', 'b'))], revision_id=b'D-id') # merged change
2514
2732
wt_this = self.get_wt_from_builder(builder)
2515
wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
2733
wt_base = wt_this.controldir.sprout('base', b'A-id').open_workingtree()
2516
2734
wt_base.lock_read()
2517
2735
self.addCleanup(wt_base.unlock)
2518
wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2736
wt_lca1 = wt_this.controldir.sprout(
2737
'b-tree', b'B-id').open_workingtree()
2519
2738
wt_lca1.lock_read()
2520
2739
self.addCleanup(wt_lca1.unlock)
2521
wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2740
wt_lca2 = wt_this.controldir.sprout(
2741
'c-tree', b'C-id').open_workingtree()
2522
2742
wt_lca2.lock_read()
2523
2743
self.addCleanup(wt_lca2.unlock)
2524
wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
2744
wt_other = wt_this.controldir.sprout(
2745
'other', b'E-id').open_workingtree()
2525
2746
wt_other.lock_read()
2526
2747
self.addCleanup(wt_other.unlock)
2527
2748
merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
2528
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2749
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2529
2750
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)),
2751
root_id = b'a-root-id'
2752
self.assertEqual([(b'a-id', False,
2753
((u'a', [u'a', u'b']), u'c', u'b'),
2754
((root_id, [root_id, root_id]), root_id, root_id),
2755
((u'a', [u'a', u'b']), u'c', u'b'),
2756
((False, [False, False]), False, False),
2759
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2760
((root_id, [root_id, root_id]), root_id, root_id),
2761
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2762
((False, [False, False]), False, False),
2541
2766
def test_nested_tree_unmodified(self):
2542
2767
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2543
2768
# 'tree-reference'
2544
2769
wt = self.make_branch_and_tree('tree',
2545
format='dirstate-with-subtree')
2770
format='development-subtree')
2546
2771
wt.lock_write()
2547
2772
self.addCleanup(wt.unlock)
2548
2773
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')])
2774
format='development-subtree')
2775
wt.set_root_id(b'a-root-id')
2776
sub_tree.set_root_id(b'sub-tree-root')
2777
self.build_tree_contents([('tree/sub-tree/file', b'text1')])
2553
2778
sub_tree.add('file')
2554
sub_tree.commit('foo', rev_id='sub-A-id')
2779
sub_tree.commit('foo', rev_id=b'sub-A-id')
2555
2780
wt.add_reference(sub_tree)
2556
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2781
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2557
2782
# 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)
2784
wt.commit('B', rev_id=b'B-id', recursive=None)
2785
wt.set_last_revision(b'A-id')
2786
wt.branch.set_last_revision_info(1, b'A-id')
2787
wt.commit('C', rev_id=b'C-id', recursive=None)
2788
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2789
wt.commit('E', rev_id=b'E-id', recursive=None)
2790
wt.set_parent_ids([b'B-id', b'C-id'])
2791
wt.branch.set_last_revision_info(2, b'B-id')
2792
wt.commit('D', rev_id=b'D-id', recursive=None)
2569
merger = _mod_merge.Merger.from_revision_ids(None,
2794
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2571
2795
merger.merge_type = _mod_merge.Merge3Merger
2572
2796
merge_obj = merger.make_merger()
2573
2797
entries = list(merge_obj._entries_lca())
2616
2839
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2617
2840
# 'tree-reference'
2618
2841
wt = self.make_branch_and_tree('tree',
2619
format='dirstate-with-subtree')
2842
format='development-subtree')
2620
2843
wt.lock_write()
2621
2844
self.addCleanup(wt.unlock)
2622
2845
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')])
2846
format='development-subtree')
2847
wt.set_root_id(b'a-root-id')
2848
sub_tree.set_root_id(b'sub-tree-root')
2849
self.build_tree_contents([('tree/sub/file', b'text1')])
2627
2850
sub_tree.add('file')
2628
sub_tree.commit('foo', rev_id='sub-A-id')
2851
sub_tree.commit('foo', rev_id=b'sub-A-id')
2629
2852
wt.add_reference(sub_tree)
2630
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2853
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2631
2854
# 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')
2856
wt.commit('B', rev_id=b'B-id', recursive=None)
2857
wt.set_last_revision(b'A-id')
2858
wt.branch.set_last_revision_info(1, b'A-id')
2859
wt.commit('C', rev_id=b'C-id', recursive=None)
2860
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2638
2861
wt.rename_one('sub', 'alt_sub')
2639
wt.commit('E', rev_id='E-id', recursive=None)
2640
wt.set_last_revision('B-id')
2862
wt.commit('E', rev_id=b'E-id', recursive=None)
2863
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)
2865
wt.set_parent_ids([b'B-id', b'C-id'])
2866
wt.branch.set_last_revision_info(2, b'B-id')
2867
wt.commit('D', rev_id=b'D-id', recursive=None)
2646
merger = _mod_merge.Merger.from_revision_ids(None,
2869
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2648
2870
merger.merge_type = _mod_merge.Merge3Merger
2649
2871
merge_obj = merger.make_merger()
2650
2872
entries = list(merge_obj._entries_lca())
2651
root_id = 'a-root-id'
2652
self.assertEqual([('sub-tree-root', False,
2873
root_id = b'a-root-id'
2874
self.assertEqual([(b'sub-tree-root', False,
2875
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2653
2876
((root_id, [root_id, root_id]), root_id, root_id),
2654
2877
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2655
((False, [False, False]), False, False)),
2878
((False, [False, False]), False, False),
2658
2882
def test_nested_tree_subtree_renamed_and_modified(self):
2659
2883
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2660
2884
# 'tree-reference'
2661
2885
wt = self.make_branch_and_tree('tree',
2662
format='dirstate-with-subtree')
2886
format='development-subtree')
2663
2887
wt.lock_write()
2664
2888
self.addCleanup(wt.unlock)
2665
2889
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')])
2890
format='development-subtree')
2891
wt.set_root_id(b'a-root-id')
2892
sub_tree.set_root_id(b'sub-tree-root')
2893
self.build_tree_contents([('tree/sub/file', b'text1')])
2670
2894
sub_tree.add('file')
2671
sub_tree.commit('foo', rev_id='sub-A-id')
2895
sub_tree.commit('foo', rev_id=b'sub-A-id')
2672
2896
wt.add_reference(sub_tree)
2673
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2897
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2674
2898
# 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')
2900
wt.commit('B', rev_id=b'B-id', recursive=None)
2901
wt.set_last_revision(b'A-id')
2902
wt.branch.set_last_revision_info(1, b'A-id')
2903
wt.commit('C', rev_id=b'C-id', recursive=None)
2904
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2905
self.build_tree_contents([('tree/sub/file', b'text2')])
2906
sub_tree.commit('modify contents', rev_id=b'sub-B-id')
2683
2907
wt.rename_one('sub', 'alt_sub')
2684
wt.commit('E', rev_id='E-id', recursive=None)
2685
wt.set_last_revision('B-id')
2908
wt.commit('E', rev_id=b'E-id', recursive=None)
2909
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)
2911
wt.set_parent_ids([b'B-id', b'C-id'])
2912
wt.branch.set_last_revision_info(2, b'B-id')
2913
wt.commit('D', rev_id=b'D-id', recursive=None)
2691
merger = _mod_merge.Merger.from_revision_ids(None,
2915
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2693
2916
merger.merge_type = _mod_merge.Merge3Merger
2694
2917
merge_obj = merger.make_merger()
2695
2918
entries = list(merge_obj._entries_lca())
2696
root_id = 'a-root-id'
2697
self.assertEqual([('sub-tree-root', False,
2919
root_id = b'a-root-id'
2920
self.assertEqual([(b'sub-tree-root', False,
2921
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2698
2922
((root_id, [root_id, root_id]), root_id, root_id),
2699
2923
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2700
((False, [False, False]), False, False)),
2924
((False, [False, False]), False, False),
2704
2929
class TestLCAMultiWay(tests.TestCase):
2706
2931
def assertLCAMultiWay(self, expected, base, lcas, other, this,
2707
2932
allow_overriding_lca=True):
2708
2933
self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
2709
(base, lcas), other, this,
2710
allow_overriding_lca=allow_overriding_lca))
2934
(base, lcas), other, this,
2935
allow_overriding_lca=allow_overriding_lca))
2712
2937
def test_other_equal_equal_lcas(self):
2713
2938
"""Test when OTHER=LCA and all LCAs are identical."""
2714
2939
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)
2940
'bval', ['bval', 'bval'], 'bval', 'bval')
2941
self.assertLCAMultiWay('this',
2942
'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
2943
self.assertLCAMultiWay('this',
2944
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
2945
self.assertLCAMultiWay('this',
2946
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
2947
self.assertLCAMultiWay('this',
2948
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
2725
2950
def test_other_equal_this(self):
2726
2951
"""Test when other and this are identical."""
2727
2952
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')
2953
'bval', ['bval', 'bval'], 'oval', 'oval')
2954
self.assertLCAMultiWay('this',
2955
'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
2956
self.assertLCAMultiWay('this',
2957
'bval', ['cval', 'dval'], 'oval', 'oval')
2958
self.assertLCAMultiWay('this',
2959
'bval', [None, 'lcaval'], 'oval', 'oval')
2960
self.assertLCAMultiWay('this',
2961
None, [None, 'lcaval'], 'oval', 'oval')
2962
self.assertLCAMultiWay('this',
2963
None, ['lcaval', 'lcaval'], 'oval', 'oval')
2964
self.assertLCAMultiWay('this',
2965
None, ['cval', 'dval'], 'oval', 'oval')
2966
self.assertLCAMultiWay('this',
2967
None, ['cval', 'dval'], None, None)
2968
self.assertLCAMultiWay('this',
2969
None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
2746
2971
def test_no_lcas(self):
2747
2972
self.assertLCAMultiWay('this',
2748
'bval', [], 'bval', 'tval')
2973
'bval', [], 'bval', 'tval')
2749
2974
self.assertLCAMultiWay('other',
2750
'bval', [], 'oval', 'bval')
2975
'bval', [], 'oval', 'bval')
2751
2976
self.assertLCAMultiWay('conflict',
2752
'bval', [], 'oval', 'tval')
2977
'bval', [], 'oval', 'tval')
2753
2978
self.assertLCAMultiWay('this',
2754
'bval', [], 'oval', 'oval')
2979
'bval', [], 'oval', 'oval')
2756
2981
def test_lca_supersedes_other_lca(self):
2757
2982
"""If one lca == base, the other lca takes precedence"""
2758
2983
self.assertLCAMultiWay('this',
2759
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2984
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2760
2985
self.assertLCAMultiWay('this',
2761
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2986
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2762
2987
# This is actually considered a 'revert' because the 'lcaval' in LCAS
2763
2988
# supersedes the BASE val (in the other LCA) but then OTHER reverts it
2764
2989
# back to bval.
2765
2990
self.assertLCAMultiWay('other',
2766
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2991
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2767
2992
self.assertLCAMultiWay('conflict',
2768
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2993
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2770
2995
def test_other_and_this_pick_different_lca(self):
2771
2996
# OTHER and THIS resolve the lca conflict in different ways
2772
2997
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')
2998
'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
2999
self.assertLCAMultiWay('conflict',
3000
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
3001
self.assertLCAMultiWay('conflict',
3002
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
2779
3004
def test_other_in_lca(self):
2780
3005
# OTHER takes a value of one of the LCAs, THIS takes a new value, which
2781
3006
# 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)
3007
self.assertLCAMultiWay(
3008
'this', 'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
3009
self.assertLCAMultiWay(
3010
'this', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val',
3012
self.assertLCAMultiWay('conflict',
3014
'lca2val'], 'lca1val', 'newval',
3015
allow_overriding_lca=False)
3016
self.assertLCAMultiWay('conflict',
3017
'bval', ['lca1val', 'lca2val',
3018
'lca3val'], 'lca1val', 'newval',
3019
allow_overriding_lca=False)
2792
3020
# 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)
3022
self.assertLCAMultiWay(
3023
'this', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val',
3025
self.assertLCAMultiWay(
3026
'this', 'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
3027
self.assertLCAMultiWay('conflict',
3028
'bval', ['lca1val', 'lca2val',
3029
'lca3val'], 'lca1val', 'bval',
3030
allow_overriding_lca=False)
3031
self.assertLCAMultiWay('conflict',
3032
'bval', ['lca1val', 'lca2val',
3033
'bval'], 'lca1val', 'bval',
3034
allow_overriding_lca=False)
2805
3036
def test_this_in_lca(self):
2806
3037
# THIS takes a value of one of the LCAs, OTHER takes a new value, which
2807
3038
# 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)
3039
self.assertLCAMultiWay(
3040
'other', 'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
3041
self.assertLCAMultiWay(
3042
'other', 'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
3043
self.assertLCAMultiWay('conflict',
3045
'lca2val'], 'oval', 'lca1val',
3046
allow_overriding_lca=False)
3047
self.assertLCAMultiWay('conflict',
3049
'lca2val'], 'oval', 'lca2val',
3050
allow_overriding_lca=False)
2818
3051
# 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)
3053
self.assertLCAMultiWay(
3054
'other', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval',
3056
self.assertLCAMultiWay(
3057
'conflict', 'bval', ['lca1val', 'lca2val', 'lca3val'],
3058
'bval', 'lca3val', allow_overriding_lca=False)
2826
3060
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')
3061
self.assertLCAMultiWay(
3062
'conflict', 'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
3063
self.assertLCAMultiWay(
3064
'conflict', 'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval',
3066
self.assertLCAMultiWay(
3067
'conflict', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval',
2835
3071
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
2903
3140
def test_hook_called_for_text_conflicts(self):
2904
3141
builder = self.make_text_conflict()
2905
conflicts = builder.merge()
2906
3143
# The hook should call the merge_text() method
2907
3144
self.assertEqual(['merge_text'], self.calls)
2909
3146
def test_hook_not_called_for_kind_change(self):
2910
3147
builder = self.make_kind_change()
2911
conflicts = builder.merge()
2912
3149
# The hook should not call the merge_text() method
2913
3150
self.assertEqual([], self.calls)
2915
3152
def test_hook_not_called_for_other_files(self):
2916
3153
builder = self.make_text_conflict('foobar')
2917
conflicts = builder.merge()
2918
3155
# The hook should not call the merge_text() method
2919
3156
self.assertEqual([], self.calls)
3159
class TestMergeIntoBase(tests.TestCaseWithTransport):
3161
def setup_simple_branch(self, relpath, shape=None, root_id=None):
3162
"""One commit, containing tree specified by optional shape.
3164
Default is empty tree (just root entry).
3167
root_id = b'%s-root-id' % (relpath.encode('ascii'),)
3168
wt = self.make_branch_and_tree(relpath)
3169
wt.set_root_id(root_id)
3170
if shape is not None:
3171
adjusted_shape = [relpath + '/' + elem for elem in shape]
3172
self.build_tree(adjusted_shape)
3174
(b'%s-%s-id' % (relpath.encode('utf-8'),
3175
basename(elem.rstrip('/')).encode('ascii')))
3177
wt.add(shape, ids=ids)
3178
rev_id = b'r1-%s' % (relpath.encode('utf-8'),)
3179
wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
3180
self.assertEqual(root_id, wt.path2id(''))
3183
def setup_two_branches(self, custom_root_ids=True):
3184
"""Setup 2 branches, one will be a library, the other a project."""
3188
root_id = inventory.ROOT_ID
3189
project_wt = self.setup_simple_branch(
3190
'project', ['README', 'dir/', 'dir/file.c'],
3192
lib_wt = self.setup_simple_branch(
3193
'lib1', ['README', 'Makefile', 'foo.c'], root_id)
3195
return project_wt, lib_wt
3197
def do_merge_into(self, location, merge_as):
3198
"""Helper for using MergeIntoMerger.
3200
:param location: location of directory to merge from, either the
3201
location of a branch or of a path inside a branch.
3202
:param merge_as: the path in a tree to add the new directory as.
3203
:returns: the conflicts from 'do_merge'.
3205
with cleanup.ExitStack() as stack:
3206
# Open and lock the various tree and branch objects
3207
wt, subdir_relpath = WorkingTree.open_containing(merge_as)
3208
stack.enter_context(wt.lock_write())
3209
branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
3211
stack.enter_context(branch_to_merge.lock_read())
3212
other_tree = branch_to_merge.basis_tree()
3213
stack.enter_context(other_tree.lock_read())
3215
merger = _mod_merge.MergeIntoMerger(
3216
this_tree=wt, other_tree=other_tree, other_branch=branch_to_merge,
3217
target_subdir=subdir_relpath, source_subpath=subdir_to_merge)
3218
merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
3219
conflicts = merger.do_merge()
3220
merger.set_pending()
3223
def assertTreeEntriesEqual(self, expected_entries, tree):
3224
"""Assert that 'tree' contains the expected inventory entries.
3226
:param expected_entries: sequence of (path, file-id) pairs.
3228
files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
3229
self.assertEqual(expected_entries, files)
3232
class TestMergeInto(TestMergeIntoBase):
3234
def test_newdir_with_unique_roots(self):
3235
"""Merge a branch with a unique root into a new directory."""
3236
project_wt, lib_wt = self.setup_two_branches()
3237
self.do_merge_into('lib1', 'project/lib1')
3238
project_wt.lock_read()
3239
self.addCleanup(project_wt.unlock)
3240
# The r1-lib1 revision should be merged into this one
3241
self.assertEqual([b'r1-project', b'r1-lib1'],
3242
project_wt.get_parent_ids())
3243
self.assertTreeEntriesEqual(
3244
[('', b'project-root-id'),
3245
('README', b'project-README-id'),
3246
('dir', b'project-dir-id'),
3247
('lib1', b'lib1-root-id'),
3248
('dir/file.c', b'project-file.c-id'),
3249
('lib1/Makefile', b'lib1-Makefile-id'),
3250
('lib1/README', b'lib1-README-id'),
3251
('lib1/foo.c', b'lib1-foo.c-id'),
3254
def test_subdir(self):
3255
"""Merge a branch into a subdirectory of an existing directory."""
3256
project_wt, lib_wt = self.setup_two_branches()
3257
self.do_merge_into('lib1', 'project/dir/lib1')
3258
project_wt.lock_read()
3259
self.addCleanup(project_wt.unlock)
3260
# The r1-lib1 revision should be merged into this one
3261
self.assertEqual([b'r1-project', b'r1-lib1'],
3262
project_wt.get_parent_ids())
3263
self.assertTreeEntriesEqual(
3264
[('', b'project-root-id'),
3265
('README', b'project-README-id'),
3266
('dir', b'project-dir-id'),
3267
('dir/file.c', b'project-file.c-id'),
3268
('dir/lib1', b'lib1-root-id'),
3269
('dir/lib1/Makefile', b'lib1-Makefile-id'),
3270
('dir/lib1/README', b'lib1-README-id'),
3271
('dir/lib1/foo.c', b'lib1-foo.c-id'),
3274
def test_newdir_with_repeat_roots(self):
3275
"""If the file-id of the dir to be merged already exists a new ID will
3276
be allocated to let the merge happen.
3278
project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
3279
root_id = project_wt.path2id('')
3280
self.do_merge_into('lib1', 'project/lib1')
3281
project_wt.lock_read()
3282
self.addCleanup(project_wt.unlock)
3283
# The r1-lib1 revision should be merged into this one
3284
self.assertEqual([b'r1-project', b'r1-lib1'],
3285
project_wt.get_parent_ids())
3286
new_lib1_id = project_wt.path2id('lib1')
3287
self.assertNotEqual(None, new_lib1_id)
3288
self.assertTreeEntriesEqual(
3290
('README', b'project-README-id'),
3291
('dir', b'project-dir-id'),
3292
('lib1', new_lib1_id),
3293
('dir/file.c', b'project-file.c-id'),
3294
('lib1/Makefile', b'lib1-Makefile-id'),
3295
('lib1/README', b'lib1-README-id'),
3296
('lib1/foo.c', b'lib1-foo.c-id'),
3299
def test_name_conflict(self):
3300
"""When the target directory name already exists a conflict is
3301
generated and the original directory is renamed to foo.moved.
3303
dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
3304
self.setup_simple_branch('src', ['README'])
3305
conflicts = self.do_merge_into('src', 'dest/dir')
3306
self.assertEqual(1, conflicts)
3308
self.addCleanup(dest_wt.unlock)
3309
# The r1-lib1 revision should be merged into this one
3310
self.assertEqual([b'r1-dest', b'r1-src'], dest_wt.get_parent_ids())
3311
self.assertTreeEntriesEqual(
3312
[('', b'dest-root-id'),
3313
('dir', b'src-root-id'),
3314
('dir.moved', b'dest-dir-id'),
3315
('dir/README', b'src-README-id'),
3316
('dir.moved/file.txt', b'dest-file.txt-id'),
3319
def test_file_id_conflict(self):
3320
"""A conflict is generated if the merge-into adds a file (or other
3321
inventory entry) with a file-id that already exists in the target tree.
3323
self.setup_simple_branch('dest', ['file.txt'])
3324
# Make a second tree with a file-id that will clash with file.txt in
3326
src_wt = self.make_branch_and_tree('src')
3327
self.build_tree(['src/README'])
3328
src_wt.add(['README'], ids=[b'dest-file.txt-id'])
3329
src_wt.commit("Rev 1 of src.", rev_id=b'r1-src')
3330
conflicts = self.do_merge_into('src', 'dest/dir')
3331
# This is an edge case that shouldn't happen to users very often. So
3332
# we don't care really about the exact presentation of the conflict,
3333
# just that there is one.
3334
self.assertEqual(1, conflicts)
3336
def test_only_subdir(self):
3337
"""When the location points to just part of a tree, merge just that
3340
dest_wt = self.setup_simple_branch('dest')
3341
self.setup_simple_branch('src', ['hello.txt', 'dir/', 'dir/foo.c'])
3342
self.do_merge_into('src/dir', 'dest/dir')
3344
self.addCleanup(dest_wt.unlock)
3345
# The r1-lib1 revision should NOT be merged into this one (this is a
3347
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3348
self.assertTreeEntriesEqual(
3349
[('', b'dest-root-id'),
3350
('dir', b'src-dir-id'),
3351
('dir/foo.c', b'src-foo.c-id'),
3354
def test_only_file(self):
3355
"""An edge case: merge just one file, not a whole dir."""
3356
dest_wt = self.setup_simple_branch('dest')
3357
self.setup_simple_branch('two-file', ['file1.txt', 'file2.txt'])
3358
self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
3360
self.addCleanup(dest_wt.unlock)
3361
# The r1-lib1 revision should NOT be merged into this one
3362
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3363
self.assertTreeEntriesEqual(
3364
[('', b'dest-root-id'), ('file1.txt', b'two-file-file1.txt-id')],
3367
def test_no_such_source_path(self):
3368
"""PathNotInTree is raised if the specified path in the source tree
3371
dest_wt = self.setup_simple_branch('dest')
3372
self.setup_simple_branch('src', ['dir/'])
3373
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3374
'src/no-such-dir', 'dest/foo')
3376
self.addCleanup(dest_wt.unlock)
3377
# The dest tree is unmodified.
3378
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3379
self.assertTreeEntriesEqual([('', b'dest-root-id')], dest_wt)
3381
def test_no_such_target_path(self):
3382
"""PathNotInTree is also raised if the specified path in the target
3383
tree does not exist.
3385
dest_wt = self.setup_simple_branch('dest')
3386
self.setup_simple_branch('src', ['file.txt'])
3387
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3388
'src', 'dest/no-such-dir/foo')
3390
self.addCleanup(dest_wt.unlock)
3391
# The dest tree is unmodified.
3392
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3393
self.assertTreeEntriesEqual([('', b'dest-root-id')], dest_wt)
3396
class TestMergeHooks(TestCaseWithTransport):
3399
super(TestMergeHooks, self).setUp()
3400
self.tree_a = self.make_branch_and_tree('tree_a')
3401
self.build_tree_contents([('tree_a/file', b'content_1')])
3402
self.tree_a.add('file', b'file-id')
3403
self.tree_a.commit('added file')
3405
self.tree_b = self.tree_a.controldir.sprout(
3406
'tree_b').open_workingtree()
3407
self.build_tree_contents([('tree_b/file', b'content_2')])
3408
self.tree_b.commit('modify file')
3410
def test_pre_merge_hook_inject_different_tree(self):
3411
tree_c = self.tree_b.controldir.sprout('tree_c').open_workingtree()
3412
self.build_tree_contents([('tree_c/file', b'content_3')])
3413
tree_c.commit("more content")
3416
def factory(merger):
3417
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3418
merger.other_tree = tree_c
3419
calls.append(merger)
3420
_mod_merge.Merger.hooks.install_named_hook('pre_merge',
3421
factory, 'test factory')
3422
self.tree_a.merge_from_branch(self.tree_b.branch)
3424
self.assertFileEqual(b"content_3", 'tree_a/file')
3425
self.assertLength(1, calls)
3427
def test_post_merge_hook_called(self):
3430
def factory(merger):
3431
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3432
calls.append(merger)
3433
_mod_merge.Merger.hooks.install_named_hook('post_merge',
3434
factory, 'test factory')
3436
self.tree_a.merge_from_branch(self.tree_b.branch)
3438
self.assertFileEqual(b"content_2", 'tree_a/file')
3439
self.assertLength(1, calls)