326
365
'c', but not 'b'.
328
367
this_tree = self.make_branch_and_tree('this')
329
self.build_tree_contents([('this/file', "a\n")])
368
self.build_tree_contents([('this/file', b"a\n")])
330
369
this_tree.add('file')
331
370
this_tree.commit('rev1')
332
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
333
self.build_tree_contents([('other/file', "a\nb\n")])
334
other_tree.commit('rev2b', rev_id='rev2b')
335
self.build_tree_contents([('other/file', "c\na\nb\n")])
336
other_tree.commit('rev3b', rev_id='rev3b')
371
other_tree = this_tree.controldir.sprout('other').open_workingtree()
372
self.build_tree_contents([('other/file', b"a\nb\n")])
373
other_tree.commit('rev2b', rev_id=b'rev2b')
374
self.build_tree_contents([('other/file', b"c\na\nb\n")])
375
other_tree.commit('rev3b', rev_id=b'rev3b')
337
376
this_tree.lock_write()
338
377
self.addCleanup(this_tree.unlock)
339
378
return this_tree, other_tree
341
380
def test_weave_cherrypick(self):
342
381
this_tree, other_tree = self.prepare_cherrypick()
343
merger = _mod_merge.Merger.from_revision_ids(None,
344
this_tree, 'rev3b', 'rev2b', other_tree.branch)
382
merger = _mod_merge.Merger.from_revision_ids(
383
this_tree, b'rev3b', b'rev2b', other_tree.branch)
345
384
merger.merge_type = _mod_merge.WeaveMerger
346
385
merger.do_merge()
347
self.assertFileEqual('c\na\n', 'this/file')
386
self.assertFileEqual(b'c\na\n', 'this/file')
349
388
def test_weave_cannot_reverse_cherrypick(self):
350
389
this_tree, other_tree = self.prepare_cherrypick()
351
merger = _mod_merge.Merger.from_revision_ids(None,
352
this_tree, 'rev2b', 'rev3b', other_tree.branch)
390
merger = _mod_merge.Merger.from_revision_ids(
391
this_tree, b'rev2b', b'rev3b', other_tree.branch)
353
392
merger.merge_type = _mod_merge.WeaveMerger
354
393
self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
356
395
def test_merge3_can_reverse_cherrypick(self):
357
396
this_tree, other_tree = self.prepare_cherrypick()
358
merger = _mod_merge.Merger.from_revision_ids(None,
359
this_tree, 'rev2b', 'rev3b', other_tree.branch)
397
merger = _mod_merge.Merger.from_revision_ids(
398
this_tree, b'rev2b', b'rev3b', other_tree.branch)
360
399
merger.merge_type = _mod_merge.Merge3Merger
361
400
merger.do_merge()
363
402
def test_merge3_will_detect_cherrypick(self):
364
403
this_tree = self.make_branch_and_tree('this')
365
self.build_tree_contents([('this/file', "a\n")])
404
self.build_tree_contents([('this/file', b"a\n")])
366
405
this_tree.add('file')
367
406
this_tree.commit('rev1')
368
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
369
self.build_tree_contents([('other/file', "a\nb\n")])
370
other_tree.commit('rev2b', rev_id='rev2b')
371
self.build_tree_contents([('other/file', "a\nb\nc\n")])
372
other_tree.commit('rev3b', rev_id='rev3b')
407
other_tree = this_tree.controldir.sprout('other').open_workingtree()
408
self.build_tree_contents([('other/file', b"a\nb\n")])
409
other_tree.commit('rev2b', rev_id=b'rev2b')
410
self.build_tree_contents([('other/file', b"a\nb\nc\n")])
411
other_tree.commit('rev3b', rev_id=b'rev3b')
373
412
this_tree.lock_write()
374
413
self.addCleanup(this_tree.unlock)
376
merger = _mod_merge.Merger.from_revision_ids(None,
377
this_tree, 'rev3b', 'rev2b', other_tree.branch)
415
merger = _mod_merge.Merger.from_revision_ids(
416
this_tree, b'rev3b', b'rev2b', other_tree.branch)
378
417
merger.merge_type = _mod_merge.Merge3Merger
379
418
merger.do_merge()
380
self.assertFileEqual('a\n'
384
'>>>>>>> MERGE-SOURCE\n',
419
self.assertFileEqual(b'a\n'
423
b'>>>>>>> MERGE-SOURCE\n',
426
def test_merge_reverse_revision_range(self):
427
tree = self.make_branch_and_tree(".")
429
self.addCleanup(tree.unlock)
430
self.build_tree(['a'])
432
first_rev = tree.commit("added a")
433
merger = _mod_merge.Merger.from_revision_ids(tree,
434
_mod_revision.NULL_REVISION,
436
merger.merge_type = _mod_merge.Merge3Merger
437
merger.interesting_files = 'a'
438
conflict_count = merger.do_merge()
439
self.assertEqual(0, conflict_count)
441
self.assertPathDoesNotExist("a")
443
self.assertPathExists("a")
387
445
def test_make_merger(self):
388
446
this_tree = self.make_branch_and_tree('this')
389
this_tree.commit('rev1', rev_id='rev1')
390
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
391
this_tree.commit('rev2', rev_id='rev2a')
392
other_tree.commit('rev2', rev_id='rev2b')
447
this_tree.commit('rev1', rev_id=b'rev1')
448
other_tree = this_tree.controldir.sprout('other').open_workingtree()
449
this_tree.commit('rev2', rev_id=b'rev2a')
450
other_tree.commit('rev2', rev_id=b'rev2b')
393
451
this_tree.lock_write()
394
452
self.addCleanup(this_tree.unlock)
395
merger = _mod_merge.Merger.from_revision_ids(None,
396
this_tree, 'rev2b', other_branch=other_tree.branch)
453
merger = _mod_merge.Merger.from_revision_ids(
454
this_tree, b'rev2b', other_branch=other_tree.branch)
397
455
merger.merge_type = _mod_merge.Merge3Merger
398
456
tree_merger = merger.make_merger()
399
457
self.assertIs(_mod_merge.Merge3Merger, tree_merger.__class__)
400
self.assertEqual('rev2b', tree_merger.other_tree.get_revision_id())
401
self.assertEqual('rev1', tree_merger.base_tree.get_revision_id())
458
self.assertEqual(b'rev2b',
459
tree_merger.other_tree.get_revision_id())
460
self.assertEqual(b'rev1',
461
tree_merger.base_tree.get_revision_id())
462
self.assertEqual(other_tree.branch, tree_merger.other_branch)
403
464
def test_make_preview_transform(self):
404
465
this_tree = self.make_branch_and_tree('this')
405
self.build_tree_contents([('this/file', '1\n')])
406
this_tree.add('file', 'file-id')
407
this_tree.commit('rev1', rev_id='rev1')
408
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
409
self.build_tree_contents([('this/file', '1\n2a\n')])
410
this_tree.commit('rev2', rev_id='rev2a')
411
self.build_tree_contents([('other/file', '2b\n1\n')])
412
other_tree.commit('rev2', rev_id='rev2b')
466
self.build_tree_contents([('this/file', b'1\n')])
467
this_tree.add('file', b'file-id')
468
this_tree.commit('rev1', rev_id=b'rev1')
469
other_tree = this_tree.controldir.sprout('other').open_workingtree()
470
self.build_tree_contents([('this/file', b'1\n2a\n')])
471
this_tree.commit('rev2', rev_id=b'rev2a')
472
self.build_tree_contents([('other/file', b'2b\n1\n')])
473
other_tree.commit('rev2', rev_id=b'rev2b')
413
474
this_tree.lock_write()
414
475
self.addCleanup(this_tree.unlock)
415
merger = _mod_merge.Merger.from_revision_ids(None,
416
this_tree, 'rev2b', other_branch=other_tree.branch)
476
merger = _mod_merge.Merger.from_revision_ids(
477
this_tree, b'rev2b', other_branch=other_tree.branch)
417
478
merger.merge_type = _mod_merge.Merge3Merger
418
479
tree_merger = merger.make_merger()
419
tt = tree_merger.make_preview_transform()
420
self.addCleanup(tt.finalize)
421
preview_tree = tt.get_preview_tree()
422
tree_file = this_tree.get_file('file-id')
424
self.assertEqual('1\n2a\n', tree_file.read())
427
preview_file = preview_tree.get_file('file-id')
429
self.assertEqual('2b\n1\n2a\n', preview_file.read())
480
with tree_merger.make_preview_transform() as tt:
481
preview_tree = tt.get_preview_tree()
482
with this_tree.get_file('file') as tree_file:
483
self.assertEqual(b'1\n2a\n', tree_file.read())
484
with preview_tree.get_file('file') as preview_file:
485
self.assertEqual(b'2b\n1\n2a\n', preview_file.read())
433
487
def test_do_merge(self):
434
488
this_tree = self.make_branch_and_tree('this')
435
self.build_tree_contents([('this/file', '1\n')])
436
this_tree.add('file', 'file-id')
437
this_tree.commit('rev1', rev_id='rev1')
438
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
439
self.build_tree_contents([('this/file', '1\n2a\n')])
440
this_tree.commit('rev2', rev_id='rev2a')
441
self.build_tree_contents([('other/file', '2b\n1\n')])
442
other_tree.commit('rev2', rev_id='rev2b')
489
self.build_tree_contents([('this/file', b'1\n')])
490
this_tree.add('file', b'file-id')
491
this_tree.commit('rev1', rev_id=b'rev1')
492
other_tree = this_tree.controldir.sprout('other').open_workingtree()
493
self.build_tree_contents([('this/file', b'1\n2a\n')])
494
this_tree.commit('rev2', rev_id=b'rev2a')
495
self.build_tree_contents([('other/file', b'2b\n1\n')])
496
other_tree.commit('rev2', rev_id=b'rev2b')
443
497
this_tree.lock_write()
444
498
self.addCleanup(this_tree.unlock)
445
merger = _mod_merge.Merger.from_revision_ids(None,
446
this_tree, 'rev2b', other_branch=other_tree.branch)
499
merger = _mod_merge.Merger.from_revision_ids(
500
this_tree, b'rev2b', other_branch=other_tree.branch)
447
501
merger.merge_type = _mod_merge.Merge3Merger
448
502
tree_merger = merger.make_merger()
449
503
tt = tree_merger.do_merge()
450
tree_file = this_tree.get_file('file-id')
452
self.assertEqual('2b\n1\n2a\n', tree_file.read())
504
with this_tree.get_file('file') as tree_file:
505
self.assertEqual(b'2b\n1\n2a\n', tree_file.read())
507
def test_merge_require_tree_root(self):
508
tree = self.make_branch_and_tree(".")
510
self.addCleanup(tree.unlock)
511
self.build_tree(['a'])
513
first_rev = tree.commit("added a")
514
old_root_id = tree.path2id('')
515
merger = _mod_merge.Merger.from_revision_ids(tree,
516
_mod_revision.NULL_REVISION,
518
merger.merge_type = _mod_merge.Merge3Merger
519
conflict_count = merger.do_merge()
520
self.assertEqual(0, conflict_count)
521
self.assertEqual({''}, set(tree.all_versioned_paths()))
522
tree.set_parent_ids([])
456
524
def test_merge_add_into_deleted_root(self):
457
525
# Yes, people actually do this. And report bugs if it breaks.
458
526
source = self.make_branch_and_tree('source', format='rich-root-pack')
459
527
self.build_tree(['source/foo/'])
460
source.add('foo', 'foo-id')
528
source.add('foo', b'foo-id')
461
529
source.commit('Add foo')
462
target = source.bzrdir.sprout('target').open_workingtree()
463
subtree = target.extract('foo-id')
530
target = source.controldir.sprout('target').open_workingtree()
531
subtree = target.extract('foo')
464
532
subtree.commit('Delete root')
465
533
self.build_tree(['source/bar'])
466
source.add('bar', 'bar-id')
534
source.add('bar', b'bar-id')
467
535
source.commit('Add bar')
468
536
subtree.merge_from_branch(source.branch)
506
575
def add_uncommitted_version(self, key, parents, text):
507
576
self.plan_merge_vf.add_lines(key, parents,
508
[c+'\n' for c in text])
577
[int2byte(c) + b'\n' for c in bytearray(text)])
510
579
def setup_plan_merge(self):
511
self.add_rev('root', 'A', [], 'abc')
512
self.add_rev('root', 'B', ['A'], 'acehg')
513
self.add_rev('root', 'C', ['A'], 'fabg')
514
return _PlanMerge('B', 'C', self.plan_merge_vf, ('root',))
580
self.add_rev(b'root', b'A', [], b'abc')
581
self.add_rev(b'root', b'B', [b'A'], b'acehg')
582
self.add_rev(b'root', b'C', [b'A'], b'fabg')
583
return _PlanMerge(b'B', b'C', self.plan_merge_vf, (b'root',))
516
585
def setup_plan_merge_uncommitted(self):
517
self.add_version(('root', 'A'), [], 'abc')
518
self.add_uncommitted_version(('root', 'B:'), [('root', 'A')], 'acehg')
519
self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
520
return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
586
self.add_version((b'root', b'A'), [], b'abc')
587
self.add_uncommitted_version(
588
(b'root', b'B:'), [(b'root', b'A')], b'acehg')
589
self.add_uncommitted_version(
590
(b'root', b'C:'), [(b'root', b'A')], b'fabg')
591
return _PlanMerge(b'B:', b'C:', self.plan_merge_vf, (b'root',))
522
593
def test_base_from_plan(self):
523
594
self.setup_plan_merge()
524
plan = self.plan_merge_vf.plan_merge('B', 'C')
595
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
525
596
pwm = versionedfile.PlanWeaveMerge(plan)
526
self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
597
self.assertEqual([b'a\n', b'b\n', b'c\n'], pwm.base_from_plan())
528
599
def test_unique_lines(self):
529
600
plan = self.setup_plan_merge()
530
601
self.assertEqual(plan._unique_lines(
531
plan._get_matching_blocks('B', 'C')),
602
plan._get_matching_blocks(b'B', b'C')),
532
603
([1, 2, 3], [0, 2]))
534
605
def test_plan_merge(self):
535
606
self.setup_plan_merge()
536
plan = self.plan_merge_vf.plan_merge('B', 'C')
607
plan = self.plan_merge_vf.plan_merge(b'B', b'C')
537
608
self.assertEqual([
539
('unchanged', 'a\n'),
610
('unchanged', b'a\n'),
611
('killed-a', b'b\n'),
612
('killed-b', b'c\n'),
548
619
def test_plan_merge_cherrypick(self):
549
self.add_rev('root', 'A', [], 'abc')
550
self.add_rev('root', 'B', ['A'], 'abcde')
551
self.add_rev('root', 'C', ['A'], 'abcefg')
552
self.add_rev('root', 'D', ['A', 'B', 'C'], 'abcdegh')
553
my_plan = _PlanMerge('B', 'D', self.plan_merge_vf, ('root',))
620
self.add_rev(b'root', b'A', [], b'abc')
621
self.add_rev(b'root', b'B', [b'A'], b'abcde')
622
self.add_rev(b'root', b'C', [b'A'], b'abcefg')
623
self.add_rev(b'root', b'D', [b'A', b'B', b'C'], b'abcdegh')
624
my_plan = _PlanMerge(b'B', b'D', self.plan_merge_vf, (b'root',))
554
625
# We shortcut when one text supersedes the other in the per-file graph.
555
626
# We don't actually need to compare the texts at this point.
556
627
self.assertEqual([
564
list(my_plan.plan_merge()))
635
list(my_plan.plan_merge()))
566
637
def test_plan_merge_no_common_ancestor(self):
567
self.add_rev('root', 'A', [], 'abc')
568
self.add_rev('root', 'B', [], 'xyz')
569
my_plan = _PlanMerge('A', 'B', self.plan_merge_vf, ('root',))
638
self.add_rev(b'root', b'A', [], b'abc')
639
self.add_rev(b'root', b'B', [], b'xyz')
640
my_plan = _PlanMerge(b'A', b'B', self.plan_merge_vf, (b'root',))
570
641
self.assertEqual([
577
list(my_plan.plan_merge()))
648
list(my_plan.plan_merge()))
579
650
def test_plan_merge_tail_ancestors(self):
580
651
# The graph looks like this:
851
922
# XX unused ancestor, should not show up in the weave
855
926
# B C B & C both introduce a new line
859
930
# D E B & C are both merged, so both are common ancestors
860
931
# In the process of merging, both sides order the new
861
932
# lines differently
863
self.add_rev('root', 'XX', [], 'qrs')
864
self.add_rev('root', 'A', ['XX'], 'abcdef')
865
self.add_rev('root', 'B', ['A'], 'abcdgef')
866
self.add_rev('root', 'C', ['A'], 'abcdhef')
867
self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
868
self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
869
plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
934
self.add_rev(b'root', b'XX', [], b'qrs')
935
self.add_rev(b'root', b'A', [b'XX'], b'abcdef')
936
self.add_rev(b'root', b'B', [b'A'], b'abcdgef')
937
self.add_rev(b'root', b'C', [b'A'], b'abcdhef')
938
self.add_rev(b'root', b'D', [b'B', b'C'], b'abcdghef')
939
self.add_rev(b'root', b'E', [b'C', b'B'], b'abcdhgef')
940
plan = list(self.plan_merge_vf.plan_merge(b'D', b'E'))
870
941
self.assertEqual([
871
('unchanged', 'a\n'),
872
('unchanged', 'b\n'),
873
('unchanged', 'c\n'),
874
('unchanged', 'd\n'),
876
('unchanged', 'g\n'),
878
('unchanged', 'e\n'),
879
('unchanged', 'f\n'),
942
('unchanged', b'a\n'),
943
('unchanged', b'b\n'),
944
('unchanged', b'c\n'),
945
('unchanged', b'd\n'),
947
('unchanged', b'g\n'),
948
('killed-b', b'h\n'),
949
('unchanged', b'e\n'),
950
('unchanged', b'f\n'),
881
952
pwm = versionedfile.PlanWeaveMerge(plan)
882
self.assertEqualDiff('\n'.join('abcdghef') + '\n',
883
''.join(pwm.base_from_plan()))
953
self.assertEqualDiff(b'a\nb\nc\nd\ng\nh\ne\nf\n',
954
b''.join(pwm.base_from_plan()))
884
955
# Reversing the order reverses the merge plan, and final order of 'hg'
886
plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
957
plan = list(self.plan_merge_vf.plan_merge(b'E', b'D'))
887
958
self.assertEqual([
888
('unchanged', 'a\n'),
889
('unchanged', 'b\n'),
890
('unchanged', 'c\n'),
891
('unchanged', 'd\n'),
893
('unchanged', 'h\n'),
895
('unchanged', 'e\n'),
896
('unchanged', 'f\n'),
959
('unchanged', b'a\n'),
960
('unchanged', b'b\n'),
961
('unchanged', b'c\n'),
962
('unchanged', b'd\n'),
964
('unchanged', b'h\n'),
965
('killed-b', b'g\n'),
966
('unchanged', b'e\n'),
967
('unchanged', b'f\n'),
898
969
pwm = versionedfile.PlanWeaveMerge(plan)
899
self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
900
''.join(pwm.base_from_plan()))
970
self.assertEqualDiff(b'a\nb\nc\nd\nh\ng\ne\nf\n',
971
b''.join(pwm.base_from_plan()))
901
972
# This is where lca differs, in that it (fairly correctly) determines
902
973
# that there is a conflict because both sides resolved the merge
904
plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
975
plan = list(self.plan_merge_vf.plan_lca_merge(b'D', b'E'))
905
976
self.assertEqual([
906
('unchanged', 'a\n'),
907
('unchanged', 'b\n'),
908
('unchanged', 'c\n'),
909
('unchanged', 'd\n'),
910
('conflicted-b', 'h\n'),
911
('unchanged', 'g\n'),
912
('conflicted-a', 'h\n'),
913
('unchanged', 'e\n'),
914
('unchanged', 'f\n'),
977
('unchanged', b'a\n'),
978
('unchanged', b'b\n'),
979
('unchanged', b'c\n'),
980
('unchanged', b'd\n'),
981
('conflicted-b', b'h\n'),
982
('unchanged', b'g\n'),
983
('conflicted-a', b'h\n'),
984
('unchanged', b'e\n'),
985
('unchanged', b'f\n'),
916
987
pwm = versionedfile.PlanWeaveMerge(plan)
917
self.assertEqualDiff('\n'.join('abcdgef') + '\n',
918
''.join(pwm.base_from_plan()))
988
self.assertEqualDiff(b'a\nb\nc\nd\ng\ne\nf\n',
989
b''.join(pwm.base_from_plan()))
919
990
# Reversing it changes what line is doubled, but still gives a
920
991
# double-conflict
921
plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
992
plan = list(self.plan_merge_vf.plan_lca_merge(b'E', b'D'))
922
993
self.assertEqual([
923
('unchanged', 'a\n'),
924
('unchanged', 'b\n'),
925
('unchanged', 'c\n'),
926
('unchanged', 'd\n'),
927
('conflicted-b', 'g\n'),
928
('unchanged', 'h\n'),
929
('conflicted-a', 'g\n'),
930
('unchanged', 'e\n'),
931
('unchanged', 'f\n'),
994
('unchanged', b'a\n'),
995
('unchanged', b'b\n'),
996
('unchanged', b'c\n'),
997
('unchanged', b'd\n'),
998
('conflicted-b', b'g\n'),
999
('unchanged', b'h\n'),
1000
('conflicted-a', b'g\n'),
1001
('unchanged', b'e\n'),
1002
('unchanged', b'f\n'),
933
1004
pwm = versionedfile.PlanWeaveMerge(plan)
934
self.assertEqualDiff('\n'.join('abcdhef') + '\n',
935
''.join(pwm.base_from_plan()))
1005
self.assertEqualDiff(b'a\nb\nc\nd\nh\ne\nf\n',
1006
b''.join(pwm.base_from_plan()))
937
1008
def assertRemoveExternalReferences(self, filtered_parent_map,
938
1009
child_map, tails, parent_map):
996
1067
self.assertPruneTails({1: []}, [5],
997
1068
{1: [], 2: [3, 4], 3: [5], 4: [5], 5: []})
998
1069
# Prune a partial chain
999
self.assertPruneTails({1: [6], 6:[]}, [5],
1070
self.assertPruneTails({1: [6], 6: []}, [5],
1000
1071
{1: [2, 6], 2: [3, 4], 3: [5], 4: [5], 5: [],
1002
1073
# Prune a chain with multiple tips, that pulls out intermediates
1003
self.assertPruneTails({1:[3], 3:[]}, [4, 5],
1004
{1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
1005
self.assertPruneTails({1:[3], 3:[]}, [5, 4],
1006
{1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
1074
self.assertPruneTails({1: [3], 3: []}, [4, 5],
1075
{1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []})
1076
self.assertPruneTails({1: [3], 3: []}, [5, 4],
1077
{1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []})
1008
1079
def test_subtract_plans(self):
1010
('unchanged', 'a\n'),
1012
('killed-a', 'c\n'),
1015
('killed-b', 'f\n'),
1016
('killed-b', 'g\n'),
1081
('unchanged', b'a\n'),
1083
('killed-a', b'c\n'),
1086
('killed-b', b'f\n'),
1087
('killed-b', b'g\n'),
1019
('unchanged', 'a\n'),
1021
('killed-a', 'c\n'),
1024
('killed-b', 'f\n'),
1025
('killed-b', 'i\n'),
1090
('unchanged', b'a\n'),
1092
('killed-a', b'c\n'),
1095
('killed-b', b'f\n'),
1096
('killed-b', b'i\n'),
1027
1098
subtracted_plan = [
1028
('unchanged', 'a\n'),
1030
('killed-a', 'c\n'),
1032
('unchanged', 'f\n'),
1033
('killed-b', 'i\n'),
1099
('unchanged', b'a\n'),
1101
('killed-a', b'c\n'),
1103
('unchanged', b'f\n'),
1104
('killed-b', b'i\n'),
1035
1106
self.assertEqual(subtracted_plan,
1036
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1107
list(_PlanMerge._subtract_plans(old_plan, new_plan)))
1038
1109
def setup_merge_with_base(self):
1039
self.add_rev('root', 'COMMON', [], 'abc')
1040
self.add_rev('root', 'THIS', ['COMMON'], 'abcd')
1041
self.add_rev('root', 'BASE', ['COMMON'], 'eabc')
1042
self.add_rev('root', 'OTHER', ['BASE'], 'eafb')
1110
self.add_rev(b'root', b'COMMON', [], b'abc')
1111
self.add_rev(b'root', b'THIS', [b'COMMON'], b'abcd')
1112
self.add_rev(b'root', b'BASE', [b'COMMON'], b'eabc')
1113
self.add_rev(b'root', b'OTHER', [b'BASE'], b'eafb')
1044
1115
def test_plan_merge_with_base(self):
1045
1116
self.setup_merge_with_base()
1046
plan = self.plan_merge_vf.plan_merge('THIS', 'OTHER', 'BASE')
1047
self.assertEqual([('unchanged', 'a\n'),
1049
('unchanged', 'b\n'),
1050
('killed-b', 'c\n'),
1117
plan = self.plan_merge_vf.plan_merge(b'THIS', b'OTHER', b'BASE')
1118
self.assertEqual([('unchanged', b'a\n'),
1120
('unchanged', b'b\n'),
1121
('killed-b', b'c\n'),
1054
1125
def test_plan_lca_merge(self):
1055
1126
self.setup_plan_merge()
1056
plan = self.plan_merge_vf.plan_lca_merge('B', 'C')
1127
plan = self.plan_merge_vf.plan_lca_merge(b'B', b'C')
1057
1128
self.assertEqual([
1059
('unchanged', 'a\n'),
1060
('killed-b', 'c\n'),
1063
('killed-a', 'b\n'),
1064
('unchanged', 'g\n')],
1130
('unchanged', b'a\n'),
1131
('killed-b', b'c\n'),
1134
('killed-a', b'b\n'),
1135
('unchanged', b'g\n')],
1067
1138
def test_plan_lca_merge_uncommitted_files(self):
1068
1139
self.setup_plan_merge_uncommitted()
1069
plan = self.plan_merge_vf.plan_lca_merge('B:', 'C:')
1140
plan = self.plan_merge_vf.plan_lca_merge(b'B:', b'C:')
1070
1141
self.assertEqual([
1072
('unchanged', 'a\n'),
1073
('killed-b', 'c\n'),
1076
('killed-a', 'b\n'),
1077
('unchanged', 'g\n')],
1143
('unchanged', b'a\n'),
1144
('killed-b', b'c\n'),
1147
('killed-a', b'b\n'),
1148
('unchanged', b'g\n')],
1080
1151
def test_plan_lca_merge_with_base(self):
1081
1152
self.setup_merge_with_base()
1082
plan = self.plan_merge_vf.plan_lca_merge('THIS', 'OTHER', 'BASE')
1083
self.assertEqual([('unchanged', 'a\n'),
1085
('unchanged', 'b\n'),
1086
('killed-b', 'c\n'),
1153
plan = self.plan_merge_vf.plan_lca_merge(b'THIS', b'OTHER', b'BASE')
1154
self.assertEqual([('unchanged', b'a\n'),
1156
('unchanged', b'b\n'),
1157
('killed-b', b'c\n'),
1090
1161
def test_plan_lca_merge_with_criss_cross(self):
1091
self.add_version(('root', 'ROOT'), [], 'abc')
1162
self.add_version((b'root', b'ROOT'), [], b'abc')
1092
1163
# each side makes a change
1093
self.add_version(('root', 'REV1'), [('root', 'ROOT')], 'abcd')
1094
self.add_version(('root', 'REV2'), [('root', 'ROOT')], 'abce')
1164
self.add_version((b'root', b'REV1'), [(b'root', b'ROOT')], b'abcd')
1165
self.add_version((b'root', b'REV2'), [(b'root', b'ROOT')], b'abce')
1095
1166
# both sides merge, discarding others' changes
1096
self.add_version(('root', 'LCA1'),
1097
[('root', 'REV1'), ('root', 'REV2')], 'abcd')
1098
self.add_version(('root', 'LCA2'),
1099
[('root', 'REV1'), ('root', 'REV2')], 'fabce')
1100
plan = self.plan_merge_vf.plan_lca_merge('LCA1', 'LCA2')
1101
self.assertEqual([('new-b', 'f\n'),
1102
('unchanged', 'a\n'),
1103
('unchanged', 'b\n'),
1104
('unchanged', 'c\n'),
1105
('conflicted-a', 'd\n'),
1106
('conflicted-b', 'e\n'),
1167
self.add_version((b'root', b'LCA1'),
1168
[(b'root', b'REV1'), (b'root', b'REV2')], b'abcd')
1169
self.add_version((b'root', b'LCA2'),
1170
[(b'root', b'REV1'), (b'root', b'REV2')], b'fabce')
1171
plan = self.plan_merge_vf.plan_lca_merge(b'LCA1', b'LCA2')
1172
self.assertEqual([('new-b', b'f\n'),
1173
('unchanged', b'a\n'),
1174
('unchanged', b'b\n'),
1175
('unchanged', b'c\n'),
1176
('conflicted-a', b'd\n'),
1177
('conflicted-b', b'e\n'),
1109
1180
def test_plan_lca_merge_with_null(self):
1110
self.add_version(('root', 'A'), [], 'ab')
1111
self.add_version(('root', 'B'), [], 'bc')
1112
plan = self.plan_merge_vf.plan_lca_merge('A', 'B')
1113
self.assertEqual([('new-a', 'a\n'),
1114
('unchanged', 'b\n'),
1181
self.add_version((b'root', b'A'), [], b'ab')
1182
self.add_version((b'root', b'B'), [], b'bc')
1183
plan = self.plan_merge_vf.plan_lca_merge(b'A', b'B')
1184
self.assertEqual([('new-a', b'a\n'),
1185
('unchanged', b'b\n'),
1118
1189
def test_plan_merge_with_delete_and_change(self):
1119
self.add_rev('root', 'C', [], 'a')
1120
self.add_rev('root', 'A', ['C'], 'b')
1121
self.add_rev('root', 'B', ['C'], '')
1122
plan = self.plan_merge_vf.plan_merge('A', 'B')
1123
self.assertEqual([('killed-both', 'a\n'),
1190
self.add_rev(b'root', b'C', [], b'a')
1191
self.add_rev(b'root', b'A', [b'C'], b'b')
1192
self.add_rev(b'root', b'B', [b'C'], b'')
1193
plan = self.plan_merge_vf.plan_merge(b'A', b'B')
1194
self.assertEqual([('killed-both', b'a\n'),
1127
1198
def test_plan_merge_with_move_and_change(self):
1128
self.add_rev('root', 'C', [], 'abcd')
1129
self.add_rev('root', 'A', ['C'], 'acbd')
1130
self.add_rev('root', 'B', ['C'], 'aBcd')
1131
plan = self.plan_merge_vf.plan_merge('A', 'B')
1132
self.assertEqual([('unchanged', 'a\n'),
1134
('killed-b', 'b\n'),
1136
('killed-a', 'c\n'),
1137
('unchanged', 'd\n'),
1199
self.add_rev(b'root', b'C', [], b'abcd')
1200
self.add_rev(b'root', b'A', [b'C'], b'acbd')
1201
self.add_rev(b'root', b'B', [b'C'], b'aBcd')
1202
plan = self.plan_merge_vf.plan_merge(b'A', b'B')
1203
self.assertEqual([('unchanged', b'a\n'),
1205
('killed-b', b'b\n'),
1207
('killed-a', b'c\n'),
1208
('unchanged', b'd\n'),
1141
1212
class LoggingMerger(object):
1262
1331
builder = self.setup_criss_cross_graph()
1263
builder.build_snapshot('F-id', ['A-id'], [])
1264
builder.build_snapshot('H-id', ['E-id', 'F-id'], [])
1265
builder.build_snapshot('G-id', ['D-id', 'F-id'], [])
1266
merger = self.make_Merger(builder, 'H-id')
1267
self.assertEqual(['B-id', 'C-id', 'F-id'],
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'],
1268
1337
[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])
1270
1360
def test_no_criss_cross_passed_to_merge_type(self):
1271
1361
class LCATreesMerger(LoggingMerger):
1272
1362
supports_lca_trees = True
1274
merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1364
merger = self.make_Merger(self.setup_simple_graph(), b'C-id')
1275
1365
merger.merge_type = LCATreesMerger
1276
1366
merge_obj = merger.make_merger()
1277
1367
self.assertIsInstance(merge_obj, LCATreesMerger)
1278
1368
self.assertFalse('lca_trees' in merge_obj.kwargs)
1280
1370
def test_criss_cross_passed_to_merge_type(self):
1281
merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
1371
merger = self.make_Merger(self.setup_criss_cross_graph(), b'E-id')
1282
1372
merger.merge_type = _mod_merge.Merge3Merger
1283
1373
merge_obj = merger.make_merger()
1284
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1285
for t in merger._lca_trees])
1374
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1375
for t in merger._lca_trees])
1287
1377
def test_criss_cross_not_supported_merge_type(self):
1288
merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
1378
merger = self.make_Merger(self.setup_criss_cross_graph(), b'E-id')
1289
1379
# We explicitly do not define supports_lca_trees
1290
1380
merger.merge_type = LoggingMerger
1291
1381
merge_obj = merger.make_merger()
1306
1396
class TestMergerEntriesLCA(TestMergerBase):
1308
1398
def make_merge_obj(self, builder, other_revision_id,
1309
interesting_files=None, interesting_ids=None):
1399
interesting_files=None):
1310
1400
merger = self.make_Merger(builder, other_revision_id,
1311
interesting_files=interesting_files,
1312
interesting_ids=interesting_ids)
1401
interesting_files=interesting_files)
1313
1402
return merger.make_merger()
1315
1404
def test_simple(self):
1316
1405
builder = self.get_builder()
1317
builder.build_snapshot('A-id', None,
1318
[('add', (u'', 'a-root-id', 'directory', None)),
1319
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1320
builder.build_snapshot('C-id', ['A-id'],
1321
[('modify', ('a-id', 'a\nb\nC\nc\n'))])
1322
builder.build_snapshot('B-id', ['A-id'],
1323
[('modify', ('a-id', 'a\nB\nb\nc\n'))])
1324
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1325
[('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
1326
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1327
[('modify', ('a-id', 'a\nB\nb\nC\nc\n'))])
1328
merge_obj = self.make_merge_obj(builder, 'E-id')
1406
builder.build_snapshot(None,
1407
[('add', (u'', b'a-root-id', 'directory', None)),
1408
('add', (u'a', b'a-id', 'file', b'a\nb\nc\n'))],
1409
revision_id=b'A-id')
1410
builder.build_snapshot([b'A-id'],
1411
[('modify', ('a', b'a\nb\nC\nc\n'))],
1412
revision_id=b'C-id')
1413
builder.build_snapshot([b'A-id'],
1414
[('modify', ('a', b'a\nB\nb\nc\n'))],
1415
revision_id=b'B-id')
1416
builder.build_snapshot([b'C-id', b'B-id'],
1417
[('modify', ('a', b'a\nB\nb\nC\nc\nE\n'))],
1418
revision_id=b'E-id')
1419
builder.build_snapshot([b'B-id', b'C-id'],
1420
[('modify', ('a', b'a\nB\nb\nC\nc\n'))],
1421
revision_id=b'D-id', )
1422
merge_obj = self.make_merge_obj(builder, b'E-id')
1330
self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
1331
for t in merge_obj._lca_trees])
1332
self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
1424
self.assertEqual([b'B-id', b'C-id'], [t.get_revision_id()
1425
for t in merge_obj._lca_trees])
1426
self.assertEqual(b'A-id', merge_obj.base_tree.get_revision_id())
1333
1427
entries = list(merge_obj._entries_lca())
1335
1429
# (file_id, changed, parents, names, executable)
1336
1430
# BASE, lca1, lca2, OTHER, THIS
1337
root_id = 'a-root-id'
1338
self.assertEqual([('a-id', True,
1431
root_id = b'a-root-id'
1432
self.assertEqual([(b'a-id', True,
1433
((u'a', [u'a', u'a']), u'a', u'a'),
1339
1434
((root_id, [root_id, root_id]), root_id, root_id),
1340
1435
((u'a', [u'a', u'a']), u'a', u'a'),
1341
1436
((False, [False, False]), False, False)),
1344
1439
def test_not_in_base(self):
1345
1440
# LCAs all have the same last-modified revision for the file, as do
1354
1449
# G modifies 'bar'
1356
1451
builder = self.get_builder()
1357
builder.build_snapshot('A-id', None,
1358
[('add', (u'', 'a-root-id', 'directory', None))])
1359
builder.build_snapshot('B-id', ['A-id'],
1360
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
1361
builder.build_snapshot('C-id', ['A-id'],
1362
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
1363
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1364
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
1365
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1366
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
1367
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1368
[('modify', (u'bar-id', 'd\ne\nf\nG\n'))])
1369
builder.build_snapshot('F-id', ['D-id', 'E-id'], [])
1370
merge_obj = self.make_merge_obj(builder, 'G-id')
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')
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())
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())
1375
1476
entries = list(merge_obj._entries_lca())
1376
root_id = 'a-root-id'
1377
self.assertEqual([('bar-id', True,
1477
root_id = b'a-root-id'
1478
self.assertEqual([(b'bar-id', True,
1479
((None, [u'bar', u'bar']), u'bar', u'bar'),
1378
1480
((None, [root_id, root_id]), root_id, root_id),
1379
1481
((None, [u'bar', u'bar']), u'bar', u'bar'),
1380
1482
((None, [False, False]), False, False)),
1383
1485
def test_not_in_this(self):
1384
1486
builder = self.get_builder()
1385
builder.build_snapshot('A-id', None,
1386
[('add', (u'', 'a-root-id', 'directory', None)),
1387
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1388
builder.build_snapshot('B-id', ['A-id'],
1389
[('modify', ('a-id', 'a\nB\nb\nc\n'))])
1390
builder.build_snapshot('C-id', ['A-id'],
1391
[('modify', ('a-id', 'a\nb\nC\nc\n'))])
1392
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1393
[('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
1394
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1395
[('unversion', 'a-id')])
1396
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
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())
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())
1402
1509
entries = list(merge_obj._entries_lca())
1403
root_id = 'a-root-id'
1404
self.assertEqual([('a-id', True,
1510
root_id = b'a-root-id'
1511
self.assertEqual([(b'a-id', True,
1512
((u'a', [u'a', u'a']), u'a', None),
1405
1513
((root_id, [root_id, root_id]), root_id, None),
1406
1514
((u'a', [u'a', u'a']), u'a', None),
1407
1515
((False, [False, False]), False, None)),
1410
1518
def test_file_not_in_one_lca(self):
1411
1519
# A # just root
1415
1523
# D E # D and E both have the file, unchanged from C
1416
1524
builder = self.get_builder()
1417
builder.build_snapshot('A-id', None,
1418
[('add', (u'', 'a-root-id', 'directory', None))])
1419
builder.build_snapshot('B-id', ['A-id'], [])
1420
builder.build_snapshot('C-id', ['A-id'],
1421
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1422
builder.build_snapshot('E-id', ['C-id', 'B-id'], []) # Inherited from C
1423
builder.build_snapshot('D-id', ['B-id', 'C-id'], # Merged from C
1424
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1425
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
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())
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())
1431
1543
entries = list(merge_obj._entries_lca())
1432
1544
self.assertEqual([], entries)
1434
1546
def test_not_in_other(self):
1435
1547
builder = self.get_builder()
1436
builder.build_snapshot('A-id', None,
1437
[('add', (u'', 'a-root-id', 'directory', None)),
1438
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1439
builder.build_snapshot('B-id', ['A-id'], [])
1440
builder.build_snapshot('C-id', ['A-id'], [])
1441
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1442
[('unversion', 'a-id')])
1443
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1444
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1446
1560
entries = list(merge_obj._entries_lca())
1447
root_id = 'a-root-id'
1448
self.assertEqual([('a-id', True,
1561
root_id = b'a-root-id'
1562
self.assertEqual([(b'a-id', True,
1563
((u'a', [u'a', u'a']), None, u'a'),
1449
1564
((root_id, [root_id, root_id]), None, root_id),
1450
1565
((u'a', [u'a', u'a']), None, u'a'),
1451
1566
((False, [False, False]), None, False)),
1454
1569
def test_not_in_other_or_lca(self):
1455
1570
# A base, introduces 'foo'
1529
1649
# A => C, add file, thus C supersedes B
1530
1650
# w/ C=BASE, D=THIS, E=OTHER we have 'happy convergence'
1531
1651
builder = self.get_builder()
1532
builder.build_snapshot('A-id', None,
1533
[('add', (u'', 'a-root-id', 'directory', None))])
1534
builder.build_snapshot('B-id', ['A-id'], [])
1535
builder.build_snapshot('C-id', ['A-id'],
1536
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1537
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1538
[('unversion', 'a-id')])
1539
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1540
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1542
1665
entries = list(merge_obj._entries_lca())
1543
1666
self.assertEqual([], entries)
1545
1668
def test_only_in_other(self):
1546
1669
builder = self.get_builder()
1547
builder.build_snapshot('A-id', None,
1548
[('add', (u'', 'a-root-id', 'directory', None))])
1549
builder.build_snapshot('B-id', ['A-id'], [])
1550
builder.build_snapshot('C-id', ['A-id'], [])
1551
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1552
[('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1553
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1554
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1556
1681
entries = list(merge_obj._entries_lca())
1557
root_id = 'a-root-id'
1558
self.assertEqual([('a-id', True,
1682
root_id = b'a-root-id'
1683
self.assertEqual([(b'a-id', True,
1684
((None, [None, None]), u'a', None),
1559
1685
((None, [None, None]), root_id, None),
1560
1686
((None, [None, None]), u'a', None),
1561
1687
((None, [None, None]), False, None)),
1564
1690
def test_one_lca_supersedes(self):
1565
1691
# One LCA supersedes the other LCAs last modified value, but the
1655
1790
# be pruned from the LCAs, even though it was newly introduced by E
1656
1791
# (superseding B).
1657
1792
builder = self.get_builder()
1658
builder.build_snapshot('A-id', None,
1659
[('add', (u'', 'a-root-id', 'directory', None)),
1660
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1661
builder.build_snapshot('C-id', ['A-id'], [])
1662
builder.build_snapshot('B-id', ['A-id'],
1663
[('rename', ('foo', 'bar'))])
1664
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1665
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1666
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1667
[('rename', ('foo', 'bar'))])
1668
builder.build_snapshot('F-id', ['D-id', 'E-id'],
1669
[('rename', ('bar', 'bing'))]) # should end up conflicting
1670
merge_obj = self.make_merge_obj(builder, 'G-id')
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')
1672
1811
entries = list(merge_obj._entries_lca())
1673
root_id = 'a-root-id'
1812
root_id = b'a-root-id'
1674
1813
self.expectFailure("We prune values from BASE even when relevant.",
1677
((root_id, [root_id, root_id]), root_id, root_id),
1678
((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
1679
((False, [False, False]), False, False)),
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)),
1682
1821
def test_both_sides_revert(self):
1683
1822
# Both sides of a criss-cross revert the text to the lca
1755
1903
# We need to conflict.
1757
1905
builder = self.get_builder()
1758
builder.build_snapshot('A-id', None,
1759
[('add', (u'', 'a-root-id', 'directory', None)),
1760
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1761
builder.build_snapshot('B-id', ['A-id'],
1762
[('modify', ('foo-id', 'B content\n'))])
1763
builder.build_snapshot('C-id', ['A-id'],
1764
[('modify', ('foo-id', 'C content\n'))])
1765
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1766
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1767
[('modify', ('foo-id', 'C content\n'))]) # Same as E
1768
builder.build_snapshot('F-id', ['D-id'],
1769
[('modify', ('foo-id', 'F content\n'))])
1770
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1772
1925
entries = list(merge_obj._entries_lca())
1773
1926
self.expectFailure("We don't detect that LCA resolution was the"
1774
1927
" same on both sides",
1775
self.assertEqual, [], entries)
1928
self.assertEqual, [], entries)
1777
1930
def test_only_path_changed(self):
1778
1931
builder = self.get_builder()
1779
builder.build_snapshot('A-id', None,
1780
[('add', (u'', 'a-root-id', 'directory', None)),
1781
('add', (u'a', 'a-id', 'file', 'content\n'))])
1782
builder.build_snapshot('B-id', ['A-id'], [])
1783
builder.build_snapshot('C-id', ['A-id'], [])
1784
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1785
[('rename', (u'a', u'b'))])
1786
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1787
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1788
1943
entries = list(merge_obj._entries_lca())
1789
root_id = 'a-root-id'
1944
root_id = b'a-root-id'
1790
1945
# The content was not changed, only the path
1791
self.assertEqual([('a-id', False,
1946
self.assertEqual([(b'a-id', False,
1947
((u'a', [u'a', u'a']), u'b', u'a'),
1792
1948
((root_id, [root_id, root_id]), root_id, root_id),
1793
1949
((u'a', [u'a', u'a']), u'b', u'a'),
1794
1950
((False, [False, False]), False, False)),
1797
1953
def test_kind_changed(self):
1798
1954
# Identical content, except 'D' changes a-id into a directory
1799
1955
builder = self.get_builder()
1800
builder.build_snapshot('A-id', None,
1801
[('add', (u'', 'a-root-id', 'directory', None)),
1802
('add', (u'a', 'a-id', 'file', 'content\n'))])
1803
builder.build_snapshot('B-id', ['A-id'], [])
1804
builder.build_snapshot('C-id', ['A-id'], [])
1805
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1806
[('unversion', 'a-id'),
1807
('add', (u'a', 'a-id', 'directory', None))])
1808
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1809
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1810
1969
entries = list(merge_obj._entries_lca())
1811
root_id = 'a-root-id'
1970
root_id = b'a-root-id'
1812
1971
# Only the kind was changed (content)
1813
self.assertEqual([('a-id', True,
1972
self.assertEqual([(b'a-id', True,
1973
((u'a', [u'a', u'a']), u'a', u'a'),
1814
1974
((root_id, [root_id, root_id]), root_id, root_id),
1815
1975
((u'a', [u'a', u'a']), u'a', u'a'),
1816
1976
((False, [False, False]), False, False)),
1819
1979
def test_this_changed_kind(self):
1820
1980
# Identical content, but THIS changes a file to a directory
1821
1981
builder = self.get_builder()
1822
builder.build_snapshot('A-id', None,
1823
[('add', (u'', 'a-root-id', 'directory', None)),
1824
('add', (u'a', 'a-id', 'file', 'content\n'))])
1825
builder.build_snapshot('B-id', ['A-id'], [])
1826
builder.build_snapshot('C-id', ['A-id'], [])
1827
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1828
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1829
[('unversion', 'a-id'),
1830
('add', (u'a', 'a-id', 'directory', None))])
1831
merge_obj = self.make_merge_obj(builder, 'E-id')
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')
1832
1995
entries = list(merge_obj._entries_lca())
1833
1996
# Only the kind was changed (content)
1834
1997
self.assertEqual([], entries)
1836
1999
def test_interesting_files(self):
1837
2000
# Two files modified, but we should filter one of them
1838
2001
builder = self.get_builder()
1839
builder.build_snapshot('A-id', None,
1840
[('add', (u'', 'a-root-id', 'directory', None)),
1841
('add', (u'a', 'a-id', 'file', 'content\n')),
1842
('add', (u'b', 'b-id', 'file', 'content\n'))])
1843
builder.build_snapshot('B-id', ['A-id'], [])
1844
builder.build_snapshot('C-id', ['A-id'], [])
1845
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1846
[('modify', ('a-id', 'new-content\n')),
1847
('modify', ('b-id', 'new-content\n'))])
1848
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1849
merge_obj = self.make_merge_obj(builder, 'E-id',
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',
1850
2015
interesting_files=['b'])
1851
2016
entries = list(merge_obj._entries_lca())
1852
root_id = 'a-root-id'
1853
self.assertEqual([('b-id', True,
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'),
1854
2020
((root_id, [root_id, root_id]), root_id, root_id),
1855
2021
((u'b', [u'b', u'b']), u'b', u'b'),
1856
2022
((False, [False, False]), False, False)),
1859
2025
def test_interesting_file_in_this(self):
1860
2026
# This renamed the file, but it should still match the entry in other
1861
2027
builder = self.get_builder()
1862
builder.build_snapshot('A-id', None,
1863
[('add', (u'', 'a-root-id', 'directory', None)),
1864
('add', (u'a', 'a-id', 'file', 'content\n')),
1865
('add', (u'b', 'b-id', 'file', 'content\n'))])
1866
builder.build_snapshot('B-id', ['A-id'], [])
1867
builder.build_snapshot('C-id', ['A-id'], [])
1868
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1869
[('modify', ('a-id', 'new-content\n')),
1870
('modify', ('b-id', 'new-content\n'))])
1871
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1872
[('rename', ('b', 'c'))])
1873
merge_obj = self.make_merge_obj(builder, 'E-id',
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',
1874
2043
interesting_files=['c'])
1875
2044
entries = list(merge_obj._entries_lca())
1876
root_id = 'a-root-id'
1877
self.assertEqual([('b-id', True,
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'),
1878
2048
((root_id, [root_id, root_id]), root_id, root_id),
1879
2049
((u'b', [u'b', u'b']), u'b', u'c'),
1880
2050
((False, [False, False]), False, False)),
1883
2053
def test_interesting_file_in_base(self):
1884
2054
# This renamed the file, but it should still match the entry in BASE
1885
2055
builder = self.get_builder()
1886
builder.build_snapshot('A-id', None,
1887
[('add', (u'', 'a-root-id', 'directory', None)),
1888
('add', (u'a', 'a-id', 'file', 'content\n')),
1889
('add', (u'c', 'c-id', 'file', 'content\n'))])
1890
builder.build_snapshot('B-id', ['A-id'],
1891
[('rename', ('c', 'b'))])
1892
builder.build_snapshot('C-id', ['A-id'],
1893
[('rename', ('c', 'b'))])
1894
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1895
[('modify', ('a-id', 'new-content\n')),
1896
('modify', ('c-id', 'new-content\n'))])
1897
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1898
merge_obj = self.make_merge_obj(builder, 'E-id',
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',
1899
2073
interesting_files=['c'])
1900
2074
entries = list(merge_obj._entries_lca())
1901
root_id = 'a-root-id'
1902
self.assertEqual([('c-id', True,
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'),
1903
2078
((root_id, [root_id, root_id]), root_id, root_id),
1904
2079
((u'c', [u'b', u'b']), u'b', u'b'),
1905
2080
((False, [False, False]), False, False)),
1908
2083
def test_interesting_file_in_lca(self):
1909
2084
# This renamed the file, but it should still match the entry in LCA
1910
2085
builder = self.get_builder()
1911
builder.build_snapshot('A-id', None,
1912
[('add', (u'', 'a-root-id', 'directory', None)),
1913
('add', (u'a', 'a-id', 'file', 'content\n')),
1914
('add', (u'b', 'b-id', 'file', 'content\n'))])
1915
builder.build_snapshot('B-id', ['A-id'],
1916
[('rename', ('b', 'c'))])
1917
builder.build_snapshot('C-id', ['A-id'], [])
1918
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1919
[('modify', ('a-id', 'new-content\n')),
1920
('modify', ('b-id', 'new-content\n'))])
1921
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1922
[('rename', ('c', 'b'))])
1923
merge_obj = self.make_merge_obj(builder, 'E-id',
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',
1924
2101
interesting_files=['c'])
1925
2102
entries = list(merge_obj._entries_lca())
1926
root_id = 'a-root-id'
1927
self.assertEqual([('b-id', True,
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'),
1928
2106
((root_id, [root_id, root_id]), root_id, root_id),
1929
2107
((u'b', [u'c', u'b']), u'b', u'b'),
1930
2108
((False, [False, False]), False, False)),
1933
def test_interesting_ids(self):
2111
def test_interesting_files(self):
1934
2112
# Two files modified, but we should filter one of them
1935
2113
builder = self.get_builder()
1936
builder.build_snapshot('A-id', None,
1937
[('add', (u'', 'a-root-id', 'directory', None)),
1938
('add', (u'a', 'a-id', 'file', 'content\n')),
1939
('add', (u'b', 'b-id', 'file', 'content\n'))])
1940
builder.build_snapshot('B-id', ['A-id'], [])
1941
builder.build_snapshot('C-id', ['A-id'], [])
1942
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1943
[('modify', ('a-id', 'new-content\n')),
1944
('modify', ('b-id', 'new-content\n'))])
1945
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1946
merge_obj = self.make_merge_obj(builder, 'E-id',
1947
interesting_ids=['b-id'])
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'])
1948
2127
entries = list(merge_obj._entries_lca())
1949
root_id = 'a-root-id'
1950
self.assertEqual([('b-id', True,
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'),
1951
2131
((root_id, [root_id, root_id]), root_id, root_id),
1952
2132
((u'b', [u'b', u'b']), u'b', u'b'),
1953
2133
((False, [False, False]), False, False)),
1958
2137
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
2223
2409
wt.lock_write()
2224
2410
self.addCleanup(wt.unlock)
2225
2411
os.symlink('bar', 'path/foo')
2226
wt.add(['foo'], ['foo-id'])
2227
wt.commit('A add symlink', rev_id='A-id')
2412
wt.add(['foo'], [b'foo-id'])
2413
wt.commit('A add symlink', rev_id=b'A-id')
2228
2414
wt.rename_one('foo', 'barry')
2229
wt.commit('B foo => barry', rev_id='B-id')
2230
wt.set_last_revision('A-id')
2231
wt.branch.set_last_revision_info(1, 'A-id')
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')
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')
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')
2238
2424
wt.rename_one('barry', 'blah')
2239
wt.commit('F barry => blah', rev_id='F-id')
2240
wt.set_last_revision('B-id')
2241
wt.branch.set_last_revision_info(2, 'B-id')
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')
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'))
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'))
2246
2432
# Check the output of the Merger object directly
2247
merger = _mod_merge.Merger.from_revision_ids(None,
2433
merger = _mod_merge.Merger.from_revision_ids(wt, b'F-id')
2249
2434
merger.merge_type = _mod_merge.Merge3Merger
2250
2435
merge_obj = merger.make_merger()
2251
2436
root_id = wt.path2id('')
2252
2437
entries = list(merge_obj._entries_lca())
2253
2438
# No content change, just a path change
2254
self.assertEqual([('foo-id', False,
2439
self.assertEqual([(b'foo-id', False,
2440
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2255
2441
((root_id, [root_id, root_id]), root_id, root_id),
2256
2442
((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
2257
2443
((False, [False, False]), False, False)),
2259
conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
2445
conflicts = wt.merge_from_branch(wt.branch, to_revision=b'F-id')
2260
2446
self.assertEqual(0, conflicts)
2261
self.assertEqual('blah', wt.id2path('foo-id'))
2447
self.assertEqual('blah', wt.id2path(b'foo-id'))
2263
2449
def test_symlink_no_content_change(self):
2264
self.requireFeature(tests.SymlinkFeature)
2450
self.requireFeature(features.SymlinkFeature)
2265
2451
# A Create symlink foo => bar
2267
2453
# B C B relinks foo => baz
2326
2511
wt = self.make_branch_and_tree('path')
2327
2512
wt.lock_write()
2328
2513
self.addCleanup(wt.unlock)
2329
wt.commit('base', rev_id='A-id')
2514
wt.commit('base', rev_id=b'A-id')
2330
2515
os.symlink('bar', 'path/foo')
2331
wt.add(['foo'], ['foo-id'])
2332
wt.commit('add symlink foo => bar', rev_id='B-id')
2333
wt.set_last_revision('A-id')
2334
wt.branch.set_last_revision_info(1, 'A-id')
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')
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'))
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'))
2339
2524
os.remove('path/foo')
2340
2525
# We have to change the link in E, or it won't try to do a comparison
2341
2526
os.symlink('bing', 'path/foo')
2342
wt.commit('E merges C & B, overrides to bing', rev_id='E-id')
2343
wt.set_last_revision('B-id')
2344
wt.branch.set_last_revision_info(2, 'B-id')
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')
2346
wt.merge_from_branch(wt.branch, 'C-id')
2531
wt.merge_from_branch(wt.branch, b'C-id')
2347
2532
os.remove('path/foo')
2348
self.build_tree_contents([('path/foo', 'file content\n')])
2533
self.build_tree_contents([('path/foo', b'file content\n')])
2349
2534
# XXX: workaround, WT doesn't detect kind changes unless you do
2350
2535
# iter_changes()
2351
2536
list(wt.iter_changes(wt.basis_tree()))
2352
wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2537
wt.commit('D merges B & C, makes it a file', rev_id=b'D-id')
2354
merger = _mod_merge.Merger.from_revision_ids(None,
2539
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2356
2540
merger.merge_type = _mod_merge.Merge3Merger
2357
2541
merge_obj = merger.make_merger()
2358
2542
entries = list(merge_obj._entries_lca())
2359
2543
root_id = wt.path2id('')
2360
self.assertEqual([('foo-id', True,
2544
self.assertEqual([(b'foo-id', True,
2545
((None, [u'foo', None]), u'foo', u'foo'),
2361
2546
((None, [root_id, None]), root_id, root_id),
2362
2547
((None, [u'foo', None]), u'foo', u'foo'),
2363
2548
((None, [False, None]), False, False)),
2366
2551
def test_symlink_all_wt(self):
2367
2552
"""Check behavior if all trees are Working Trees."""
2368
self.requireFeature(tests.SymlinkFeature)
2553
self.requireFeature(features.SymlinkFeature)
2369
2554
# The big issue is that entry.symlink_target is None for WorkingTrees.
2370
2555
# So we need to make sure we handle that case correctly.
2382
2567
wt.lock_write()
2383
2568
self.addCleanup(wt.unlock)
2384
2569
os.symlink('bar', 'path/foo')
2385
wt.add(['foo'], ['foo-id'])
2386
wt.commit('add symlink', rev_id='A-id')
2570
wt.add(['foo'], [b'foo-id'])
2571
wt.commit('add symlink', rev_id=b'A-id')
2387
2572
os.remove('path/foo')
2388
2573
os.symlink('baz', 'path/foo')
2389
wt.commit('foo => baz', rev_id='B-id')
2390
wt.set_last_revision('A-id')
2391
wt.branch.set_last_revision_info(1, 'A-id')
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')
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')
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')
2397
2582
os.remove('path/foo')
2398
2583
os.symlink('bing', 'path/foo')
2399
wt.commit('F foo => bing', rev_id='F-id')
2400
wt.set_last_revision('B-id')
2401
wt.branch.set_last_revision_info(2, 'B-id')
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')
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()
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()
2406
2591
wt_base.lock_read()
2407
2592
self.addCleanup(wt_base.unlock)
2408
wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2593
wt_lca1 = wt.controldir.sprout('b-tree', b'B-id').open_workingtree()
2409
2594
wt_lca1.lock_read()
2410
2595
self.addCleanup(wt_lca1.unlock)
2411
wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2596
wt_lca2 = wt.controldir.sprout('c-tree', b'C-id').open_workingtree()
2412
2597
wt_lca2.lock_read()
2413
2598
self.addCleanup(wt_lca2.unlock)
2414
wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
2599
wt_other = wt.controldir.sprout('other', b'F-id').open_workingtree()
2415
2600
wt_other.lock_read()
2416
2601
self.addCleanup(wt_other.unlock)
2417
2602
merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
2418
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2603
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2419
2604
entries = list(merge_obj._entries_lca())
2420
2605
root_id = wt.path2id('')
2421
self.assertEqual([('foo-id', True,
2606
self.assertEqual([(b'foo-id', True,
2607
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2422
2608
((root_id, [root_id, root_id]), root_id, root_id),
2423
2609
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2424
2610
((False, [False, False]), False, False)),
2427
2613
def test_other_reverted_path_to_base(self):
2428
2614
# A Path at 'foo'
2436
2622
# F Path at 'foo'
2437
2623
builder = self.get_builder()
2438
builder.build_snapshot('A-id', None,
2439
[('add', (u'', 'a-root-id', 'directory', None)),
2440
('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
2441
builder.build_snapshot('C-id', ['A-id'], [])
2442
builder.build_snapshot('B-id', ['A-id'],
2443
[('rename', ('foo', 'bar'))])
2444
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2445
[('rename', ('foo', 'bar'))]) # merge the rename
2446
builder.build_snapshot('F-id', ['E-id'],
2447
[('rename', ('bar', 'foo'))]) # Rename back to BASE
2448
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2449
wt, conflicts = self.do_merge(builder, 'F-id')
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')
2450
2637
self.assertEqual(0, conflicts)
2451
self.assertEqual('foo', wt.id2path('foo-id'))
2638
self.assertEqual('foo', wt.id2path(b'foo-id'))
2453
2640
def test_other_reverted_content_to_base(self):
2454
2641
builder = self.get_builder()
2455
builder.build_snapshot('A-id', None,
2456
[('add', (u'', 'a-root-id', 'directory', None)),
2457
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2458
builder.build_snapshot('C-id', ['A-id'], [])
2459
builder.build_snapshot('B-id', ['A-id'],
2460
[('modify', ('foo-id', 'B content\n'))])
2461
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2462
[('modify', ('foo-id', 'B content\n'))]) # merge the content
2463
builder.build_snapshot('F-id', ['E-id'],
2464
[('modify', ('foo-id', 'base content\n'))]) # Revert back to BASE
2465
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2466
wt, conflicts = self.do_merge(builder, 'F-id')
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')
2467
2658
self.assertEqual(0, conflicts)
2468
2659
# TODO: We need to use the per-file graph to properly select a BASE
2469
2660
# before this will work. Or at least use the LCA trees to find
2470
2661
# the appropriate content base. (which is B, not A).
2471
self.assertEqual('base content\n', wt.get_file_text('foo-id'))
2662
self.assertEqual(b'base content\n', wt.get_file_text('foo'))
2473
2664
def test_other_modified_content(self):
2474
2665
builder = self.get_builder()
2475
builder.build_snapshot('A-id', None,
2476
[('add', (u'', 'a-root-id', 'directory', None)),
2477
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2478
builder.build_snapshot('C-id', ['A-id'], [])
2479
builder.build_snapshot('B-id', ['A-id'],
2480
[('modify', ('foo-id', 'B content\n'))])
2481
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2482
[('modify', ('foo-id', 'B content\n'))]) # merge the content
2483
builder.build_snapshot('F-id', ['E-id'],
2484
[('modify', ('foo-id', 'F content\n'))]) # Override B content
2485
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2486
wt, conflicts = self.do_merge(builder, 'F-id')
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')
2487
2682
self.assertEqual(0, conflicts)
2488
self.assertEqual('F content\n', wt.get_file_text('foo-id'))
2683
self.assertEqual(b'F content\n', wt.get_file_text('foo'))
2490
2685
def test_all_wt(self):
2491
2686
"""Check behavior if all trees are Working Trees."""
2499
2694
# D E E updates content, renames 'b' => 'c'
2500
2695
builder = self.get_builder()
2501
builder.build_snapshot('A-id', None,
2502
[('add', (u'', 'a-root-id', 'directory', None)),
2503
('add', (u'a', 'a-id', 'file', 'base content\n')),
2504
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2505
builder.build_snapshot('B-id', ['A-id'],
2506
[('modify', ('foo-id', 'B content\n'))])
2507
builder.build_snapshot('C-id', ['A-id'],
2508
[('rename', ('a', 'b'))])
2509
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2510
[('rename', ('b', 'c')),
2511
('modify', ('foo-id', 'E content\n'))])
2512
builder.build_snapshot('D-id', ['B-id', 'C-id'],
2513
[('rename', ('a', 'b'))]) # merged change
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
2514
2713
wt_this = self.get_wt_from_builder(builder)
2515
wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
2714
wt_base = wt_this.controldir.sprout('base', b'A-id').open_workingtree()
2516
2715
wt_base.lock_read()
2517
2716
self.addCleanup(wt_base.unlock)
2518
wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2717
wt_lca1 = wt_this.controldir.sprout(
2718
'b-tree', b'B-id').open_workingtree()
2519
2719
wt_lca1.lock_read()
2520
2720
self.addCleanup(wt_lca1.unlock)
2521
wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2721
wt_lca2 = wt_this.controldir.sprout(
2722
'c-tree', b'C-id').open_workingtree()
2522
2723
wt_lca2.lock_read()
2523
2724
self.addCleanup(wt_lca2.unlock)
2524
wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
2725
wt_other = wt_this.controldir.sprout(
2726
'other', b'E-id').open_workingtree()
2525
2727
wt_other.lock_read()
2526
2728
self.addCleanup(wt_other.unlock)
2527
2729
merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
2528
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2730
wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
2529
2731
entries = list(merge_obj._entries_lca())
2530
root_id = 'a-root-id'
2531
self.assertEqual([('a-id', False,
2532
((root_id, [root_id, root_id]), root_id, root_id),
2533
((u'a', [u'a', u'b']), u'c', u'b'),
2534
((False, [False, False]), False, False)),
2536
((root_id, [root_id, root_id]), root_id, root_id),
2537
((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
2538
((False, [False, False]), False, False)),
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)),
2541
2745
def test_nested_tree_unmodified(self):
2542
2746
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2543
2747
# 'tree-reference'
2544
2748
wt = self.make_branch_and_tree('tree',
2545
format='dirstate-with-subtree')
2749
format='development-subtree')
2546
2750
wt.lock_write()
2547
2751
self.addCleanup(wt.unlock)
2548
2752
sub_tree = self.make_branch_and_tree('tree/sub-tree',
2549
format='dirstate-with-subtree')
2550
wt.set_root_id('a-root-id')
2551
sub_tree.set_root_id('sub-tree-root')
2552
self.build_tree_contents([('tree/sub-tree/file', 'text1')])
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')])
2553
2757
sub_tree.add('file')
2554
sub_tree.commit('foo', rev_id='sub-A-id')
2758
sub_tree.commit('foo', rev_id=b'sub-A-id')
2555
2759
wt.add_reference(sub_tree)
2556
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2760
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2557
2761
# Now create a criss-cross merge in the parent, without modifying the
2559
wt.commit('B', rev_id='B-id', recursive=None)
2560
wt.set_last_revision('A-id')
2561
wt.branch.set_last_revision_info(1, 'A-id')
2562
wt.commit('C', rev_id='C-id', recursive=None)
2563
wt.merge_from_branch(wt.branch, to_revision='B-id')
2564
wt.commit('E', rev_id='E-id', recursive=None)
2565
wt.set_parent_ids(['B-id', 'C-id'])
2566
wt.branch.set_last_revision_info(2, 'B-id')
2567
wt.commit('D', rev_id='D-id', recursive=None)
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)
2569
merger = _mod_merge.Merger.from_revision_ids(None,
2773
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2571
2774
merger.merge_type = _mod_merge.Merge3Merger
2572
2775
merge_obj = merger.make_merger()
2573
2776
entries = list(merge_obj._entries_lca())
2616
2818
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2617
2819
# 'tree-reference'
2618
2820
wt = self.make_branch_and_tree('tree',
2619
format='dirstate-with-subtree')
2821
format='development-subtree')
2620
2822
wt.lock_write()
2621
2823
self.addCleanup(wt.unlock)
2622
2824
sub_tree = self.make_branch_and_tree('tree/sub',
2623
format='dirstate-with-subtree')
2624
wt.set_root_id('a-root-id')
2625
sub_tree.set_root_id('sub-tree-root')
2626
self.build_tree_contents([('tree/sub/file', 'text1')])
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')])
2627
2829
sub_tree.add('file')
2628
sub_tree.commit('foo', rev_id='sub-A-id')
2830
sub_tree.commit('foo', rev_id=b'sub-A-id')
2629
2831
wt.add_reference(sub_tree)
2630
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2832
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2631
2833
# Now create a criss-cross merge in the parent, without modifying the
2633
wt.commit('B', rev_id='B-id', recursive=None)
2634
wt.set_last_revision('A-id')
2635
wt.branch.set_last_revision_info(1, 'A-id')
2636
wt.commit('C', rev_id='C-id', recursive=None)
2637
wt.merge_from_branch(wt.branch, to_revision='B-id')
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')
2638
2840
wt.rename_one('sub', 'alt_sub')
2639
wt.commit('E', rev_id='E-id', recursive=None)
2640
wt.set_last_revision('B-id')
2841
wt.commit('E', rev_id=b'E-id', recursive=None)
2842
wt.set_last_revision(b'B-id')
2642
wt.set_parent_ids(['B-id', 'C-id'])
2643
wt.branch.set_last_revision_info(2, 'B-id')
2644
wt.commit('D', rev_id='D-id', recursive=None)
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)
2646
merger = _mod_merge.Merger.from_revision_ids(None,
2848
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2648
2849
merger.merge_type = _mod_merge.Merge3Merger
2649
2850
merge_obj = merger.make_merger()
2650
2851
entries = list(merge_obj._entries_lca())
2651
root_id = 'a-root-id'
2652
self.assertEqual([('sub-tree-root', False,
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'),
2653
2855
((root_id, [root_id, root_id]), root_id, root_id),
2654
2856
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2655
2857
((False, [False, False]), False, False)),
2658
2860
def test_nested_tree_subtree_renamed_and_modified(self):
2659
2861
# Tested with a real WT, because BranchBuilder/MemoryTree don't handle
2660
2862
# 'tree-reference'
2661
2863
wt = self.make_branch_and_tree('tree',
2662
format='dirstate-with-subtree')
2864
format='development-subtree')
2663
2865
wt.lock_write()
2664
2866
self.addCleanup(wt.unlock)
2665
2867
sub_tree = self.make_branch_and_tree('tree/sub',
2666
format='dirstate-with-subtree')
2667
wt.set_root_id('a-root-id')
2668
sub_tree.set_root_id('sub-tree-root')
2669
self.build_tree_contents([('tree/sub/file', 'text1')])
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')])
2670
2872
sub_tree.add('file')
2671
sub_tree.commit('foo', rev_id='sub-A-id')
2873
sub_tree.commit('foo', rev_id=b'sub-A-id')
2672
2874
wt.add_reference(sub_tree)
2673
wt.commit('set text to 1', rev_id='A-id', recursive=None)
2875
wt.commit('set text to 1', rev_id=b'A-id', recursive=None)
2674
2876
# Now create a criss-cross merge in the parent, without modifying the
2676
wt.commit('B', rev_id='B-id', recursive=None)
2677
wt.set_last_revision('A-id')
2678
wt.branch.set_last_revision_info(1, 'A-id')
2679
wt.commit('C', rev_id='C-id', recursive=None)
2680
wt.merge_from_branch(wt.branch, to_revision='B-id')
2681
self.build_tree_contents([('tree/sub/file', 'text2')])
2682
sub_tree.commit('modify contents', rev_id='sub-B-id')
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')
2683
2885
wt.rename_one('sub', 'alt_sub')
2684
wt.commit('E', rev_id='E-id', recursive=None)
2685
wt.set_last_revision('B-id')
2886
wt.commit('E', rev_id=b'E-id', recursive=None)
2887
wt.set_last_revision(b'B-id')
2687
wt.set_parent_ids(['B-id', 'C-id'])
2688
wt.branch.set_last_revision_info(2, 'B-id')
2689
wt.commit('D', rev_id='D-id', recursive=None)
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)
2691
merger = _mod_merge.Merger.from_revision_ids(None,
2893
merger = _mod_merge.Merger.from_revision_ids(wt, b'E-id')
2693
2894
merger.merge_type = _mod_merge.Merge3Merger
2694
2895
merge_obj = merger.make_merger()
2695
2896
entries = list(merge_obj._entries_lca())
2696
root_id = 'a-root-id'
2697
self.assertEqual([('sub-tree-root', False,
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'),
2698
2900
((root_id, [root_id, root_id]), root_id, root_id),
2699
2901
((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
2700
2902
((False, [False, False]), False, False)),
2704
2906
class TestLCAMultiWay(tests.TestCase):
2706
2908
def assertLCAMultiWay(self, expected, base, lcas, other, this,
2707
2909
allow_overriding_lca=True):
2708
2910
self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
2709
(base, lcas), other, this,
2710
allow_overriding_lca=allow_overriding_lca))
2911
(base, lcas), other, this,
2912
allow_overriding_lca=allow_overriding_lca))
2712
2914
def test_other_equal_equal_lcas(self):
2713
2915
"""Test when OTHER=LCA and all LCAs are identical."""
2714
2916
self.assertLCAMultiWay('this',
2715
'bval', ['bval', 'bval'], 'bval', 'bval')
2716
self.assertLCAMultiWay('this',
2717
'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
2718
self.assertLCAMultiWay('this',
2719
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
2720
self.assertLCAMultiWay('this',
2721
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
2722
self.assertLCAMultiWay('this',
2723
'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
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)
2725
2927
def test_other_equal_this(self):
2726
2928
"""Test when other and this are identical."""
2727
2929
self.assertLCAMultiWay('this',
2728
'bval', ['bval', 'bval'], 'oval', 'oval')
2729
self.assertLCAMultiWay('this',
2730
'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
2731
self.assertLCAMultiWay('this',
2732
'bval', ['cval', 'dval'], 'oval', 'oval')
2733
self.assertLCAMultiWay('this',
2734
'bval', [None, 'lcaval'], 'oval', 'oval')
2735
self.assertLCAMultiWay('this',
2736
None, [None, 'lcaval'], 'oval', 'oval')
2737
self.assertLCAMultiWay('this',
2738
None, ['lcaval', 'lcaval'], 'oval', 'oval')
2739
self.assertLCAMultiWay('this',
2740
None, ['cval', 'dval'], 'oval', 'oval')
2741
self.assertLCAMultiWay('this',
2742
None, ['cval', 'dval'], None, None)
2743
self.assertLCAMultiWay('this',
2744
None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
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')
2746
2948
def test_no_lcas(self):
2747
2949
self.assertLCAMultiWay('this',
2748
'bval', [], 'bval', 'tval')
2950
'bval', [], 'bval', 'tval')
2749
2951
self.assertLCAMultiWay('other',
2750
'bval', [], 'oval', 'bval')
2952
'bval', [], 'oval', 'bval')
2751
2953
self.assertLCAMultiWay('conflict',
2752
'bval', [], 'oval', 'tval')
2954
'bval', [], 'oval', 'tval')
2753
2955
self.assertLCAMultiWay('this',
2754
'bval', [], 'oval', 'oval')
2956
'bval', [], 'oval', 'oval')
2756
2958
def test_lca_supersedes_other_lca(self):
2757
2959
"""If one lca == base, the other lca takes precedence"""
2758
2960
self.assertLCAMultiWay('this',
2759
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2961
'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
2760
2962
self.assertLCAMultiWay('this',
2761
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2963
'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
2762
2964
# This is actually considered a 'revert' because the 'lcaval' in LCAS
2763
2965
# supersedes the BASE val (in the other LCA) but then OTHER reverts it
2764
2966
# back to bval.
2765
2967
self.assertLCAMultiWay('other',
2766
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2968
'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
2767
2969
self.assertLCAMultiWay('conflict',
2768
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2970
'bval', ['bval', 'lcaval'], 'bval', 'tval')
2770
2972
def test_other_and_this_pick_different_lca(self):
2771
2973
# OTHER and THIS resolve the lca conflict in different ways
2772
2974
self.assertLCAMultiWay('conflict',
2773
'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
2774
self.assertLCAMultiWay('conflict',
2775
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
2776
self.assertLCAMultiWay('conflict',
2777
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
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')
2779
2981
def test_other_in_lca(self):
2780
2982
# OTHER takes a value of one of the LCAs, THIS takes a new value, which
2781
2983
# theoretically supersedes both LCA values and 'wins'
2782
self.assertLCAMultiWay('this',
2783
'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
2784
self.assertLCAMultiWay('this',
2785
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval')
2786
self.assertLCAMultiWay('conflict',
2787
'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval',
2788
allow_overriding_lca=False)
2789
self.assertLCAMultiWay('conflict',
2790
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval',
2791
allow_overriding_lca=False)
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)
2792
2997
# THIS reverted back to BASE, but that is an explicit supersede of all
2794
self.assertLCAMultiWay('this',
2795
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval')
2796
self.assertLCAMultiWay('this',
2797
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
2798
self.assertLCAMultiWay('conflict',
2799
'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval',
2800
allow_overriding_lca=False)
2801
self.assertLCAMultiWay('conflict',
2802
'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval',
2803
allow_overriding_lca=False)
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)
2805
3013
def test_this_in_lca(self):
2806
3014
# THIS takes a value of one of the LCAs, OTHER takes a new value, which
2807
3015
# theoretically supersedes both LCA values and 'wins'
2808
self.assertLCAMultiWay('other',
2809
'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
2810
self.assertLCAMultiWay('other',
2811
'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
2812
self.assertLCAMultiWay('conflict',
2813
'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val',
2814
allow_overriding_lca=False)
2815
self.assertLCAMultiWay('conflict',
2816
'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val',
2817
allow_overriding_lca=False)
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)
2818
3028
# OTHER reverted back to BASE, but that is an explicit supersede of all
2820
self.assertLCAMultiWay('other',
2821
'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val')
2822
self.assertLCAMultiWay('conflict',
2823
'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val',
2824
allow_overriding_lca=False)
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)
2826
3037
def test_all_differ(self):
2827
self.assertLCAMultiWay('conflict',
2828
'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
2829
self.assertLCAMultiWay('conflict',
2830
'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
2831
self.assertLCAMultiWay('conflict',
2832
'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')
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',
2835
3048
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
2903
3117
def test_hook_called_for_text_conflicts(self):
2904
3118
builder = self.make_text_conflict()
2905
conflicts = builder.merge()
2906
3120
# The hook should call the merge_text() method
2907
3121
self.assertEqual(['merge_text'], self.calls)
2909
3123
def test_hook_not_called_for_kind_change(self):
2910
3124
builder = self.make_kind_change()
2911
conflicts = builder.merge()
2912
3126
# The hook should not call the merge_text() method
2913
3127
self.assertEqual([], self.calls)
2915
3129
def test_hook_not_called_for_other_files(self):
2916
3130
builder = self.make_text_conflict('foobar')
2917
conflicts = builder.merge()
2918
3132
# The hook should not call the merge_text() method
2919
3133
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)