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