~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-10 15:46:03 UTC
  • mfrom: (4985.3.21 update)
  • mto: This revision was merged to the branch mainline in revision 5021.
  • Revision ID: v.ladeuil+lp@free.fr-20100210154603-k4no1gvfuqpzrw7p
Update performs two merges in a more logical order but stop on conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
from StringIO import StringIO
29
29
    transform,
30
30
    versionedfile,
31
31
    )
32
 
from bzrlib.branch import Branch
33
32
from bzrlib.conflicts import ConflictList, TextConflict
34
 
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
 
33
from bzrlib.errors import UnrelatedBranches, NoCommits
35
34
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
36
35
from bzrlib.osutils import pathjoin, file_kind
37
 
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
38
 
from bzrlib.trace import (enable_test_log, disable_test_log)
 
36
from bzrlib.tests import (
 
37
    TestCaseWithMemoryTransport,
 
38
    TestCaseWithTransport,
 
39
    test_merge_core,
 
40
    )
39
41
from bzrlib.workingtree import WorkingTree
40
42
 
41
43
 
89
91
        self.failIfExists('bar')
90
92
        wt2 = WorkingTree.open('.') # opens branch2
91
93
        self.assertEqual([tip], wt2.get_parent_ids())
92
 
        
 
94
 
93
95
    def test_pending_with_null(self):
94
96
        """When base is forced to revno 0, parent_ids are set"""
95
97
        wt2 = self.test_unrelated()
96
98
        wt1 = WorkingTree.open('.')
97
99
        br1 = wt1.branch
98
100
        br1.fetch(wt2.branch)
99
 
        # merge all of branch 2 into branch 1 even though they 
 
101
        # merge all of branch 2 into branch 1 even though they
100
102
        # are not related.
101
103
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
102
104
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
150
152
        self.addCleanup(tree_b.unlock)
151
153
        tree_a.commit(message="hello again")
152
154
        log = StringIO()
153
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
155
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
154
156
                    this_tree=tree_b, ignore_zero=True)
155
 
        log = self._get_log(keep_log_file=True)
156
 
        self.failUnless('All changes applied successfully.\n' not in log)
 
157
        self.failUnless('All changes applied successfully.\n' not in
 
158
            self.get_log())
157
159
        tree_b.revert()
158
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
160
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
159
161
                    this_tree=tree_b, ignore_zero=False)
160
 
        log = self._get_log(keep_log_file=True)
161
 
        self.failUnless('All changes applied successfully.\n' in log)
 
162
        self.failUnless('All changes applied successfully.\n' in self.get_log())
162
163
 
163
164
    def test_merge_inner_conflicts(self):
164
165
        tree_a = self.make_branch_and_tree('a')
219
220
        tree_a.add('file')
220
221
        tree_a.commit('commit base')
221
222
        # basis_tree() is only guaranteed to be valid as long as it is actually
222
 
        # the basis tree. This mutates the tree after grabbing basis, so go to
223
 
        # the repository.
 
223
        # the basis tree. This test commits to the tree after grabbing basis,
 
224
        # so we go to the repository.
224
225
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
225
226
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
226
227
        self.build_tree_contents([('tree_a/file', 'content_2')])
227
228
        tree_a.commit('commit other')
228
229
        other_tree = tree_a.basis_tree()
 
230
        # 'file' is now missing but isn't altered in any commit in b so no
 
231
        # change should be applied.
229
232
        os.unlink('tree_b/file')
230
233
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
231
234
 
248
251
        self.assertEqual(tree_b.conflicts(),
249
252
                         [conflicts.ContentsConflict('file',
250
253
                          file_id='file-id')])
251
 
    
 
254
 
252
255
    def test_merge_type_registry(self):
253
256
        merge_type_option = option.Option.OPTIONS['merge-type']
254
 
        self.assertFalse('merge4' in [x[0] for x in 
 
257
        self.assertFalse('merge4' in [x[0] for x in
255
258
                        merge_type_option.iter_switches()])
256
259
        registry = _mod_merge.get_merge_type_registry()
257
260
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
258
261
                               'time-travelling merge')
259
 
        self.assertTrue('merge4' in [x[0] for x in 
 
262
        self.assertTrue('merge4' in [x[0] for x in
260
263
                        merge_type_option.iter_switches()])
261
264
        registry.remove('merge4')
262
 
        self.assertFalse('merge4' in [x[0] for x in 
 
265
        self.assertFalse('merge4' in [x[0] for x in
263
266
                        merge_type_option.iter_switches()])
264
267
 
265
268
    def test_merge_other_moves_we_deleted(self):
292
295
        tree_a.commit('commit 2')
293
296
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
294
297
        tree_b.rename_one('file_1', 'renamed')
295
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
296
 
                                                    progress.DummyProgress())
 
298
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
297
299
        merger.merge_type = _mod_merge.Merge3Merger
298
300
        merger.do_merge()
299
301
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
307
309
        tree_a.commit('commit 2')
308
310
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
309
311
        tree_b.rename_one('file_1', 'renamed')
310
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
311
 
                                                    progress.DummyProgress())
 
312
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
312
313
        merger.merge_type = _mod_merge.WeaveMerger
313
314
        merger.do_merge()
314
315
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
339
340
 
340
341
    def test_weave_cherrypick(self):
341
342
        this_tree, other_tree = self.prepare_cherrypick()
342
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
343
        merger = _mod_merge.Merger.from_revision_ids(None,
343
344
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
344
345
        merger.merge_type = _mod_merge.WeaveMerger
345
346
        merger.do_merge()
347
348
 
348
349
    def test_weave_cannot_reverse_cherrypick(self):
349
350
        this_tree, other_tree = self.prepare_cherrypick()
350
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
351
        merger = _mod_merge.Merger.from_revision_ids(None,
351
352
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
352
353
        merger.merge_type = _mod_merge.WeaveMerger
353
354
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
354
355
 
355
356
    def test_merge3_can_reverse_cherrypick(self):
356
357
        this_tree, other_tree = self.prepare_cherrypick()
357
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
358
        merger = _mod_merge.Merger.from_revision_ids(None,
358
359
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
359
360
        merger.merge_type = _mod_merge.Merge3Merger
360
361
        merger.do_merge()
372
373
        this_tree.lock_write()
373
374
        self.addCleanup(this_tree.unlock)
374
375
 
375
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
376
        merger = _mod_merge.Merger.from_revision_ids(None,
376
377
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
377
378
        merger.merge_type = _mod_merge.Merge3Merger
378
379
        merger.do_merge()
391
392
        other_tree.commit('rev2', rev_id='rev2b')
392
393
        this_tree.lock_write()
393
394
        self.addCleanup(this_tree.unlock)
394
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
395
        merger = _mod_merge.Merger.from_revision_ids(None,
395
396
            this_tree, 'rev2b', other_branch=other_tree.branch)
396
397
        merger.merge_type = _mod_merge.Merge3Merger
397
398
        tree_merger = merger.make_merger()
411
412
        other_tree.commit('rev2', rev_id='rev2b')
412
413
        this_tree.lock_write()
413
414
        self.addCleanup(this_tree.unlock)
414
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
415
        merger = _mod_merge.Merger.from_revision_ids(None,
415
416
            this_tree, 'rev2b', other_branch=other_tree.branch)
416
417
        merger.merge_type = _mod_merge.Merge3Merger
417
418
        tree_merger = merger.make_merger()
441
442
        other_tree.commit('rev2', rev_id='rev2b')
442
443
        this_tree.lock_write()
443
444
        self.addCleanup(this_tree.unlock)
444
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
445
        merger = _mod_merge.Merger.from_revision_ids(None,
445
446
            this_tree, 'rev2b', other_branch=other_tree.branch)
446
447
        merger.merge_type = _mod_merge.Merge3Merger
447
448
        tree_merger = merger.make_merger()
518
519
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
519
520
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
520
521
 
 
522
    def test_base_from_plan(self):
 
523
        self.setup_plan_merge()
 
524
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
525
        pwm = versionedfile.PlanWeaveMerge(plan)
 
526
        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
 
527
 
521
528
    def test_unique_lines(self):
522
529
        plan = self.setup_plan_merge()
523
530
        self.assertEqual(plan._unique_lines(
718
725
 
719
726
    def test_plan_merge_insert_order(self):
720
727
        """Weave merges are sensitive to the order of insertion.
721
 
        
 
728
 
722
729
        Specifically for overlapping regions, it effects which region gets put
723
730
        'first'. And when a user resolves an overlapping merge, if they use the
724
731
        same ordering, then the lines match the parents, if they don't only
821
828
                          ('unchanged', 'f\n'),
822
829
                          ('unchanged', 'g\n')],
823
830
                         list(plan))
 
831
        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
 
832
        # This is one of the main differences between plan_merge and
 
833
        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
 
834
        # because 'x' was not present in one of the bases. However, in this
 
835
        # case it is spurious because 'x' does not exist in the global base A.
 
836
        self.assertEqual([
 
837
                          ('unchanged', 'h\n'),
 
838
                          ('unchanged', 'a\n'),
 
839
                          ('conflicted-a', 'x\n'),
 
840
                          ('new-b', 'z\n'),
 
841
                          ('unchanged', 'c\n'),
 
842
                          ('unchanged', 'd\n'),
 
843
                          ('unchanged', 'y\n'),
 
844
                          ('unchanged', 'f\n'),
 
845
                          ('unchanged', 'g\n')],
 
846
                         list(plan))
 
847
 
 
848
    def test_criss_cross_flip_flop(self):
 
849
        # This is specificly trying to trigger problems when using limited
 
850
        # ancestry and weaves. The ancestry graph looks like:
 
851
        #       XX      unused ancestor, should not show up in the weave
 
852
        #       |
 
853
        #       A       Unique LCA
 
854
        #      / \  
 
855
        #     B   C     B & C both introduce a new line
 
856
        #     |\ /|  
 
857
        #     | X |  
 
858
        #     |/ \| 
 
859
        #     D   E     B & C are both merged, so both are common ancestors
 
860
        #               In the process of merging, both sides order the new
 
861
        #               lines differently
 
862
        #
 
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'))
 
870
        self.assertEqual([
 
871
                          ('unchanged', 'a\n'),
 
872
                          ('unchanged', 'b\n'),
 
873
                          ('unchanged', 'c\n'),
 
874
                          ('unchanged', 'd\n'),
 
875
                          ('new-b', 'h\n'),
 
876
                          ('unchanged', 'g\n'),
 
877
                          ('killed-b', 'h\n'),
 
878
                          ('unchanged', 'e\n'),
 
879
                          ('unchanged', 'f\n'),
 
880
                         ], plan)
 
881
        pwm = versionedfile.PlanWeaveMerge(plan)
 
882
        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
 
883
                             ''.join(pwm.base_from_plan()))
 
884
        # Reversing the order reverses the merge plan, and final order of 'hg'
 
885
        # => 'gh'
 
886
        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
 
887
        self.assertEqual([
 
888
                          ('unchanged', 'a\n'),
 
889
                          ('unchanged', 'b\n'),
 
890
                          ('unchanged', 'c\n'),
 
891
                          ('unchanged', 'd\n'),
 
892
                          ('new-b', 'g\n'),
 
893
                          ('unchanged', 'h\n'),
 
894
                          ('killed-b', 'g\n'),
 
895
                          ('unchanged', 'e\n'),
 
896
                          ('unchanged', 'f\n'),
 
897
                         ], plan)
 
898
        pwm = versionedfile.PlanWeaveMerge(plan)
 
899
        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
 
900
                             ''.join(pwm.base_from_plan()))
 
901
        # This is where lca differs, in that it (fairly correctly) determines
 
902
        # that there is a conflict because both sides resolved the merge
 
903
        # differently
 
904
        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
 
905
        self.assertEqual([
 
906
                          ('unchanged', 'a\n'),
 
907
                          ('unchanged', 'b\n'),
 
908
                          ('unchanged', 'c\n'),
 
909
                          ('unchanged', 'd\n'),
 
910
                          ('conflicted-b', 'h\n'),
 
911
                          ('unchanged', 'g\n'),
 
912
                          ('conflicted-a', 'h\n'),
 
913
                          ('unchanged', 'e\n'),
 
914
                          ('unchanged', 'f\n'),
 
915
                         ], plan)
 
916
        pwm = versionedfile.PlanWeaveMerge(plan)
 
917
        self.assertEqualDiff('\n'.join('abcdgef') + '\n',
 
918
                             ''.join(pwm.base_from_plan()))
 
919
        # Reversing it changes what line is doubled, but still gives a
 
920
        # double-conflict
 
921
        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
 
922
        self.assertEqual([
 
923
                          ('unchanged', 'a\n'),
 
924
                          ('unchanged', 'b\n'),
 
925
                          ('unchanged', 'c\n'),
 
926
                          ('unchanged', 'd\n'),
 
927
                          ('conflicted-b', 'g\n'),
 
928
                          ('unchanged', 'h\n'),
 
929
                          ('conflicted-a', 'g\n'),
 
930
                          ('unchanged', 'e\n'),
 
931
                          ('unchanged', 'f\n'),
 
932
                         ], plan)
 
933
        pwm = versionedfile.PlanWeaveMerge(plan)
 
934
        self.assertEqualDiff('\n'.join('abcdhef') + '\n',
 
935
                             ''.join(pwm.base_from_plan()))
824
936
 
825
937
    def assertRemoveExternalReferences(self, filtered_parent_map,
826
938
                                       child_map, tails, parent_map):
1026
1138
                         ], list(plan))
1027
1139
 
1028
1140
 
1029
 
class TestMergeImplementation(object):
1030
 
 
1031
 
    def do_merge(self, target_tree, source_tree, **kwargs):
1032
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
1033
 
            target_tree, source_tree.last_revision(),
1034
 
            other_branch=source_tree.branch)
1035
 
        merger.merge_type=self.merge_type
1036
 
        for name, value in kwargs.items():
1037
 
            setattr(merger, name, value)
1038
 
        merger.do_merge()
1039
 
 
1040
 
    def test_merge_specific_file(self):
1041
 
        this_tree = self.make_branch_and_tree('this')
1042
 
        this_tree.lock_write()
1043
 
        self.addCleanup(this_tree.unlock)
1044
 
        self.build_tree_contents([
1045
 
            ('this/file1', 'a\nb\n'),
1046
 
            ('this/file2', 'a\nb\n')
1047
 
        ])
1048
 
        this_tree.add(['file1', 'file2'])
1049
 
        this_tree.commit('Added files')
1050
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1051
 
        self.build_tree_contents([
1052
 
            ('other/file1', 'a\nb\nc\n'),
1053
 
            ('other/file2', 'a\nb\nc\n')
1054
 
        ])
1055
 
        other_tree.commit('modified both')
1056
 
        self.build_tree_contents([
1057
 
            ('this/file1', 'd\na\nb\n'),
1058
 
            ('this/file2', 'd\na\nb\n')
1059
 
        ])
1060
 
        this_tree.commit('modified both')
1061
 
        self.do_merge(this_tree, other_tree, interesting_files=['file1'])
1062
 
        self.assertFileEqual('d\na\nb\nc\n', 'this/file1')
1063
 
        self.assertFileEqual('d\na\nb\n', 'this/file2')
1064
 
 
1065
 
    def test_merge_move_and_change(self):
1066
 
        this_tree = self.make_branch_and_tree('this')
1067
 
        this_tree.lock_write()
1068
 
        self.addCleanup(this_tree.unlock)
1069
 
        self.build_tree_contents([
1070
 
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
1071
 
        ])
1072
 
        this_tree.add('file1',)
1073
 
        this_tree.commit('Added file')
1074
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1075
 
        self.build_tree_contents([
1076
 
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
1077
 
        ])
1078
 
        other_tree.commit('Changed 2 to 2.1')
1079
 
        self.build_tree_contents([
1080
 
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
1081
 
        ])
1082
 
        this_tree.commit('Swapped 2 & 3')
1083
 
        self.do_merge(this_tree, other_tree)
1084
 
        self.assertFileEqual('line 1\n'
1085
 
            '<<<<<<< TREE\n'
1086
 
            'line 3\n'
1087
 
            'line 2\n'
1088
 
            '=======\n'
1089
 
            'line 2 to 2.1\n'
1090
 
            'line 3\n'
1091
 
            '>>>>>>> MERGE-SOURCE\n'
1092
 
            'line 4\n', 'this/file1')
1093
 
 
1094
 
 
1095
 
class TestMerge3Merge(TestCaseWithTransport, TestMergeImplementation):
1096
 
 
1097
 
    merge_type = _mod_merge.Merge3Merger
1098
 
 
1099
 
 
1100
 
class TestWeaveMerge(TestCaseWithTransport, TestMergeImplementation):
1101
 
 
1102
 
    merge_type = _mod_merge.WeaveMerger
1103
 
 
1104
 
 
1105
 
class TestLCAMerge(TestCaseWithTransport, TestMergeImplementation):
1106
 
 
1107
 
    merge_type = _mod_merge.LCAMerger
1108
 
 
1109
 
    def test_merge_move_and_change(self):
1110
 
        self.expectFailure("lca merge doesn't conflict for move and change",
1111
 
            super(TestLCAMerge, self).test_merge_move_and_change)
1112
 
 
1113
 
 
1114
1141
class LoggingMerger(object):
1115
1142
    # These seem to be the required attributes
1116
1143
    requires_base = False
1171
1198
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
1172
1199
        mem_tree.lock_write()
1173
1200
        self.addCleanup(mem_tree.unlock)
1174
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1201
        merger = _mod_merge.Merger.from_revision_ids(None,
1175
1202
            mem_tree, other_revision_id)
1176
1203
        merger.set_interesting_files(interesting_files)
1177
1204
        # It seems there is no matching function for set_interesting_ids
1182
1209
 
1183
1210
class TestMergerInMemory(TestMergerBase):
1184
1211
 
 
1212
    def test_cache_trees_with_revision_ids_None(self):
 
1213
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1214
        original_cache = dict(merger._cached_trees)
 
1215
        merger.cache_trees_with_revision_ids([None])
 
1216
        self.assertEqual(original_cache, merger._cached_trees)
 
1217
 
 
1218
    def test_cache_trees_with_revision_ids_no_revision_id(self):
 
1219
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1220
        original_cache = dict(merger._cached_trees)
 
1221
        tree = self.make_branch_and_memory_tree('tree')
 
1222
        merger.cache_trees_with_revision_ids([tree])
 
1223
        self.assertEqual(original_cache, merger._cached_trees)
 
1224
 
 
1225
    def test_cache_trees_with_revision_ids_having_revision_id(self):
 
1226
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1227
        original_cache = dict(merger._cached_trees)
 
1228
        tree = merger.this_branch.repository.revision_tree('B-id')
 
1229
        original_cache['B-id'] = tree
 
1230
        merger.cache_trees_with_revision_ids([tree])
 
1231
        self.assertEqual(original_cache, merger._cached_trees)
 
1232
 
1185
1233
    def test_find_base(self):
1186
1234
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1187
1235
        self.assertEqual('A-id', merger.base_rev_id)
1930
1978
 
1931
1979
    def do_merge(self, builder, other_revision_id):
1932
1980
        wt = self.get_wt_from_builder(builder)
1933
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1981
        merger = _mod_merge.Merger.from_revision_ids(None,
1934
1982
            wt, other_revision_id)
1935
1983
        merger.merge_type = _mod_merge.Merge3Merger
1936
1984
        return wt, merger.do_merge()
2196
2244
        wt.commit('D merges B & C', rev_id='D-id')
2197
2245
        self.assertEqual('barry', wt.id2path('foo-id'))
2198
2246
        # Check the output of the Merger object directly
2199
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2247
        merger = _mod_merge.Merger.from_revision_ids(None,
2200
2248
            wt, 'F-id')
2201
2249
        merger.merge_type = _mod_merge.Merge3Merger
2202
2250
        merge_obj = merger.make_merger()
2252
2300
        wt.commit('F foo => bing', rev_id='F-id')
2253
2301
 
2254
2302
        # Check the output of the Merger object directly
2255
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2303
        merger = _mod_merge.Merger.from_revision_ids(None,
2256
2304
            wt, 'E-id')
2257
2305
        merger.merge_type = _mod_merge.Merge3Merger
2258
2306
        merge_obj = merger.make_merger()
2303
2351
        list(wt.iter_changes(wt.basis_tree()))
2304
2352
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2305
2353
 
2306
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2354
        merger = _mod_merge.Merger.from_revision_ids(None,
2307
2355
            wt, 'E-id')
2308
2356
        merger.merge_type = _mod_merge.Merge3Merger
2309
2357
        merge_obj = merger.make_merger()
2518
2566
        wt.branch.set_last_revision_info(2, 'B-id')
2519
2567
        wt.commit('D', rev_id='D-id', recursive=None)
2520
2568
 
2521
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2569
        merger = _mod_merge.Merger.from_revision_ids(None,
2522
2570
            wt, 'E-id')
2523
2571
        merger.merge_type = _mod_merge.Merge3Merger
2524
2572
        merge_obj = merger.make_merger()
2555
2603
        wt.branch.set_last_revision_info(2, 'B-id')
2556
2604
        wt.commit('D', rev_id='D-id', recursive=None)
2557
2605
 
2558
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2606
        merger = _mod_merge.Merger.from_revision_ids(None,
2559
2607
            wt, 'E-id')
2560
2608
        merger.merge_type = _mod_merge.Merge3Merger
2561
2609
        merge_obj = merger.make_merger()
2595
2643
        wt.branch.set_last_revision_info(2, 'B-id')
2596
2644
        wt.commit('D', rev_id='D-id', recursive=None)
2597
2645
 
2598
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2646
        merger = _mod_merge.Merger.from_revision_ids(None,
2599
2647
            wt, 'E-id')
2600
2648
        merger.merge_type = _mod_merge.Merge3Merger
2601
2649
        merge_obj = merger.make_merger()
2640
2688
        wt.branch.set_last_revision_info(2, 'B-id')
2641
2689
        wt.commit('D', rev_id='D-id', recursive=None)
2642
2690
 
2643
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2691
        merger = _mod_merge.Merger.from_revision_ids(None,
2644
2692
            wt, 'E-id')
2645
2693
        merger.merge_type = _mod_merge.Merge3Merger
2646
2694
        merge_obj = merger.make_merger()
2782
2830
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
2783
2831
        self.assertLCAMultiWay('conflict',
2784
2832
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')
 
2833
 
 
2834
 
 
2835
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
 
2836
 
 
2837
    def setUp(self):
 
2838
        super(TestConfigurableFileMerger, self).setUp()
 
2839
        self.calls = []
 
2840
 
 
2841
    def get_merger_factory(self):
 
2842
        # Allows  the inner methods to access the test attributes
 
2843
        test = self
 
2844
 
 
2845
        class FooMerger(_mod_merge.ConfigurableFileMerger):
 
2846
            name_prefix = "foo"
 
2847
            default_files = ['bar']
 
2848
 
 
2849
            def merge_text(self, params):
 
2850
                test.calls.append('merge_text')
 
2851
                return ('not_applicable', None)
 
2852
 
 
2853
        def factory(merger):
 
2854
            result = FooMerger(merger)
 
2855
            # Make sure we start with a clean slate
 
2856
            self.assertEqual(None, result.affected_files)
 
2857
            # Track the original merger
 
2858
            self.merger = result
 
2859
            return result
 
2860
 
 
2861
        return factory
 
2862
 
 
2863
    def _install_hook(self, factory):
 
2864
        _mod_merge.Merger.hooks.install_named_hook('merge_file_content',
 
2865
                                                   factory, 'test factory')
 
2866
 
 
2867
    def make_builder(self):
 
2868
        builder = test_merge_core.MergeBuilder(self.test_base_dir)
 
2869
        self.addCleanup(builder.cleanup)
 
2870
        return builder
 
2871
 
 
2872
    def make_text_conflict(self, file_name='bar'):
 
2873
        factory = self.get_merger_factory()
 
2874
        self._install_hook(factory)
 
2875
        builder = self.make_builder()
 
2876
        builder.add_file('bar-id', builder.tree_root, file_name, 'text1', True)
 
2877
        builder.change_contents('bar-id', other='text4', this='text3')
 
2878
        return builder
 
2879
 
 
2880
    def make_kind_change(self):
 
2881
        factory = self.get_merger_factory()
 
2882
        self._install_hook(factory)
 
2883
        builder = self.make_builder()
 
2884
        builder.add_file('bar-id', builder.tree_root, 'bar', 'text1', True,
 
2885
                         this=False)
 
2886
        builder.add_dir('bar-dir', builder.tree_root, 'bar-id',
 
2887
                        base=False, other=False)
 
2888
        return builder
 
2889
 
 
2890
    def test_affected_files_cached(self):
 
2891
        """Ensures that the config variable is cached"""
 
2892
        builder = self.make_text_conflict()
 
2893
        conflicts = builder.merge()
 
2894
        # The hook should set the variable
 
2895
        self.assertEqual(['bar'], self.merger.affected_files)
 
2896
        self.assertEqual(1, len(conflicts))
 
2897
 
 
2898
    def test_hook_called_for_text_conflicts(self):
 
2899
        builder = self.make_text_conflict()
 
2900
        conflicts = builder.merge()
 
2901
        # The hook should call the merge_text() method
 
2902
        self.assertEqual(['merge_text'], self.calls)
 
2903
 
 
2904
    def test_hook_not_called_for_kind_change(self):
 
2905
        builder = self.make_kind_change()
 
2906
        conflicts = builder.merge()
 
2907
        # The hook should not call the merge_text() method
 
2908
        self.assertEqual([], self.calls)
 
2909
 
 
2910
    def test_hook_not_called_for_other_files(self):
 
2911
        builder = self.make_text_conflict('foobar')
 
2912
        conflicts = builder.merge()
 
2913
        # The hook should not call the merge_text() method
 
2914
        self.assertEqual([], self.calls)