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