~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-18 09:13:28 UTC
  • mfrom: (5096.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100318091328-8fo347hq4at1usky
(vila) Get better feedback about why
        TestGetFileMTime.test_get_file_mtime is failing

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
21
21
    conflicts,
22
22
    errors,
23
23
    knit,
 
24
    memorytree,
24
25
    merge as _mod_merge,
25
26
    option,
26
27
    progress,
 
28
    tests,
27
29
    transform,
28
30
    versionedfile,
29
31
    )
30
 
from bzrlib.branch import Branch
31
32
from bzrlib.conflicts import ConflictList, TextConflict
32
 
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
 
33
from bzrlib.errors import UnrelatedBranches, NoCommits
33
34
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
34
35
from bzrlib.osutils import pathjoin, file_kind
35
 
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
36
 
from bzrlib.trace import (enable_test_log, disable_test_log)
 
36
from bzrlib.tests import (
 
37
    TestCaseWithMemoryTransport,
 
38
    TestCaseWithTransport,
 
39
    test_merge_core,
 
40
    )
37
41
from bzrlib.workingtree import WorkingTree
38
42
 
39
43
 
87
91
        self.failIfExists('bar')
88
92
        wt2 = WorkingTree.open('.') # opens branch2
89
93
        self.assertEqual([tip], wt2.get_parent_ids())
90
 
        
 
94
 
91
95
    def test_pending_with_null(self):
92
96
        """When base is forced to revno 0, parent_ids are set"""
93
97
        wt2 = self.test_unrelated()
94
98
        wt1 = WorkingTree.open('.')
95
99
        br1 = wt1.branch
96
100
        br1.fetch(wt2.branch)
97
 
        # merge all of branch 2 into branch 1 even though they 
 
101
        # merge all of branch 2 into branch 1 even though they
98
102
        # are not related.
99
103
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
100
104
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
148
152
        self.addCleanup(tree_b.unlock)
149
153
        tree_a.commit(message="hello again")
150
154
        log = StringIO()
151
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
155
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
152
156
                    this_tree=tree_b, ignore_zero=True)
153
 
        log = self._get_log(keep_log_file=True)
154
 
        self.failUnless('All changes applied successfully.\n' not in log)
 
157
        self.failUnless('All changes applied successfully.\n' not in
 
158
            self.get_log())
155
159
        tree_b.revert()
156
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
160
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
157
161
                    this_tree=tree_b, ignore_zero=False)
158
 
        log = self._get_log(keep_log_file=True)
159
 
        self.failUnless('All changes applied successfully.\n' in log)
 
162
        self.failUnless('All changes applied successfully.\n' in self.get_log())
160
163
 
161
164
    def test_merge_inner_conflicts(self):
162
165
        tree_a = self.make_branch_and_tree('a')
217
220
        tree_a.add('file')
218
221
        tree_a.commit('commit base')
219
222
        # basis_tree() is only guaranteed to be valid as long as it is actually
220
 
        # the basis tree. This mutates the tree after grabbing basis, so go to
221
 
        # the repository.
 
223
        # the basis tree. This test commits to the tree after grabbing basis,
 
224
        # so we go to the repository.
222
225
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
223
226
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
224
227
        self.build_tree_contents([('tree_a/file', 'content_2')])
225
228
        tree_a.commit('commit other')
226
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.
227
232
        os.unlink('tree_b/file')
228
233
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
229
234
 
246
251
        self.assertEqual(tree_b.conflicts(),
247
252
                         [conflicts.ContentsConflict('file',
248
253
                          file_id='file-id')])
249
 
    
 
254
 
250
255
    def test_merge_type_registry(self):
251
256
        merge_type_option = option.Option.OPTIONS['merge-type']
252
 
        self.assertFalse('merge4' in [x[0] for x in 
 
257
        self.assertFalse('merge4' in [x[0] for x in
253
258
                        merge_type_option.iter_switches()])
254
259
        registry = _mod_merge.get_merge_type_registry()
255
260
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
256
261
                               'time-travelling merge')
257
 
        self.assertTrue('merge4' in [x[0] for x in 
 
262
        self.assertTrue('merge4' in [x[0] for x in
258
263
                        merge_type_option.iter_switches()])
259
264
        registry.remove('merge4')
260
 
        self.assertFalse('merge4' in [x[0] for x in 
 
265
        self.assertFalse('merge4' in [x[0] for x in
261
266
                        merge_type_option.iter_switches()])
262
267
 
263
268
    def test_merge_other_moves_we_deleted(self):
290
295
        tree_a.commit('commit 2')
291
296
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
292
297
        tree_b.rename_one('file_1', 'renamed')
293
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
294
 
                                                    progress.DummyProgress())
 
298
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
295
299
        merger.merge_type = _mod_merge.Merge3Merger
296
300
        merger.do_merge()
297
301
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
305
309
        tree_a.commit('commit 2')
306
310
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
307
311
        tree_b.rename_one('file_1', 'renamed')
308
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
309
 
                                                    progress.DummyProgress())
 
312
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
310
313
        merger.merge_type = _mod_merge.WeaveMerger
311
314
        merger.do_merge()
312
315
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
337
340
 
338
341
    def test_weave_cherrypick(self):
339
342
        this_tree, other_tree = self.prepare_cherrypick()
340
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
343
        merger = _mod_merge.Merger.from_revision_ids(None,
341
344
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
342
345
        merger.merge_type = _mod_merge.WeaveMerger
343
346
        merger.do_merge()
345
348
 
346
349
    def test_weave_cannot_reverse_cherrypick(self):
347
350
        this_tree, other_tree = self.prepare_cherrypick()
348
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
351
        merger = _mod_merge.Merger.from_revision_ids(None,
349
352
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
350
353
        merger.merge_type = _mod_merge.WeaveMerger
351
354
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
352
355
 
353
356
    def test_merge3_can_reverse_cherrypick(self):
354
357
        this_tree, other_tree = self.prepare_cherrypick()
355
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
358
        merger = _mod_merge.Merger.from_revision_ids(None,
356
359
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
357
360
        merger.merge_type = _mod_merge.Merge3Merger
358
361
        merger.do_merge()
370
373
        this_tree.lock_write()
371
374
        self.addCleanup(this_tree.unlock)
372
375
 
373
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
376
        merger = _mod_merge.Merger.from_revision_ids(None,
374
377
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
375
378
        merger.merge_type = _mod_merge.Merge3Merger
376
379
        merger.do_merge()
389
392
        other_tree.commit('rev2', rev_id='rev2b')
390
393
        this_tree.lock_write()
391
394
        self.addCleanup(this_tree.unlock)
392
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
395
        merger = _mod_merge.Merger.from_revision_ids(None,
393
396
            this_tree, 'rev2b', other_branch=other_tree.branch)
394
397
        merger.merge_type = _mod_merge.Merge3Merger
395
398
        tree_merger = merger.make_merger()
409
412
        other_tree.commit('rev2', rev_id='rev2b')
410
413
        this_tree.lock_write()
411
414
        self.addCleanup(this_tree.unlock)
412
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
415
        merger = _mod_merge.Merger.from_revision_ids(None,
413
416
            this_tree, 'rev2b', other_branch=other_tree.branch)
414
417
        merger.merge_type = _mod_merge.Merge3Merger
415
418
        tree_merger = merger.make_merger()
439
442
        other_tree.commit('rev2', rev_id='rev2b')
440
443
        this_tree.lock_write()
441
444
        self.addCleanup(this_tree.unlock)
442
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
445
        merger = _mod_merge.Merger.from_revision_ids(None,
443
446
            this_tree, 'rev2b', other_branch=other_tree.branch)
444
447
        merger.merge_type = _mod_merge.Merge3Merger
445
448
        tree_merger = merger.make_merger()
516
519
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
517
520
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
518
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
 
519
528
    def test_unique_lines(self):
520
529
        plan = self.setup_plan_merge()
521
530
        self.assertEqual(plan._unique_lines(
716
725
 
717
726
    def test_plan_merge_insert_order(self):
718
727
        """Weave merges are sensitive to the order of insertion.
719
 
        
 
728
 
720
729
        Specifically for overlapping regions, it effects which region gets put
721
730
        'first'. And when a user resolves an overlapping merge, if they use the
722
731
        same ordering, then the lines match the parents, if they don't only
819
828
                          ('unchanged', 'f\n'),
820
829
                          ('unchanged', 'g\n')],
821
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()))
822
936
 
823
937
    def assertRemoveExternalReferences(self, filtered_parent_map,
824
938
                                       child_map, tails, parent_map):
1024
1138
                         ], list(plan))
1025
1139
 
1026
1140
 
1027
 
class TestMergeImplementation(object):
1028
 
 
1029
 
    def do_merge(self, target_tree, source_tree, **kwargs):
1030
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
1031
 
            target_tree, source_tree.last_revision(),
1032
 
            other_branch=source_tree.branch)
1033
 
        merger.merge_type=self.merge_type
1034
 
        for name, value in kwargs.items():
1035
 
            setattr(merger, name, value)
1036
 
        merger.do_merge()
1037
 
 
1038
 
    def test_merge_specific_file(self):
1039
 
        this_tree = self.make_branch_and_tree('this')
1040
 
        this_tree.lock_write()
1041
 
        self.addCleanup(this_tree.unlock)
1042
 
        self.build_tree_contents([
1043
 
            ('this/file1', 'a\nb\n'),
1044
 
            ('this/file2', 'a\nb\n')
1045
 
        ])
1046
 
        this_tree.add(['file1', 'file2'])
1047
 
        this_tree.commit('Added files')
1048
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1049
 
        self.build_tree_contents([
1050
 
            ('other/file1', 'a\nb\nc\n'),
1051
 
            ('other/file2', 'a\nb\nc\n')
1052
 
        ])
1053
 
        other_tree.commit('modified both')
1054
 
        self.build_tree_contents([
1055
 
            ('this/file1', 'd\na\nb\n'),
1056
 
            ('this/file2', 'd\na\nb\n')
1057
 
        ])
1058
 
        this_tree.commit('modified both')
1059
 
        self.do_merge(this_tree, other_tree, interesting_files=['file1'])
1060
 
        self.assertFileEqual('d\na\nb\nc\n', 'this/file1')
1061
 
        self.assertFileEqual('d\na\nb\n', 'this/file2')
1062
 
 
1063
 
    def test_merge_move_and_change(self):
1064
 
        this_tree = self.make_branch_and_tree('this')
1065
 
        this_tree.lock_write()
1066
 
        self.addCleanup(this_tree.unlock)
1067
 
        self.build_tree_contents([
1068
 
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
1069
 
        ])
1070
 
        this_tree.add('file1',)
1071
 
        this_tree.commit('Added file')
1072
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1073
 
        self.build_tree_contents([
1074
 
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
1075
 
        ])
1076
 
        other_tree.commit('Changed 2 to 2.1')
1077
 
        self.build_tree_contents([
1078
 
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
1079
 
        ])
1080
 
        this_tree.commit('Swapped 2 & 3')
1081
 
        self.do_merge(this_tree, other_tree)
1082
 
        self.assertFileEqual('line 1\n'
1083
 
            '<<<<<<< TREE\n'
1084
 
            'line 3\n'
1085
 
            'line 2\n'
1086
 
            '=======\n'
1087
 
            'line 2 to 2.1\n'
1088
 
            'line 3\n'
1089
 
            '>>>>>>> MERGE-SOURCE\n'
1090
 
            'line 4\n', 'this/file1')
1091
 
 
1092
 
 
1093
 
class TestMerge3Merge(TestCaseWithTransport, TestMergeImplementation):
1094
 
 
1095
 
    merge_type = _mod_merge.Merge3Merger
1096
 
 
1097
 
 
1098
 
class TestWeaveMerge(TestCaseWithTransport, TestMergeImplementation):
1099
 
 
1100
 
    merge_type = _mod_merge.WeaveMerger
1101
 
 
1102
 
 
1103
 
class TestLCAMerge(TestCaseWithTransport, TestMergeImplementation):
1104
 
 
1105
 
    merge_type = _mod_merge.LCAMerger
1106
 
 
1107
 
    def test_merge_move_and_change(self):
1108
 
        self.expectFailure("lca merge doesn't conflict for move and change",
1109
 
            super(TestLCAMerge, self).test_merge_move_and_change)
 
1141
class LoggingMerger(object):
 
1142
    # These seem to be the required attributes
 
1143
    requires_base = False
 
1144
    supports_reprocess = False
 
1145
    supports_show_base = False
 
1146
    supports_cherrypick = False
 
1147
    # We intentionally do not define supports_lca_trees
 
1148
 
 
1149
    def __init__(self, *args, **kwargs):
 
1150
        self.args = args
 
1151
        self.kwargs = kwargs
 
1152
 
 
1153
 
 
1154
class TestMergerBase(TestCaseWithMemoryTransport):
 
1155
    """Common functionality for Merger tests that don't write to disk."""
 
1156
 
 
1157
    def get_builder(self):
 
1158
        builder = self.make_branch_builder('path')
 
1159
        builder.start_series()
 
1160
        self.addCleanup(builder.finish_series)
 
1161
        return builder
 
1162
 
 
1163
    def setup_simple_graph(self):
 
1164
        """Create a simple 3-node graph.
 
1165
 
 
1166
        :return: A BranchBuilder
 
1167
        """
 
1168
        #
 
1169
        #  A
 
1170
        #  |\
 
1171
        #  B C
 
1172
        #
 
1173
        builder = self.get_builder()
 
1174
        builder.build_snapshot('A-id', None,
 
1175
            [('add', ('', None, 'directory', None))])
 
1176
        builder.build_snapshot('C-id', ['A-id'], [])
 
1177
        builder.build_snapshot('B-id', ['A-id'], [])
 
1178
        return builder
 
1179
 
 
1180
    def setup_criss_cross_graph(self):
 
1181
        """Create a 5-node graph with a criss-cross.
 
1182
 
 
1183
        :return: A BranchBuilder
 
1184
        """
 
1185
        # A
 
1186
        # |\
 
1187
        # B C
 
1188
        # |X|
 
1189
        # D E
 
1190
        builder = self.setup_simple_graph()
 
1191
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1192
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1193
        return builder
 
1194
 
 
1195
    def make_Merger(self, builder, other_revision_id,
 
1196
                    interesting_files=None, interesting_ids=None):
 
1197
        """Make a Merger object from a branch builder"""
 
1198
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
 
1199
        mem_tree.lock_write()
 
1200
        self.addCleanup(mem_tree.unlock)
 
1201
        merger = _mod_merge.Merger.from_revision_ids(None,
 
1202
            mem_tree, other_revision_id)
 
1203
        merger.set_interesting_files(interesting_files)
 
1204
        # It seems there is no matching function for set_interesting_ids
 
1205
        merger.interesting_ids = interesting_ids
 
1206
        merger.merge_type = _mod_merge.Merge3Merger
 
1207
        return merger
 
1208
 
 
1209
 
 
1210
class TestMergerInMemory(TestMergerBase):
 
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
 
 
1233
    def test_find_base(self):
 
1234
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1235
        self.assertEqual('A-id', merger.base_rev_id)
 
1236
        self.assertFalse(merger._is_criss_cross)
 
1237
        self.assertIs(None, merger._lca_trees)
 
1238
 
 
1239
    def test_find_base_criss_cross(self):
 
1240
        builder = self.setup_criss_cross_graph()
 
1241
        merger = self.make_Merger(builder, 'E-id')
 
1242
        self.assertEqual('A-id', merger.base_rev_id)
 
1243
        self.assertTrue(merger._is_criss_cross)
 
1244
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1245
                                            for t in merger._lca_trees])
 
1246
        # If we swap the order, we should get a different lca order
 
1247
        builder.build_snapshot('F-id', ['E-id'], [])
 
1248
        merger = self.make_Merger(builder, 'D-id')
 
1249
        self.assertEqual(['C-id', 'B-id'], [t.get_revision_id()
 
1250
                                            for t in merger._lca_trees])
 
1251
 
 
1252
    def test_find_base_triple_criss_cross(self):
 
1253
        #       A-.
 
1254
        #      / \ \
 
1255
        #     B   C F # F is merged into both branches
 
1256
        #     |\ /| |
 
1257
        #     | X | |\
 
1258
        #     |/ \| | :
 
1259
        #   : D   E |
 
1260
        #    \|   |/
 
1261
        #     G   H
 
1262
        builder = self.setup_criss_cross_graph()
 
1263
        builder.build_snapshot('F-id', ['A-id'], [])
 
1264
        builder.build_snapshot('H-id', ['E-id', 'F-id'], [])
 
1265
        builder.build_snapshot('G-id', ['D-id', 'F-id'], [])
 
1266
        merger = self.make_Merger(builder, 'H-id')
 
1267
        self.assertEqual(['B-id', 'C-id', 'F-id'],
 
1268
                         [t.get_revision_id() for t in merger._lca_trees])
 
1269
 
 
1270
    def test_no_criss_cross_passed_to_merge_type(self):
 
1271
        class LCATreesMerger(LoggingMerger):
 
1272
            supports_lca_trees = True
 
1273
 
 
1274
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1275
        merger.merge_type = LCATreesMerger
 
1276
        merge_obj = merger.make_merger()
 
1277
        self.assertIsInstance(merge_obj, LCATreesMerger)
 
1278
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1279
 
 
1280
    def test_criss_cross_passed_to_merge_type(self):
 
1281
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1282
        merger.merge_type = _mod_merge.Merge3Merger
 
1283
        merge_obj = merger.make_merger()
 
1284
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1285
                                            for t in merger._lca_trees])
 
1286
 
 
1287
    def test_criss_cross_not_supported_merge_type(self):
 
1288
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1289
        # We explicitly do not define supports_lca_trees
 
1290
        merger.merge_type = LoggingMerger
 
1291
        merge_obj = merger.make_merger()
 
1292
        self.assertIsInstance(merge_obj, LoggingMerger)
 
1293
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1294
 
 
1295
    def test_criss_cross_unsupported_merge_type(self):
 
1296
        class UnsupportedLCATreesMerger(LoggingMerger):
 
1297
            supports_lca_trees = False
 
1298
 
 
1299
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1300
        merger.merge_type = UnsupportedLCATreesMerger
 
1301
        merge_obj = merger.make_merger()
 
1302
        self.assertIsInstance(merge_obj, UnsupportedLCATreesMerger)
 
1303
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1304
 
 
1305
 
 
1306
class TestMergerEntriesLCA(TestMergerBase):
 
1307
 
 
1308
    def make_merge_obj(self, builder, other_revision_id,
 
1309
                       interesting_files=None, interesting_ids=None):
 
1310
        merger = self.make_Merger(builder, other_revision_id,
 
1311
            interesting_files=interesting_files,
 
1312
            interesting_ids=interesting_ids)
 
1313
        return merger.make_merger()
 
1314
 
 
1315
    def test_simple(self):
 
1316
        builder = self.get_builder()
 
1317
        builder.build_snapshot('A-id', None,
 
1318
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1319
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1320
        builder.build_snapshot('C-id', ['A-id'],
 
1321
            [('modify', ('a-id', 'a\nb\nC\nc\n'))])
 
1322
        builder.build_snapshot('B-id', ['A-id'],
 
1323
            [('modify', ('a-id', 'a\nB\nb\nc\n'))])
 
1324
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1325
            [('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
 
1326
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1327
            [('modify', ('a-id', 'a\nB\nb\nC\nc\n'))])
 
1328
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1329
 
 
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())
 
1333
        entries = list(merge_obj._entries_lca())
 
1334
 
 
1335
        # (file_id, changed, parents, names, executable)
 
1336
        # BASE, lca1, lca2, OTHER, THIS
 
1337
        root_id = 'a-root-id'
 
1338
        self.assertEqual([('a-id', True,
 
1339
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1340
                           ((u'a', [u'a', u'a']), u'a', u'a'),
 
1341
                           ((False, [False, False]), False, False)),
 
1342
                         ], entries)
 
1343
 
 
1344
    def test_not_in_base(self):
 
1345
        # LCAs all have the same last-modified revision for the file, as do
 
1346
        # the tips, but the base has something different
 
1347
        #       A    base, doesn't have the file
 
1348
        #       |\
 
1349
        #       B C  B introduces 'foo', C introduces 'bar'
 
1350
        #       |X|
 
1351
        #       D E  D and E now both have 'foo' and 'bar'
 
1352
        #       |X|
 
1353
        #       F G  the files are now in F, G, D and E, but not in A
 
1354
        #            G modifies 'bar'
 
1355
 
 
1356
        builder = self.get_builder()
 
1357
        builder.build_snapshot('A-id', None,
 
1358
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1359
        builder.build_snapshot('B-id', ['A-id'],
 
1360
            [('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1361
        builder.build_snapshot('C-id', ['A-id'],
 
1362
            [('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
 
1363
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1364
            [('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
 
1365
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1366
            [('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1367
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1368
            [('modify', (u'bar-id', 'd\ne\nf\nG\n'))])
 
1369
        builder.build_snapshot('F-id', ['D-id', 'E-id'], [])
 
1370
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1371
 
 
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())
 
1375
        entries = list(merge_obj._entries_lca())
 
1376
        root_id = 'a-root-id'
 
1377
        self.assertEqual([('bar-id', True,
 
1378
                           ((None, [root_id, root_id]), root_id, root_id),
 
1379
                           ((None, [u'bar', u'bar']), u'bar', u'bar'),
 
1380
                           ((None, [False, False]), False, False)),
 
1381
                         ], entries)
 
1382
 
 
1383
    def test_not_in_this(self):
 
1384
        builder = self.get_builder()
 
1385
        builder.build_snapshot('A-id', None,
 
1386
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1387
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1388
        builder.build_snapshot('B-id', ['A-id'],
 
1389
            [('modify', ('a-id', 'a\nB\nb\nc\n'))])
 
1390
        builder.build_snapshot('C-id', ['A-id'],
 
1391
            [('modify', ('a-id', 'a\nb\nC\nc\n'))])
 
1392
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1393
            [('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
 
1394
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1395
            [('unversion', 'a-id')])
 
1396
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1397
 
 
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())
 
1401
 
 
1402
        entries = list(merge_obj._entries_lca())
 
1403
        root_id = 'a-root-id'
 
1404
        self.assertEqual([('a-id', True,
 
1405
                           ((root_id, [root_id, root_id]), root_id, None),
 
1406
                           ((u'a', [u'a', u'a']), u'a', None),
 
1407
                           ((False, [False, False]), False, None)),
 
1408
                         ], entries)
 
1409
 
 
1410
    def test_file_not_in_one_lca(self):
 
1411
        #   A   # just root
 
1412
        #   |\
 
1413
        #   B C # B no file, C introduces a file
 
1414
        #   |X|
 
1415
        #   D E # D and E both have the file, unchanged from C
 
1416
        builder = self.get_builder()
 
1417
        builder.build_snapshot('A-id', None,
 
1418
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1419
        builder.build_snapshot('B-id', ['A-id'], [])
 
1420
        builder.build_snapshot('C-id', ['A-id'],
 
1421
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1422
        builder.build_snapshot('E-id', ['C-id', 'B-id'], []) # Inherited from C
 
1423
        builder.build_snapshot('D-id', ['B-id', 'C-id'], # Merged from C
 
1424
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1425
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1426
 
 
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())
 
1430
 
 
1431
        entries = list(merge_obj._entries_lca())
 
1432
        self.assertEqual([], entries)
 
1433
 
 
1434
    def test_not_in_other(self):
 
1435
        builder = self.get_builder()
 
1436
        builder.build_snapshot('A-id', None,
 
1437
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1438
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1439
        builder.build_snapshot('B-id', ['A-id'], [])
 
1440
        builder.build_snapshot('C-id', ['A-id'], [])
 
1441
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1442
            [('unversion', 'a-id')])
 
1443
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1444
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1445
 
 
1446
        entries = list(merge_obj._entries_lca())
 
1447
        root_id = 'a-root-id'
 
1448
        self.assertEqual([('a-id', True,
 
1449
                           ((root_id, [root_id, root_id]), None, root_id),
 
1450
                           ((u'a', [u'a', u'a']), None, u'a'),
 
1451
                           ((False, [False, False]), None, False)),
 
1452
                         ], entries)
 
1453
 
 
1454
    def test_not_in_other_or_lca(self):
 
1455
        #       A    base, introduces 'foo'
 
1456
        #       |\
 
1457
        #       B C  B nothing, C deletes foo
 
1458
        #       |X|
 
1459
        #       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
 
1466
        builder = self.get_builder()
 
1467
        builder.build_snapshot('A-id', None,
 
1468
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1469
             ('add', (u'foo', 'foo-id', 'file', 'content\n'))])
 
1470
        builder.build_snapshot('B-id', ['A-id'], [])
 
1471
        builder.build_snapshot('C-id', ['A-id'],
 
1472
            [('unversion', 'foo-id')])
 
1473
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1474
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1475
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1476
 
 
1477
        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
        root_id = 'a-root-id'
 
1509
        self.assertEqual([('foo-id', True,
 
1510
                           ((root_id, [root_id, None]), None, root_id),
 
1511
                           ((u'foo', [u'foo', None]), None, 'foo'),
 
1512
                           ((False, [False, None]), None, False)),
 
1513
                         ], entries)
 
1514
 
 
1515
    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
        builder = self.get_builder()
 
1532
        builder.build_snapshot('A-id', None,
 
1533
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1534
        builder.build_snapshot('B-id', ['A-id'], [])
 
1535
        builder.build_snapshot('C-id', ['A-id'],
 
1536
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1537
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1538
            [('unversion', 'a-id')])
 
1539
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1540
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1541
 
 
1542
        entries = list(merge_obj._entries_lca())
 
1543
        self.assertEqual([], entries)
 
1544
 
 
1545
    def test_only_in_other(self):
 
1546
        builder = self.get_builder()
 
1547
        builder.build_snapshot('A-id', None,
 
1548
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1549
        builder.build_snapshot('B-id', ['A-id'], [])
 
1550
        builder.build_snapshot('C-id', ['A-id'], [])
 
1551
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1552
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1553
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1554
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1555
 
 
1556
        entries = list(merge_obj._entries_lca())
 
1557
        root_id = 'a-root-id'
 
1558
        self.assertEqual([('a-id', True,
 
1559
                           ((None, [None, None]), root_id, None),
 
1560
                           ((None, [None, None]), u'a', None),
 
1561
                           ((None, [None, None]), False, None)),
 
1562
                         ], entries)
 
1563
 
 
1564
    def test_one_lca_supersedes(self):
 
1565
        # One LCA supersedes the other LCAs last modified value, but the
 
1566
        # value is not the same as BASE.
 
1567
        #       A    base, introduces 'foo', last mod A
 
1568
        #       |\
 
1569
        #       B C  B modifies 'foo' (mod B), C does nothing (mod A)
 
1570
        #       |X|
 
1571
        #       D E  D does nothing (mod B), E updates 'foo' (mod E)
 
1572
        #       |X|
 
1573
        #       F G  F updates 'foo' (mod F). G does nothing (mod E)
 
1574
        #
 
1575
        #   At this point, G should not be considered to modify 'foo', even
 
1576
        #   though its LCAs disagree. This is because the modification in E
 
1577
        #   completely supersedes the value in D.
 
1578
        builder = self.get_builder()
 
1579
        builder.build_snapshot('A-id', None,
 
1580
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1581
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1582
        builder.build_snapshot('C-id', ['A-id'], [])
 
1583
        builder.build_snapshot('B-id', ['A-id'],
 
1584
            [('modify', ('foo-id', 'B content\n'))])
 
1585
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1586
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1587
            [('modify', ('foo-id', 'E content\n'))])
 
1588
        builder.build_snapshot('G-id', ['E-id', 'D-id'], [])
 
1589
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1590
            [('modify', ('foo-id', 'F content\n'))])
 
1591
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1592
 
 
1593
        self.assertEqual([], list(merge_obj._entries_lca()))
 
1594
 
 
1595
    def test_one_lca_supersedes_path(self):
 
1596
        # Double-criss-cross merge, the ultimate base value is different from
 
1597
        # the intermediate.
 
1598
        #   A    value 'foo'
 
1599
        #   |\
 
1600
        #   B C  B value 'bar', C = 'foo'
 
1601
        #   |X|
 
1602
        #   D E  D = 'bar', E supersedes to 'bing'
 
1603
        #   |X|
 
1604
        #   F G  F = 'bing', G supersedes to 'barry'
 
1605
        #
 
1606
        # In this case, we technically should not care about the value 'bar' for
 
1607
        # D, because it was clearly superseded by E's 'bing'. The
 
1608
        # per-file/attribute graph would actually look like:
 
1609
        #   A
 
1610
        #   |
 
1611
        #   B
 
1612
        #   |
 
1613
        #   E
 
1614
        #   |
 
1615
        #   G
 
1616
        #
 
1617
        # Because the other side of the merge never modifies the value, it just
 
1618
        # takes the value from the merge.
 
1619
        #
 
1620
        # ATM this fails because we will prune 'foo' from the LCAs, but we
 
1621
        # won't prune 'bar'. This is getting far off into edge-case land, so we
 
1622
        # aren't supporting it yet.
 
1623
        #
 
1624
        builder = self.get_builder()
 
1625
        builder.build_snapshot('A-id', None,
 
1626
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1627
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1628
        builder.build_snapshot('C-id', ['A-id'], [])
 
1629
        builder.build_snapshot('B-id', ['A-id'],
 
1630
            [('rename', ('foo', 'bar'))])
 
1631
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1632
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1633
            [('rename', ('foo', 'bing'))]) # override to bing
 
1634
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1635
            [('rename', ('bing', 'barry'))]) # override to barry
 
1636
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1637
            [('rename', ('bar', 'bing'))]) # Merge in E's change
 
1638
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1639
 
 
1640
        self.expectFailure("We don't do an actual heads() check on lca values,"
 
1641
            " or use the per-attribute graph",
 
1642
            self.assertEqual, [], list(merge_obj._entries_lca()))
 
1643
 
 
1644
    def test_one_lca_accidentally_pruned(self):
 
1645
        # Another incorrect resolution from the same basic flaw:
 
1646
        #   A    value 'foo'
 
1647
        #   |\
 
1648
        #   B C  B value 'bar', C = 'foo'
 
1649
        #   |X|
 
1650
        #   D E  D = 'bar', E reverts to 'foo'
 
1651
        #   |X|
 
1652
        #   F G  F = 'bing', G switches to 'bar'
 
1653
        #
 
1654
        # 'bar' will not be seen as an interesting change, because 'foo' will
 
1655
        # be pruned from the LCAs, even though it was newly introduced by E
 
1656
        # (superseding B).
 
1657
        builder = self.get_builder()
 
1658
        builder.build_snapshot('A-id', None,
 
1659
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1660
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1661
        builder.build_snapshot('C-id', ['A-id'], [])
 
1662
        builder.build_snapshot('B-id', ['A-id'],
 
1663
            [('rename', ('foo', 'bar'))])
 
1664
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1665
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1666
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1667
            [('rename', ('foo', 'bar'))])
 
1668
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1669
            [('rename', ('bar', 'bing'))]) # should end up conflicting
 
1670
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1671
 
 
1672
        entries = list(merge_obj._entries_lca())
 
1673
        root_id = 'a-root-id'
 
1674
        self.expectFailure("We prune values from BASE even when relevant.",
 
1675
            self.assertEqual,
 
1676
                [('foo-id', 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)),
 
1680
                ], entries)
 
1681
 
 
1682
    def test_both_sides_revert(self):
 
1683
        # Both sides of a criss-cross revert the text to the lca
 
1684
        #       A    base, introduces 'foo'
 
1685
        #       |\
 
1686
        #       B C  B modifies 'foo', C modifies 'foo'
 
1687
        #       |X|
 
1688
        #       D E  D reverts to B, E reverts to C
 
1689
        # This should conflict
 
1690
        builder = self.get_builder()
 
1691
        builder.build_snapshot('A-id', None,
 
1692
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1693
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1694
        builder.build_snapshot('B-id', ['A-id'],
 
1695
            [('modify', ('foo-id', 'B content\n'))])
 
1696
        builder.build_snapshot('C-id', ['A-id'],
 
1697
            [('modify', ('foo-id', 'C content\n'))])
 
1698
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1699
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1700
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1701
 
 
1702
        entries = list(merge_obj._entries_lca())
 
1703
        root_id = 'a-root-id'
 
1704
        self.assertEqual([('foo-id', True,
 
1705
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1706
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1707
                           ((False, [False, False]), False, False)),
 
1708
                         ], entries)
 
1709
 
 
1710
    def test_different_lca_resolve_one_side_updates_content(self):
 
1711
        # Both sides converge, but then one side updates the text.
 
1712
        #       A    base, introduces 'foo'
 
1713
        #       |\
 
1714
        #       B C  B modifies 'foo', C modifies 'foo'
 
1715
        #       |X|
 
1716
        #       D E  D reverts to B, E reverts to C
 
1717
        #       |
 
1718
        #       F    F updates to a new value
 
1719
        # We need to emit an entry for 'foo', because D & E differed on the
 
1720
        # merge resolution
 
1721
        builder = self.get_builder()
 
1722
        builder.build_snapshot('A-id', None,
 
1723
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1724
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1725
        builder.build_snapshot('B-id', ['A-id'],
 
1726
            [('modify', ('foo-id', 'B content\n'))])
 
1727
        builder.build_snapshot('C-id', ['A-id'],
 
1728
            [('modify', ('foo-id', 'C content\n'))])
 
1729
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1730
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1731
        builder.build_snapshot('F-id', ['D-id'],
 
1732
            [('modify', ('foo-id', 'F content\n'))])
 
1733
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1734
 
 
1735
        entries = list(merge_obj._entries_lca())
 
1736
        root_id = 'a-root-id'
 
1737
        self.assertEqual([('foo-id', True,
 
1738
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1739
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1740
                           ((False, [False, False]), False, False)),
 
1741
                         ], entries)
 
1742
 
 
1743
    def test_same_lca_resolution_one_side_updates_content(self):
 
1744
        # Both sides converge, but then one side updates the text.
 
1745
        #       A    base, introduces 'foo'
 
1746
        #       |\
 
1747
        #       B C  B modifies 'foo', C modifies 'foo'
 
1748
        #       |X|
 
1749
        #       D E  D and E use C's value
 
1750
        #       |
 
1751
        #       F    F updates to a new value
 
1752
        # I think it is a bug that this conflicts, but we don't have a way to
 
1753
        # detect otherwise. And because of:
 
1754
        #   test_different_lca_resolve_one_side_updates_content
 
1755
        # We need to conflict.
 
1756
 
 
1757
        builder = self.get_builder()
 
1758
        builder.build_snapshot('A-id', None,
 
1759
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1760
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1761
        builder.build_snapshot('B-id', ['A-id'],
 
1762
            [('modify', ('foo-id', 'B content\n'))])
 
1763
        builder.build_snapshot('C-id', ['A-id'],
 
1764
            [('modify', ('foo-id', 'C content\n'))])
 
1765
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1766
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1767
            [('modify', ('foo-id', 'C content\n'))]) # Same as E
 
1768
        builder.build_snapshot('F-id', ['D-id'],
 
1769
            [('modify', ('foo-id', 'F content\n'))])
 
1770
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1771
 
 
1772
        entries = list(merge_obj._entries_lca())
 
1773
        self.expectFailure("We don't detect that LCA resolution was the"
 
1774
                           " same on both sides",
 
1775
            self.assertEqual, [], entries)
 
1776
 
 
1777
    def test_only_path_changed(self):
 
1778
        builder = self.get_builder()
 
1779
        builder.build_snapshot('A-id', None,
 
1780
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1781
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1782
        builder.build_snapshot('B-id', ['A-id'], [])
 
1783
        builder.build_snapshot('C-id', ['A-id'], [])
 
1784
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1785
            [('rename', (u'a', u'b'))])
 
1786
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1787
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1788
        entries = list(merge_obj._entries_lca())
 
1789
        root_id = 'a-root-id'
 
1790
        # The content was not changed, only the path
 
1791
        self.assertEqual([('a-id', False,
 
1792
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1793
                           ((u'a', [u'a', u'a']), u'b', u'a'),
 
1794
                           ((False, [False, False]), False, False)),
 
1795
                         ], entries)
 
1796
 
 
1797
    def test_kind_changed(self):
 
1798
        # Identical content, except 'D' changes a-id into a directory
 
1799
        builder = self.get_builder()
 
1800
        builder.build_snapshot('A-id', None,
 
1801
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1802
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1803
        builder.build_snapshot('B-id', ['A-id'], [])
 
1804
        builder.build_snapshot('C-id', ['A-id'], [])
 
1805
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1806
            [('unversion', 'a-id'),
 
1807
             ('add', (u'a', 'a-id', 'directory', None))])
 
1808
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1809
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1810
        entries = list(merge_obj._entries_lca())
 
1811
        root_id = 'a-root-id'
 
1812
        # Only the kind was changed (content)
 
1813
        self.assertEqual([('a-id', True,
 
1814
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1815
                           ((u'a', [u'a', u'a']), u'a', u'a'),
 
1816
                           ((False, [False, False]), False, False)),
 
1817
                         ], entries)
 
1818
 
 
1819
    def test_this_changed_kind(self):
 
1820
        # Identical content, but THIS changes a file to a directory
 
1821
        builder = self.get_builder()
 
1822
        builder.build_snapshot('A-id', None,
 
1823
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1824
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1825
        builder.build_snapshot('B-id', ['A-id'], [])
 
1826
        builder.build_snapshot('C-id', ['A-id'], [])
 
1827
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1828
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1829
            [('unversion', 'a-id'),
 
1830
             ('add', (u'a', 'a-id', 'directory', None))])
 
1831
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1832
        entries = list(merge_obj._entries_lca())
 
1833
        # Only the kind was changed (content)
 
1834
        self.assertEqual([], entries)
 
1835
 
 
1836
    def test_interesting_files(self):
 
1837
        # Two files modified, but we should filter one of them
 
1838
        builder = self.get_builder()
 
1839
        builder.build_snapshot('A-id', None,
 
1840
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1841
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1842
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1843
        builder.build_snapshot('B-id', ['A-id'], [])
 
1844
        builder.build_snapshot('C-id', ['A-id'], [])
 
1845
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1846
            [('modify', ('a-id', 'new-content\n')),
 
1847
             ('modify', ('b-id', 'new-content\n'))])
 
1848
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1849
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1850
                                        interesting_files=['b'])
 
1851
        entries = list(merge_obj._entries_lca())
 
1852
        root_id = 'a-root-id'
 
1853
        self.assertEqual([('b-id', True,
 
1854
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1855
                           ((u'b', [u'b', u'b']), u'b', u'b'),
 
1856
                           ((False, [False, False]), False, False)),
 
1857
                         ], entries)
 
1858
 
 
1859
    def test_interesting_file_in_this(self):
 
1860
        # This renamed the file, but it should still match the entry in other
 
1861
        builder = self.get_builder()
 
1862
        builder.build_snapshot('A-id', None,
 
1863
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1864
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1865
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1866
        builder.build_snapshot('B-id', ['A-id'], [])
 
1867
        builder.build_snapshot('C-id', ['A-id'], [])
 
1868
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1869
            [('modify', ('a-id', 'new-content\n')),
 
1870
             ('modify', ('b-id', 'new-content\n'))])
 
1871
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1872
            [('rename', ('b', 'c'))])
 
1873
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1874
                                        interesting_files=['c'])
 
1875
        entries = list(merge_obj._entries_lca())
 
1876
        root_id = 'a-root-id'
 
1877
        self.assertEqual([('b-id', True,
 
1878
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1879
                           ((u'b', [u'b', u'b']), u'b', u'c'),
 
1880
                           ((False, [False, False]), False, False)),
 
1881
                         ], entries)
 
1882
 
 
1883
    def test_interesting_file_in_base(self):
 
1884
        # This renamed the file, but it should still match the entry in BASE
 
1885
        builder = self.get_builder()
 
1886
        builder.build_snapshot('A-id', None,
 
1887
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1888
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1889
             ('add', (u'c', 'c-id', 'file', 'content\n'))])
 
1890
        builder.build_snapshot('B-id', ['A-id'],
 
1891
            [('rename', ('c', 'b'))])
 
1892
        builder.build_snapshot('C-id', ['A-id'],
 
1893
            [('rename', ('c', 'b'))])
 
1894
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1895
            [('modify', ('a-id', 'new-content\n')),
 
1896
             ('modify', ('c-id', 'new-content\n'))])
 
1897
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1898
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1899
                                        interesting_files=['c'])
 
1900
        entries = list(merge_obj._entries_lca())
 
1901
        root_id = 'a-root-id'
 
1902
        self.assertEqual([('c-id', True,
 
1903
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1904
                           ((u'c', [u'b', u'b']), u'b', u'b'),
 
1905
                           ((False, [False, False]), False, False)),
 
1906
                         ], entries)
 
1907
 
 
1908
    def test_interesting_file_in_lca(self):
 
1909
        # This renamed the file, but it should still match the entry in LCA
 
1910
        builder = self.get_builder()
 
1911
        builder.build_snapshot('A-id', None,
 
1912
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1913
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1914
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1915
        builder.build_snapshot('B-id', ['A-id'],
 
1916
            [('rename', ('b', 'c'))])
 
1917
        builder.build_snapshot('C-id', ['A-id'], [])
 
1918
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1919
            [('modify', ('a-id', 'new-content\n')),
 
1920
             ('modify', ('b-id', 'new-content\n'))])
 
1921
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1922
            [('rename', ('c', 'b'))])
 
1923
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1924
                                        interesting_files=['c'])
 
1925
        entries = list(merge_obj._entries_lca())
 
1926
        root_id = 'a-root-id'
 
1927
        self.assertEqual([('b-id', True,
 
1928
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1929
                           ((u'b', [u'c', u'b']), u'b', u'b'),
 
1930
                           ((False, [False, False]), False, False)),
 
1931
                         ], entries)
 
1932
 
 
1933
    def test_interesting_ids(self):
 
1934
        # Two files modified, but we should filter one of them
 
1935
        builder = self.get_builder()
 
1936
        builder.build_snapshot('A-id', None,
 
1937
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1938
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1939
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1940
        builder.build_snapshot('B-id', ['A-id'], [])
 
1941
        builder.build_snapshot('C-id', ['A-id'], [])
 
1942
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1943
            [('modify', ('a-id', 'new-content\n')),
 
1944
             ('modify', ('b-id', 'new-content\n'))])
 
1945
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1946
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1947
                                        interesting_ids=['b-id'])
 
1948
        entries = list(merge_obj._entries_lca())
 
1949
        root_id = 'a-root-id'
 
1950
        self.assertEqual([('b-id', True,
 
1951
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1952
                           ((u'b', [u'b', u'b']), u'b', u'b'),
 
1953
                           ((False, [False, False]), False, False)),
 
1954
                         ], entries)
 
1955
 
 
1956
 
 
1957
 
 
1958
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
 
1959
 
 
1960
    def get_builder(self):
 
1961
        builder = self.make_branch_builder('path')
 
1962
        builder.start_series()
 
1963
        self.addCleanup(builder.finish_series)
 
1964
        return builder
 
1965
 
 
1966
    def get_wt_from_builder(self, builder):
 
1967
        """Get a real WorkingTree from the builder."""
 
1968
        the_branch = builder.get_branch()
 
1969
        wt = the_branch.bzrdir.create_workingtree()
 
1970
        # Note: This is a little bit ugly, but we are holding the branch
 
1971
        #       write-locked as part of the build process, and we would like to
 
1972
        #       maintain that. So we just force the WT to re-use the same
 
1973
        #       branch object.
 
1974
        wt._branch = the_branch
 
1975
        wt.lock_write()
 
1976
        self.addCleanup(wt.unlock)
 
1977
        return wt
 
1978
 
 
1979
    def do_merge(self, builder, other_revision_id):
 
1980
        wt = self.get_wt_from_builder(builder)
 
1981
        merger = _mod_merge.Merger.from_revision_ids(None,
 
1982
            wt, other_revision_id)
 
1983
        merger.merge_type = _mod_merge.Merge3Merger
 
1984
        return wt, merger.do_merge()
 
1985
 
 
1986
    def test_simple_lca(self):
 
1987
        builder = self.get_builder()
 
1988
        builder.build_snapshot('A-id', None,
 
1989
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1990
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1991
        builder.build_snapshot('C-id', ['A-id'], [])
 
1992
        builder.build_snapshot('B-id', ['A-id'], [])
 
1993
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1994
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1995
            [('modify', ('a-id', 'a\nb\nc\nd\ne\nf\n'))])
 
1996
        wt, conflicts = self.do_merge(builder, 'E-id')
 
1997
        self.assertEqual(0, conflicts)
 
1998
        # The merge should have simply update the contents of 'a'
 
1999
        self.assertEqual('a\nb\nc\nd\ne\nf\n', wt.get_file_text('a-id'))
 
2000
 
 
2001
    def test_conflict_without_lca(self):
 
2002
        # This test would cause a merge conflict, unless we use the lca trees
 
2003
        # to determine the real ancestry
 
2004
        #   A       Path at 'foo'
 
2005
        #  / \
 
2006
        # B   C     Path renamed to 'bar' in B
 
2007
        # |\ /|
 
2008
        # | X |
 
2009
        # |/ \|
 
2010
        # D   E     Path at 'bar' in D and E
 
2011
        #     |
 
2012
        #     F     Path at 'baz' in F, which supersedes 'bar' and 'foo'
 
2013
        builder = self.get_builder()
 
2014
        builder.build_snapshot('A-id', None,
 
2015
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2016
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2017
        builder.build_snapshot('C-id', ['A-id'], [])
 
2018
        builder.build_snapshot('B-id', ['A-id'],
 
2019
            [('rename', ('foo', 'bar'))])
 
2020
        builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
 
2021
            [('rename', ('foo', 'bar'))])
 
2022
        builder.build_snapshot('F-id', ['E-id'],
 
2023
            [('rename', ('bar', 'baz'))])
 
2024
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2025
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2026
        self.assertEqual(0, conflicts)
 
2027
        # The merge should simply recognize that the final rename takes
 
2028
        # precedence
 
2029
        self.assertEqual('baz', wt.id2path('foo-id'))
 
2030
 
 
2031
    def test_other_deletes_lca_renames(self):
 
2032
        # This test would cause a merge conflict, unless we use the lca trees
 
2033
        # to determine the real ancestry
 
2034
        #   A       Path at 'foo'
 
2035
        #  / \
 
2036
        # B   C     Path renamed to 'bar' in B
 
2037
        # |\ /|
 
2038
        # | X |
 
2039
        # |/ \|
 
2040
        # D   E     Path at 'bar' in D and E
 
2041
        #     |
 
2042
        #     F     F deletes 'bar'
 
2043
        builder = self.get_builder()
 
2044
        builder.build_snapshot('A-id', None,
 
2045
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2046
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2047
        builder.build_snapshot('C-id', ['A-id'], [])
 
2048
        builder.build_snapshot('B-id', ['A-id'],
 
2049
            [('rename', ('foo', 'bar'))])
 
2050
        builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
 
2051
            [('rename', ('foo', 'bar'))])
 
2052
        builder.build_snapshot('F-id', ['E-id'],
 
2053
            [('unversion', 'foo-id')])
 
2054
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2055
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2056
        self.assertEqual(0, conflicts)
 
2057
        self.assertRaises(errors.NoSuchId, wt.id2path, 'foo-id')
 
2058
 
 
2059
    def test_executable_changes(self):
 
2060
        #   A       Path at 'foo'
 
2061
        #  / \
 
2062
        # B   C
 
2063
        # |\ /|
 
2064
        # | X |
 
2065
        # |/ \|
 
2066
        # D   E
 
2067
        #     |
 
2068
        #     F     Executable bit changed
 
2069
        builder = self.get_builder()
 
2070
        builder.build_snapshot('A-id', None,
 
2071
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2072
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2073
        builder.build_snapshot('C-id', ['A-id'], [])
 
2074
        builder.build_snapshot('B-id', ['A-id'], [])
 
2075
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2076
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2077
        # Have to use a real WT, because BranchBuilder doesn't support exec bit
 
2078
        wt = self.get_wt_from_builder(builder)
 
2079
        tt = transform.TreeTransform(wt)
 
2080
        try:
 
2081
            tt.set_executability(True, tt.trans_id_tree_file_id('foo-id'))
 
2082
            tt.apply()
 
2083
        except:
 
2084
            tt.finalize()
 
2085
            raise
 
2086
        self.assertTrue(wt.is_executable('foo-id'))
 
2087
        wt.commit('F-id', rev_id='F-id')
 
2088
        # Reset to D, so that we can merge F
 
2089
        wt.set_parent_ids(['D-id'])
 
2090
        wt.branch.set_last_revision_info(3, 'D-id')
 
2091
        wt.revert()
 
2092
        self.assertFalse(wt.is_executable('foo-id'))
 
2093
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2094
        self.assertEqual(0, conflicts)
 
2095
        self.assertTrue(wt.is_executable('foo-id'))
 
2096
 
 
2097
    def test_create_symlink(self):
 
2098
        self.requireFeature(tests.SymlinkFeature)
 
2099
        #   A
 
2100
        #  / \
 
2101
        # B   C
 
2102
        # |\ /|
 
2103
        # | X |
 
2104
        # |/ \|
 
2105
        # D   E
 
2106
        #     |
 
2107
        #     F     Add a symlink 'foo' => 'bar'
 
2108
        # Have to use a real WT, because BranchBuilder and MemoryTree don't
 
2109
        # have symlink support
 
2110
        builder = self.get_builder()
 
2111
        builder.build_snapshot('A-id', None,
 
2112
            [('add', (u'', 'a-root-id', 'directory', None))])
 
2113
        builder.build_snapshot('C-id', ['A-id'], [])
 
2114
        builder.build_snapshot('B-id', ['A-id'], [])
 
2115
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2116
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2117
        # Have to use a real WT, because BranchBuilder doesn't support exec bit
 
2118
        wt = self.get_wt_from_builder(builder)
 
2119
        os.symlink('bar', 'path/foo')
 
2120
        wt.add(['foo'], ['foo-id'])
 
2121
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2122
        wt.commit('add symlink', rev_id='F-id')
 
2123
        # Reset to D, so that we can merge F
 
2124
        wt.set_parent_ids(['D-id'])
 
2125
        wt.branch.set_last_revision_info(3, 'D-id')
 
2126
        wt.revert()
 
2127
        self.assertIs(None, wt.path2id('foo'))
 
2128
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2129
        self.assertEqual(0, conflicts)
 
2130
        self.assertEqual('foo-id', wt.path2id('foo'))
 
2131
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2132
 
 
2133
    def test_both_sides_revert(self):
 
2134
        # Both sides of a criss-cross revert the text to the lca
 
2135
        #       A    base, introduces 'foo'
 
2136
        #       |\
 
2137
        #       B C  B modifies 'foo', C modifies 'foo'
 
2138
        #       |X|
 
2139
        #       D E  D reverts to B, E reverts to C
 
2140
        # This should conflict
 
2141
        # This must be done with a real WorkingTree, because normally their
 
2142
        # inventory contains "None" rather than a real sha1
 
2143
        builder = self.get_builder()
 
2144
        builder.build_snapshot('A-id', None,
 
2145
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2146
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
2147
        builder.build_snapshot('B-id', ['A-id'],
 
2148
            [('modify', ('foo-id', 'B content\n'))])
 
2149
        builder.build_snapshot('C-id', ['A-id'],
 
2150
            [('modify', ('foo-id', 'C content\n'))])
 
2151
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2152
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2153
        wt, conflicts = self.do_merge(builder, 'E-id')
 
2154
        self.assertEqual(1, conflicts)
 
2155
        self.assertEqualDiff('<<<<<<< TREE\n'
 
2156
                             'B content\n'
 
2157
                             '=======\n'
 
2158
                             'C content\n'
 
2159
                             '>>>>>>> MERGE-SOURCE\n',
 
2160
                             wt.get_file_text('foo-id'))
 
2161
 
 
2162
    def test_modified_symlink(self):
 
2163
        self.requireFeature(tests.SymlinkFeature)
 
2164
        #   A       Create symlink foo => bar
 
2165
        #  / \
 
2166
        # B   C     B relinks foo => baz
 
2167
        # |\ /|
 
2168
        # | X |
 
2169
        # |/ \|
 
2170
        # D   E     D & E have foo => baz
 
2171
        #     |
 
2172
        #     F     F changes it to bing
 
2173
        #
 
2174
        # Merging D & F should result in F cleanly overriding D, because D's
 
2175
        # value actually comes from B
 
2176
 
 
2177
        # Have to use a real WT, because BranchBuilder and MemoryTree don't
 
2178
        # have symlink support
 
2179
        wt = self.make_branch_and_tree('path')
 
2180
        wt.lock_write()
 
2181
        self.addCleanup(wt.unlock)
 
2182
        os.symlink('bar', 'path/foo')
 
2183
        wt.add(['foo'], ['foo-id'])
 
2184
        wt.commit('add symlink', rev_id='A-id')
 
2185
        os.remove('path/foo')
 
2186
        os.symlink('baz', 'path/foo')
 
2187
        wt.commit('foo => baz', rev_id='B-id')
 
2188
        wt.set_last_revision('A-id')
 
2189
        wt.branch.set_last_revision_info(1, 'A-id')
 
2190
        wt.revert()
 
2191
        wt.commit('C', rev_id='C-id')
 
2192
        wt.merge_from_branch(wt.branch, 'B-id')
 
2193
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2194
        wt.commit('E merges C & B', rev_id='E-id')
 
2195
        os.remove('path/foo')
 
2196
        os.symlink('bing', 'path/foo')
 
2197
        wt.commit('F foo => bing', rev_id='F-id')
 
2198
        wt.set_last_revision('B-id')
 
2199
        wt.branch.set_last_revision_info(2, 'B-id')
 
2200
        wt.revert()
 
2201
        wt.merge_from_branch(wt.branch, 'C-id')
 
2202
        wt.commit('D merges B & C', rev_id='D-id')
 
2203
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2204
        self.assertEqual(0, conflicts)
 
2205
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
 
2206
 
 
2207
    def test_renamed_symlink(self):
 
2208
        self.requireFeature(tests.SymlinkFeature)
 
2209
        #   A       Create symlink foo => bar
 
2210
        #  / \
 
2211
        # B   C     B renames foo => barry
 
2212
        # |\ /|
 
2213
        # | X |
 
2214
        # |/ \|
 
2215
        # D   E     D & E have barry
 
2216
        #     |
 
2217
        #     F     F renames barry to blah
 
2218
        #
 
2219
        # Merging D & F should result in F cleanly overriding D, because D's
 
2220
        # value actually comes from B
 
2221
 
 
2222
        wt = self.make_branch_and_tree('path')
 
2223
        wt.lock_write()
 
2224
        self.addCleanup(wt.unlock)
 
2225
        os.symlink('bar', 'path/foo')
 
2226
        wt.add(['foo'], ['foo-id'])
 
2227
        wt.commit('A add symlink', rev_id='A-id')
 
2228
        wt.rename_one('foo', 'barry')
 
2229
        wt.commit('B foo => barry', rev_id='B-id')
 
2230
        wt.set_last_revision('A-id')
 
2231
        wt.branch.set_last_revision_info(1, 'A-id')
 
2232
        wt.revert()
 
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')
 
2238
        wt.rename_one('barry', 'blah')
 
2239
        wt.commit('F barry => blah', rev_id='F-id')
 
2240
        wt.set_last_revision('B-id')
 
2241
        wt.branch.set_last_revision_info(2, 'B-id')
 
2242
        wt.revert()
 
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'))
 
2246
        # Check the output of the Merger object directly
 
2247
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2248
            wt, 'F-id')
 
2249
        merger.merge_type = _mod_merge.Merge3Merger
 
2250
        merge_obj = merger.make_merger()
 
2251
        root_id = wt.path2id('')
 
2252
        entries = list(merge_obj._entries_lca())
 
2253
        # No content change, just a path change
 
2254
        self.assertEqual([('foo-id', False,
 
2255
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2256
                           ((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
 
2257
                           ((False, [False, False]), False, False)),
 
2258
                         ], entries)
 
2259
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2260
        self.assertEqual(0, conflicts)
 
2261
        self.assertEqual('blah', wt.id2path('foo-id'))
 
2262
 
 
2263
    def test_symlink_no_content_change(self):
 
2264
        self.requireFeature(tests.SymlinkFeature)
 
2265
        #   A       Create symlink foo => bar
 
2266
        #  / \
 
2267
        # B   C     B relinks foo => baz
 
2268
        # |\ /|
 
2269
        # | X |
 
2270
        # |/ \|
 
2271
        # D   E     D & E have foo => baz
 
2272
        # |
 
2273
        # F         F has foo => bing
 
2274
        #
 
2275
        # Merging E into F should not cause a conflict, because E doesn't have
 
2276
        # a content change relative to the LCAs (it does relative to A)
 
2277
        wt = self.make_branch_and_tree('path')
 
2278
        wt.lock_write()
 
2279
        self.addCleanup(wt.unlock)
 
2280
        os.symlink('bar', 'path/foo')
 
2281
        wt.add(['foo'], ['foo-id'])
 
2282
        wt.commit('add symlink', rev_id='A-id')
 
2283
        os.remove('path/foo')
 
2284
        os.symlink('baz', 'path/foo')
 
2285
        wt.commit('foo => baz', rev_id='B-id')
 
2286
        wt.set_last_revision('A-id')
 
2287
        wt.branch.set_last_revision_info(1, 'A-id')
 
2288
        wt.revert()
 
2289
        wt.commit('C', rev_id='C-id')
 
2290
        wt.merge_from_branch(wt.branch, 'B-id')
 
2291
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2292
        wt.commit('E merges C & B', rev_id='E-id')
 
2293
        wt.set_last_revision('B-id')
 
2294
        wt.branch.set_last_revision_info(2, 'B-id')
 
2295
        wt.revert()
 
2296
        wt.merge_from_branch(wt.branch, 'C-id')
 
2297
        wt.commit('D merges B & C', rev_id='D-id')
 
2298
        os.remove('path/foo')
 
2299
        os.symlink('bing', 'path/foo')
 
2300
        wt.commit('F foo => bing', rev_id='F-id')
 
2301
 
 
2302
        # Check the output of the Merger object directly
 
2303
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2304
            wt, 'E-id')
 
2305
        merger.merge_type = _mod_merge.Merge3Merger
 
2306
        merge_obj = merger.make_merger()
 
2307
        # Nothing interesting happened in OTHER relative to BASE
 
2308
        self.assertEqual([], list(merge_obj._entries_lca()))
 
2309
        # Now do a real merge, just to test the rest of the stack
 
2310
        conflicts = wt.merge_from_branch(wt.branch, to_revision='E-id')
 
2311
        self.assertEqual(0, conflicts)
 
2312
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
 
2313
 
 
2314
    def test_symlink_this_changed_kind(self):
 
2315
        self.requireFeature(tests.SymlinkFeature)
 
2316
        #   A       Nothing
 
2317
        #  / \
 
2318
        # B   C     B creates symlink foo => bar
 
2319
        # |\ /|
 
2320
        # | X |
 
2321
        # |/ \|
 
2322
        # D   E     D changes foo into a file, E has foo => bing
 
2323
        #
 
2324
        # Mostly, this is trying to test that we don't try to os.readlink() on
 
2325
        # a file, or when there is nothing there
 
2326
        wt = self.make_branch_and_tree('path')
 
2327
        wt.lock_write()
 
2328
        self.addCleanup(wt.unlock)
 
2329
        wt.commit('base', rev_id='A-id')
 
2330
        os.symlink('bar', 'path/foo')
 
2331
        wt.add(['foo'], ['foo-id'])
 
2332
        wt.commit('add symlink foo => bar', rev_id='B-id')
 
2333
        wt.set_last_revision('A-id')
 
2334
        wt.branch.set_last_revision_info(1, 'A-id')
 
2335
        wt.revert()
 
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'))
 
2339
        os.remove('path/foo')
 
2340
        # We have to change the link in E, or it won't try to do a comparison
 
2341
        os.symlink('bing', 'path/foo')
 
2342
        wt.commit('E merges C & B, overrides to bing', rev_id='E-id')
 
2343
        wt.set_last_revision('B-id')
 
2344
        wt.branch.set_last_revision_info(2, 'B-id')
 
2345
        wt.revert()
 
2346
        wt.merge_from_branch(wt.branch, 'C-id')
 
2347
        os.remove('path/foo')
 
2348
        self.build_tree_contents([('path/foo', 'file content\n')])
 
2349
        # XXX: workaround, WT doesn't detect kind changes unless you do
 
2350
        # iter_changes()
 
2351
        list(wt.iter_changes(wt.basis_tree()))
 
2352
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
 
2353
 
 
2354
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2355
            wt, 'E-id')
 
2356
        merger.merge_type = _mod_merge.Merge3Merger
 
2357
        merge_obj = merger.make_merger()
 
2358
        entries = list(merge_obj._entries_lca())
 
2359
        root_id = wt.path2id('')
 
2360
        self.assertEqual([('foo-id', True,
 
2361
                           ((None, [root_id, None]), root_id, root_id),
 
2362
                           ((None, [u'foo', None]), u'foo', u'foo'),
 
2363
                           ((None, [False, None]), False, False)),
 
2364
                         ], entries)
 
2365
 
 
2366
    def test_symlink_all_wt(self):
 
2367
        """Check behavior if all trees are Working Trees."""
 
2368
        self.requireFeature(tests.SymlinkFeature)
 
2369
        # The big issue is that entry.symlink_target is None for WorkingTrees.
 
2370
        # So we need to make sure we handle that case correctly.
 
2371
        #   A   foo => bar
 
2372
        #   |\
 
2373
        #   B C B relinks foo => baz
 
2374
        #   |X|
 
2375
        #   D E D & E have foo => baz
 
2376
        #     |
 
2377
        #     F F changes it to bing
 
2378
        # Merging D & F should result in F cleanly overriding D, because D's
 
2379
        # value actually comes from B
 
2380
 
 
2381
        wt = self.make_branch_and_tree('path')
 
2382
        wt.lock_write()
 
2383
        self.addCleanup(wt.unlock)
 
2384
        os.symlink('bar', 'path/foo')
 
2385
        wt.add(['foo'], ['foo-id'])
 
2386
        wt.commit('add symlink', rev_id='A-id')
 
2387
        os.remove('path/foo')
 
2388
        os.symlink('baz', 'path/foo')
 
2389
        wt.commit('foo => baz', rev_id='B-id')
 
2390
        wt.set_last_revision('A-id')
 
2391
        wt.branch.set_last_revision_info(1, 'A-id')
 
2392
        wt.revert()
 
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')
 
2397
        os.remove('path/foo')
 
2398
        os.symlink('bing', 'path/foo')
 
2399
        wt.commit('F foo => bing', rev_id='F-id')
 
2400
        wt.set_last_revision('B-id')
 
2401
        wt.branch.set_last_revision_info(2, 'B-id')
 
2402
        wt.revert()
 
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()
 
2406
        wt_base.lock_read()
 
2407
        self.addCleanup(wt_base.unlock)
 
2408
        wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
 
2409
        wt_lca1.lock_read()
 
2410
        self.addCleanup(wt_lca1.unlock)
 
2411
        wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
 
2412
        wt_lca2.lock_read()
 
2413
        self.addCleanup(wt_lca2.unlock)
 
2414
        wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
 
2415
        wt_other.lock_read()
 
2416
        self.addCleanup(wt_other.unlock)
 
2417
        merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
 
2418
            wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
 
2419
        entries = list(merge_obj._entries_lca())
 
2420
        root_id = wt.path2id('')
 
2421
        self.assertEqual([('foo-id', True,
 
2422
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2423
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
2424
                           ((False, [False, False]), False, False)),
 
2425
                         ], entries)
 
2426
 
 
2427
    def test_other_reverted_path_to_base(self):
 
2428
        #   A       Path at 'foo'
 
2429
        #  / \
 
2430
        # B   C     Path at 'bar' in B
 
2431
        # |\ /|
 
2432
        # | X |
 
2433
        # |/ \|
 
2434
        # D   E     Path at 'bar'
 
2435
        #     |
 
2436
        #     F     Path at 'foo'
 
2437
        builder = self.get_builder()
 
2438
        builder.build_snapshot('A-id', None,
 
2439
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2440
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2441
        builder.build_snapshot('C-id', ['A-id'], [])
 
2442
        builder.build_snapshot('B-id', ['A-id'],
 
2443
            [('rename', ('foo', 'bar'))])
 
2444
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2445
            [('rename', ('foo', 'bar'))]) # merge the rename
 
2446
        builder.build_snapshot('F-id', ['E-id'],
 
2447
            [('rename', ('bar', 'foo'))]) # Rename back to BASE
 
2448
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2449
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2450
        self.assertEqual(0, conflicts)
 
2451
        self.assertEqual('foo', wt.id2path('foo-id'))
 
2452
 
 
2453
    def test_other_reverted_content_to_base(self):
 
2454
        builder = self.get_builder()
 
2455
        builder.build_snapshot('A-id', None,
 
2456
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2457
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2458
        builder.build_snapshot('C-id', ['A-id'], [])
 
2459
        builder.build_snapshot('B-id', ['A-id'],
 
2460
            [('modify', ('foo-id', 'B content\n'))])
 
2461
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2462
            [('modify', ('foo-id', 'B content\n'))]) # merge the content
 
2463
        builder.build_snapshot('F-id', ['E-id'],
 
2464
            [('modify', ('foo-id', 'base content\n'))]) # Revert back to BASE
 
2465
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2466
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2467
        self.assertEqual(0, conflicts)
 
2468
        # TODO: We need to use the per-file graph to properly select a BASE
 
2469
        #       before this will work. Or at least use the LCA trees to find
 
2470
        #       the appropriate content base. (which is B, not A).
 
2471
        self.assertEqual('base content\n', wt.get_file_text('foo-id'))
 
2472
 
 
2473
    def test_other_modified_content(self):
 
2474
        builder = self.get_builder()
 
2475
        builder.build_snapshot('A-id', None,
 
2476
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2477
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2478
        builder.build_snapshot('C-id', ['A-id'], [])
 
2479
        builder.build_snapshot('B-id', ['A-id'],
 
2480
            [('modify', ('foo-id', 'B content\n'))])
 
2481
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2482
            [('modify', ('foo-id', 'B content\n'))]) # merge the content
 
2483
        builder.build_snapshot('F-id', ['E-id'],
 
2484
            [('modify', ('foo-id', 'F content\n'))]) # Override B content
 
2485
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2486
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2487
        self.assertEqual(0, conflicts)
 
2488
        self.assertEqual('F content\n', wt.get_file_text('foo-id'))
 
2489
 
 
2490
    def test_all_wt(self):
 
2491
        """Check behavior if all trees are Working Trees."""
 
2492
        # The big issue is that entry.revision is None for WorkingTrees. (as is
 
2493
        # entry.text_sha1, etc. So we need to make sure we handle that case
 
2494
        # correctly.
 
2495
        #   A   Content of 'foo', path of 'a'
 
2496
        #   |\
 
2497
        #   B C B modifies content, C renames 'a' => 'b'
 
2498
        #   |X|
 
2499
        #   D E E updates content, renames 'b' => 'c'
 
2500
        builder = self.get_builder()
 
2501
        builder.build_snapshot('A-id', None,
 
2502
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2503
             ('add', (u'a', 'a-id', 'file', 'base content\n')),
 
2504
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2505
        builder.build_snapshot('B-id', ['A-id'],
 
2506
            [('modify', ('foo-id', 'B content\n'))])
 
2507
        builder.build_snapshot('C-id', ['A-id'],
 
2508
            [('rename', ('a', 'b'))])
 
2509
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2510
            [('rename', ('b', 'c')),
 
2511
             ('modify', ('foo-id', 'E content\n'))])
 
2512
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
2513
            [('rename', ('a', 'b'))]) # merged change
 
2514
        wt_this = self.get_wt_from_builder(builder)
 
2515
        wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
 
2516
        wt_base.lock_read()
 
2517
        self.addCleanup(wt_base.unlock)
 
2518
        wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
 
2519
        wt_lca1.lock_read()
 
2520
        self.addCleanup(wt_lca1.unlock)
 
2521
        wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
 
2522
        wt_lca2.lock_read()
 
2523
        self.addCleanup(wt_lca2.unlock)
 
2524
        wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
 
2525
        wt_other.lock_read()
 
2526
        self.addCleanup(wt_other.unlock)
 
2527
        merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
 
2528
            wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
 
2529
        entries = list(merge_obj._entries_lca())
 
2530
        root_id = 'a-root-id'
 
2531
        self.assertEqual([('a-id', False,
 
2532
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2533
                           ((u'a', [u'a', u'b']), u'c', u'b'),
 
2534
                           ((False, [False, False]), False, False)),
 
2535
                          ('foo-id', True,
 
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)),
 
2539
                         ], entries)
 
2540
 
 
2541
    def test_nested_tree_unmodified(self):
 
2542
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2543
        # 'tree-reference'
 
2544
        wt = self.make_branch_and_tree('tree',
 
2545
            format='dirstate-with-subtree')
 
2546
        wt.lock_write()
 
2547
        self.addCleanup(wt.unlock)
 
2548
        sub_tree = self.make_branch_and_tree('tree/sub-tree',
 
2549
            format='dirstate-with-subtree')
 
2550
        wt.set_root_id('a-root-id')
 
2551
        sub_tree.set_root_id('sub-tree-root')
 
2552
        self.build_tree_contents([('tree/sub-tree/file', 'text1')])
 
2553
        sub_tree.add('file')
 
2554
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2555
        wt.add_reference(sub_tree)
 
2556
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2557
        # Now create a criss-cross merge in the parent, without modifying the
 
2558
        # subtree
 
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)
 
2568
 
 
2569
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2570
            wt, 'E-id')
 
2571
        merger.merge_type = _mod_merge.Merge3Merger
 
2572
        merge_obj = merger.make_merger()
 
2573
        entries = list(merge_obj._entries_lca())
 
2574
        self.assertEqual([], entries)
 
2575
 
 
2576
    def test_nested_tree_subtree_modified(self):
 
2577
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2578
        # 'tree-reference'
 
2579
        wt = self.make_branch_and_tree('tree',
 
2580
            format='dirstate-with-subtree')
 
2581
        wt.lock_write()
 
2582
        self.addCleanup(wt.unlock)
 
2583
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2584
            format='dirstate-with-subtree')
 
2585
        wt.set_root_id('a-root-id')
 
2586
        sub_tree.set_root_id('sub-tree-root')
 
2587
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2588
        sub_tree.add('file')
 
2589
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2590
        wt.add_reference(sub_tree)
 
2591
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2592
        # Now create a criss-cross merge in the parent, without modifying the
 
2593
        # subtree
 
2594
        wt.commit('B', rev_id='B-id', recursive=None)
 
2595
        wt.set_last_revision('A-id')
 
2596
        wt.branch.set_last_revision_info(1, 'A-id')
 
2597
        wt.commit('C', rev_id='C-id', recursive=None)
 
2598
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2599
        self.build_tree_contents([('tree/sub/file', 'text2')])
 
2600
        sub_tree.commit('modify contents', rev_id='sub-B-id')
 
2601
        wt.commit('E', rev_id='E-id', recursive=None)
 
2602
        wt.set_parent_ids(['B-id', 'C-id'])
 
2603
        wt.branch.set_last_revision_info(2, 'B-id')
 
2604
        wt.commit('D', rev_id='D-id', recursive=None)
 
2605
 
 
2606
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2607
            wt, 'E-id')
 
2608
        merger.merge_type = _mod_merge.Merge3Merger
 
2609
        merge_obj = merger.make_merger()
 
2610
        entries = list(merge_obj._entries_lca())
 
2611
        # Nothing interesting about this sub-tree, because content changes are
 
2612
        # computed at a higher level
 
2613
        self.assertEqual([], entries)
 
2614
 
 
2615
    def test_nested_tree_subtree_renamed(self):
 
2616
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2617
        # 'tree-reference'
 
2618
        wt = self.make_branch_and_tree('tree',
 
2619
            format='dirstate-with-subtree')
 
2620
        wt.lock_write()
 
2621
        self.addCleanup(wt.unlock)
 
2622
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2623
            format='dirstate-with-subtree')
 
2624
        wt.set_root_id('a-root-id')
 
2625
        sub_tree.set_root_id('sub-tree-root')
 
2626
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2627
        sub_tree.add('file')
 
2628
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2629
        wt.add_reference(sub_tree)
 
2630
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2631
        # Now create a criss-cross merge in the parent, without modifying the
 
2632
        # subtree
 
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')
 
2638
        wt.rename_one('sub', 'alt_sub')
 
2639
        wt.commit('E', rev_id='E-id', recursive=None)
 
2640
        wt.set_last_revision('B-id')
 
2641
        wt.revert()
 
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)
 
2645
 
 
2646
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2647
            wt, 'E-id')
 
2648
        merger.merge_type = _mod_merge.Merge3Merger
 
2649
        merge_obj = merger.make_merger()
 
2650
        entries = list(merge_obj._entries_lca())
 
2651
        root_id = 'a-root-id'
 
2652
        self.assertEqual([('sub-tree-root', False,
 
2653
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2654
                           ((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
 
2655
                           ((False, [False, False]), False, False)),
 
2656
                         ], entries)
 
2657
 
 
2658
    def test_nested_tree_subtree_renamed_and_modified(self):
 
2659
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2660
        # 'tree-reference'
 
2661
        wt = self.make_branch_and_tree('tree',
 
2662
            format='dirstate-with-subtree')
 
2663
        wt.lock_write()
 
2664
        self.addCleanup(wt.unlock)
 
2665
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2666
            format='dirstate-with-subtree')
 
2667
        wt.set_root_id('a-root-id')
 
2668
        sub_tree.set_root_id('sub-tree-root')
 
2669
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2670
        sub_tree.add('file')
 
2671
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2672
        wt.add_reference(sub_tree)
 
2673
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2674
        # Now create a criss-cross merge in the parent, without modifying the
 
2675
        # subtree
 
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')
 
2683
        wt.rename_one('sub', 'alt_sub')
 
2684
        wt.commit('E', rev_id='E-id', recursive=None)
 
2685
        wt.set_last_revision('B-id')
 
2686
        wt.revert()
 
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)
 
2690
 
 
2691
        merger = _mod_merge.Merger.from_revision_ids(None,
 
2692
            wt, 'E-id')
 
2693
        merger.merge_type = _mod_merge.Merge3Merger
 
2694
        merge_obj = merger.make_merger()
 
2695
        entries = list(merge_obj._entries_lca())
 
2696
        root_id = 'a-root-id'
 
2697
        self.assertEqual([('sub-tree-root', False,
 
2698
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2699
                           ((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
 
2700
                           ((False, [False, False]), False, False)),
 
2701
                         ], entries)
 
2702
 
 
2703
 
 
2704
class TestLCAMultiWay(tests.TestCase):
 
2705
 
 
2706
    def assertLCAMultiWay(self, expected, base, lcas, other, this,
 
2707
                          allow_overriding_lca=True):
 
2708
        self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
 
2709
                                (base, lcas), other, this,
 
2710
                                allow_overriding_lca=allow_overriding_lca))
 
2711
 
 
2712
    def test_other_equal_equal_lcas(self):
 
2713
        """Test when OTHER=LCA and all LCAs are identical."""
 
2714
        self.assertLCAMultiWay('this',
 
2715
            'bval', ['bval', 'bval'], 'bval', 'bval')
 
2716
        self.assertLCAMultiWay('this',
 
2717
            'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
 
2718
        self.assertLCAMultiWay('this',
 
2719
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
 
2720
        self.assertLCAMultiWay('this',
 
2721
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
 
2722
        self.assertLCAMultiWay('this',
 
2723
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
 
2724
 
 
2725
    def test_other_equal_this(self):
 
2726
        """Test when other and this are identical."""
 
2727
        self.assertLCAMultiWay('this',
 
2728
            'bval', ['bval', 'bval'], 'oval', 'oval')
 
2729
        self.assertLCAMultiWay('this',
 
2730
            'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
 
2731
        self.assertLCAMultiWay('this',
 
2732
            'bval', ['cval', 'dval'], 'oval', 'oval')
 
2733
        self.assertLCAMultiWay('this',
 
2734
            'bval', [None, 'lcaval'], 'oval', 'oval')
 
2735
        self.assertLCAMultiWay('this',
 
2736
            None, [None, 'lcaval'], 'oval', 'oval')
 
2737
        self.assertLCAMultiWay('this',
 
2738
            None, ['lcaval', 'lcaval'], 'oval', 'oval')
 
2739
        self.assertLCAMultiWay('this',
 
2740
            None, ['cval', 'dval'], 'oval', 'oval')
 
2741
        self.assertLCAMultiWay('this',
 
2742
            None, ['cval', 'dval'], None, None)
 
2743
        self.assertLCAMultiWay('this',
 
2744
            None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
 
2745
 
 
2746
    def test_no_lcas(self):
 
2747
        self.assertLCAMultiWay('this',
 
2748
            'bval', [], 'bval', 'tval')
 
2749
        self.assertLCAMultiWay('other',
 
2750
            'bval', [], 'oval', 'bval')
 
2751
        self.assertLCAMultiWay('conflict',
 
2752
            'bval', [], 'oval', 'tval')
 
2753
        self.assertLCAMultiWay('this',
 
2754
            'bval', [], 'oval', 'oval')
 
2755
 
 
2756
    def test_lca_supersedes_other_lca(self):
 
2757
        """If one lca == base, the other lca takes precedence"""
 
2758
        self.assertLCAMultiWay('this',
 
2759
            'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
 
2760
        self.assertLCAMultiWay('this',
 
2761
            'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
 
2762
        # This is actually considered a 'revert' because the 'lcaval' in LCAS
 
2763
        # supersedes the BASE val (in the other LCA) but then OTHER reverts it
 
2764
        # back to bval.
 
2765
        self.assertLCAMultiWay('other',
 
2766
            'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
 
2767
        self.assertLCAMultiWay('conflict',
 
2768
            'bval', ['bval', 'lcaval'], 'bval', 'tval')
 
2769
 
 
2770
    def test_other_and_this_pick_different_lca(self):
 
2771
        # OTHER and THIS resolve the lca conflict in different ways
 
2772
        self.assertLCAMultiWay('conflict',
 
2773
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
 
2774
        self.assertLCAMultiWay('conflict',
 
2775
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
 
2776
        self.assertLCAMultiWay('conflict',
 
2777
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
 
2778
 
 
2779
    def test_other_in_lca(self):
 
2780
        # OTHER takes a value of one of the LCAs, THIS takes a new value, which
 
2781
        # theoretically supersedes both LCA values and 'wins'
 
2782
        self.assertLCAMultiWay('this',
 
2783
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
 
2784
        self.assertLCAMultiWay('this',
 
2785
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval')
 
2786
        self.assertLCAMultiWay('conflict',
 
2787
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval',
 
2788
            allow_overriding_lca=False)
 
2789
        self.assertLCAMultiWay('conflict',
 
2790
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval',
 
2791
            allow_overriding_lca=False)
 
2792
        # THIS reverted back to BASE, but that is an explicit supersede of all
 
2793
        # LCAs
 
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)
 
2804
 
 
2805
    def test_this_in_lca(self):
 
2806
        # THIS takes a value of one of the LCAs, OTHER takes a new value, which
 
2807
        # theoretically supersedes both LCA values and 'wins'
 
2808
        self.assertLCAMultiWay('other',
 
2809
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
 
2810
        self.assertLCAMultiWay('other',
 
2811
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
 
2812
        self.assertLCAMultiWay('conflict',
 
2813
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val',
 
2814
            allow_overriding_lca=False)
 
2815
        self.assertLCAMultiWay('conflict',
 
2816
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val',
 
2817
            allow_overriding_lca=False)
 
2818
        # OTHER reverted back to BASE, but that is an explicit supersede of all
 
2819
        # LCAs
 
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)
 
2825
 
 
2826
    def test_all_differ(self):
 
2827
        self.assertLCAMultiWay('conflict',
 
2828
            'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
 
2829
        self.assertLCAMultiWay('conflict',
 
2830
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
 
2831
        self.assertLCAMultiWay('conflict',
 
2832
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')
 
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)