365
326
'c', but not 'b'.
367
328
this_tree = self.make_branch_and_tree('this')
368
self.build_tree_contents([('this/file', b"a\n")])
329
self.build_tree_contents([('this/file', "a\n")])
369
330
this_tree.add('file')
370
331
this_tree.commit('rev1')
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')
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')
376
337
this_tree.lock_write()
377
338
self.addCleanup(this_tree.unlock)
378
339
return this_tree, other_tree
380
341
def test_weave_cherrypick(self):
381
342
this_tree, other_tree = self.prepare_cherrypick()
382
merger = _mod_merge.Merger.from_revision_ids(
383
this_tree, b'rev3b', b'rev2b', other_tree.branch)
343
merger = _mod_merge.Merger.from_revision_ids(None,
344
this_tree, 'rev3b', 'rev2b', other_tree.branch)
384
345
merger.merge_type = _mod_merge.WeaveMerger
385
346
merger.do_merge()
386
self.assertFileEqual(b'c\na\n', 'this/file')
347
self.assertFileEqual('c\na\n', 'this/file')
388
349
def test_weave_cannot_reverse_cherrypick(self):
389
350
this_tree, other_tree = self.prepare_cherrypick()
390
merger = _mod_merge.Merger.from_revision_ids(
391
this_tree, b'rev2b', b'rev3b', other_tree.branch)
351
merger = _mod_merge.Merger.from_revision_ids(None,
352
this_tree, 'rev2b', 'rev3b', other_tree.branch)
392
353
merger.merge_type = _mod_merge.WeaveMerger
393
354
self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
395
356
def test_merge3_can_reverse_cherrypick(self):
396
357
this_tree, other_tree = self.prepare_cherrypick()
397
merger = _mod_merge.Merger.from_revision_ids(
398
this_tree, b'rev2b', b'rev3b', other_tree.branch)
358
merger = _mod_merge.Merger.from_revision_ids(None,
359
this_tree, 'rev2b', 'rev3b', other_tree.branch)
399
360
merger.merge_type = _mod_merge.Merge3Merger
400
361
merger.do_merge()
402
363
def test_merge3_will_detect_cherrypick(self):
403
364
this_tree = self.make_branch_and_tree('this')
404
self.build_tree_contents([('this/file', b"a\n")])
365
self.build_tree_contents([('this/file', "a\n")])
405
366
this_tree.add('file')
406
367
this_tree.commit('rev1')
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')
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')
412
373
this_tree.lock_write()
413
374
self.addCleanup(this_tree.unlock)
415
merger = _mod_merge.Merger.from_revision_ids(
416
this_tree, b'rev3b', b'rev2b', other_tree.branch)
376
merger = _mod_merge.Merger.from_revision_ids(None,
377
this_tree, 'rev3b', 'rev2b', other_tree.branch)
417
378
merger.merge_type = _mod_merge.Merge3Merger
418
379
merger.do_merge()
419
self.assertFileEqual(b'a\n'
423
b'>>>>>>> MERGE-SOURCE\n',
380
self.assertFileEqual('a\n'
384
'>>>>>>> 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")
445
387
def test_make_merger(self):
446
388
this_tree = self.make_branch_and_tree('this')
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')
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')
451
393
this_tree.lock_write()
452
394
self.addCleanup(this_tree.unlock)
453
merger = _mod_merge.Merger.from_revision_ids(
454
this_tree, b'rev2b', other_branch=other_tree.branch)
395
merger = _mod_merge.Merger.from_revision_ids(None,
396
this_tree, 'rev2b', other_branch=other_tree.branch)
455
397
merger.merge_type = _mod_merge.Merge3Merger
456
398
tree_merger = merger.make_merger()
457
399
self.assertIs(_mod_merge.Merge3Merger, tree_merger.__class__)
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)
400
self.assertEqual('rev2b', tree_merger.other_tree.get_revision_id())
401
self.assertEqual('rev1', tree_merger.base_tree.get_revision_id())
464
403
def test_make_preview_transform(self):
465
404
this_tree = self.make_branch_and_tree('this')
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')
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')
474
413
this_tree.lock_write()
475
414
self.addCleanup(this_tree.unlock)
476
merger = _mod_merge.Merger.from_revision_ids(
477
this_tree, b'rev2b', other_branch=other_tree.branch)
415
merger = _mod_merge.Merger.from_revision_ids(None,
416
this_tree, 'rev2b', other_branch=other_tree.branch)
478
417
merger.merge_type = _mod_merge.Merge3Merger
479
418
tree_merger = merger.make_merger()
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())
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())
487
433
def test_do_merge(self):
488
434
this_tree = self.make_branch_and_tree('this')
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')
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')
497
443
this_tree.lock_write()
498
444
self.addCleanup(this_tree.unlock)
499
merger = _mod_merge.Merger.from_revision_ids(
500
this_tree, b'rev2b', other_branch=other_tree.branch)
445
merger = _mod_merge.Merger.from_revision_ids(None,
446
this_tree, 'rev2b', other_branch=other_tree.branch)
501
447
merger.merge_type = _mod_merge.Merge3Merger
502
448
tree_merger = merger.make_merger()
503
449
tt = tree_merger.do_merge()
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([])
450
tree_file = this_tree.get_file('file-id')
452
self.assertEqual('2b\n1\n2a\n', tree_file.read())
524
456
def test_merge_add_into_deleted_root(self):
525
457
# Yes, people actually do this. And report bugs if it breaks.
526
458
source = self.make_branch_and_tree('source', format='rich-root-pack')
527
459
self.build_tree(['source/foo/'])
528
source.add('foo', b'foo-id')
460
source.add('foo', 'foo-id')
529
461
source.commit('Add foo')
530
target = source.controldir.sprout('target').open_workingtree()
531
subtree = target.extract('foo')
462
target = source.bzrdir.sprout('target').open_workingtree()
463
subtree = target.extract('foo-id')
532
464
subtree.commit('Delete root')
533
465
self.build_tree(['source/bar'])
534
source.add('bar', b'bar-id')
466
source.add('bar', 'bar-id')
535
467
source.commit('Add bar')
536
468
subtree.merge_from_branch(source.branch)
575
506
def add_uncommitted_version(self, key, parents, text):
576
507
self.plan_merge_vf.add_lines(key, parents,
577
[int2byte(c) + b'\n' for c in bytearray(text)])
508
[c+'\n' for c in text])
579
510
def setup_plan_merge(self):
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',))
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',))
585
516
def setup_plan_merge_uncommitted(self):
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',))
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',))
593
522
def test_base_from_plan(self):
594
523
self.setup_plan_merge()
595
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
524
plan = self.plan_merge_vf.plan_merge('B', 'C')
596
525
pwm = versionedfile.PlanWeaveMerge(plan)
597
self.assertEqual([b'a\n', b'b\n', b'c\n'], pwm.base_from_plan())
526
self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
599
528
def test_unique_lines(self):
600
529
plan = self.setup_plan_merge()
601
530
self.assertEqual(plan._unique_lines(
602
plan._get_matching_blocks(b'B', b'C')),
531
plan._get_matching_blocks('B', 'C')),
603
532
([1, 2, 3], [0, 2]))
605
534
def test_plan_merge(self):
606
535
self.setup_plan_merge()
607
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
536
plan = self.plan_merge_vf.plan_merge('B', 'C')
608
537
self.assertEqual([
610
('unchanged', b'a\n'),
611
('killed-a', b'b\n'),
612
('killed-b', b'c\n'),
539
('unchanged', 'a\n'),
619
548
def test_plan_merge_cherrypick(self):
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',))
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',))
625
554
# We shortcut when one text supersedes the other in the per-file graph.
626
555
# We don't actually need to compare the texts at this point.
627
556
self.assertEqual([
635
list(my_plan.plan_merge()))
564
list(my_plan.plan_merge()))
637
566
def test_plan_merge_no_common_ancestor(self):
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',))
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',))
641
570
self.assertEqual([
648
list(my_plan.plan_merge()))
577
list(my_plan.plan_merge()))
650
579
def test_plan_merge_tail_ancestors(self):
651
580
# The graph looks like this:
922
851
# XX unused ancestor, should not show up in the weave
926
855
# B C B & C both introduce a new line
930
859
# D E B & C are both merged, so both are common ancestors
931
860
# In the process of merging, both sides order the new
932
861
# lines differently
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'))
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'))
941
870
self.assertEqual([
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'),
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'),
952
881
pwm = versionedfile.PlanWeaveMerge(plan)
953
self.assertEqualDiff(b'a\nb\nc\nd\ng\nh\ne\nf\n',
954
b''.join(pwm.base_from_plan()))
882
self.assertEqualDiff('\n'.join('abcdghef') + '\n',
883
''.join(pwm.base_from_plan()))
955
884
# Reversing the order reverses the merge plan, and final order of 'hg'
957
plan = list(self.plan_merge_vf.plan_merge(b'E', b'D'))
886
plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
958
887
self.assertEqual([
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'),
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'),
969
898
pwm = versionedfile.PlanWeaveMerge(plan)
970
self.assertEqualDiff(b'a\nb\nc\nd\nh\ng\ne\nf\n',
971
b''.join(pwm.base_from_plan()))
899
self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
900
''.join(pwm.base_from_plan()))
972
901
# This is where lca differs, in that it (fairly correctly) determines
973
902
# that there is a conflict because both sides resolved the merge
975
plan = list(self.plan_merge_vf.plan_lca_merge(b'D', b'E'))
904
plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
976
905
self.assertEqual([
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'),
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'),
987
916
pwm = versionedfile.PlanWeaveMerge(plan)
988
self.assertEqualDiff(b'a\nb\nc\nd\ng\ne\nf\n',
989
b''.join(pwm.base_from_plan()))
917
self.assertEqualDiff('\n'.join('abcdgef') + '\n',
918
''.join(pwm.base_from_plan()))
990
919
# Reversing it changes what line is doubled, but still gives a
991
920
# double-conflict
992
plan = list(self.plan_merge_vf.plan_lca_merge(b'E', b'D'))
921
plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
993
922
self.assertEqual([
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'),
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'),
1004
933
pwm = versionedfile.PlanWeaveMerge(plan)
1005
self.assertEqualDiff(b'a\nb\nc\nd\nh\ne\nf\n',
1006
b''.join(pwm.base_from_plan()))
934
self.assertEqualDiff('\n'.join('abcdhef') + '\n',
935
''.join(pwm.base_from_plan()))
1008
937
def assertRemoveExternalReferences(self, filtered_parent_map,
1009
938
child_map, tails, parent_map):
1067
996
self.assertPruneTails({1: []}, [5],
1068
997
{1: [], 2: [3, 4], 3: [5], 4: [5], 5: []})
1069
998
# Prune a partial chain
1070
self.assertPruneTails({1: [6], 6: []}, [5],
999
self.assertPruneTails({1: [6], 6:[]}, [5],
1071
1000
{1: [2, 6], 2: [3, 4], 3: [5], 4: [5], 5: [],
1073
1002
# Prune a chain with multiple tips, that pulls out intermediates
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: []})
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:[]})
1079
1008
def test_subtract_plans(self):
1081
('unchanged', b'a\n'),
1083
('killed-a', b'c\n'),
1086
('killed-b', b'f\n'),
1087
('killed-b', b'g\n'),
1010
('unchanged', 'a\n'),
1012
('killed-a', 'c\n'),
1015
('killed-b', 'f\n'),
1016
('killed-b', 'g\n'),
1090
('unchanged', b'a\n'),
1092
('killed-a', b'c\n'),
1095
('killed-b', b'f\n'),
1096
('killed-b', b'i\n'),
1019
('unchanged', 'a\n'),
1021
('killed-a', 'c\n'),
1024
('killed-b', 'f\n'),
1025
('killed-b', 'i\n'),
1098
1027
subtracted_plan = [
1099
('unchanged', b'a\n'),
1101
('killed-a', b'c\n'),
1103
('unchanged', b'f\n'),
1104
('killed-b', b'i\n'),
1028
('unchanged', 'a\n'),
1030
('killed-a', 'c\n'),
1032
('unchanged', 'f\n'),
1033
('killed-b', 'i\n'),
1106
1035
self.assertEqual(subtracted_plan,
1107
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1036
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1109
1038
def setup_merge_with_base(self):
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')
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')
1115
1044
def test_plan_merge_with_base(self):
1116
1045
self.setup_merge_with_base()
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'),
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'),
1125
1054
def test_plan_lca_merge(self):
1126
1055
self.setup_plan_merge()
1127
plan = self.plan_merge_vf.plan_lca_merge(b'B', b'C')
1056
plan = self.plan_merge_vf.plan_lca_merge('B', 'C')
1128
1057
self.assertEqual([
1130
('unchanged', b'a\n'),
1131
('killed-b', b'c\n'),
1134
('killed-a', b'b\n'),
1135
('unchanged', b'g\n')],
1059
('unchanged', 'a\n'),
1060
('killed-b', 'c\n'),
1063
('killed-a', 'b\n'),
1064
('unchanged', 'g\n')],
1138
1067
def test_plan_lca_merge_uncommitted_files(self):
1139
1068
self.setup_plan_merge_uncommitted()
1140
plan = self.plan_merge_vf.plan_lca_merge(b'B:', b'C:')
1069
plan = self.plan_merge_vf.plan_lca_merge('B:', 'C:')
1141
1070
self.assertEqual([
1143
('unchanged', b'a\n'),
1144
('killed-b', b'c\n'),
1147
('killed-a', b'b\n'),
1148
('unchanged', b'g\n')],
1072
('unchanged', 'a\n'),
1073
('killed-b', 'c\n'),
1076
('killed-a', 'b\n'),
1077
('unchanged', 'g\n')],
1151
1080
def test_plan_lca_merge_with_base(self):
1152
1081
self.setup_merge_with_base()
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'),
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'),
1161
1090
def test_plan_lca_merge_with_criss_cross(self):
1162
self.add_version((b'root', b'ROOT'), [], b'abc')
1091
self.add_version(('root', 'ROOT'), [], 'abc')
1163
1092
# each side makes a change
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')
1093
self.add_version(('root', 'REV1'), [('root', 'ROOT')], 'abcd')
1094
self.add_version(('root', 'REV2'), [('root', 'ROOT')], 'abce')
1166
1095
# both sides merge, discarding others' changes
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'),
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'),
1180
1109
def test_plan_lca_merge_with_null(self):
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'),
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'),
1189
1118
def test_plan_merge_with_delete_and_change(self):
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'),
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'),
1198
1127
def test_plan_merge_with_move_and_change(self):
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'),
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'),
1212
1141
class LoggingMerger(object):
1331
1262
builder = self.setup_criss_cross_graph()
1332
builder.build_snapshot([b'A-id'], [], revision_id=b'F-id')
1333
builder.build_snapshot([b'E-id', b'F-id'], [], revision_id=b'H-id')
1334
builder.build_snapshot([b'D-id', b'F-id'], [], revision_id=b'G-id')
1335
merger = self.make_Merger(builder, b'H-id')
1336
self.assertEqual([b'B-id', b'C-id', b'F-id'],
1263
builder.build_snapshot('F-id', ['A-id'], [])
1264
builder.build_snapshot('H-id', ['E-id', 'F-id'], [])
1265
builder.build_snapshot('G-id', ['D-id', 'F-id'], [])
1266
merger = self.make_Merger(builder, 'H-id')
1267
self.assertEqual(['B-id', 'C-id', 'F-id'],
1337
1268
[t.get_revision_id() for t in merger._lca_trees])
1339
def test_find_base_new_root_criss_cross(self):
1345
builder = self.get_builder()
1346
builder.build_snapshot(None,
1347
[('add', ('', None, 'directory', None))],
1348
revision_id=b'A-id')
1349
builder.build_snapshot([],
1350
[('add', ('', None, 'directory', None))],
1351
revision_id=b'B-id')
1352
builder.build_snapshot([b'A-id', b'B-id'], [], revision_id=b'D-id')
1353
builder.build_snapshot([b'A-id', b'B-id'], [], revision_id=b'C-id')
1354
merger = self.make_Merger(builder, b'D-id')
1355
self.assertEqual(b'A-id', merger.base_rev_id)
1356
self.assertTrue(merger._is_criss_cross)
1357
self.assertEqual([b'A-id', b'B-id'], [t.get_revision_id()
1358
for t in merger._lca_trees])
1360
1270
def test_no_criss_cross_passed_to_merge_type(self):
1361
1271
class LCATreesMerger(LoggingMerger):
1362
1272
supports_lca_trees = True
1364
merger = self.make_Merger(self.setup_simple_graph(), b'C-id')
1274
merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1365
1275
merger.merge_type = LCATreesMerger
1366
1276
merge_obj = merger.make_merger()
1367
1277
self.assertIsInstance(merge_obj, LCATreesMerger)
1368
1278
self.assertFalse('lca_trees' in merge_obj.kwargs)
1370
1280
def test_criss_cross_passed_to_merge_type(self):
1371
merger = self.make_Merger(self.setup_criss_cross_graph(), b'E-id')
1281
merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
1372
1282
merger.merge_type = _mod_merge.Merge3Merger
1373
1283
merge_obj = merger.make_merger()
1374
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1375
for t in merger._lca_trees])
1284
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1285
for t in merger._lca_trees])
1377
1287
def test_criss_cross_not_supported_merge_type(self):
1378
merger = self.make_Merger(self.setup_criss_cross_graph(), b'E-id')
1288
merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
1379
1289
# We explicitly do not define supports_lca_trees
1380
1290
merger.merge_type = LoggingMerger
1381
1291
merge_obj = merger.make_merger()
1396
1306
class TestMergerEntriesLCA(TestMergerBase):
1398
1308
def make_merge_obj(self, builder, other_revision_id,
1399
interesting_files=None):
1309
interesting_files=None, interesting_ids=None):
1400
1310
merger = self.make_Merger(builder, other_revision_id,
1401
interesting_files=interesting_files)
1311
interesting_files=interesting_files,
1312
interesting_ids=interesting_ids)
1402
1313
return merger.make_merger()
1404
1315
def test_simple(self):
1405
1316
builder = self.get_builder()
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')
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')
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())
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())
1427
1333
entries = list(merge_obj._entries_lca())
1429
1335
# (file_id, changed, parents, names, executable)
1430
1336
# BASE, lca1, lca2, OTHER, THIS
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'),
1337
root_id = 'a-root-id'
1338
self.assertEqual([('a-id', True,
1434
1339
((root_id, [root_id, root_id]), root_id, root_id),
1435
1340
((u'a', [u'a', u'a']), u'a', u'a'),
1436
1341
((False, [False, False]), False, False)),
1439
1344
def test_not_in_base(self):
1440
1345
# LCAs all have the same last-modified revision for the file, as do
1449
1354
# G modifies 'bar'
1451
1356
builder = self.get_builder()
1452
builder.build_snapshot(None,
1453
[('add', (u'', b'a-root-id', 'directory', None))],
1454
revision_id=b'A-id')
1455
builder.build_snapshot([b'A-id'],
1456
[('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
1457
revision_id=b'B-id')
1458
builder.build_snapshot([b'A-id'],
1459
[('add', (u'bar', b'bar-id', 'file', b'd\ne\nf\n'))],
1460
revision_id=b'C-id')
1461
builder.build_snapshot([b'B-id', b'C-id'],
1462
[('add', (u'bar', b'bar-id', 'file', b'd\ne\nf\n'))],
1463
revision_id=b'D-id')
1464
builder.build_snapshot([b'C-id', b'B-id'],
1465
[('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
1466
revision_id=b'E-id')
1467
builder.build_snapshot([b'E-id', b'D-id'],
1468
[('modify', (u'bar', b'd\ne\nf\nG\n'))],
1469
revision_id=b'G-id')
1470
builder.build_snapshot([b'D-id', b'E-id'], [], revision_id=b'F-id')
1471
merge_obj = self.make_merge_obj(builder, b'G-id')
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')
1473
self.assertEqual([b'D-id', b'E-id'], [t.get_revision_id()
1474
for t in merge_obj._lca_trees])
1475
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_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())
1476
1375
entries = list(merge_obj._entries_lca())
1477
root_id = b'a-root-id'
1478
self.assertEqual([(b'bar-id', True,
1479
((None, [u'bar', u'bar']), u'bar', u'bar'),
1376
root_id = 'a-root-id'
1377
self.assertEqual([('bar-id', True,
1480
1378
((None, [root_id, root_id]), root_id, root_id),
1481
1379
((None, [u'bar', u'bar']), u'bar', u'bar'),
1482
1380
((None, [False, False]), False, False)),
1485
1383
def test_not_in_this(self):
1486
1384
builder = self.get_builder()
1487
builder.build_snapshot(None,
1488
[('add', (u'', b'a-root-id', 'directory', None)),
1489
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1490
revision_id=b'A-id')
1491
builder.build_snapshot([b'A-id'],
1492
[('modify', ('a', b'a\nB\nb\nc\n'))],
1493
revision_id=b'B-id')
1494
builder.build_snapshot([b'A-id'],
1495
[('modify', ('a', b'a\nb\nC\nc\n'))],
1496
revision_id=b'C-id')
1497
builder.build_snapshot([b'C-id', b'B-id'],
1498
[('modify', ('a', b'a\nB\nb\nC\nc\nE\n'))],
1499
revision_id=b'E-id')
1500
builder.build_snapshot([b'B-id', b'C-id'],
1501
[('unversion', 'a')],
1502
revision_id=b'D-id')
1503
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1505
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1506
for t in merge_obj._lca_trees])
1507
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_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())
1509
1402
entries = list(merge_obj._entries_lca())
1510
root_id = b'a-root-id'
1511
self.assertEqual([(b'a-id', True,
1512
((u'a', [u'a', u'a']), u'a', None),
1403
root_id = 'a-root-id'
1404
self.assertEqual([('a-id', True,
1513
1405
((root_id, [root_id, root_id]), root_id, None),
1514
1406
((u'a', [u'a', u'a']), u'a', None),
1515
1407
((False, [False, False]), False, None)),
1518
1410
def test_file_not_in_one_lca(self):
1519
1411
# A # just root
1523
1415
# D E # D and E both have the file, unchanged from C
1524
1416
builder = self.get_builder()
1525
builder.build_snapshot(None,
1526
[('add', (u'', b'a-root-id', 'directory', None))],
1527
revision_id=b'A-id')
1528
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1529
builder.build_snapshot([b'A-id'],
1530
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1531
revision_id=b'C-id')
1532
builder.build_snapshot([b'C-id', b'B-id'],
1533
[], revision_id=b'E-id') # Inherited from C
1534
builder.build_snapshot([b'B-id', b'C-id'], # Merged from C
1535
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1536
revision_id=b'D-id')
1537
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1539
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1540
for t in merge_obj._lca_trees])
1541
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_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())
1543
1431
entries = list(merge_obj._entries_lca())
1544
1432
self.assertEqual([], entries)
1546
1434
def test_not_in_other(self):
1547
1435
builder = self.get_builder()
1548
builder.build_snapshot(None,
1549
[('add', (u'', b'a-root-id', 'directory', None)),
1550
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1551
revision_id=b'A-id')
1552
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1553
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1554
builder.build_snapshot(
1556
[('unversion', 'a')], revision_id=b'E-id')
1557
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1558
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1560
1446
entries = list(merge_obj._entries_lca())
1561
root_id = b'a-root-id'
1562
self.assertEqual([(b'a-id', True,
1563
((u'a', [u'a', u'a']), None, u'a'),
1447
root_id = 'a-root-id'
1448
self.assertEqual([('a-id', True,
1564
1449
((root_id, [root_id, root_id]), None, root_id),
1565
1450
((u'a', [u'a', u'a']), None, u'a'),
1566
1451
((False, [False, False]), None, False)),
1569
1454
def test_not_in_other_or_lca(self):
1570
1455
# A base, introduces 'foo'
1649
1529
# A => C, add file, thus C supersedes B
1650
1530
# w/ C=BASE, D=THIS, E=OTHER we have 'happy convergence'
1651
1531
builder = self.get_builder()
1652
builder.build_snapshot(None,
1653
[('add', (u'', b'a-root-id', 'directory', None))],
1654
revision_id=b'A-id')
1655
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1656
builder.build_snapshot([b'A-id'],
1657
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1658
revision_id=b'C-id')
1659
builder.build_snapshot([b'C-id', b'B-id'],
1660
[('unversion', 'a')],
1661
revision_id=b'E-id')
1662
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1663
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1665
1542
entries = list(merge_obj._entries_lca())
1666
1543
self.assertEqual([], entries)
1668
1545
def test_only_in_other(self):
1669
1546
builder = self.get_builder()
1670
builder.build_snapshot(None,
1671
[('add', (u'', b'a-root-id', 'directory', None))],
1672
revision_id=b'A-id')
1673
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1674
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1675
builder.build_snapshot([b'C-id', b'B-id'],
1676
[('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1677
revision_id=b'E-id')
1678
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1679
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1681
1556
entries = list(merge_obj._entries_lca())
1682
root_id = b'a-root-id'
1683
self.assertEqual([(b'a-id', True,
1684
((None, [None, None]), u'a', None),
1557
root_id = 'a-root-id'
1558
self.assertEqual([('a-id', True,
1685
1559
((None, [None, None]), root_id, None),
1686
1560
((None, [None, None]), u'a', None),
1687
1561
((None, [None, None]), False, None)),
1690
1564
def test_one_lca_supersedes(self):
1691
1565
# One LCA supersedes the other LCAs last modified value, but the
1790
1655
# be pruned from the LCAs, even though it was newly introduced by E
1791
1656
# (superseding B).
1792
1657
builder = self.get_builder()
1793
builder.build_snapshot(None,
1794
[('add', (u'', b'a-root-id', 'directory', None)),
1795
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1796
revision_id=b'A-id')
1797
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1798
builder.build_snapshot([b'A-id'],
1799
[('rename', ('foo', 'bar'))],
1800
revision_id=b'B-id')
1801
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1802
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1803
builder.build_snapshot([b'E-id', b'D-id'],
1804
[('rename', ('foo', 'bar'))],
1805
revision_id=b'G-id')
1806
builder.build_snapshot([b'D-id', b'E-id'],
1807
[('rename', ('bar', 'bing'))],
1808
revision_id=b'F-id') # should end up conflicting
1809
merge_obj = self.make_merge_obj(builder, b'G-id')
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')
1811
1672
entries = list(merge_obj._entries_lca())
1812
root_id = b'a-root-id'
1673
root_id = 'a-root-id'
1813
1674
self.expectFailure("We prune values from BASE even when relevant.",
1816
((root_id, [root_id, root_id]), root_id, root_id),
1817
((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
1818
((False, [False, False]), False, False)),
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)),
1821
1682
def test_both_sides_revert(self):
1822
1683
# Both sides of a criss-cross revert the text to the lca
1903
1755
# We need to conflict.
1905
1757
builder = self.get_builder()
1906
builder.build_snapshot(None,
1907
[('add', (u'', b'a-root-id', 'directory', None)),
1908
('add', (u'foo', b'foo-id', 'file', b'A content\n'))],
1909
revision_id=b'A-id')
1910
builder.build_snapshot([b'A-id'],
1911
[('modify', ('foo', b'B content\n'))],
1912
revision_id=b'B-id')
1913
builder.build_snapshot([b'A-id'],
1914
[('modify', ('foo', b'C content\n'))],
1915
revision_id=b'C-id')
1916
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1917
builder.build_snapshot([b'B-id', b'C-id'],
1918
[('modify', ('foo', b'C content\n'))],
1919
revision_id=b'D-id') # Same as E
1920
builder.build_snapshot([b'D-id'],
1921
[('modify', ('foo', b'F content\n'))],
1922
revision_id=b'F-id')
1923
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1925
1772
entries = list(merge_obj._entries_lca())
1926
1773
self.expectFailure("We don't detect that LCA resolution was the"
1927
1774
" same on both sides",
1928
self.assertEqual, [], entries)
1775
self.assertEqual, [], entries)
1930
1777
def test_only_path_changed(self):
1931
1778
builder = self.get_builder()
1932
builder.build_snapshot(None,
1933
[('add', (u'', b'a-root-id', 'directory', None)),
1934
('add', (u'a', b'a-id', 'file', b'content\n'))],
1935
revision_id=b'A-id')
1936
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1937
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1938
builder.build_snapshot([b'C-id', b'B-id'],
1939
[('rename', (u'a', u'b'))],
1940
revision_id=b'E-id')
1941
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1942
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1943
1788
entries = list(merge_obj._entries_lca())
1944
root_id = b'a-root-id'
1789
root_id = 'a-root-id'
1945
1790
# The content was not changed, only the path
1946
self.assertEqual([(b'a-id', False,
1947
((u'a', [u'a', u'a']), u'b', u'a'),
1791
self.assertEqual([('a-id', False,
1948
1792
((root_id, [root_id, root_id]), root_id, root_id),
1949
1793
((u'a', [u'a', u'a']), u'b', u'a'),
1950
1794
((False, [False, False]), False, False)),
1953
1797
def test_kind_changed(self):
1954
1798
# Identical content, except 'D' changes a-id into a directory
1955
1799
builder = self.get_builder()
1956
builder.build_snapshot(None,
1957
[('add', (u'', b'a-root-id', 'directory', None)),
1958
('add', (u'a', b'a-id', 'file', b'content\n'))],
1959
revision_id=b'A-id')
1960
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1961
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1962
builder.build_snapshot([b'C-id', b'B-id'],
1963
[('unversion', 'a'),
1965
('add', (u'a', b'a-id', 'directory', None))],
1966
revision_id=b'E-id')
1967
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
1968
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1969
1810
entries = list(merge_obj._entries_lca())
1970
root_id = b'a-root-id'
1811
root_id = 'a-root-id'
1971
1812
# Only the kind was changed (content)
1972
self.assertEqual([(b'a-id', True,
1973
((u'a', [u'a', u'a']), u'a', u'a'),
1813
self.assertEqual([('a-id', True,
1974
1814
((root_id, [root_id, root_id]), root_id, root_id),
1975
1815
((u'a', [u'a', u'a']), u'a', u'a'),
1976
1816
((False, [False, False]), False, False)),
1979
1819
def test_this_changed_kind(self):
1980
1820
# Identical content, but THIS changes a file to a directory
1981
1821
builder = self.get_builder()
1982
builder.build_snapshot(None,
1983
[('add', (u'', b'a-root-id', 'directory', None)),
1984
('add', (u'a', b'a-id', 'file', b'content\n'))],
1985
revision_id=b'A-id')
1986
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
1987
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
1988
builder.build_snapshot([b'C-id', b'B-id'], [], revision_id=b'E-id')
1989
builder.build_snapshot([b'B-id', b'C-id'],
1990
[('unversion', 'a'),
1992
('add', (u'a', b'a-id', 'directory', None))],
1993
revision_id=b'D-id')
1994
merge_obj = self.make_merge_obj(builder, b'E-id')
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')
1995
1832
entries = list(merge_obj._entries_lca())
1996
1833
# Only the kind was changed (content)
1997
1834
self.assertEqual([], entries)
1999
1836
def test_interesting_files(self):
2000
1837
# Two files modified, but we should filter one of them
2001
1838
builder = self.get_builder()
2002
builder.build_snapshot(None,
2003
[('add', (u'', b'a-root-id', 'directory', None)),
2004
('add', (u'a', b'a-id', 'file', b'content\n')),
2005
('add', (u'b', b'b-id', 'file', b'content\n'))],
2006
revision_id=b'A-id')
2007
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2008
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2009
builder.build_snapshot([b'C-id', b'B-id'],
2010
[('modify', ('a', b'new-content\n')),
2011
('modify', ('b', b'new-content\n'))],
2012
revision_id=b'E-id')
2013
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2014
merge_obj = self.make_merge_obj(builder, b'E-id',
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',
2015
1850
interesting_files=['b'])
2016
1851
entries = list(merge_obj._entries_lca())
2017
root_id = b'a-root-id'
2018
self.assertEqual([(b'b-id', True,
2019
((u'b', [u'b', u'b']), u'b', u'b'),
1852
root_id = 'a-root-id'
1853
self.assertEqual([('b-id', True,
2020
1854
((root_id, [root_id, root_id]), root_id, root_id),
2021
1855
((u'b', [u'b', u'b']), u'b', u'b'),
2022
1856
((False, [False, False]), False, False)),
2025
1859
def test_interesting_file_in_this(self):
2026
1860
# This renamed the file, but it should still match the entry in other
2027
1861
builder = self.get_builder()
2028
builder.build_snapshot(None,
2029
[('add', (u'', b'a-root-id', 'directory', None)),
2030
('add', (u'a', b'a-id', 'file', b'content\n')),
2031
('add', (u'b', b'b-id', 'file', b'content\n'))],
2032
revision_id=b'A-id')
2033
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2034
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2035
builder.build_snapshot([b'C-id', b'B-id'],
2036
[('modify', ('a', b'new-content\n')),
2037
('modify', ('b', b'new-content\n'))],
2038
revision_id=b'E-id')
2039
builder.build_snapshot([b'B-id', b'C-id'],
2040
[('rename', ('b', 'c'))],
2041
revision_id=b'D-id')
2042
merge_obj = self.make_merge_obj(builder, b'E-id',
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',
2043
1874
interesting_files=['c'])
2044
1875
entries = list(merge_obj._entries_lca())
2045
root_id = b'a-root-id'
2046
self.assertEqual([(b'b-id', True,
2047
((u'b', [u'b', u'b']), u'b', u'c'),
1876
root_id = 'a-root-id'
1877
self.assertEqual([('b-id', True,
2048
1878
((root_id, [root_id, root_id]), root_id, root_id),
2049
1879
((u'b', [u'b', u'b']), u'b', u'c'),
2050
1880
((False, [False, False]), False, False)),
2053
1883
def test_interesting_file_in_base(self):
2054
1884
# This renamed the file, but it should still match the entry in BASE
2055
1885
builder = self.get_builder()
2056
builder.build_snapshot(None,
2057
[('add', (u'', b'a-root-id', 'directory', None)),
2058
('add', (u'a', b'a-id', 'file', b'content\n')),
2059
('add', (u'c', b'c-id', 'file', b'content\n'))],
2060
revision_id=b'A-id')
2061
builder.build_snapshot([b'A-id'],
2062
[('rename', ('c', 'b'))],
2063
revision_id=b'B-id')
2064
builder.build_snapshot([b'A-id'],
2065
[('rename', ('c', 'b'))],
2066
revision_id=b'C-id')
2067
builder.build_snapshot([b'C-id', b'B-id'],
2068
[('modify', ('a', b'new-content\n')),
2069
('modify', ('b', b'new-content\n'))],
2070
revision_id=b'E-id')
2071
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2072
merge_obj = self.make_merge_obj(builder, b'E-id',
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',
2073
1899
interesting_files=['c'])
2074
1900
entries = list(merge_obj._entries_lca())
2075
root_id = b'a-root-id'
2076
self.assertEqual([(b'c-id', True,
2077
((u'c', [u'b', u'b']), u'b', u'b'),
1901
root_id = 'a-root-id'
1902
self.assertEqual([('c-id', True,
2078
1903
((root_id, [root_id, root_id]), root_id, root_id),
2079
1904
((u'c', [u'b', u'b']), u'b', u'b'),
2080
1905
((False, [False, False]), False, False)),
2083
1908
def test_interesting_file_in_lca(self):
2084
1909
# This renamed the file, but it should still match the entry in LCA
2085
1910
builder = self.get_builder()
2086
builder.build_snapshot(None,
2087
[('add', (u'', b'a-root-id', 'directory', None)),
2088
('add', (u'a', b'a-id', 'file', b'content\n')),
2089
('add', (u'b', b'b-id', 'file', b'content\n'))],
2090
revision_id=b'A-id')
2091
builder.build_snapshot([b'A-id'],
2092
[('rename', ('b', 'c'))], revision_id=b'B-id')
2093
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2094
builder.build_snapshot([b'C-id', b'B-id'],
2095
[('modify', ('a', b'new-content\n')),
2096
('modify', ('b', b'new-content\n'))],
2097
revision_id=b'E-id')
2098
builder.build_snapshot([b'B-id', b'C-id'],
2099
[('rename', ('c', 'b'))], revision_id=b'D-id')
2100
merge_obj = self.make_merge_obj(builder, b'E-id',
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',
2101
1924
interesting_files=['c'])
2102
1925
entries = list(merge_obj._entries_lca())
2103
root_id = b'a-root-id'
2104
self.assertEqual([(b'b-id', True,
2105
((u'b', [u'c', u'b']), u'b', u'b'),
1926
root_id = 'a-root-id'
1927
self.assertEqual([('b-id', True,
2106
1928
((root_id, [root_id, root_id]), root_id, root_id),
2107
1929
((u'b', [u'c', u'b']), u'b', u'b'),
2108
1930
((False, [False, False]), False, False)),
2111
def test_interesting_files(self):
1933
def test_interesting_ids(self):
2112
1934
# Two files modified, but we should filter one of them
2113
1935
builder = self.get_builder()
2114
builder.build_snapshot(None,
2115
[('add', (u'', b'a-root-id', 'directory', None)),
2116
('add', (u'a', b'a-id', 'file', b'content\n')),
2117
('add', (u'b', b'b-id', 'file', b'content\n'))],
2118
revision_id=b'A-id')
2119
builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
2120
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2121
builder.build_snapshot([b'C-id', b'B-id'],
2122
[('modify', ('a', b'new-content\n')),
2123
('modify', ('b', b'new-content\n'))], revision_id=b'E-id')
2124
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2125
merge_obj = self.make_merge_obj(builder, b'E-id',
2126
interesting_files=['b'])
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'])
2127
1948
entries = list(merge_obj._entries_lca())
2128
root_id = b'a-root-id'
2129
self.assertEqual([(b'b-id', True,
2130
((u'b', [u'b', u'b']), u'b', u'b'),
1949
root_id = 'a-root-id'
1950
self.assertEqual([('b-id', True,
2131
1951
((root_id, [root_id, root_id]), root_id, root_id),
2132
1952
((u'b', [u'b', u'b']), u'b', u'b'),
2133
1953
((False, [False, False]), False, False)),
2137
1958
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
2409
2223
wt.lock_write()
2410
2224
self.addCleanup(wt.unlock)
2411
2225
os.symlink('bar', 'path/foo')
2412
wt.add(['foo'], [b'foo-id'])
2413
wt.commit('A add symlink', rev_id=b'A-id')
2226
wt.add(['foo'], ['foo-id'])
2227
wt.commit('A add symlink', rev_id='A-id')
2414
2228
wt.rename_one('foo', 'barry')
2415
wt.commit('B foo => barry', rev_id=b'B-id')
2416
wt.set_last_revision(b'A-id')
2417
wt.branch.set_last_revision_info(1, b'A-id')
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')
2419
wt.commit('C', rev_id=b'C-id')
2420
wt.merge_from_branch(wt.branch, b'B-id')
2421
self.assertEqual('barry', wt.id2path(b'foo-id'))
2422
self.assertEqual('bar', wt.get_symlink_target('barry'))
2423
wt.commit('E merges C & B', rev_id=b'E-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')
2424
2238
wt.rename_one('barry', 'blah')
2425
wt.commit('F barry => blah', rev_id=b'F-id')
2426
wt.set_last_revision(b'B-id')
2427
wt.branch.set_last_revision_info(2, b'B-id')
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')
2429
wt.merge_from_branch(wt.branch, b'C-id')
2430
wt.commit('D merges B & C', rev_id=b'D-id')
2431
self.assertEqual('barry', wt.id2path(b'foo-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'))
2432
2246
# Check the output of the Merger object directly
2433
merger = _mod_merge.Merger.from_revision_ids(wt, b'F-id')
2247
merger = _mod_merge.Merger.from_revision_ids(None,
2434
2249
merger.merge_type = _mod_merge.Merge3Merger
2435
2250
merge_obj = merger.make_merger()
2436
2251
root_id = wt.path2id('')
2437
2252
entries = list(merge_obj._entries_lca())
2438
2253
# No content change, just a path change
2439
self.assertEqual([(b'foo-id', False,
2440
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2254
self.assertEqual([('foo-id', False,
2441
2255
((root_id, [root_id, root_id]), root_id, root_id),
2442
2256
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2443
2257
((False, [False, False]), False, False)),
2445
conflicts = wt.merge_from_branch(wt.branch, to_revision=b'F-id')
2259
conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
2446
2260
self.assertEqual(0, conflicts)
2447
self.assertEqual('blah', wt.id2path(b'foo-id'))
2261
self.assertEqual('blah', wt.id2path('foo-id'))
2449
2263
def test_symlink_no_content_change(self):
2450
self.requireFeature(features.SymlinkFeature)
2264
self.requireFeature(tests.SymlinkFeature)
2451
2265
# A Create symlink foo => bar
2453
2267
# B C B relinks foo => baz
2511
2326
wt = self.make_branch_and_tree('path')
2512
2327
wt.lock_write()
2513
2328
self.addCleanup(wt.unlock)
2514
wt.commit('base', rev_id=b'A-id')
2329
wt.commit('base', rev_id='A-id')
2515
2330
os.symlink('bar', 'path/foo')
2516
wt.add(['foo'], [b'foo-id'])
2517
wt.commit('add symlink foo => bar', rev_id=b'B-id')
2518
wt.set_last_revision(b'A-id')
2519
wt.branch.set_last_revision_info(1, b'A-id')
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')
2521
wt.commit('C', rev_id=b'C-id')
2522
wt.merge_from_branch(wt.branch, b'B-id')
2523
self.assertEqual('bar', wt.get_symlink_target('foo'))
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'))
2524
2339
os.remove('path/foo')
2525
2340
# We have to change the link in E, or it won't try to do a comparison
2526
2341
os.symlink('bing', 'path/foo')
2527
wt.commit('E merges C & B, overrides to bing', rev_id=b'E-id')
2528
wt.set_last_revision(b'B-id')
2529
wt.branch.set_last_revision_info(2, b'B-id')
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')
2531
wt.merge_from_branch(wt.branch, b'C-id')
2346
wt.merge_from_branch(wt.branch, 'C-id')
2532
2347
os.remove('path/foo')
2533
self.build_tree_contents([('path/foo', b'file content\n')])
2348
self.build_tree_contents([('path/foo', 'file content\n')])
2534
2349
# XXX: workaround, WT doesn't detect kind changes unless you do
2535
2350
# iter_changes()
2536
2351
list(wt.iter_changes(wt.basis_tree()))
2537
wt.commit('D merges B & C, makes it a file', rev_id=b'D-id')
2352
wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2539
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2354
merger = _mod_merge.Merger.from_revision_ids(None,
2540
2356
merger.merge_type = _mod_merge.Merge3Merger
2541
2357
merge_obj = merger.make_merger()
2542
2358
entries = list(merge_obj._entries_lca())
2543
2359
root_id = wt.path2id('')
2544
self.assertEqual([(b'foo-id', True,
2545
((None, [u'foo', None]), u'foo', u'foo'),
2360
self.assertEqual([('foo-id', True,
2546
2361
((None, [root_id, None]), root_id, root_id),
2547
2362
((None, [u'foo', None]), u'foo', u'foo'),
2548
2363
((None, [False, None]), False, False)),
2551
2366
def test_symlink_all_wt(self):
2552
2367
"""Check behavior if all trees are Working Trees."""
2553
self.requireFeature(features.SymlinkFeature)
2368
self.requireFeature(tests.SymlinkFeature)
2554
2369
# The big issue is that entry.symlink_target is None for WorkingTrees.
2555
2370
# So we need to make sure we handle that case correctly.
2567
2382
wt.lock_write()
2568
2383
self.addCleanup(wt.unlock)
2569
2384
os.symlink('bar', 'path/foo')
2570
wt.add(['foo'], [b'foo-id'])
2571
wt.commit('add symlink', rev_id=b'A-id')
2385
wt.add(['foo'], ['foo-id'])
2386
wt.commit('add symlink', rev_id='A-id')
2572
2387
os.remove('path/foo')
2573
2388
os.symlink('baz', 'path/foo')
2574
wt.commit('foo => baz', rev_id=b'B-id')
2575
wt.set_last_revision(b'A-id')
2576
wt.branch.set_last_revision_info(1, b'A-id')
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')
2578
wt.commit('C', rev_id=b'C-id')
2579
wt.merge_from_branch(wt.branch, b'B-id')
2580
self.assertEqual('baz', wt.get_symlink_target('foo'))
2581
wt.commit('E merges C & B', rev_id=b'E-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')
2582
2397
os.remove('path/foo')
2583
2398
os.symlink('bing', 'path/foo')
2584
wt.commit('F foo => bing', rev_id=b'F-id')
2585
wt.set_last_revision(b'B-id')
2586
wt.branch.set_last_revision_info(2, b'B-id')
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')
2588
wt.merge_from_branch(wt.branch, b'C-id')
2589
wt.commit('D merges B & C', rev_id=b'D-id')
2590
wt_base = wt.controldir.sprout('base', b'A-id').open_workingtree()
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()
2591
2406
wt_base.lock_read()
2592
2407
self.addCleanup(wt_base.unlock)
2593
wt_lca1 = wt.controldir.sprout('b-tree', b'B-id').open_workingtree()
2408
wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2594
2409
wt_lca1.lock_read()
2595
2410
self.addCleanup(wt_lca1.unlock)
2596
wt_lca2 = wt.controldir.sprout('c-tree', b'C-id').open_workingtree()
2411
wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2597
2412
wt_lca2.lock_read()
2598
2413
self.addCleanup(wt_lca2.unlock)
2599
wt_other = wt.controldir.sprout('other', b'F-id').open_workingtree()
2414
wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
2600
2415
wt_other.lock_read()
2601
2416
self.addCleanup(wt_other.unlock)
2602
2417
merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
2603
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2418
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2604
2419
entries = list(merge_obj._entries_lca())
2605
2420
root_id = wt.path2id('')
2606
self.assertEqual([(b'foo-id', True,
2607
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2421
self.assertEqual([('foo-id', True,
2608
2422
((root_id, [root_id, root_id]), root_id, root_id),
2609
2423
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2610
2424
((False, [False, False]), False, False)),
2613
2427
def test_other_reverted_path_to_base(self):
2614
2428
# A Path at 'foo'
2622
2436
# F Path at 'foo'
2623
2437
builder = self.get_builder()
2624
builder.build_snapshot(None,
2625
[('add', (u'', b'a-root-id', 'directory', None)),
2626
('add', (u'foo', b'foo-id', 'file', b'a\nb\nc\n'))],
2627
revision_id=b'A-id')
2628
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2629
builder.build_snapshot([b'A-id'],
2630
[('rename', ('foo', 'bar'))], revision_id=b'B-id')
2631
builder.build_snapshot([b'C-id', b'B-id'],
2632
[('rename', ('foo', 'bar'))], revision_id=b'E-id') # merge the rename
2633
builder.build_snapshot([b'E-id'],
2634
[('rename', ('bar', 'foo'))], revision_id=b'F-id') # Rename back to BASE
2635
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2636
wt, conflicts = self.do_merge(builder, b'F-id')
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')
2637
2450
self.assertEqual(0, conflicts)
2638
self.assertEqual('foo', wt.id2path(b'foo-id'))
2451
self.assertEqual('foo', wt.id2path('foo-id'))
2640
2453
def test_other_reverted_content_to_base(self):
2641
2454
builder = self.get_builder()
2642
builder.build_snapshot(None,
2643
[('add', (u'', b'a-root-id', 'directory', None)),
2644
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2645
revision_id=b'A-id')
2646
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2647
builder.build_snapshot([b'A-id'],
2648
[('modify', ('foo', b'B content\n'))],
2649
revision_id=b'B-id')
2650
builder.build_snapshot([b'C-id', b'B-id'],
2651
[('modify', ('foo', b'B content\n'))],
2652
revision_id=b'E-id') # merge the content
2653
builder.build_snapshot([b'E-id'],
2654
[('modify', ('foo', b'base content\n'))],
2655
revision_id=b'F-id') # Revert back to BASE
2656
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2657
wt, conflicts = self.do_merge(builder, b'F-id')
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')
2658
2467
self.assertEqual(0, conflicts)
2659
2468
# TODO: We need to use the per-file graph to properly select a BASE
2660
2469
# before this will work. Or at least use the LCA trees to find
2661
2470
# the appropriate content base. (which is B, not A).
2662
self.assertEqual(b'base content\n', wt.get_file_text('foo'))
2471
self.assertEqual('base content\n', wt.get_file_text('foo-id'))
2664
2473
def test_other_modified_content(self):
2665
2474
builder = self.get_builder()
2666
builder.build_snapshot(None,
2667
[('add', (u'', b'a-root-id', 'directory', None)),
2668
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2669
revision_id=b'A-id')
2670
builder.build_snapshot([b'A-id'], [], revision_id=b'C-id')
2671
builder.build_snapshot([b'A-id'],
2672
[('modify', ('foo', b'B content\n'))],
2673
revision_id=b'B-id')
2674
builder.build_snapshot([b'C-id', b'B-id'],
2675
[('modify', ('foo', b'B content\n'))],
2676
revision_id=b'E-id') # merge the content
2677
builder.build_snapshot([b'E-id'],
2678
[('modify', ('foo', b'F content\n'))],
2679
revision_id=b'F-id') # Override B content
2680
builder.build_snapshot([b'B-id', b'C-id'], [], revision_id=b'D-id')
2681
wt, conflicts = self.do_merge(builder, b'F-id')
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')
2682
2487
self.assertEqual(0, conflicts)
2683
self.assertEqual(b'F content\n', wt.get_file_text('foo'))
2488
self.assertEqual('F content\n', wt.get_file_text('foo-id'))
2685
2490
def test_all_wt(self):
2686
2491
"""Check behavior if all trees are Working Trees."""
2694
2499
# D E E updates content, renames 'b' => 'c'
2695
2500
builder = self.get_builder()
2696
builder.build_snapshot(None,
2697
[('add', (u'', b'a-root-id', 'directory', None)),
2698
('add', (u'a', b'a-id', 'file', b'base content\n')),
2699
('add', (u'foo', b'foo-id', 'file', b'base content\n'))],
2700
revision_id=b'A-id')
2701
builder.build_snapshot([b'A-id'],
2702
[('modify', ('foo', b'B content\n'))],
2703
revision_id=b'B-id')
2704
builder.build_snapshot([b'A-id'],
2705
[('rename', ('a', 'b'))],
2706
revision_id=b'C-id')
2707
builder.build_snapshot([b'C-id', b'B-id'],
2708
[('rename', ('b', 'c')),
2709
('modify', ('foo', b'E content\n'))],
2710
revision_id=b'E-id')
2711
builder.build_snapshot([b'B-id', b'C-id'],
2712
[('rename', ('a', 'b'))], revision_id=b'D-id') # merged change
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
2713
2514
wt_this = self.get_wt_from_builder(builder)
2714
wt_base = wt_this.controldir.sprout('base', b'A-id').open_workingtree()
2515
wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
2715
2516
wt_base.lock_read()
2716
2517
self.addCleanup(wt_base.unlock)
2717
wt_lca1 = wt_this.controldir.sprout(
2718
'b-tree', b'B-id').open_workingtree()
2518
wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2719
2519
wt_lca1.lock_read()
2720
2520
self.addCleanup(wt_lca1.unlock)
2721
wt_lca2 = wt_this.controldir.sprout(
2722
'c-tree', b'C-id').open_workingtree()
2521
wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2723
2522
wt_lca2.lock_read()
2724
2523
self.addCleanup(wt_lca2.unlock)
2725
wt_other = wt_this.controldir.sprout(
2726
'other', b'E-id').open_workingtree()
2524
wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
2727
2525
wt_other.lock_read()
2728
2526
self.addCleanup(wt_other.unlock)
2729
2527
merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
2730
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2528
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2731
2529
entries = list(merge_obj._entries_lca())
2732
root_id = b'a-root-id'
2733
self.assertEqual([(b'a-id', False,
2734
((u'a', [u'a', u'b']), u'c', u'b'),
2735
((root_id, [root_id, root_id]), root_id, root_id),
2736
((u'a', [u'a', u'b']), u'c', u'b'),
2737
((False, [False, False]), False, False)),
2739
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2740
((root_id, [root_id, root_id]), root_id, root_id),
2741
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2742
((False, [False, False]), False, False)),
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)),
2745
2541
def test_nested_tree_unmodified(self):
2746
2542
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2747
2543
# 'tree-reference'
2748
2544
wt = self.make_branch_and_tree('tree',
2749
format='development-subtree')
2545
format='dirstate-with-subtree')
2750
2546
wt.lock_write()
2751
2547
self.addCleanup(wt.unlock)
2752
2548
sub_tree = self.make_branch_and_tree('tree/sub-tree',
2753
format='development-subtree')
2754
wt.set_root_id(b'a-root-id')
2755
sub_tree.set_root_id(b'sub-tree-root')
2756
self.build_tree_contents([('tree/sub-tree/file', b'text1')])
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')])
2757
2553
sub_tree.add('file')
2758
sub_tree.commit('foo', rev_id=b'sub-A-id')
2554
sub_tree.commit('foo', rev_id='sub-A-id')
2759
2555
wt.add_reference(sub_tree)
2760
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2556
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2761
2557
# Now create a criss-cross merge in the parent, without modifying the
2763
wt.commit('B', rev_id=b'B-id', recursive=None)
2764
wt.set_last_revision(b'A-id')
2765
wt.branch.set_last_revision_info(1, b'A-id')
2766
wt.commit('C', rev_id=b'C-id', recursive=None)
2767
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2768
wt.commit('E', rev_id=b'E-id', recursive=None)
2769
wt.set_parent_ids([b'B-id', b'C-id'])
2770
wt.branch.set_last_revision_info(2, b'B-id')
2771
wt.commit('D', rev_id=b'D-id', recursive=None)
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)
2773
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2569
merger = _mod_merge.Merger.from_revision_ids(None,
2774
2571
merger.merge_type = _mod_merge.Merge3Merger
2775
2572
merge_obj = merger.make_merger()
2776
2573
entries = list(merge_obj._entries_lca())
2818
2616
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2819
2617
# 'tree-reference'
2820
2618
wt = self.make_branch_and_tree('tree',
2821
format='development-subtree')
2619
format='dirstate-with-subtree')
2822
2620
wt.lock_write()
2823
2621
self.addCleanup(wt.unlock)
2824
2622
sub_tree = self.make_branch_and_tree('tree/sub',
2825
format='development-subtree')
2826
wt.set_root_id(b'a-root-id')
2827
sub_tree.set_root_id(b'sub-tree-root')
2828
self.build_tree_contents([('tree/sub/file', b'text1')])
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')])
2829
2627
sub_tree.add('file')
2830
sub_tree.commit('foo', rev_id=b'sub-A-id')
2628
sub_tree.commit('foo', rev_id='sub-A-id')
2831
2629
wt.add_reference(sub_tree)
2832
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2630
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2833
2631
# Now create a criss-cross merge in the parent, without modifying the
2835
wt.commit('B', rev_id=b'B-id', recursive=None)
2836
wt.set_last_revision(b'A-id')
2837
wt.branch.set_last_revision_info(1, b'A-id')
2838
wt.commit('C', rev_id=b'C-id', recursive=None)
2839
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
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')
2840
2638
wt.rename_one('sub', 'alt_sub')
2841
wt.commit('E', rev_id=b'E-id', recursive=None)
2842
wt.set_last_revision(b'B-id')
2639
wt.commit('E', rev_id='E-id', recursive=None)
2640
wt.set_last_revision('B-id')
2844
wt.set_parent_ids([b'B-id', b'C-id'])
2845
wt.branch.set_last_revision_info(2, b'B-id')
2846
wt.commit('D', rev_id=b'D-id', recursive=None)
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)
2848
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2646
merger = _mod_merge.Merger.from_revision_ids(None,
2849
2648
merger.merge_type = _mod_merge.Merge3Merger
2850
2649
merge_obj = merger.make_merger()
2851
2650
entries = list(merge_obj._entries_lca())
2852
root_id = b'a-root-id'
2853
self.assertEqual([(b'sub-tree-root', False,
2854
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2651
root_id = 'a-root-id'
2652
self.assertEqual([('sub-tree-root', False,
2855
2653
((root_id, [root_id, root_id]), root_id, root_id),
2856
2654
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2857
2655
((False, [False, False]), False, False)),
2860
2658
def test_nested_tree_subtree_renamed_and_modified(self):
2861
2659
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2862
2660
# 'tree-reference'
2863
2661
wt = self.make_branch_and_tree('tree',
2864
format='development-subtree')
2662
format='dirstate-with-subtree')
2865
2663
wt.lock_write()
2866
2664
self.addCleanup(wt.unlock)
2867
2665
sub_tree = self.make_branch_and_tree('tree/sub',
2868
format='development-subtree')
2869
wt.set_root_id(b'a-root-id')
2870
sub_tree.set_root_id(b'sub-tree-root')
2871
self.build_tree_contents([('tree/sub/file', b'text1')])
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')])
2872
2670
sub_tree.add('file')
2873
sub_tree.commit('foo', rev_id=b'sub-A-id')
2671
sub_tree.commit('foo', rev_id='sub-A-id')
2874
2672
wt.add_reference(sub_tree)
2875
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2673
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2876
2674
# Now create a criss-cross merge in the parent, without modifying the
2878
wt.commit('B', rev_id=b'B-id', recursive=None)
2879
wt.set_last_revision(b'A-id')
2880
wt.branch.set_last_revision_info(1, b'A-id')
2881
wt.commit('C', rev_id=b'C-id', recursive=None)
2882
wt.merge_from_branch(wt.branch, to_revision=b'B-id')
2883
self.build_tree_contents([('tree/sub/file', b'text2')])
2884
sub_tree.commit('modify contents', rev_id=b'sub-B-id')
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')
2885
2683
wt.rename_one('sub', 'alt_sub')
2886
wt.commit('E', rev_id=b'E-id', recursive=None)
2887
wt.set_last_revision(b'B-id')
2684
wt.commit('E', rev_id='E-id', recursive=None)
2685
wt.set_last_revision('B-id')
2889
wt.set_parent_ids([b'B-id', b'C-id'])
2890
wt.branch.set_last_revision_info(2, b'B-id')
2891
wt.commit('D', rev_id=b'D-id', recursive=None)
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)
2893
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2691
merger = _mod_merge.Merger.from_revision_ids(None,
2894
2693
merger.merge_type = _mod_merge.Merge3Merger
2895
2694
merge_obj = merger.make_merger()
2896
2695
entries = list(merge_obj._entries_lca())
2897
root_id = b'a-root-id'
2898
self.assertEqual([(b'sub-tree-root', False,
2899
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2696
root_id = 'a-root-id'
2697
self.assertEqual([('sub-tree-root', False,
2900
2698
((root_id, [root_id, root_id]), root_id, root_id),
2901
2699
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2902
2700
((False, [False, False]), False, False)),
2906
2704
class TestLCAMultiWay(tests.TestCase):
2908
2706
def assertLCAMultiWay(self, expected, base, lcas, other, this,
2909
2707
allow_overriding_lca=True):
2910
2708
self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
2911
(base, lcas), other, this,
2912
allow_overriding_lca=allow_overriding_lca))
2709
(base, lcas), other, this,
2710
allow_overriding_lca=allow_overriding_lca))
2914
2712
def test_other_equal_equal_lcas(self):
2915
2713
"""Test when OTHER=LCA and all LCAs are identical."""
2916
2714
self.assertLCAMultiWay('this',
2917
'bval', ['bval', 'bval'], 'bval', 'bval')
2918
self.assertLCAMultiWay('this',
2919
'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
2920
self.assertLCAMultiWay('this',
2921
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
2922
self.assertLCAMultiWay('this',
2923
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
2924
self.assertLCAMultiWay('this',
2925
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
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)
2927
2725
def test_other_equal_this(self):
2928
2726
"""Test when other and this are identical."""
2929
2727
self.assertLCAMultiWay('this',
2930
'bval', ['bval', 'bval'], 'oval', 'oval')
2931
self.assertLCAMultiWay('this',
2932
'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
2933
self.assertLCAMultiWay('this',
2934
'bval', ['cval', 'dval'], 'oval', 'oval')
2935
self.assertLCAMultiWay('this',
2936
'bval', [None, 'lcaval'], 'oval', 'oval')
2937
self.assertLCAMultiWay('this',
2938
None, [None, 'lcaval'], 'oval', 'oval')
2939
self.assertLCAMultiWay('this',
2940
None, ['lcaval', 'lcaval'], 'oval', 'oval')
2941
self.assertLCAMultiWay('this',
2942
None, ['cval', 'dval'], 'oval', 'oval')
2943
self.assertLCAMultiWay('this',
2944
None, ['cval', 'dval'], None, None)
2945
self.assertLCAMultiWay('this',
2946
None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
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')
2948
2746
def test_no_lcas(self):
2949
2747
self.assertLCAMultiWay('this',
2950
'bval', [], 'bval', 'tval')
2748
'bval', [], 'bval', 'tval')
2951
2749
self.assertLCAMultiWay('other',
2952
'bval', [], 'oval', 'bval')
2750
'bval', [], 'oval', 'bval')
2953
2751
self.assertLCAMultiWay('conflict',
2954
'bval', [], 'oval', 'tval')
2752
'bval', [], 'oval', 'tval')
2955
2753
self.assertLCAMultiWay('this',
2956
'bval', [], 'oval', 'oval')
2754
'bval', [], 'oval', 'oval')
2958
2756
def test_lca_supersedes_other_lca(self):
2959
2757
"""If one lca == base, the other lca takes precedence"""
2960
2758
self.assertLCAMultiWay('this',
2961
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2759
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2962
2760
self.assertLCAMultiWay('this',
2963
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2761
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2964
2762
# This is actually considered a 'revert' because the 'lcaval' in LCAS
2965
2763
# supersedes the BASE val (in the other LCA) but then OTHER reverts it
2966
2764
# back to bval.
2967
2765
self.assertLCAMultiWay('other',
2968
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2766
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2969
2767
self.assertLCAMultiWay('conflict',
2970
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2768
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2972
2770
def test_other_and_this_pick_different_lca(self):
2973
2771
# OTHER and THIS resolve the lca conflict in different ways
2974
2772
self.assertLCAMultiWay('conflict',
2975
'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
2976
self.assertLCAMultiWay('conflict',
2977
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
2978
self.assertLCAMultiWay('conflict',
2979
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
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')
2981
2779
def test_other_in_lca(self):
2982
2780
# OTHER takes a value of one of the LCAs, THIS takes a new value, which
2983
2781
# theoretically supersedes both LCA values and 'wins'
2984
self.assertLCAMultiWay(
2985
'this', 'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
2986
self.assertLCAMultiWay(
2987
'this', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val',
2989
self.assertLCAMultiWay('conflict',
2991
'lca2val'], 'lca1val', 'newval',
2992
allow_overriding_lca=False)
2993
self.assertLCAMultiWay('conflict',
2994
'bval', ['lca1val', 'lca2val',
2995
'lca3val'], 'lca1val', 'newval',
2996
allow_overriding_lca=False)
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)
2997
2792
# THIS reverted back to BASE, but that is an explicit supersede of all
2999
self.assertLCAMultiWay(
3000
'this', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val',
3002
self.assertLCAMultiWay(
3003
'this', 'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
3004
self.assertLCAMultiWay('conflict',
3005
'bval', ['lca1val', 'lca2val',
3006
'lca3val'], 'lca1val', 'bval',
3007
allow_overriding_lca=False)
3008
self.assertLCAMultiWay('conflict',
3009
'bval', ['lca1val', 'lca2val',
3010
'bval'], 'lca1val', 'bval',
3011
allow_overriding_lca=False)
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)
3013
2805
def test_this_in_lca(self):
3014
2806
# THIS takes a value of one of the LCAs, OTHER takes a new value, which
3015
2807
# theoretically supersedes both LCA values and 'wins'
3016
self.assertLCAMultiWay(
3017
'other', 'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
3018
self.assertLCAMultiWay(
3019
'other', 'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
3020
self.assertLCAMultiWay('conflict',
3022
'lca2val'], 'oval', 'lca1val',
3023
allow_overriding_lca=False)
3024
self.assertLCAMultiWay('conflict',
3026
'lca2val'], 'oval', 'lca2val',
3027
allow_overriding_lca=False)
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)
3028
2818
# OTHER reverted back to BASE, but that is an explicit supersede of all
3030
self.assertLCAMultiWay(
3031
'other', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval',
3033
self.assertLCAMultiWay(
3034
'conflict', 'bval', ['lca1val', 'lca2val', 'lca3val'],
3035
'bval', 'lca3val', allow_overriding_lca=False)
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)
3037
2826
def test_all_differ(self):
3038
self.assertLCAMultiWay(
3039
'conflict', 'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
3040
self.assertLCAMultiWay(
3041
'conflict', 'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval',
3043
self.assertLCAMultiWay(
3044
'conflict', 'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval',
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')
3048
2835
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
3117
2903
def test_hook_called_for_text_conflicts(self):
3118
2904
builder = self.make_text_conflict()
2905
conflicts = builder.merge()
3120
2906
# The hook should call the merge_text() method
3121
2907
self.assertEqual(['merge_text'], self.calls)
3123
2909
def test_hook_not_called_for_kind_change(self):
3124
2910
builder = self.make_kind_change()
2911
conflicts = builder.merge()
3126
2912
# The hook should not call the merge_text() method
3127
2913
self.assertEqual([], self.calls)
3129
2915
def test_hook_not_called_for_other_files(self):
3130
2916
builder = self.make_text_conflict('foobar')
2917
conflicts = builder.merge()
3132
2918
# The hook should not call the merge_text() method
3133
2919
self.assertEqual([], self.calls)
3136
class TestMergeIntoBase(tests.TestCaseWithTransport):
3138
def setup_simple_branch(self, relpath, shape=None, root_id=None):
3139
"""One commit, containing tree specified by optional shape.
3141
Default is empty tree (just root entry).
3144
root_id = b'%s-root-id' % (relpath.encode('ascii'),)
3145
wt = self.make_branch_and_tree(relpath)
3146
wt.set_root_id(root_id)
3147
if shape is not None:
3148
adjusted_shape = [relpath + '/' + elem for elem in shape]
3149
self.build_tree(adjusted_shape)
3151
(b'%s-%s-id' % (relpath.encode('utf-8'),
3152
basename(elem.rstrip('/')).encode('ascii')))
3154
wt.add(shape, ids=ids)
3155
rev_id = b'r1-%s' % (relpath.encode('utf-8'),)
3156
wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
3157
self.assertEqual(root_id, wt.path2id(''))
3160
def setup_two_branches(self, custom_root_ids=True):
3161
"""Setup 2 branches, one will be a library, the other a project."""
3165
root_id = inventory.ROOT_ID
3166
project_wt = self.setup_simple_branch(
3167
'project', ['README', 'dir/', 'dir/file.c'],
3169
lib_wt = self.setup_simple_branch(
3170
'lib1', ['README', 'Makefile', 'foo.c'], root_id)
3172
return project_wt, lib_wt
3174
def do_merge_into(self, location, merge_as):
3175
"""Helper for using MergeIntoMerger.
3177
:param location: location of directory to merge from, either the
3178
location of a branch or of a path inside a branch.
3179
:param merge_as: the path in a tree to add the new directory as.
3180
:returns: the conflicts from 'do_merge'.
3182
with cleanup.ExitStack() as stack:
3183
# Open and lock the various tree and branch objects
3184
wt, subdir_relpath = WorkingTree.open_containing(merge_as)
3185
stack.enter_context(wt.lock_write())
3186
branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
3188
stack.enter_context(branch_to_merge.lock_read())
3189
other_tree = branch_to_merge.basis_tree()
3190
stack.enter_context(other_tree.lock_read())
3192
merger = _mod_merge.MergeIntoMerger(
3193
this_tree=wt, other_tree=other_tree, other_branch=branch_to_merge,
3194
target_subdir=subdir_relpath, source_subpath=subdir_to_merge)
3195
merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
3196
conflicts = merger.do_merge()
3197
merger.set_pending()
3200
def assertTreeEntriesEqual(self, expected_entries, tree):
3201
"""Assert that 'tree' contains the expected inventory entries.
3203
:param expected_entries: sequence of (path, file-id) pairs.
3205
files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
3206
self.assertEqual(expected_entries, files)
3209
class TestMergeInto(TestMergeIntoBase):
3211
def test_newdir_with_unique_roots(self):
3212
"""Merge a branch with a unique root into a new directory."""
3213
project_wt, lib_wt = self.setup_two_branches()
3214
self.do_merge_into('lib1', 'project/lib1')
3215
project_wt.lock_read()
3216
self.addCleanup(project_wt.unlock)
3217
# The r1-lib1 revision should be merged into this one
3218
self.assertEqual([b'r1-project', b'r1-lib1'],
3219
project_wt.get_parent_ids())
3220
self.assertTreeEntriesEqual(
3221
[('', b'project-root-id'),
3222
('README', b'project-README-id'),
3223
('dir', b'project-dir-id'),
3224
('lib1', b'lib1-root-id'),
3225
('dir/file.c', b'project-file.c-id'),
3226
('lib1/Makefile', b'lib1-Makefile-id'),
3227
('lib1/README', b'lib1-README-id'),
3228
('lib1/foo.c', b'lib1-foo.c-id'),
3231
def test_subdir(self):
3232
"""Merge a branch into a subdirectory of an existing directory."""
3233
project_wt, lib_wt = self.setup_two_branches()
3234
self.do_merge_into('lib1', 'project/dir/lib1')
3235
project_wt.lock_read()
3236
self.addCleanup(project_wt.unlock)
3237
# The r1-lib1 revision should be merged into this one
3238
self.assertEqual([b'r1-project', b'r1-lib1'],
3239
project_wt.get_parent_ids())
3240
self.assertTreeEntriesEqual(
3241
[('', b'project-root-id'),
3242
('README', b'project-README-id'),
3243
('dir', b'project-dir-id'),
3244
('dir/file.c', b'project-file.c-id'),
3245
('dir/lib1', b'lib1-root-id'),
3246
('dir/lib1/Makefile', b'lib1-Makefile-id'),
3247
('dir/lib1/README', b'lib1-README-id'),
3248
('dir/lib1/foo.c', b'lib1-foo.c-id'),
3251
def test_newdir_with_repeat_roots(self):
3252
"""If the file-id of the dir to be merged already exists a new ID will
3253
be allocated to let the merge happen.
3255
project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
3256
root_id = project_wt.path2id('')
3257
self.do_merge_into('lib1', 'project/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
new_lib1_id = project_wt.path2id('lib1')
3264
self.assertNotEqual(None, new_lib1_id)
3265
self.assertTreeEntriesEqual(
3267
('README', b'project-README-id'),
3268
('dir', b'project-dir-id'),
3269
('lib1', new_lib1_id),
3270
('dir/file.c', b'project-file.c-id'),
3271
('lib1/Makefile', b'lib1-Makefile-id'),
3272
('lib1/README', b'lib1-README-id'),
3273
('lib1/foo.c', b'lib1-foo.c-id'),
3276
def test_name_conflict(self):
3277
"""When the target directory name already exists a conflict is
3278
generated and the original directory is renamed to foo.moved.
3280
dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
3281
self.setup_simple_branch('src', ['README'])
3282
conflicts = self.do_merge_into('src', 'dest/dir')
3283
self.assertEqual(1, conflicts)
3285
self.addCleanup(dest_wt.unlock)
3286
# The r1-lib1 revision should be merged into this one
3287
self.assertEqual([b'r1-dest', b'r1-src'], dest_wt.get_parent_ids())
3288
self.assertTreeEntriesEqual(
3289
[('', b'dest-root-id'),
3290
('dir', b'src-root-id'),
3291
('dir.moved', b'dest-dir-id'),
3292
('dir/README', b'src-README-id'),
3293
('dir.moved/file.txt', b'dest-file.txt-id'),
3296
def test_file_id_conflict(self):
3297
"""A conflict is generated if the merge-into adds a file (or other
3298
inventory entry) with a file-id that already exists in the target tree.
3300
self.setup_simple_branch('dest', ['file.txt'])
3301
# Make a second tree with a file-id that will clash with file.txt in
3303
src_wt = self.make_branch_and_tree('src')
3304
self.build_tree(['src/README'])
3305
src_wt.add(['README'], ids=[b'dest-file.txt-id'])
3306
src_wt.commit("Rev 1 of src.", rev_id=b'r1-src')
3307
conflicts = self.do_merge_into('src', 'dest/dir')
3308
# This is an edge case that shouldn't happen to users very often. So
3309
# we don't care really about the exact presentation of the conflict,
3310
# just that there is one.
3311
self.assertEqual(1, conflicts)
3313
def test_only_subdir(self):
3314
"""When the location points to just part of a tree, merge just that
3317
dest_wt = self.setup_simple_branch('dest')
3318
self.setup_simple_branch('src', ['hello.txt', 'dir/', 'dir/foo.c'])
3319
self.do_merge_into('src/dir', 'dest/dir')
3321
self.addCleanup(dest_wt.unlock)
3322
# The r1-lib1 revision should NOT be merged into this one (this is a
3324
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3325
self.assertTreeEntriesEqual(
3326
[('', b'dest-root-id'),
3327
('dir', b'src-dir-id'),
3328
('dir/foo.c', b'src-foo.c-id'),
3331
def test_only_file(self):
3332
"""An edge case: merge just one file, not a whole dir."""
3333
dest_wt = self.setup_simple_branch('dest')
3334
self.setup_simple_branch('two-file', ['file1.txt', 'file2.txt'])
3335
self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
3337
self.addCleanup(dest_wt.unlock)
3338
# The r1-lib1 revision should NOT be merged into this one
3339
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3340
self.assertTreeEntriesEqual(
3341
[('', b'dest-root-id'), ('file1.txt', b'two-file-file1.txt-id')],
3344
def test_no_such_source_path(self):
3345
"""PathNotInTree is raised if the specified path in the source tree
3348
dest_wt = self.setup_simple_branch('dest')
3349
self.setup_simple_branch('src', ['dir/'])
3350
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3351
'src/no-such-dir', 'dest/foo')
3353
self.addCleanup(dest_wt.unlock)
3354
# The dest tree is unmodified.
3355
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3356
self.assertTreeEntriesEqual([('', b'dest-root-id')], dest_wt)
3358
def test_no_such_target_path(self):
3359
"""PathNotInTree is also raised if the specified path in the target
3360
tree does not exist.
3362
dest_wt = self.setup_simple_branch('dest')
3363
self.setup_simple_branch('src', ['file.txt'])
3364
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3365
'src', 'dest/no-such-dir/foo')
3367
self.addCleanup(dest_wt.unlock)
3368
# The dest tree is unmodified.
3369
self.assertEqual([b'r1-dest'], dest_wt.get_parent_ids())
3370
self.assertTreeEntriesEqual([('', b'dest-root-id')], dest_wt)
3373
class TestMergeHooks(TestCaseWithTransport):
3376
super(TestMergeHooks, self).setUp()
3377
self.tree_a = self.make_branch_and_tree('tree_a')
3378
self.build_tree_contents([('tree_a/file', b'content_1')])
3379
self.tree_a.add('file', b'file-id')
3380
self.tree_a.commit('added file')
3382
self.tree_b = self.tree_a.controldir.sprout(
3383
'tree_b').open_workingtree()
3384
self.build_tree_contents([('tree_b/file', b'content_2')])
3385
self.tree_b.commit('modify file')
3387
def test_pre_merge_hook_inject_different_tree(self):
3388
tree_c = self.tree_b.controldir.sprout('tree_c').open_workingtree()
3389
self.build_tree_contents([('tree_c/file', b'content_3')])
3390
tree_c.commit("more content")
3393
def factory(merger):
3394
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3395
merger.other_tree = tree_c
3396
calls.append(merger)
3397
_mod_merge.Merger.hooks.install_named_hook('pre_merge',
3398
factory, 'test factory')
3399
self.tree_a.merge_from_branch(self.tree_b.branch)
3401
self.assertFileEqual(b"content_3", 'tree_a/file')
3402
self.assertLength(1, calls)
3404
def test_post_merge_hook_called(self):
3407
def factory(merger):
3408
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3409
calls.append(merger)
3410
_mod_merge.Merger.hooks.install_named_hook('post_merge',
3411
factory, 'test factory')
3413
self.tree_a.merge_from_branch(self.tree_b.branch)
3415
self.assertFileEqual(b"content_2", 'tree_a/file')
3416
self.assertLength(1, calls)