/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: John Arbash Meinel
  • Date: 2008-10-14 21:35:27 UTC
  • mto: This revision was merged to the branch mainline in revision 3805.
  • Revision ID: john@arbash-meinel.com-20081014213527-4j9uc93aq1qmn43b
Add in a shortcut when we haven't cached much yet.

Document the current algorithm more completely, including the proper
justification for the various steps.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
32
33
from bzrlib.conflicts import ConflictList, TextConflict
33
 
from bzrlib.errors import UnrelatedBranches, NoCommits
 
34
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
34
35
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
35
36
from bzrlib.osutils import pathjoin, file_kind
36
 
from bzrlib.tests import (
37
 
    TestCaseWithMemoryTransport,
38
 
    TestCaseWithTransport,
39
 
    test_merge_core,
40
 
    )
 
37
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
 
38
from bzrlib.trace import (enable_test_log, disable_test_log)
41
39
from bzrlib.workingtree import WorkingTree
42
40
 
43
41
 
91
89
        self.failIfExists('bar')
92
90
        wt2 = WorkingTree.open('.') # opens branch2
93
91
        self.assertEqual([tip], wt2.get_parent_ids())
94
 
 
 
92
        
95
93
    def test_pending_with_null(self):
96
94
        """When base is forced to revno 0, parent_ids are set"""
97
95
        wt2 = self.test_unrelated()
98
96
        wt1 = WorkingTree.open('.')
99
97
        br1 = wt1.branch
100
98
        br1.fetch(wt2.branch)
101
 
        # merge all of branch 2 into branch 1 even though they
 
99
        # merge all of branch 2 into branch 1 even though they 
102
100
        # are not related.
103
101
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
104
102
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
152
150
        self.addCleanup(tree_b.unlock)
153
151
        tree_a.commit(message="hello again")
154
152
        log = StringIO()
155
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
 
153
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
156
154
                    this_tree=tree_b, ignore_zero=True)
157
 
        self.failUnless('All changes applied successfully.\n' not in
158
 
            self.get_log())
 
155
        log = self._get_log(keep_log_file=True)
 
156
        self.failUnless('All changes applied successfully.\n' not in log)
159
157
        tree_b.revert()
160
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
 
158
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
161
159
                    this_tree=tree_b, ignore_zero=False)
162
 
        self.failUnless('All changes applied successfully.\n' in self.get_log())
 
160
        log = self._get_log(keep_log_file=True)
 
161
        self.failUnless('All changes applied successfully.\n' in log)
163
162
 
164
163
    def test_merge_inner_conflicts(self):
165
164
        tree_a = self.make_branch_and_tree('a')
220
219
        tree_a.add('file')
221
220
        tree_a.commit('commit base')
222
221
        # basis_tree() is only guaranteed to be valid as long as it is actually
223
 
        # the basis tree. This test commits to the tree after grabbing basis,
224
 
        # so we go to the repository.
 
222
        # the basis tree. This mutates the tree after grabbing basis, so go to
 
223
        # the repository.
225
224
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
226
225
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
227
226
        self.build_tree_contents([('tree_a/file', 'content_2')])
228
227
        tree_a.commit('commit other')
229
228
        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.
232
229
        os.unlink('tree_b/file')
233
230
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
234
231
 
251
248
        self.assertEqual(tree_b.conflicts(),
252
249
                         [conflicts.ContentsConflict('file',
253
250
                          file_id='file-id')])
254
 
 
 
251
    
255
252
    def test_merge_type_registry(self):
256
253
        merge_type_option = option.Option.OPTIONS['merge-type']
257
 
        self.assertFalse('merge4' in [x[0] for x in
 
254
        self.assertFalse('merge4' in [x[0] for x in 
258
255
                        merge_type_option.iter_switches()])
259
256
        registry = _mod_merge.get_merge_type_registry()
260
257
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
261
258
                               'time-travelling merge')
262
 
        self.assertTrue('merge4' in [x[0] for x in
 
259
        self.assertTrue('merge4' in [x[0] for x in 
263
260
                        merge_type_option.iter_switches()])
264
261
        registry.remove('merge4')
265
 
        self.assertFalse('merge4' in [x[0] for x in
 
262
        self.assertFalse('merge4' in [x[0] for x in 
266
263
                        merge_type_option.iter_switches()])
267
264
 
268
265
    def test_merge_other_moves_we_deleted(self):
295
292
        tree_a.commit('commit 2')
296
293
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
297
294
        tree_b.rename_one('file_1', 'renamed')
298
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
 
295
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
296
                                                    progress.DummyProgress())
299
297
        merger.merge_type = _mod_merge.Merge3Merger
300
298
        merger.do_merge()
301
299
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
309
307
        tree_a.commit('commit 2')
310
308
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
311
309
        tree_b.rename_one('file_1', 'renamed')
312
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
 
310
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
311
                                                    progress.DummyProgress())
313
312
        merger.merge_type = _mod_merge.WeaveMerger
314
313
        merger.do_merge()
315
314
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
340
339
 
341
340
    def test_weave_cherrypick(self):
342
341
        this_tree, other_tree = self.prepare_cherrypick()
343
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
342
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
344
343
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
345
344
        merger.merge_type = _mod_merge.WeaveMerger
346
345
        merger.do_merge()
348
347
 
349
348
    def test_weave_cannot_reverse_cherrypick(self):
350
349
        this_tree, other_tree = self.prepare_cherrypick()
351
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
350
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
352
351
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
353
352
        merger.merge_type = _mod_merge.WeaveMerger
354
353
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
355
354
 
356
355
    def test_merge3_can_reverse_cherrypick(self):
357
356
        this_tree, other_tree = self.prepare_cherrypick()
358
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
357
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
359
358
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
360
359
        merger.merge_type = _mod_merge.Merge3Merger
361
360
        merger.do_merge()
373
372
        this_tree.lock_write()
374
373
        self.addCleanup(this_tree.unlock)
375
374
 
376
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
375
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
377
376
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
378
377
        merger.merge_type = _mod_merge.Merge3Merger
379
378
        merger.do_merge()
392
391
        other_tree.commit('rev2', rev_id='rev2b')
393
392
        this_tree.lock_write()
394
393
        self.addCleanup(this_tree.unlock)
395
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
394
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
396
395
            this_tree, 'rev2b', other_branch=other_tree.branch)
397
396
        merger.merge_type = _mod_merge.Merge3Merger
398
397
        tree_merger = merger.make_merger()
412
411
        other_tree.commit('rev2', rev_id='rev2b')
413
412
        this_tree.lock_write()
414
413
        self.addCleanup(this_tree.unlock)
415
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
414
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
416
415
            this_tree, 'rev2b', other_branch=other_tree.branch)
417
416
        merger.merge_type = _mod_merge.Merge3Merger
418
417
        tree_merger = merger.make_merger()
442
441
        other_tree.commit('rev2', rev_id='rev2b')
443
442
        this_tree.lock_write()
444
443
        self.addCleanup(this_tree.unlock)
445
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
444
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
446
445
            this_tree, 'rev2b', other_branch=other_tree.branch)
447
446
        merger.merge_type = _mod_merge.Merge3Merger
448
447
        tree_merger = merger.make_merger()
519
518
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
520
519
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
521
520
 
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
 
 
528
521
    def test_unique_lines(self):
529
522
        plan = self.setup_plan_merge()
530
523
        self.assertEqual(plan._unique_lines(
725
718
 
726
719
    def test_plan_merge_insert_order(self):
727
720
        """Weave merges are sensitive to the order of insertion.
728
 
 
 
721
        
729
722
        Specifically for overlapping regions, it effects which region gets put
730
723
        'first'. And when a user resolves an overlapping merge, if they use the
731
724
        same ordering, then the lines match the parents, if they don't only
828
821
                          ('unchanged', 'f\n'),
829
822
                          ('unchanged', 'g\n')],
830
823
                         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()))
936
824
 
937
825
    def assertRemoveExternalReferences(self, filtered_parent_map,
938
826
                                       child_map, tails, parent_map):
1138
1026
                         ], list(plan))
1139
1027
 
1140
1028
 
 
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
 
1141
1114
class LoggingMerger(object):
1142
1115
    # These seem to be the required attributes
1143
1116
    requires_base = False
1198
1171
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
1199
1172
        mem_tree.lock_write()
1200
1173
        self.addCleanup(mem_tree.unlock)
1201
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
1174
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
1202
1175
            mem_tree, other_revision_id)
1203
1176
        merger.set_interesting_files(interesting_files)
1204
1177
        # It seems there is no matching function for set_interesting_ids
1209
1182
 
1210
1183
class TestMergerInMemory(TestMergerBase):
1211
1184
 
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
 
 
1233
1185
    def test_find_base(self):
1234
1186
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1235
1187
        self.assertEqual('A-id', merger.base_rev_id)
1429
1381
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
1430
1382
 
1431
1383
        entries = list(merge_obj._entries_lca())
 
1384
        root_id = 'a-root-id'
1432
1385
        self.assertEqual([], entries)
1433
1386
 
1434
1387
    def test_not_in_other(self):
1457
1410
        #       B C  B nothing, C deletes foo
1458
1411
        #       |X|
1459
1412
        #       D E  D restores foo (same as B), E leaves it deleted
1460
 
        # Analysis:
1461
 
        #   A => B, no changes
1462
 
        #   A => C, delete foo (C should supersede B)
1463
 
        #   C => D, restore foo
1464
 
        #   C => E, no changes
1465
 
        # D would then win 'cleanly' and no record would be given
 
1413
        # We should emit an entry for this
1466
1414
        builder = self.get_builder()
1467
1415
        builder.build_snapshot('A-id', None,
1468
1416
            [('add', (u'', 'a-root-id', 'directory', None)),
1475
1423
        merge_obj = self.make_merge_obj(builder, 'E-id')
1476
1424
 
1477
1425
        entries = list(merge_obj._entries_lca())
1478
 
        self.assertEqual([], entries)
1479
 
 
1480
 
    def test_not_in_other_mod_in_lca1_not_in_lca2(self):
1481
 
        #       A    base, introduces 'foo'
1482
 
        #       |\
1483
 
        #       B C  B changes 'foo', C deletes foo
1484
 
        #       |X|
1485
 
        #       D E  D restores foo (same as B), E leaves it deleted (as C)
1486
 
        # Analysis:
1487
 
        #   A => B, modified foo
1488
 
        #   A => C, delete foo, C does not supersede B
1489
 
        #   B => D, no changes
1490
 
        #   C => D, resolve in favor of B
1491
 
        #   B => E, resolve in favor of E
1492
 
        #   C => E, no changes
1493
 
        # In this case, we have a conflict of how the changes were resolved. E
1494
 
        # picked C and D picked B, so we should issue a conflict
1495
 
        builder = self.get_builder()
1496
 
        builder.build_snapshot('A-id', None,
1497
 
            [('add', (u'', 'a-root-id', 'directory', None)),
1498
 
             ('add', (u'foo', 'foo-id', 'file', 'content\n'))])
1499
 
        builder.build_snapshot('B-id', ['A-id'], [
1500
 
            ('modify', ('foo-id', 'new-content\n'))])
1501
 
        builder.build_snapshot('C-id', ['A-id'],
1502
 
            [('unversion', 'foo-id')])
1503
 
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1504
 
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1505
 
        merge_obj = self.make_merge_obj(builder, 'E-id')
1506
 
 
1507
 
        entries = list(merge_obj._entries_lca())
1508
1426
        root_id = 'a-root-id'
1509
1427
        self.assertEqual([('foo-id', True,
1510
1428
                           ((root_id, [root_id, None]), None, root_id),
1513
1431
                         ], entries)
1514
1432
 
1515
1433
    def test_only_in_one_lca(self):
1516
 
        #   A   add only root
1517
 
        #   |\
1518
 
        #   B C B nothing, C add file
1519
 
        #   |X|
1520
 
        #   D E D still has nothing, E removes file
1521
 
        # Analysis:
1522
 
        #   B => D, no change
1523
 
        #   C => D, removed the file
1524
 
        #   B => E, no change
1525
 
        #   C => E, removed the file
1526
 
        # Thus D & E have identical changes, and this is a no-op
1527
 
        # Alternatively:
1528
 
        #   A => B, no change
1529
 
        #   A => C, add file, thus C supersedes B
1530
 
        #   w/ C=BASE, D=THIS, E=OTHER we have 'happy convergence'
1531
1434
        builder = self.get_builder()
1532
1435
        builder.build_snapshot('A-id', None,
1533
1436
            [('add', (u'', 'a-root-id', 'directory', None))])
1540
1443
        merge_obj = self.make_merge_obj(builder, 'E-id')
1541
1444
 
1542
1445
        entries = list(merge_obj._entries_lca())
1543
 
        self.assertEqual([], entries)
 
1446
        root_id = 'a-root-id'
 
1447
        self.assertEqual([('a-id', True,
 
1448
                           ((None, [None, root_id]), None, None),
 
1449
                           ((None, [None, u'a']), None, None),
 
1450
                           ((None, [None, False]), None, None)),
 
1451
                         ], entries)
1544
1452
 
1545
1453
    def test_only_in_other(self):
1546
1454
        builder = self.get_builder()
1770
1678
        merge_obj = self.make_merge_obj(builder, 'E-id')
1771
1679
 
1772
1680
        entries = list(merge_obj._entries_lca())
 
1681
        root_id = 'a-root-id'
1773
1682
        self.expectFailure("We don't detect that LCA resolution was the"
1774
1683
                           " same on both sides",
1775
1684
            self.assertEqual, [], entries)
1830
1739
             ('add', (u'a', 'a-id', 'directory', None))])
1831
1740
        merge_obj = self.make_merge_obj(builder, 'E-id')
1832
1741
        entries = list(merge_obj._entries_lca())
 
1742
        root_id = 'a-root-id'
1833
1743
        # Only the kind was changed (content)
1834
1744
        self.assertEqual([], entries)
1835
1745
 
1978
1888
 
1979
1889
    def do_merge(self, builder, other_revision_id):
1980
1890
        wt = self.get_wt_from_builder(builder)
1981
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
1891
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
1982
1892
            wt, other_revision_id)
1983
1893
        merger.merge_type = _mod_merge.Merge3Merger
1984
1894
        return wt, merger.do_merge()
2201
2111
        wt.merge_from_branch(wt.branch, 'C-id')
2202
2112
        wt.commit('D merges B & C', rev_id='D-id')
2203
2113
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
2204
 
        self.assertEqual(0, conflicts)
 
2114
        self.expectFailure("Merge3Merger doesn't use lcas for symlink content",
 
2115
            self.assertEqual, 0, conflicts)
2205
2116
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
2206
2117
 
2207
2118
    def test_renamed_symlink(self):
2244
2155
        wt.commit('D merges B & C', rev_id='D-id')
2245
2156
        self.assertEqual('barry', wt.id2path('foo-id'))
2246
2157
        # Check the output of the Merger object directly
2247
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2158
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2248
2159
            wt, 'F-id')
2249
2160
        merger.merge_type = _mod_merge.Merge3Merger
2250
2161
        merge_obj = merger.make_merger()
2300
2211
        wt.commit('F foo => bing', rev_id='F-id')
2301
2212
 
2302
2213
        # Check the output of the Merger object directly
2303
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2214
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2304
2215
            wt, 'E-id')
2305
2216
        merger.merge_type = _mod_merge.Merge3Merger
2306
2217
        merge_obj = merger.make_merger()
2351
2262
        list(wt.iter_changes(wt.basis_tree()))
2352
2263
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2353
2264
 
2354
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2265
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2355
2266
            wt, 'E-id')
2356
2267
        merger.merge_type = _mod_merge.Merge3Merger
2357
2268
        merge_obj = merger.make_merger()
2468
2379
        # TODO: We need to use the per-file graph to properly select a BASE
2469
2380
        #       before this will work. Or at least use the LCA trees to find
2470
2381
        #       the appropriate content base. (which is B, not A).
2471
 
        self.assertEqual('base content\n', wt.get_file_text('foo-id'))
 
2382
        self.expectFailure("Merge3Merger doesn't recognize reverted content",
 
2383
            self.assertEqual, 'base content\n', wt.get_file_text('foo-id'))
2472
2384
 
2473
2385
    def test_other_modified_content(self):
2474
2386
        builder = self.get_builder()
2484
2396
            [('modify', ('foo-id', 'F content\n'))]) # Override B content
2485
2397
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2486
2398
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2399
        self.expectFailure("Merge3Merger only uses BASE for content",
 
2400
            self.assertEqual, 'F content\n', wt.get_file_text('foo-id'))
2487
2401
        self.assertEqual(0, conflicts)
2488
2402
        self.assertEqual('F content\n', wt.get_file_text('foo-id'))
2489
2403
 
2566
2480
        wt.branch.set_last_revision_info(2, 'B-id')
2567
2481
        wt.commit('D', rev_id='D-id', recursive=None)
2568
2482
 
2569
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2483
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2570
2484
            wt, 'E-id')
2571
2485
        merger.merge_type = _mod_merge.Merge3Merger
2572
2486
        merge_obj = merger.make_merger()
2603
2517
        wt.branch.set_last_revision_info(2, 'B-id')
2604
2518
        wt.commit('D', rev_id='D-id', recursive=None)
2605
2519
 
2606
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2520
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2607
2521
            wt, 'E-id')
2608
2522
        merger.merge_type = _mod_merge.Merge3Merger
2609
2523
        merge_obj = merger.make_merger()
2643
2557
        wt.branch.set_last_revision_info(2, 'B-id')
2644
2558
        wt.commit('D', rev_id='D-id', recursive=None)
2645
2559
 
2646
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2560
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2647
2561
            wt, 'E-id')
2648
2562
        merger.merge_type = _mod_merge.Merge3Merger
2649
2563
        merge_obj = merger.make_merger()
2688
2602
        wt.branch.set_last_revision_info(2, 'B-id')
2689
2603
        wt.commit('D', rev_id='D-id', recursive=None)
2690
2604
 
2691
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2605
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
2692
2606
            wt, 'E-id')
2693
2607
        merger.merge_type = _mod_merge.Merge3Merger
2694
2608
        merge_obj = merger.make_merger()
2830
2744
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
2831
2745
        self.assertLCAMultiWay('conflict',
2832
2746
            '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_uses_this_branch(self):
2891
 
        builder = self.make_text_conflict()
2892
 
        tt = builder.make_preview_transform()
2893
 
        self.addCleanup(tt.finalize)
2894
 
 
2895
 
    def test_affected_files_cached(self):
2896
 
        """Ensures that the config variable is cached"""
2897
 
        builder = self.make_text_conflict()
2898
 
        conflicts = builder.merge()
2899
 
        # The hook should set the variable
2900
 
        self.assertEqual(['bar'], self.merger.affected_files)
2901
 
        self.assertEqual(1, len(conflicts))
2902
 
 
2903
 
    def test_hook_called_for_text_conflicts(self):
2904
 
        builder = self.make_text_conflict()
2905
 
        conflicts = builder.merge()
2906
 
        # The hook should call the merge_text() method
2907
 
        self.assertEqual(['merge_text'], self.calls)
2908
 
 
2909
 
    def test_hook_not_called_for_kind_change(self):
2910
 
        builder = self.make_kind_change()
2911
 
        conflicts = builder.merge()
2912
 
        # The hook should not call the merge_text() method
2913
 
        self.assertEqual([], self.calls)
2914
 
 
2915
 
    def test_hook_not_called_for_other_files(self):
2916
 
        builder = self.make_text_conflict('foobar')
2917
 
        conflicts = builder.merge()
2918
 
        # The hook should not call the merge_text() method
2919
 
        self.assertEqual([], self.calls)