~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-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

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
19
19
 
20
20
from bzrlib import (
 
21
    branch as _mod_branch,
 
22
    cleanup,
21
23
    conflicts,
22
24
    errors,
 
25
    inventory,
23
26
    knit,
24
27
    memorytree,
25
28
    merge as _mod_merge,
26
29
    option,
27
 
    progress,
 
30
    revision as _mod_revision,
28
31
    tests,
29
32
    transform,
30
33
    versionedfile,
31
34
    )
32
 
from bzrlib.branch import Branch
33
35
from bzrlib.conflicts import ConflictList, TextConflict
34
 
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
 
36
from bzrlib.errors import UnrelatedBranches, NoCommits
35
37
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
36
 
from bzrlib.osutils import pathjoin, file_kind
37
 
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
38
 
from bzrlib.trace import (enable_test_log, disable_test_log)
 
38
from bzrlib.osutils import basename, pathjoin, file_kind
 
39
from bzrlib.tests import (
 
40
    TestCaseWithMemoryTransport,
 
41
    TestCaseWithTransport,
 
42
    test_merge_core,
 
43
    )
39
44
from bzrlib.workingtree import WorkingTree
40
45
 
41
46
 
89
94
        self.failIfExists('bar')
90
95
        wt2 = WorkingTree.open('.') # opens branch2
91
96
        self.assertEqual([tip], wt2.get_parent_ids())
92
 
        
 
97
 
93
98
    def test_pending_with_null(self):
94
99
        """When base is forced to revno 0, parent_ids are set"""
95
100
        wt2 = self.test_unrelated()
96
101
        wt1 = WorkingTree.open('.')
97
102
        br1 = wt1.branch
98
103
        br1.fetch(wt2.branch)
99
 
        # merge all of branch 2 into branch 1 even though they 
 
104
        # merge all of branch 2 into branch 1 even though they
100
105
        # are not related.
101
106
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
102
107
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
150
155
        self.addCleanup(tree_b.unlock)
151
156
        tree_a.commit(message="hello again")
152
157
        log = StringIO()
153
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
158
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
154
159
                    this_tree=tree_b, ignore_zero=True)
155
 
        log = self._get_log(keep_log_file=True)
156
 
        self.failUnless('All changes applied successfully.\n' not in log)
 
160
        self.failUnless('All changes applied successfully.\n' not in
 
161
            self.get_log())
157
162
        tree_b.revert()
158
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
163
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
159
164
                    this_tree=tree_b, ignore_zero=False)
160
 
        log = self._get_log(keep_log_file=True)
161
 
        self.failUnless('All changes applied successfully.\n' in log)
 
165
        self.failUnless('All changes applied successfully.\n' in self.get_log())
162
166
 
163
167
    def test_merge_inner_conflicts(self):
164
168
        tree_a = self.make_branch_and_tree('a')
219
223
        tree_a.add('file')
220
224
        tree_a.commit('commit base')
221
225
        # basis_tree() is only guaranteed to be valid as long as it is actually
222
 
        # the basis tree. This mutates the tree after grabbing basis, so go to
223
 
        # the repository.
 
226
        # the basis tree. This test commits to the tree after grabbing basis,
 
227
        # so we go to the repository.
224
228
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
225
229
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
226
230
        self.build_tree_contents([('tree_a/file', 'content_2')])
227
231
        tree_a.commit('commit other')
228
232
        other_tree = tree_a.basis_tree()
 
233
        # 'file' is now missing but isn't altered in any commit in b so no
 
234
        # change should be applied.
229
235
        os.unlink('tree_b/file')
230
236
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
231
237
 
248
254
        self.assertEqual(tree_b.conflicts(),
249
255
                         [conflicts.ContentsConflict('file',
250
256
                          file_id='file-id')])
251
 
    
 
257
 
252
258
    def test_merge_type_registry(self):
253
259
        merge_type_option = option.Option.OPTIONS['merge-type']
254
 
        self.assertFalse('merge4' in [x[0] for x in 
 
260
        self.assertFalse('merge4' in [x[0] for x in
255
261
                        merge_type_option.iter_switches()])
256
262
        registry = _mod_merge.get_merge_type_registry()
257
263
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
258
264
                               'time-travelling merge')
259
 
        self.assertTrue('merge4' in [x[0] for x in 
 
265
        self.assertTrue('merge4' in [x[0] for x in
260
266
                        merge_type_option.iter_switches()])
261
267
        registry.remove('merge4')
262
 
        self.assertFalse('merge4' in [x[0] for x in 
 
268
        self.assertFalse('merge4' in [x[0] for x in
263
269
                        merge_type_option.iter_switches()])
264
270
 
265
271
    def test_merge_other_moves_we_deleted(self):
292
298
        tree_a.commit('commit 2')
293
299
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
294
300
        tree_b.rename_one('file_1', 'renamed')
295
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
296
 
                                                    progress.DummyProgress())
 
301
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
297
302
        merger.merge_type = _mod_merge.Merge3Merger
298
303
        merger.do_merge()
299
304
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
307
312
        tree_a.commit('commit 2')
308
313
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
309
314
        tree_b.rename_one('file_1', 'renamed')
310
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
311
 
                                                    progress.DummyProgress())
 
315
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
312
316
        merger.merge_type = _mod_merge.WeaveMerger
313
317
        merger.do_merge()
314
318
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
339
343
 
340
344
    def test_weave_cherrypick(self):
341
345
        this_tree, other_tree = self.prepare_cherrypick()
342
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
346
        merger = _mod_merge.Merger.from_revision_ids(None,
343
347
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
344
348
        merger.merge_type = _mod_merge.WeaveMerger
345
349
        merger.do_merge()
347
351
 
348
352
    def test_weave_cannot_reverse_cherrypick(self):
349
353
        this_tree, other_tree = self.prepare_cherrypick()
350
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
354
        merger = _mod_merge.Merger.from_revision_ids(None,
351
355
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
352
356
        merger.merge_type = _mod_merge.WeaveMerger
353
357
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
354
358
 
355
359
    def test_merge3_can_reverse_cherrypick(self):
356
360
        this_tree, other_tree = self.prepare_cherrypick()
357
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
361
        merger = _mod_merge.Merger.from_revision_ids(None,
358
362
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
359
363
        merger.merge_type = _mod_merge.Merge3Merger
360
364
        merger.do_merge()
372
376
        this_tree.lock_write()
373
377
        self.addCleanup(this_tree.unlock)
374
378
 
375
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
379
        merger = _mod_merge.Merger.from_revision_ids(None,
376
380
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
377
381
        merger.merge_type = _mod_merge.Merge3Merger
378
382
        merger.do_merge()
391
395
        other_tree.commit('rev2', rev_id='rev2b')
392
396
        this_tree.lock_write()
393
397
        self.addCleanup(this_tree.unlock)
394
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
398
        merger = _mod_merge.Merger.from_revision_ids(None,
395
399
            this_tree, 'rev2b', other_branch=other_tree.branch)
396
400
        merger.merge_type = _mod_merge.Merge3Merger
397
401
        tree_merger = merger.make_merger()
411
415
        other_tree.commit('rev2', rev_id='rev2b')
412
416
        this_tree.lock_write()
413
417
        self.addCleanup(this_tree.unlock)
414
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
418
        merger = _mod_merge.Merger.from_revision_ids(None,
415
419
            this_tree, 'rev2b', other_branch=other_tree.branch)
416
420
        merger.merge_type = _mod_merge.Merge3Merger
417
421
        tree_merger = merger.make_merger()
441
445
        other_tree.commit('rev2', rev_id='rev2b')
442
446
        this_tree.lock_write()
443
447
        self.addCleanup(this_tree.unlock)
444
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
448
        merger = _mod_merge.Merger.from_revision_ids(None,
445
449
            this_tree, 'rev2b', other_branch=other_tree.branch)
446
450
        merger.merge_type = _mod_merge.Merge3Merger
447
451
        tree_merger = merger.make_merger()
518
522
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
519
523
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
520
524
 
 
525
    def test_base_from_plan(self):
 
526
        self.setup_plan_merge()
 
527
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
528
        pwm = versionedfile.PlanWeaveMerge(plan)
 
529
        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
 
530
 
521
531
    def test_unique_lines(self):
522
532
        plan = self.setup_plan_merge()
523
533
        self.assertEqual(plan._unique_lines(
718
728
 
719
729
    def test_plan_merge_insert_order(self):
720
730
        """Weave merges are sensitive to the order of insertion.
721
 
        
 
731
 
722
732
        Specifically for overlapping regions, it effects which region gets put
723
733
        'first'. And when a user resolves an overlapping merge, if they use the
724
734
        same ordering, then the lines match the parents, if they don't only
821
831
                          ('unchanged', 'f\n'),
822
832
                          ('unchanged', 'g\n')],
823
833
                         list(plan))
 
834
        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
 
835
        # This is one of the main differences between plan_merge and
 
836
        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
 
837
        # because 'x' was not present in one of the bases. However, in this
 
838
        # case it is spurious because 'x' does not exist in the global base A.
 
839
        self.assertEqual([
 
840
                          ('unchanged', 'h\n'),
 
841
                          ('unchanged', 'a\n'),
 
842
                          ('conflicted-a', 'x\n'),
 
843
                          ('new-b', 'z\n'),
 
844
                          ('unchanged', 'c\n'),
 
845
                          ('unchanged', 'd\n'),
 
846
                          ('unchanged', 'y\n'),
 
847
                          ('unchanged', 'f\n'),
 
848
                          ('unchanged', 'g\n')],
 
849
                         list(plan))
 
850
 
 
851
    def test_criss_cross_flip_flop(self):
 
852
        # This is specificly trying to trigger problems when using limited
 
853
        # ancestry and weaves. The ancestry graph looks like:
 
854
        #       XX      unused ancestor, should not show up in the weave
 
855
        #       |
 
856
        #       A       Unique LCA
 
857
        #      / \  
 
858
        #     B   C     B & C both introduce a new line
 
859
        #     |\ /|  
 
860
        #     | X |  
 
861
        #     |/ \| 
 
862
        #     D   E     B & C are both merged, so both are common ancestors
 
863
        #               In the process of merging, both sides order the new
 
864
        #               lines differently
 
865
        #
 
866
        self.add_rev('root', 'XX', [], 'qrs')
 
867
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
868
        self.add_rev('root', 'B', ['A'], 'abcdgef')
 
869
        self.add_rev('root', 'C', ['A'], 'abcdhef')
 
870
        self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
 
871
        self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
 
872
        plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
 
873
        self.assertEqual([
 
874
                          ('unchanged', 'a\n'),
 
875
                          ('unchanged', 'b\n'),
 
876
                          ('unchanged', 'c\n'),
 
877
                          ('unchanged', 'd\n'),
 
878
                          ('new-b', 'h\n'),
 
879
                          ('unchanged', 'g\n'),
 
880
                          ('killed-b', 'h\n'),
 
881
                          ('unchanged', 'e\n'),
 
882
                          ('unchanged', 'f\n'),
 
883
                         ], plan)
 
884
        pwm = versionedfile.PlanWeaveMerge(plan)
 
885
        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
 
886
                             ''.join(pwm.base_from_plan()))
 
887
        # Reversing the order reverses the merge plan, and final order of 'hg'
 
888
        # => 'gh'
 
889
        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
 
890
        self.assertEqual([
 
891
                          ('unchanged', 'a\n'),
 
892
                          ('unchanged', 'b\n'),
 
893
                          ('unchanged', 'c\n'),
 
894
                          ('unchanged', 'd\n'),
 
895
                          ('new-b', 'g\n'),
 
896
                          ('unchanged', 'h\n'),
 
897
                          ('killed-b', 'g\n'),
 
898
                          ('unchanged', 'e\n'),
 
899
                          ('unchanged', 'f\n'),
 
900
                         ], plan)
 
901
        pwm = versionedfile.PlanWeaveMerge(plan)
 
902
        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
 
903
                             ''.join(pwm.base_from_plan()))
 
904
        # This is where lca differs, in that it (fairly correctly) determines
 
905
        # that there is a conflict because both sides resolved the merge
 
906
        # differently
 
907
        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
 
908
        self.assertEqual([
 
909
                          ('unchanged', 'a\n'),
 
910
                          ('unchanged', 'b\n'),
 
911
                          ('unchanged', 'c\n'),
 
912
                          ('unchanged', 'd\n'),
 
913
                          ('conflicted-b', 'h\n'),
 
914
                          ('unchanged', 'g\n'),
 
915
                          ('conflicted-a', 'h\n'),
 
916
                          ('unchanged', 'e\n'),
 
917
                          ('unchanged', 'f\n'),
 
918
                         ], plan)
 
919
        pwm = versionedfile.PlanWeaveMerge(plan)
 
920
        self.assertEqualDiff('\n'.join('abcdgef') + '\n',
 
921
                             ''.join(pwm.base_from_plan()))
 
922
        # Reversing it changes what line is doubled, but still gives a
 
923
        # double-conflict
 
924
        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
 
925
        self.assertEqual([
 
926
                          ('unchanged', 'a\n'),
 
927
                          ('unchanged', 'b\n'),
 
928
                          ('unchanged', 'c\n'),
 
929
                          ('unchanged', 'd\n'),
 
930
                          ('conflicted-b', 'g\n'),
 
931
                          ('unchanged', 'h\n'),
 
932
                          ('conflicted-a', 'g\n'),
 
933
                          ('unchanged', 'e\n'),
 
934
                          ('unchanged', 'f\n'),
 
935
                         ], plan)
 
936
        pwm = versionedfile.PlanWeaveMerge(plan)
 
937
        self.assertEqualDiff('\n'.join('abcdhef') + '\n',
 
938
                             ''.join(pwm.base_from_plan()))
824
939
 
825
940
    def assertRemoveExternalReferences(self, filtered_parent_map,
826
941
                                       child_map, tails, parent_map):
1026
1141
                         ], list(plan))
1027
1142
 
1028
1143
 
1029
 
class TestMergeImplementation(object):
1030
 
 
1031
 
    def do_merge(self, target_tree, source_tree, **kwargs):
1032
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
1033
 
            target_tree, source_tree.last_revision(),
1034
 
            other_branch=source_tree.branch)
1035
 
        merger.merge_type=self.merge_type
1036
 
        for name, value in kwargs.items():
1037
 
            setattr(merger, name, value)
1038
 
        merger.do_merge()
1039
 
 
1040
 
    def test_merge_specific_file(self):
1041
 
        this_tree = self.make_branch_and_tree('this')
1042
 
        this_tree.lock_write()
1043
 
        self.addCleanup(this_tree.unlock)
1044
 
        self.build_tree_contents([
1045
 
            ('this/file1', 'a\nb\n'),
1046
 
            ('this/file2', 'a\nb\n')
1047
 
        ])
1048
 
        this_tree.add(['file1', 'file2'])
1049
 
        this_tree.commit('Added files')
1050
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1051
 
        self.build_tree_contents([
1052
 
            ('other/file1', 'a\nb\nc\n'),
1053
 
            ('other/file2', 'a\nb\nc\n')
1054
 
        ])
1055
 
        other_tree.commit('modified both')
1056
 
        self.build_tree_contents([
1057
 
            ('this/file1', 'd\na\nb\n'),
1058
 
            ('this/file2', 'd\na\nb\n')
1059
 
        ])
1060
 
        this_tree.commit('modified both')
1061
 
        self.do_merge(this_tree, other_tree, interesting_files=['file1'])
1062
 
        self.assertFileEqual('d\na\nb\nc\n', 'this/file1')
1063
 
        self.assertFileEqual('d\na\nb\n', 'this/file2')
1064
 
 
1065
 
    def test_merge_move_and_change(self):
1066
 
        this_tree = self.make_branch_and_tree('this')
1067
 
        this_tree.lock_write()
1068
 
        self.addCleanup(this_tree.unlock)
1069
 
        self.build_tree_contents([
1070
 
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
1071
 
        ])
1072
 
        this_tree.add('file1',)
1073
 
        this_tree.commit('Added file')
1074
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1075
 
        self.build_tree_contents([
1076
 
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
1077
 
        ])
1078
 
        other_tree.commit('Changed 2 to 2.1')
1079
 
        self.build_tree_contents([
1080
 
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
1081
 
        ])
1082
 
        this_tree.commit('Swapped 2 & 3')
1083
 
        self.do_merge(this_tree, other_tree)
1084
 
        self.assertFileEqual('line 1\n'
1085
 
            '<<<<<<< TREE\n'
1086
 
            'line 3\n'
1087
 
            'line 2\n'
1088
 
            '=======\n'
1089
 
            'line 2 to 2.1\n'
1090
 
            'line 3\n'
1091
 
            '>>>>>>> MERGE-SOURCE\n'
1092
 
            'line 4\n', 'this/file1')
1093
 
 
1094
 
 
1095
 
class TestMerge3Merge(TestCaseWithTransport, TestMergeImplementation):
1096
 
 
1097
 
    merge_type = _mod_merge.Merge3Merger
1098
 
 
1099
 
 
1100
 
class TestWeaveMerge(TestCaseWithTransport, TestMergeImplementation):
1101
 
 
1102
 
    merge_type = _mod_merge.WeaveMerger
1103
 
 
1104
 
 
1105
 
class TestLCAMerge(TestCaseWithTransport, TestMergeImplementation):
1106
 
 
1107
 
    merge_type = _mod_merge.LCAMerger
1108
 
 
1109
 
    def test_merge_move_and_change(self):
1110
 
        self.expectFailure("lca merge doesn't conflict for move and change",
1111
 
            super(TestLCAMerge, self).test_merge_move_and_change)
1112
 
 
1113
 
 
1114
1144
class LoggingMerger(object):
1115
1145
    # These seem to be the required attributes
1116
1146
    requires_base = False
1171
1201
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
1172
1202
        mem_tree.lock_write()
1173
1203
        self.addCleanup(mem_tree.unlock)
1174
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1204
        merger = _mod_merge.Merger.from_revision_ids(None,
1175
1205
            mem_tree, other_revision_id)
1176
1206
        merger.set_interesting_files(interesting_files)
1177
1207
        # It seems there is no matching function for set_interesting_ids
1182
1212
 
1183
1213
class TestMergerInMemory(TestMergerBase):
1184
1214
 
 
1215
    def test_cache_trees_with_revision_ids_None(self):
 
1216
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1217
        original_cache = dict(merger._cached_trees)
 
1218
        merger.cache_trees_with_revision_ids([None])
 
1219
        self.assertEqual(original_cache, merger._cached_trees)
 
1220
 
 
1221
    def test_cache_trees_with_revision_ids_no_revision_id(self):
 
1222
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1223
        original_cache = dict(merger._cached_trees)
 
1224
        tree = self.make_branch_and_memory_tree('tree')
 
1225
        merger.cache_trees_with_revision_ids([tree])
 
1226
        self.assertEqual(original_cache, merger._cached_trees)
 
1227
 
 
1228
    def test_cache_trees_with_revision_ids_having_revision_id(self):
 
1229
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1230
        original_cache = dict(merger._cached_trees)
 
1231
        tree = merger.this_branch.repository.revision_tree('B-id')
 
1232
        original_cache['B-id'] = tree
 
1233
        merger.cache_trees_with_revision_ids([tree])
 
1234
        self.assertEqual(original_cache, merger._cached_trees)
 
1235
 
1185
1236
    def test_find_base(self):
1186
1237
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1187
1238
        self.assertEqual('A-id', merger.base_rev_id)
1930
1981
 
1931
1982
    def do_merge(self, builder, other_revision_id):
1932
1983
        wt = self.get_wt_from_builder(builder)
1933
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1984
        merger = _mod_merge.Merger.from_revision_ids(None,
1934
1985
            wt, other_revision_id)
1935
1986
        merger.merge_type = _mod_merge.Merge3Merger
1936
1987
        return wt, merger.do_merge()
2196
2247
        wt.commit('D merges B & C', rev_id='D-id')
2197
2248
        self.assertEqual('barry', wt.id2path('foo-id'))
2198
2249
        # Check the output of the Merger object directly
2199
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2250
        merger = _mod_merge.Merger.from_revision_ids(None,
2200
2251
            wt, 'F-id')
2201
2252
        merger.merge_type = _mod_merge.Merge3Merger
2202
2253
        merge_obj = merger.make_merger()
2252
2303
        wt.commit('F foo => bing', rev_id='F-id')
2253
2304
 
2254
2305
        # Check the output of the Merger object directly
2255
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2306
        merger = _mod_merge.Merger.from_revision_ids(None,
2256
2307
            wt, 'E-id')
2257
2308
        merger.merge_type = _mod_merge.Merge3Merger
2258
2309
        merge_obj = merger.make_merger()
2303
2354
        list(wt.iter_changes(wt.basis_tree()))
2304
2355
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2305
2356
 
2306
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2357
        merger = _mod_merge.Merger.from_revision_ids(None,
2307
2358
            wt, 'E-id')
2308
2359
        merger.merge_type = _mod_merge.Merge3Merger
2309
2360
        merge_obj = merger.make_merger()
2518
2569
        wt.branch.set_last_revision_info(2, 'B-id')
2519
2570
        wt.commit('D', rev_id='D-id', recursive=None)
2520
2571
 
2521
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2572
        merger = _mod_merge.Merger.from_revision_ids(None,
2522
2573
            wt, 'E-id')
2523
2574
        merger.merge_type = _mod_merge.Merge3Merger
2524
2575
        merge_obj = merger.make_merger()
2555
2606
        wt.branch.set_last_revision_info(2, 'B-id')
2556
2607
        wt.commit('D', rev_id='D-id', recursive=None)
2557
2608
 
2558
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2609
        merger = _mod_merge.Merger.from_revision_ids(None,
2559
2610
            wt, 'E-id')
2560
2611
        merger.merge_type = _mod_merge.Merge3Merger
2561
2612
        merge_obj = merger.make_merger()
2595
2646
        wt.branch.set_last_revision_info(2, 'B-id')
2596
2647
        wt.commit('D', rev_id='D-id', recursive=None)
2597
2648
 
2598
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2649
        merger = _mod_merge.Merger.from_revision_ids(None,
2599
2650
            wt, 'E-id')
2600
2651
        merger.merge_type = _mod_merge.Merge3Merger
2601
2652
        merge_obj = merger.make_merger()
2640
2691
        wt.branch.set_last_revision_info(2, 'B-id')
2641
2692
        wt.commit('D', rev_id='D-id', recursive=None)
2642
2693
 
2643
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2694
        merger = _mod_merge.Merger.from_revision_ids(None,
2644
2695
            wt, 'E-id')
2645
2696
        merger.merge_type = _mod_merge.Merge3Merger
2646
2697
        merge_obj = merger.make_merger()
2782
2833
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
2783
2834
        self.assertLCAMultiWay('conflict',
2784
2835
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')
 
2836
 
 
2837
 
 
2838
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
 
2839
 
 
2840
    def setUp(self):
 
2841
        super(TestConfigurableFileMerger, self).setUp()
 
2842
        self.calls = []
 
2843
 
 
2844
    def get_merger_factory(self):
 
2845
        # Allows  the inner methods to access the test attributes
 
2846
        test = self
 
2847
 
 
2848
        class FooMerger(_mod_merge.ConfigurableFileMerger):
 
2849
            name_prefix = "foo"
 
2850
            default_files = ['bar']
 
2851
 
 
2852
            def merge_text(self, params):
 
2853
                test.calls.append('merge_text')
 
2854
                return ('not_applicable', None)
 
2855
 
 
2856
        def factory(merger):
 
2857
            result = FooMerger(merger)
 
2858
            # Make sure we start with a clean slate
 
2859
            self.assertEqual(None, result.affected_files)
 
2860
            # Track the original merger
 
2861
            self.merger = result
 
2862
            return result
 
2863
 
 
2864
        return factory
 
2865
 
 
2866
    def _install_hook(self, factory):
 
2867
        _mod_merge.Merger.hooks.install_named_hook('merge_file_content',
 
2868
                                                   factory, 'test factory')
 
2869
 
 
2870
    def make_builder(self):
 
2871
        builder = test_merge_core.MergeBuilder(self.test_base_dir)
 
2872
        self.addCleanup(builder.cleanup)
 
2873
        return builder
 
2874
 
 
2875
    def make_text_conflict(self, file_name='bar'):
 
2876
        factory = self.get_merger_factory()
 
2877
        self._install_hook(factory)
 
2878
        builder = self.make_builder()
 
2879
        builder.add_file('bar-id', builder.tree_root, file_name, 'text1', True)
 
2880
        builder.change_contents('bar-id', other='text4', this='text3')
 
2881
        return builder
 
2882
 
 
2883
    def make_kind_change(self):
 
2884
        factory = self.get_merger_factory()
 
2885
        self._install_hook(factory)
 
2886
        builder = self.make_builder()
 
2887
        builder.add_file('bar-id', builder.tree_root, 'bar', 'text1', True,
 
2888
                         this=False)
 
2889
        builder.add_dir('bar-dir', builder.tree_root, 'bar-id',
 
2890
                        base=False, other=False)
 
2891
        return builder
 
2892
 
 
2893
    def test_uses_this_branch(self):
 
2894
        builder = self.make_text_conflict()
 
2895
        tt = builder.make_preview_transform()
 
2896
        self.addCleanup(tt.finalize)
 
2897
 
 
2898
    def test_affected_files_cached(self):
 
2899
        """Ensures that the config variable is cached"""
 
2900
        builder = self.make_text_conflict()
 
2901
        conflicts = builder.merge()
 
2902
        # The hook should set the variable
 
2903
        self.assertEqual(['bar'], self.merger.affected_files)
 
2904
        self.assertEqual(1, len(conflicts))
 
2905
 
 
2906
    def test_hook_called_for_text_conflicts(self):
 
2907
        builder = self.make_text_conflict()
 
2908
        conflicts = builder.merge()
 
2909
        # The hook should call the merge_text() method
 
2910
        self.assertEqual(['merge_text'], self.calls)
 
2911
 
 
2912
    def test_hook_not_called_for_kind_change(self):
 
2913
        builder = self.make_kind_change()
 
2914
        conflicts = builder.merge()
 
2915
        # The hook should not call the merge_text() method
 
2916
        self.assertEqual([], self.calls)
 
2917
 
 
2918
    def test_hook_not_called_for_other_files(self):
 
2919
        builder = self.make_text_conflict('foobar')
 
2920
        conflicts = builder.merge()
 
2921
        # The hook should not call the merge_text() method
 
2922
        self.assertEqual([], self.calls)
 
2923
 
 
2924
 
 
2925
class TestMergeIntoBase(tests.TestCaseWithTransport):
 
2926
 
 
2927
    def setup_simple_branch(self, relpath, shape=None, root_id=None):
 
2928
        """One commit, containing tree specified by optional shape.
 
2929
        
 
2930
        Default is empty tree (just root entry).
 
2931
        """
 
2932
        if root_id is None:
 
2933
            root_id = '%s-root-id' % (relpath,)
 
2934
        wt = self.make_branch_and_tree(relpath)
 
2935
        wt.set_root_id(root_id)
 
2936
        if shape is not None:
 
2937
            adjusted_shape = [relpath + '/' + elem for elem in shape]
 
2938
            self.build_tree(adjusted_shape)
 
2939
            ids = ['%s-%s-id' % (relpath, basename(elem.rstrip('/')))
 
2940
                   for elem in shape]
 
2941
            wt.add(shape, ids=ids)
 
2942
        rev_id = 'r1-%s' % (relpath,)
 
2943
        wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
 
2944
        self.assertEqual(root_id, wt.path2id(''))
 
2945
        return wt
 
2946
 
 
2947
    def setup_two_branches(self, custom_root_ids=True):
 
2948
        """Setup 2 branches, one will be a library, the other a project."""
 
2949
        if custom_root_ids:
 
2950
            root_id = None
 
2951
        else:
 
2952
            root_id = inventory.ROOT_ID
 
2953
        project_wt = self.setup_simple_branch(
 
2954
            'project', ['README', 'dir/', 'dir/file.c'],
 
2955
            root_id)
 
2956
        lib_wt = self.setup_simple_branch(
 
2957
            'lib1', ['README', 'Makefile', 'foo.c'], root_id)
 
2958
 
 
2959
        return project_wt, lib_wt
 
2960
 
 
2961
    def do_merge_into(self, location, merge_as):
 
2962
        """Helper for using MergeIntoMerger.
 
2963
        
 
2964
        :param location: location of directory to merge from, either the
 
2965
            location of a branch or of a path inside a branch.
 
2966
        :param merge_as: the path in a tree to add the new directory as.
 
2967
        :returns: the conflicts from 'do_merge'.
 
2968
        """
 
2969
        operation = cleanup.OperationWithCleanups(self._merge_into)
 
2970
        return operation.run(location, merge_as)
 
2971
 
 
2972
    def _merge_into(self, op, location, merge_as):
 
2973
        # Open and lock the various tree and branch objects
 
2974
        wt, subdir_relpath = WorkingTree.open_containing(merge_as)
 
2975
        op.add_cleanup(wt.lock_write().unlock)
 
2976
        branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
 
2977
            location)
 
2978
        op.add_cleanup(branch_to_merge.lock_read().unlock)
 
2979
        other_tree = branch_to_merge.basis_tree()
 
2980
        op.add_cleanup(other_tree.lock_read().unlock)
 
2981
        # Perform the merge
 
2982
        merger = _mod_merge.MergeIntoMerger(this_tree=wt, other_tree=other_tree,
 
2983
            other_branch=branch_to_merge, target_subdir=subdir_relpath,
 
2984
            source_subpath=subdir_to_merge)
 
2985
        merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
 
2986
        conflicts = merger.do_merge()
 
2987
        merger.set_pending()
 
2988
        return conflicts
 
2989
 
 
2990
    def assertTreeEntriesEqual(self, expected_entries, tree):
 
2991
        """Assert that 'tree' contains the expected inventory entries.
 
2992
 
 
2993
        :param expected_entries: sequence of (path, file-id) pairs.
 
2994
        """
 
2995
        files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
 
2996
        self.assertEqual(expected_entries, files)
 
2997
 
 
2998
 
 
2999
class TestMergeInto(TestMergeIntoBase):
 
3000
 
 
3001
    def test_newdir_with_unique_roots(self):
 
3002
        """Merge a branch with a unique root into a new directory."""
 
3003
        project_wt, lib_wt = self.setup_two_branches()
 
3004
        self.do_merge_into('lib1', 'project/lib1')
 
3005
        project_wt.lock_read()
 
3006
        self.addCleanup(project_wt.unlock)
 
3007
        # The r1-lib1 revision should be merged into this one
 
3008
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3009
        self.assertTreeEntriesEqual(
 
3010
            [('', 'project-root-id'),
 
3011
             ('README', 'project-README-id'),
 
3012
             ('dir', 'project-dir-id'),
 
3013
             ('lib1', 'lib1-root-id'),
 
3014
             ('dir/file.c', 'project-file.c-id'),
 
3015
             ('lib1/Makefile', 'lib1-Makefile-id'),
 
3016
             ('lib1/README', 'lib1-README-id'),
 
3017
             ('lib1/foo.c', 'lib1-foo.c-id'),
 
3018
            ], project_wt)
 
3019
 
 
3020
    def test_subdir(self):
 
3021
        """Merge a branch into a subdirectory of an existing directory."""
 
3022
        project_wt, lib_wt = self.setup_two_branches()
 
3023
        self.do_merge_into('lib1', 'project/dir/lib1')
 
3024
        project_wt.lock_read()
 
3025
        self.addCleanup(project_wt.unlock)
 
3026
        # The r1-lib1 revision should be merged into this one
 
3027
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3028
        self.assertTreeEntriesEqual(
 
3029
            [('', 'project-root-id'),
 
3030
             ('README', 'project-README-id'),
 
3031
             ('dir', 'project-dir-id'),
 
3032
             ('dir/file.c', 'project-file.c-id'),
 
3033
             ('dir/lib1', 'lib1-root-id'),
 
3034
             ('dir/lib1/Makefile', 'lib1-Makefile-id'),
 
3035
             ('dir/lib1/README', 'lib1-README-id'),
 
3036
             ('dir/lib1/foo.c', 'lib1-foo.c-id'),
 
3037
            ], project_wt)
 
3038
 
 
3039
    def test_newdir_with_repeat_roots(self):
 
3040
        """If the file-id of the dir to be merged already exists a new ID will
 
3041
        be allocated to let the merge happen.
 
3042
        """
 
3043
        project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
 
3044
        root_id = project_wt.path2id('')
 
3045
        self.do_merge_into('lib1', 'project/lib1')
 
3046
        project_wt.lock_read()
 
3047
        self.addCleanup(project_wt.unlock)
 
3048
        # The r1-lib1 revision should be merged into this one
 
3049
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3050
        new_lib1_id = project_wt.path2id('lib1')
 
3051
        self.assertNotEqual(None, new_lib1_id)
 
3052
        self.assertTreeEntriesEqual(
 
3053
            [('', root_id),
 
3054
             ('README', 'project-README-id'),
 
3055
             ('dir', 'project-dir-id'),
 
3056
             ('lib1', new_lib1_id),
 
3057
             ('dir/file.c', 'project-file.c-id'),
 
3058
             ('lib1/Makefile', 'lib1-Makefile-id'),
 
3059
             ('lib1/README', 'lib1-README-id'),
 
3060
             ('lib1/foo.c', 'lib1-foo.c-id'),
 
3061
            ], project_wt)
 
3062
 
 
3063
    def test_name_conflict(self):
 
3064
        """When the target directory name already exists a conflict is
 
3065
        generated and the original directory is renamed to foo.moved.
 
3066
        """
 
3067
        dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
 
3068
        src_wt = self.setup_simple_branch('src', ['README'])
 
3069
        conflicts = self.do_merge_into('src', 'dest/dir')
 
3070
        self.assertEqual(1, conflicts)
 
3071
        dest_wt.lock_read()
 
3072
        self.addCleanup(dest_wt.unlock)
 
3073
        # The r1-lib1 revision should be merged into this one
 
3074
        self.assertEqual(['r1-dest', 'r1-src'], dest_wt.get_parent_ids())
 
3075
        self.assertTreeEntriesEqual(
 
3076
            [('', 'dest-root-id'),
 
3077
             ('dir', 'src-root-id'),
 
3078
             ('dir.moved', 'dest-dir-id'),
 
3079
             ('dir/README', 'src-README-id'),
 
3080
             ('dir.moved/file.txt', 'dest-file.txt-id'),
 
3081
            ], dest_wt)
 
3082
 
 
3083
    def test_file_id_conflict(self):
 
3084
        """A conflict is generated if the merge-into adds a file (or other
 
3085
        inventory entry) with a file-id that already exists in the target tree.
 
3086
        """
 
3087
        dest_wt = self.setup_simple_branch('dest', ['file.txt'])
 
3088
        # Make a second tree with a file-id that will clash with file.txt in
 
3089
        # dest.
 
3090
        src_wt = self.make_branch_and_tree('src')
 
3091
        self.build_tree(['src/README'])
 
3092
        src_wt.add(['README'], ids=['dest-file.txt-id'])
 
3093
        src_wt.commit("Rev 1 of src.", rev_id='r1-src')
 
3094
        conflicts = self.do_merge_into('src', 'dest/dir')
 
3095
        # This is an edge case that shouldn't happen to users very often.  So
 
3096
        # we don't care really about the exact presentation of the conflict,
 
3097
        # just that there is one.
 
3098
        self.assertEqual(1, conflicts)
 
3099
 
 
3100
    def test_only_subdir(self):
 
3101
        """When the location points to just part of a tree, merge just that
 
3102
        subtree.
 
3103
        """
 
3104
        dest_wt = self.setup_simple_branch('dest')
 
3105
        src_wt = self.setup_simple_branch(
 
3106
            'src', ['hello.txt', 'dir/', 'dir/foo.c'])
 
3107
        conflicts = self.do_merge_into('src/dir', 'dest/dir')
 
3108
        dest_wt.lock_read()
 
3109
        self.addCleanup(dest_wt.unlock)
 
3110
        # The r1-lib1 revision should NOT be merged into this one (this is a
 
3111
        # partial merge).
 
3112
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3113
        self.assertTreeEntriesEqual(
 
3114
            [('', 'dest-root-id'),
 
3115
             ('dir', 'src-dir-id'),
 
3116
             ('dir/foo.c', 'src-foo.c-id'),
 
3117
            ], dest_wt)
 
3118
 
 
3119
    def test_only_file(self):
 
3120
        """An edge case: merge just one file, not a whole dir."""
 
3121
        dest_wt = self.setup_simple_branch('dest')
 
3122
        two_file_wt = self.setup_simple_branch(
 
3123
            'two-file', ['file1.txt', 'file2.txt'])
 
3124
        conflicts = self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
 
3125
        dest_wt.lock_read()
 
3126
        self.addCleanup(dest_wt.unlock)
 
3127
        # The r1-lib1 revision should NOT be merged into this one
 
3128
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3129
        self.assertTreeEntriesEqual(
 
3130
            [('', 'dest-root-id'), ('file1.txt', 'two-file-file1.txt-id')],
 
3131
            dest_wt)
 
3132
 
 
3133
    def test_no_such_source_path(self):
 
3134
        """PathNotInTree is raised if the specified path in the source tree
 
3135
        does not exist.
 
3136
        """
 
3137
        dest_wt = self.setup_simple_branch('dest')
 
3138
        two_file_wt = self.setup_simple_branch('src', ['dir/'])
 
3139
        self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
 
3140
            'src/no-such-dir', 'dest/foo')
 
3141
        dest_wt.lock_read()
 
3142
        self.addCleanup(dest_wt.unlock)
 
3143
        # The dest tree is unmodified.
 
3144
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3145
        self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)
 
3146
 
 
3147
    def test_no_such_target_path(self):
 
3148
        """PathNotInTree is also raised if the specified path in the target
 
3149
        tree does not exist.
 
3150
        """
 
3151
        dest_wt = self.setup_simple_branch('dest')
 
3152
        two_file_wt = self.setup_simple_branch('src', ['file.txt'])
 
3153
        self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
 
3154
            'src', 'dest/no-such-dir/foo')
 
3155
        dest_wt.lock_read()
 
3156
        self.addCleanup(dest_wt.unlock)
 
3157
        # The dest tree is unmodified.
 
3158
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3159
        self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)