~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

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
    features,
 
41
    TestCaseWithMemoryTransport,
 
42
    TestCaseWithTransport,
 
43
    test_merge_core,
 
44
    )
39
45
from bzrlib.workingtree import WorkingTree
40
46
 
41
47
 
85
91
        os.chdir('branch2')
86
92
        self.run_bzr('merge ../branch1/baz', retcode=3)
87
93
        self.run_bzr('merge ../branch1/foo')
88
 
        self.failUnlessExists('foo')
89
 
        self.failIfExists('bar')
 
94
        self.assertPathExists('foo')
 
95
        self.assertPathDoesNotExist('bar')
90
96
        wt2 = WorkingTree.open('.') # opens branch2
91
97
        self.assertEqual([tip], wt2.get_parent_ids())
92
 
        
 
98
 
93
99
    def test_pending_with_null(self):
94
100
        """When base is forced to revno 0, parent_ids are set"""
95
101
        wt2 = self.test_unrelated()
96
102
        wt1 = WorkingTree.open('.')
97
103
        br1 = wt1.branch
98
104
        br1.fetch(wt2.branch)
99
 
        # merge all of branch 2 into branch 1 even though they 
 
105
        # merge all of branch 2 into branch 1 even though they
100
106
        # are not related.
101
107
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
102
108
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
116
122
        finally:
117
123
            wt1.unlock()
118
124
 
 
125
    def test_merge_into_null_tree(self):
 
126
        wt = self.make_branch_and_tree('tree')
 
127
        null_tree = wt.basis_tree()
 
128
        self.build_tree(['tree/file'])
 
129
        wt.add('file')
 
130
        wt.commit('tree with root')
 
131
        merger = _mod_merge.Merge3Merger(null_tree, null_tree, null_tree, wt,
 
132
                                         this_branch=wt.branch,
 
133
                                         do_merge=False)
 
134
        with merger.make_preview_transform() as tt:
 
135
            self.assertEqual([], tt.find_conflicts())
 
136
            preview = tt.get_preview_tree()
 
137
            self.assertEqual(wt.get_root_id(), preview.get_root_id())
 
138
 
 
139
    def test_merge_unrelated_retains_root(self):
 
140
        wt = self.make_branch_and_tree('tree')
 
141
        other_tree = self.make_branch_and_tree('other')
 
142
        self.addCleanup(other_tree.lock_read().unlock)
 
143
        merger = _mod_merge.Merge3Merger(wt, wt, wt.basis_tree(), other_tree,
 
144
                                         this_branch=wt.branch,
 
145
                                         do_merge=False)
 
146
        with transform.TransformPreview(wt) as merger.tt:
 
147
            merger._compute_transform()
 
148
            new_root_id = merger.tt.final_file_id(merger.tt.root)
 
149
            self.assertEqual(wt.get_root_id(), new_root_id)
 
150
 
119
151
    def test_create_rename(self):
120
152
        """Rename an inventory entry while creating the file"""
121
153
        tree =self.make_branch_and_tree('.')
150
182
        self.addCleanup(tree_b.unlock)
151
183
        tree_a.commit(message="hello again")
152
184
        log = StringIO()
153
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
185
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
154
186
                    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)
 
187
        self.assertTrue('All changes applied successfully.\n' not in
 
188
            self.get_log())
157
189
        tree_b.revert()
158
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
190
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
159
191
                    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)
 
192
        self.assertTrue('All changes applied successfully.\n' in self.get_log())
162
193
 
163
194
    def test_merge_inner_conflicts(self):
164
195
        tree_a = self.make_branch_and_tree('a')
219
250
        tree_a.add('file')
220
251
        tree_a.commit('commit base')
221
252
        # 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.
 
253
        # the basis tree. This test commits to the tree after grabbing basis,
 
254
        # so we go to the repository.
224
255
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
225
256
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
226
257
        self.build_tree_contents([('tree_a/file', 'content_2')])
227
258
        tree_a.commit('commit other')
228
259
        other_tree = tree_a.basis_tree()
 
260
        # 'file' is now missing but isn't altered in any commit in b so no
 
261
        # change should be applied.
229
262
        os.unlink('tree_b/file')
230
263
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
231
264
 
248
281
        self.assertEqual(tree_b.conflicts(),
249
282
                         [conflicts.ContentsConflict('file',
250
283
                          file_id='file-id')])
251
 
    
 
284
 
252
285
    def test_merge_type_registry(self):
253
286
        merge_type_option = option.Option.OPTIONS['merge-type']
254
 
        self.assertFalse('merge4' in [x[0] for x in 
 
287
        self.assertFalse('merge4' in [x[0] for x in
255
288
                        merge_type_option.iter_switches()])
256
289
        registry = _mod_merge.get_merge_type_registry()
257
290
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
258
291
                               'time-travelling merge')
259
 
        self.assertTrue('merge4' in [x[0] for x in 
 
292
        self.assertTrue('merge4' in [x[0] for x in
260
293
                        merge_type_option.iter_switches()])
261
294
        registry.remove('merge4')
262
 
        self.assertFalse('merge4' in [x[0] for x in 
 
295
        self.assertFalse('merge4' in [x[0] for x in
263
296
                        merge_type_option.iter_switches()])
264
297
 
265
298
    def test_merge_other_moves_we_deleted(self):
292
325
        tree_a.commit('commit 2')
293
326
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
294
327
        tree_b.rename_one('file_1', 'renamed')
295
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
296
 
                                                    progress.DummyProgress())
 
328
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
297
329
        merger.merge_type = _mod_merge.Merge3Merger
298
330
        merger.do_merge()
299
331
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
307
339
        tree_a.commit('commit 2')
308
340
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
309
341
        tree_b.rename_one('file_1', 'renamed')
310
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
311
 
                                                    progress.DummyProgress())
 
342
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
312
343
        merger.merge_type = _mod_merge.WeaveMerger
313
344
        merger.do_merge()
314
345
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
339
370
 
340
371
    def test_weave_cherrypick(self):
341
372
        this_tree, other_tree = self.prepare_cherrypick()
342
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
373
        merger = _mod_merge.Merger.from_revision_ids(None,
343
374
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
344
375
        merger.merge_type = _mod_merge.WeaveMerger
345
376
        merger.do_merge()
347
378
 
348
379
    def test_weave_cannot_reverse_cherrypick(self):
349
380
        this_tree, other_tree = self.prepare_cherrypick()
350
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
381
        merger = _mod_merge.Merger.from_revision_ids(None,
351
382
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
352
383
        merger.merge_type = _mod_merge.WeaveMerger
353
384
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
354
385
 
355
386
    def test_merge3_can_reverse_cherrypick(self):
356
387
        this_tree, other_tree = self.prepare_cherrypick()
357
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
388
        merger = _mod_merge.Merger.from_revision_ids(None,
358
389
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
359
390
        merger.merge_type = _mod_merge.Merge3Merger
360
391
        merger.do_merge()
372
403
        this_tree.lock_write()
373
404
        self.addCleanup(this_tree.unlock)
374
405
 
375
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
406
        merger = _mod_merge.Merger.from_revision_ids(None,
376
407
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
377
408
        merger.merge_type = _mod_merge.Merge3Merger
378
409
        merger.do_merge()
383
414
                             '>>>>>>> MERGE-SOURCE\n',
384
415
                             'this/file')
385
416
 
 
417
    def test_merge_reverse_revision_range(self):
 
418
        tree = self.make_branch_and_tree(".")
 
419
        tree.lock_write()
 
420
        self.addCleanup(tree.unlock)
 
421
        self.build_tree(['a'])
 
422
        tree.add('a')
 
423
        tree.commit("added a")
 
424
        first_rev = tree.branch.revision_history()[0]
 
425
        merger = _mod_merge.Merger.from_revision_ids(None, tree,
 
426
                                          _mod_revision.NULL_REVISION,
 
427
                                          first_rev)
 
428
        merger.merge_type = _mod_merge.Merge3Merger
 
429
        merger.interesting_files = 'a'
 
430
        conflict_count = merger.do_merge()
 
431
        self.assertEqual(0, conflict_count)
 
432
 
 
433
        self.assertPathDoesNotExist("a")
 
434
        tree.revert()
 
435
        self.assertPathExists("a")
 
436
 
386
437
    def test_make_merger(self):
387
438
        this_tree = self.make_branch_and_tree('this')
388
439
        this_tree.commit('rev1', rev_id='rev1')
391
442
        other_tree.commit('rev2', rev_id='rev2b')
392
443
        this_tree.lock_write()
393
444
        self.addCleanup(this_tree.unlock)
394
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
445
        merger = _mod_merge.Merger.from_revision_ids(None,
395
446
            this_tree, 'rev2b', other_branch=other_tree.branch)
396
447
        merger.merge_type = _mod_merge.Merge3Merger
397
448
        tree_merger = merger.make_merger()
411
462
        other_tree.commit('rev2', rev_id='rev2b')
412
463
        this_tree.lock_write()
413
464
        self.addCleanup(this_tree.unlock)
414
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
465
        merger = _mod_merge.Merger.from_revision_ids(None,
415
466
            this_tree, 'rev2b', other_branch=other_tree.branch)
416
467
        merger.merge_type = _mod_merge.Merge3Merger
417
468
        tree_merger = merger.make_merger()
441
492
        other_tree.commit('rev2', rev_id='rev2b')
442
493
        this_tree.lock_write()
443
494
        self.addCleanup(this_tree.unlock)
444
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
495
        merger = _mod_merge.Merger.from_revision_ids(None,
445
496
            this_tree, 'rev2b', other_branch=other_tree.branch)
446
497
        merger.merge_type = _mod_merge.Merge3Merger
447
498
        tree_merger = merger.make_merger()
452
503
        finally:
453
504
            tree_file.close()
454
505
 
 
506
    def test_merge_require_tree_root(self):
 
507
        tree = self.make_branch_and_tree(".")
 
508
        tree.lock_write()
 
509
        self.addCleanup(tree.unlock)
 
510
        self.build_tree(['a'])
 
511
        tree.add('a')
 
512
        tree.commit("added a")
 
513
        old_root_id = tree.get_root_id()
 
514
        first_rev = tree.branch.revision_history()[0]
 
515
        merger = _mod_merge.Merger.from_revision_ids(None, tree,
 
516
                                          _mod_revision.NULL_REVISION,
 
517
                                          first_rev)
 
518
        merger.merge_type = _mod_merge.Merge3Merger
 
519
        conflict_count = merger.do_merge()
 
520
        self.assertEqual(0, conflict_count)
 
521
        self.assertEquals(set([old_root_id]), tree.all_file_ids())
 
522
        tree.set_parent_ids([])
 
523
 
455
524
    def test_merge_add_into_deleted_root(self):
456
525
        # Yes, people actually do this.  And report bugs if it breaks.
457
526
        source = self.make_branch_and_tree('source', format='rich-root-pack')
518
587
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
519
588
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
520
589
 
 
590
    def test_base_from_plan(self):
 
591
        self.setup_plan_merge()
 
592
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
593
        pwm = versionedfile.PlanWeaveMerge(plan)
 
594
        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
 
595
 
521
596
    def test_unique_lines(self):
522
597
        plan = self.setup_plan_merge()
523
598
        self.assertEqual(plan._unique_lines(
718
793
 
719
794
    def test_plan_merge_insert_order(self):
720
795
        """Weave merges are sensitive to the order of insertion.
721
 
        
 
796
 
722
797
        Specifically for overlapping regions, it effects which region gets put
723
798
        'first'. And when a user resolves an overlapping merge, if they use the
724
799
        same ordering, then the lines match the parents, if they don't only
821
896
                          ('unchanged', 'f\n'),
822
897
                          ('unchanged', 'g\n')],
823
898
                         list(plan))
 
899
        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
 
900
        # This is one of the main differences between plan_merge and
 
901
        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
 
902
        # because 'x' was not present in one of the bases. However, in this
 
903
        # case it is spurious because 'x' does not exist in the global base A.
 
904
        self.assertEqual([
 
905
                          ('unchanged', 'h\n'),
 
906
                          ('unchanged', 'a\n'),
 
907
                          ('conflicted-a', 'x\n'),
 
908
                          ('new-b', 'z\n'),
 
909
                          ('unchanged', 'c\n'),
 
910
                          ('unchanged', 'd\n'),
 
911
                          ('unchanged', 'y\n'),
 
912
                          ('unchanged', 'f\n'),
 
913
                          ('unchanged', 'g\n')],
 
914
                         list(plan))
 
915
 
 
916
    def test_criss_cross_flip_flop(self):
 
917
        # This is specificly trying to trigger problems when using limited
 
918
        # ancestry and weaves. The ancestry graph looks like:
 
919
        #       XX      unused ancestor, should not show up in the weave
 
920
        #       |
 
921
        #       A       Unique LCA
 
922
        #      / \  
 
923
        #     B   C     B & C both introduce a new line
 
924
        #     |\ /|  
 
925
        #     | X |  
 
926
        #     |/ \| 
 
927
        #     D   E     B & C are both merged, so both are common ancestors
 
928
        #               In the process of merging, both sides order the new
 
929
        #               lines differently
 
930
        #
 
931
        self.add_rev('root', 'XX', [], 'qrs')
 
932
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
933
        self.add_rev('root', 'B', ['A'], 'abcdgef')
 
934
        self.add_rev('root', 'C', ['A'], 'abcdhef')
 
935
        self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
 
936
        self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
 
937
        plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
 
938
        self.assertEqual([
 
939
                          ('unchanged', 'a\n'),
 
940
                          ('unchanged', 'b\n'),
 
941
                          ('unchanged', 'c\n'),
 
942
                          ('unchanged', 'd\n'),
 
943
                          ('new-b', 'h\n'),
 
944
                          ('unchanged', 'g\n'),
 
945
                          ('killed-b', 'h\n'),
 
946
                          ('unchanged', 'e\n'),
 
947
                          ('unchanged', 'f\n'),
 
948
                         ], plan)
 
949
        pwm = versionedfile.PlanWeaveMerge(plan)
 
950
        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
 
951
                             ''.join(pwm.base_from_plan()))
 
952
        # Reversing the order reverses the merge plan, and final order of 'hg'
 
953
        # => 'gh'
 
954
        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
 
955
        self.assertEqual([
 
956
                          ('unchanged', 'a\n'),
 
957
                          ('unchanged', 'b\n'),
 
958
                          ('unchanged', 'c\n'),
 
959
                          ('unchanged', 'd\n'),
 
960
                          ('new-b', 'g\n'),
 
961
                          ('unchanged', 'h\n'),
 
962
                          ('killed-b', 'g\n'),
 
963
                          ('unchanged', 'e\n'),
 
964
                          ('unchanged', 'f\n'),
 
965
                         ], plan)
 
966
        pwm = versionedfile.PlanWeaveMerge(plan)
 
967
        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
 
968
                             ''.join(pwm.base_from_plan()))
 
969
        # This is where lca differs, in that it (fairly correctly) determines
 
970
        # that there is a conflict because both sides resolved the merge
 
971
        # differently
 
972
        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
 
973
        self.assertEqual([
 
974
                          ('unchanged', 'a\n'),
 
975
                          ('unchanged', 'b\n'),
 
976
                          ('unchanged', 'c\n'),
 
977
                          ('unchanged', 'd\n'),
 
978
                          ('conflicted-b', 'h\n'),
 
979
                          ('unchanged', 'g\n'),
 
980
                          ('conflicted-a', 'h\n'),
 
981
                          ('unchanged', 'e\n'),
 
982
                          ('unchanged', 'f\n'),
 
983
                         ], plan)
 
984
        pwm = versionedfile.PlanWeaveMerge(plan)
 
985
        self.assertEqualDiff('\n'.join('abcdgef') + '\n',
 
986
                             ''.join(pwm.base_from_plan()))
 
987
        # Reversing it changes what line is doubled, but still gives a
 
988
        # double-conflict
 
989
        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
 
990
        self.assertEqual([
 
991
                          ('unchanged', 'a\n'),
 
992
                          ('unchanged', 'b\n'),
 
993
                          ('unchanged', 'c\n'),
 
994
                          ('unchanged', 'd\n'),
 
995
                          ('conflicted-b', 'g\n'),
 
996
                          ('unchanged', 'h\n'),
 
997
                          ('conflicted-a', 'g\n'),
 
998
                          ('unchanged', 'e\n'),
 
999
                          ('unchanged', 'f\n'),
 
1000
                         ], plan)
 
1001
        pwm = versionedfile.PlanWeaveMerge(plan)
 
1002
        self.assertEqualDiff('\n'.join('abcdhef') + '\n',
 
1003
                             ''.join(pwm.base_from_plan()))
824
1004
 
825
1005
    def assertRemoveExternalReferences(self, filtered_parent_map,
826
1006
                                       child_map, tails, parent_map):
1026
1206
                         ], list(plan))
1027
1207
 
1028
1208
 
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
1209
class LoggingMerger(object):
1115
1210
    # These seem to be the required attributes
1116
1211
    requires_base = False
1171
1266
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
1172
1267
        mem_tree.lock_write()
1173
1268
        self.addCleanup(mem_tree.unlock)
1174
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1269
        merger = _mod_merge.Merger.from_revision_ids(None,
1175
1270
            mem_tree, other_revision_id)
1176
1271
        merger.set_interesting_files(interesting_files)
1177
1272
        # It seems there is no matching function for set_interesting_ids
1182
1277
 
1183
1278
class TestMergerInMemory(TestMergerBase):
1184
1279
 
 
1280
    def test_cache_trees_with_revision_ids_None(self):
 
1281
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1282
        original_cache = dict(merger._cached_trees)
 
1283
        merger.cache_trees_with_revision_ids([None])
 
1284
        self.assertEqual(original_cache, merger._cached_trees)
 
1285
 
 
1286
    def test_cache_trees_with_revision_ids_no_revision_id(self):
 
1287
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1288
        original_cache = dict(merger._cached_trees)
 
1289
        tree = self.make_branch_and_memory_tree('tree')
 
1290
        merger.cache_trees_with_revision_ids([tree])
 
1291
        self.assertEqual(original_cache, merger._cached_trees)
 
1292
 
 
1293
    def test_cache_trees_with_revision_ids_having_revision_id(self):
 
1294
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1295
        original_cache = dict(merger._cached_trees)
 
1296
        tree = merger.this_branch.repository.revision_tree('B-id')
 
1297
        original_cache['B-id'] = tree
 
1298
        merger.cache_trees_with_revision_ids([tree])
 
1299
        self.assertEqual(original_cache, merger._cached_trees)
 
1300
 
1185
1301
    def test_find_base(self):
1186
1302
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1187
1303
        self.assertEqual('A-id', merger.base_rev_id)
1219
1335
        self.assertEqual(['B-id', 'C-id', 'F-id'],
1220
1336
                         [t.get_revision_id() for t in merger._lca_trees])
1221
1337
 
 
1338
    def test_find_base_new_root_criss_cross(self):
 
1339
        # A   B
 
1340
        # |\ /|
 
1341
        # | X |
 
1342
        # |/ \|
 
1343
        # C   D
 
1344
        
 
1345
        builder = self.get_builder()
 
1346
        builder.build_snapshot('A-id', None,
 
1347
            [('add', ('', None, 'directory', None))])
 
1348
        builder.build_snapshot('B-id', [],
 
1349
            [('add', ('', None, 'directory', None))])
 
1350
        builder.build_snapshot('D-id', ['A-id', 'B-id'], [])
 
1351
        builder.build_snapshot('C-id', ['A-id', 'B-id'], [])
 
1352
        merger = self.make_Merger(builder, 'D-id')
 
1353
        self.assertEqual('A-id', merger.base_rev_id)
 
1354
        self.assertTrue(merger._is_criss_cross)
 
1355
        self.assertEqual(['A-id', 'B-id'], [t.get_revision_id()
 
1356
                                            for t in merger._lca_trees])
 
1357
 
1222
1358
    def test_no_criss_cross_passed_to_merge_type(self):
1223
1359
        class LCATreesMerger(LoggingMerger):
1224
1360
            supports_lca_trees = True
1756
1892
        builder.build_snapshot('C-id', ['A-id'], [])
1757
1893
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
1758
1894
            [('unversion', 'a-id'),
 
1895
             ('flush', None),
1759
1896
             ('add', (u'a', 'a-id', 'directory', None))])
1760
1897
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1761
1898
        merge_obj = self.make_merge_obj(builder, 'E-id')
1779
1916
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1780
1917
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
1781
1918
            [('unversion', 'a-id'),
 
1919
             ('flush', None),
1782
1920
             ('add', (u'a', 'a-id', 'directory', None))])
1783
1921
        merge_obj = self.make_merge_obj(builder, 'E-id')
1784
1922
        entries = list(merge_obj._entries_lca())
1930
2068
 
1931
2069
    def do_merge(self, builder, other_revision_id):
1932
2070
        wt = self.get_wt_from_builder(builder)
1933
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2071
        merger = _mod_merge.Merger.from_revision_ids(None,
1934
2072
            wt, other_revision_id)
1935
2073
        merger.merge_type = _mod_merge.Merge3Merger
1936
2074
        return wt, merger.do_merge()
2047
2185
        self.assertTrue(wt.is_executable('foo-id'))
2048
2186
 
2049
2187
    def test_create_symlink(self):
2050
 
        self.requireFeature(tests.SymlinkFeature)
 
2188
        self.requireFeature(features.SymlinkFeature)
2051
2189
        #   A
2052
2190
        #  / \
2053
2191
        # B   C
2112
2250
                             wt.get_file_text('foo-id'))
2113
2251
 
2114
2252
    def test_modified_symlink(self):
2115
 
        self.requireFeature(tests.SymlinkFeature)
 
2253
        self.requireFeature(features.SymlinkFeature)
2116
2254
        #   A       Create symlink foo => bar
2117
2255
        #  / \
2118
2256
        # B   C     B relinks foo => baz
2157
2295
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
2158
2296
 
2159
2297
    def test_renamed_symlink(self):
2160
 
        self.requireFeature(tests.SymlinkFeature)
 
2298
        self.requireFeature(features.SymlinkFeature)
2161
2299
        #   A       Create symlink foo => bar
2162
2300
        #  / \
2163
2301
        # B   C     B renames foo => barry
2196
2334
        wt.commit('D merges B & C', rev_id='D-id')
2197
2335
        self.assertEqual('barry', wt.id2path('foo-id'))
2198
2336
        # Check the output of the Merger object directly
2199
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2337
        merger = _mod_merge.Merger.from_revision_ids(None,
2200
2338
            wt, 'F-id')
2201
2339
        merger.merge_type = _mod_merge.Merge3Merger
2202
2340
        merge_obj = merger.make_merger()
2213
2351
        self.assertEqual('blah', wt.id2path('foo-id'))
2214
2352
 
2215
2353
    def test_symlink_no_content_change(self):
2216
 
        self.requireFeature(tests.SymlinkFeature)
 
2354
        self.requireFeature(features.SymlinkFeature)
2217
2355
        #   A       Create symlink foo => bar
2218
2356
        #  / \
2219
2357
        # B   C     B relinks foo => baz
2252
2390
        wt.commit('F foo => bing', rev_id='F-id')
2253
2391
 
2254
2392
        # Check the output of the Merger object directly
2255
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2393
        merger = _mod_merge.Merger.from_revision_ids(None,
2256
2394
            wt, 'E-id')
2257
2395
        merger.merge_type = _mod_merge.Merge3Merger
2258
2396
        merge_obj = merger.make_merger()
2264
2402
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
2265
2403
 
2266
2404
    def test_symlink_this_changed_kind(self):
2267
 
        self.requireFeature(tests.SymlinkFeature)
 
2405
        self.requireFeature(features.SymlinkFeature)
2268
2406
        #   A       Nothing
2269
2407
        #  / \
2270
2408
        # B   C     B creates symlink foo => bar
2303
2441
        list(wt.iter_changes(wt.basis_tree()))
2304
2442
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
2305
2443
 
2306
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2444
        merger = _mod_merge.Merger.from_revision_ids(None,
2307
2445
            wt, 'E-id')
2308
2446
        merger.merge_type = _mod_merge.Merge3Merger
2309
2447
        merge_obj = merger.make_merger()
2317
2455
 
2318
2456
    def test_symlink_all_wt(self):
2319
2457
        """Check behavior if all trees are Working Trees."""
2320
 
        self.requireFeature(tests.SymlinkFeature)
 
2458
        self.requireFeature(features.SymlinkFeature)
2321
2459
        # The big issue is that entry.symlink_target is None for WorkingTrees.
2322
2460
        # So we need to make sure we handle that case correctly.
2323
2461
        #   A   foo => bar
2518
2656
        wt.branch.set_last_revision_info(2, 'B-id')
2519
2657
        wt.commit('D', rev_id='D-id', recursive=None)
2520
2658
 
2521
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2659
        merger = _mod_merge.Merger.from_revision_ids(None,
2522
2660
            wt, 'E-id')
2523
2661
        merger.merge_type = _mod_merge.Merge3Merger
2524
2662
        merge_obj = merger.make_merger()
2555
2693
        wt.branch.set_last_revision_info(2, 'B-id')
2556
2694
        wt.commit('D', rev_id='D-id', recursive=None)
2557
2695
 
2558
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2696
        merger = _mod_merge.Merger.from_revision_ids(None,
2559
2697
            wt, 'E-id')
2560
2698
        merger.merge_type = _mod_merge.Merge3Merger
2561
2699
        merge_obj = merger.make_merger()
2595
2733
        wt.branch.set_last_revision_info(2, 'B-id')
2596
2734
        wt.commit('D', rev_id='D-id', recursive=None)
2597
2735
 
2598
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2736
        merger = _mod_merge.Merger.from_revision_ids(None,
2599
2737
            wt, 'E-id')
2600
2738
        merger.merge_type = _mod_merge.Merge3Merger
2601
2739
        merge_obj = merger.make_merger()
2640
2778
        wt.branch.set_last_revision_info(2, 'B-id')
2641
2779
        wt.commit('D', rev_id='D-id', recursive=None)
2642
2780
 
2643
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2781
        merger = _mod_merge.Merger.from_revision_ids(None,
2644
2782
            wt, 'E-id')
2645
2783
        merger.merge_type = _mod_merge.Merge3Merger
2646
2784
        merge_obj = merger.make_merger()
2782
2920
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
2783
2921
        self.assertLCAMultiWay('conflict',
2784
2922
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')
 
2923
 
 
2924
 
 
2925
class TestConfigurableFileMerger(tests.TestCaseWithTransport):
 
2926
 
 
2927
    def setUp(self):
 
2928
        super(TestConfigurableFileMerger, self).setUp()
 
2929
        self.calls = []
 
2930
 
 
2931
    def get_merger_factory(self):
 
2932
        # Allows  the inner methods to access the test attributes
 
2933
        calls = self.calls
 
2934
 
 
2935
        class FooMerger(_mod_merge.ConfigurableFileMerger):
 
2936
            name_prefix = "foo"
 
2937
            default_files = ['bar']
 
2938
 
 
2939
            def merge_text(self, params):
 
2940
                calls.append('merge_text')
 
2941
                return ('not_applicable', None)
 
2942
 
 
2943
        def factory(merger):
 
2944
            result = FooMerger(merger)
 
2945
            # Make sure we start with a clean slate
 
2946
            self.assertEqual(None, result.affected_files)
 
2947
            # Track the original merger
 
2948
            self.merger = result
 
2949
            return result
 
2950
 
 
2951
        return factory
 
2952
 
 
2953
    def _install_hook(self, factory):
 
2954
        _mod_merge.Merger.hooks.install_named_hook('merge_file_content',
 
2955
                                                   factory, 'test factory')
 
2956
 
 
2957
    def make_builder(self):
 
2958
        builder = test_merge_core.MergeBuilder(self.test_base_dir)
 
2959
        self.addCleanup(builder.cleanup)
 
2960
        return builder
 
2961
 
 
2962
    def make_text_conflict(self, file_name='bar'):
 
2963
        factory = self.get_merger_factory()
 
2964
        self._install_hook(factory)
 
2965
        builder = self.make_builder()
 
2966
        builder.add_file('bar-id', builder.tree_root, file_name, 'text1', True)
 
2967
        builder.change_contents('bar-id', other='text4', this='text3')
 
2968
        return builder
 
2969
 
 
2970
    def make_kind_change(self):
 
2971
        factory = self.get_merger_factory()
 
2972
        self._install_hook(factory)
 
2973
        builder = self.make_builder()
 
2974
        builder.add_file('bar-id', builder.tree_root, 'bar', 'text1', True,
 
2975
                         this=False)
 
2976
        builder.add_dir('bar-dir', builder.tree_root, 'bar-id',
 
2977
                        base=False, other=False)
 
2978
        return builder
 
2979
 
 
2980
    def test_uses_this_branch(self):
 
2981
        builder = self.make_text_conflict()
 
2982
        tt = builder.make_preview_transform()
 
2983
        self.addCleanup(tt.finalize)
 
2984
 
 
2985
    def test_affected_files_cached(self):
 
2986
        """Ensures that the config variable is cached"""
 
2987
        builder = self.make_text_conflict()
 
2988
        conflicts = builder.merge()
 
2989
        # The hook should set the variable
 
2990
        self.assertEqual(['bar'], self.merger.affected_files)
 
2991
        self.assertEqual(1, len(conflicts))
 
2992
 
 
2993
    def test_hook_called_for_text_conflicts(self):
 
2994
        builder = self.make_text_conflict()
 
2995
        conflicts = builder.merge()
 
2996
        # The hook should call the merge_text() method
 
2997
        self.assertEqual(['merge_text'], self.calls)
 
2998
 
 
2999
    def test_hook_not_called_for_kind_change(self):
 
3000
        builder = self.make_kind_change()
 
3001
        conflicts = builder.merge()
 
3002
        # The hook should not call the merge_text() method
 
3003
        self.assertEqual([], self.calls)
 
3004
 
 
3005
    def test_hook_not_called_for_other_files(self):
 
3006
        builder = self.make_text_conflict('foobar')
 
3007
        conflicts = builder.merge()
 
3008
        # The hook should not call the merge_text() method
 
3009
        self.assertEqual([], self.calls)
 
3010
 
 
3011
 
 
3012
class TestMergeIntoBase(tests.TestCaseWithTransport):
 
3013
 
 
3014
    def setup_simple_branch(self, relpath, shape=None, root_id=None):
 
3015
        """One commit, containing tree specified by optional shape.
 
3016
        
 
3017
        Default is empty tree (just root entry).
 
3018
        """
 
3019
        if root_id is None:
 
3020
            root_id = '%s-root-id' % (relpath,)
 
3021
        wt = self.make_branch_and_tree(relpath)
 
3022
        wt.set_root_id(root_id)
 
3023
        if shape is not None:
 
3024
            adjusted_shape = [relpath + '/' + elem for elem in shape]
 
3025
            self.build_tree(adjusted_shape)
 
3026
            ids = ['%s-%s-id' % (relpath, basename(elem.rstrip('/')))
 
3027
                   for elem in shape]
 
3028
            wt.add(shape, ids=ids)
 
3029
        rev_id = 'r1-%s' % (relpath,)
 
3030
        wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
 
3031
        self.assertEqual(root_id, wt.path2id(''))
 
3032
        return wt
 
3033
 
 
3034
    def setup_two_branches(self, custom_root_ids=True):
 
3035
        """Setup 2 branches, one will be a library, the other a project."""
 
3036
        if custom_root_ids:
 
3037
            root_id = None
 
3038
        else:
 
3039
            root_id = inventory.ROOT_ID
 
3040
        project_wt = self.setup_simple_branch(
 
3041
            'project', ['README', 'dir/', 'dir/file.c'],
 
3042
            root_id)
 
3043
        lib_wt = self.setup_simple_branch(
 
3044
            'lib1', ['README', 'Makefile', 'foo.c'], root_id)
 
3045
 
 
3046
        return project_wt, lib_wt
 
3047
 
 
3048
    def do_merge_into(self, location, merge_as):
 
3049
        """Helper for using MergeIntoMerger.
 
3050
        
 
3051
        :param location: location of directory to merge from, either the
 
3052
            location of a branch or of a path inside a branch.
 
3053
        :param merge_as: the path in a tree to add the new directory as.
 
3054
        :returns: the conflicts from 'do_merge'.
 
3055
        """
 
3056
        operation = cleanup.OperationWithCleanups(self._merge_into)
 
3057
        return operation.run(location, merge_as)
 
3058
 
 
3059
    def _merge_into(self, op, location, merge_as):
 
3060
        # Open and lock the various tree and branch objects
 
3061
        wt, subdir_relpath = WorkingTree.open_containing(merge_as)
 
3062
        op.add_cleanup(wt.lock_write().unlock)
 
3063
        branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
 
3064
            location)
 
3065
        op.add_cleanup(branch_to_merge.lock_read().unlock)
 
3066
        other_tree = branch_to_merge.basis_tree()
 
3067
        op.add_cleanup(other_tree.lock_read().unlock)
 
3068
        # Perform the merge
 
3069
        merger = _mod_merge.MergeIntoMerger(this_tree=wt, other_tree=other_tree,
 
3070
            other_branch=branch_to_merge, target_subdir=subdir_relpath,
 
3071
            source_subpath=subdir_to_merge)
 
3072
        merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
 
3073
        conflicts = merger.do_merge()
 
3074
        merger.set_pending()
 
3075
        return conflicts
 
3076
 
 
3077
    def assertTreeEntriesEqual(self, expected_entries, tree):
 
3078
        """Assert that 'tree' contains the expected inventory entries.
 
3079
 
 
3080
        :param expected_entries: sequence of (path, file-id) pairs.
 
3081
        """
 
3082
        files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
 
3083
        self.assertEqual(expected_entries, files)
 
3084
 
 
3085
 
 
3086
class TestMergeInto(TestMergeIntoBase):
 
3087
 
 
3088
    def test_newdir_with_unique_roots(self):
 
3089
        """Merge a branch with a unique root into a new directory."""
 
3090
        project_wt, lib_wt = self.setup_two_branches()
 
3091
        self.do_merge_into('lib1', 'project/lib1')
 
3092
        project_wt.lock_read()
 
3093
        self.addCleanup(project_wt.unlock)
 
3094
        # The r1-lib1 revision should be merged into this one
 
3095
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3096
        self.assertTreeEntriesEqual(
 
3097
            [('', 'project-root-id'),
 
3098
             ('README', 'project-README-id'),
 
3099
             ('dir', 'project-dir-id'),
 
3100
             ('lib1', 'lib1-root-id'),
 
3101
             ('dir/file.c', 'project-file.c-id'),
 
3102
             ('lib1/Makefile', 'lib1-Makefile-id'),
 
3103
             ('lib1/README', 'lib1-README-id'),
 
3104
             ('lib1/foo.c', 'lib1-foo.c-id'),
 
3105
            ], project_wt)
 
3106
 
 
3107
    def test_subdir(self):
 
3108
        """Merge a branch into a subdirectory of an existing directory."""
 
3109
        project_wt, lib_wt = self.setup_two_branches()
 
3110
        self.do_merge_into('lib1', 'project/dir/lib1')
 
3111
        project_wt.lock_read()
 
3112
        self.addCleanup(project_wt.unlock)
 
3113
        # The r1-lib1 revision should be merged into this one
 
3114
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3115
        self.assertTreeEntriesEqual(
 
3116
            [('', 'project-root-id'),
 
3117
             ('README', 'project-README-id'),
 
3118
             ('dir', 'project-dir-id'),
 
3119
             ('dir/file.c', 'project-file.c-id'),
 
3120
             ('dir/lib1', 'lib1-root-id'),
 
3121
             ('dir/lib1/Makefile', 'lib1-Makefile-id'),
 
3122
             ('dir/lib1/README', 'lib1-README-id'),
 
3123
             ('dir/lib1/foo.c', 'lib1-foo.c-id'),
 
3124
            ], project_wt)
 
3125
 
 
3126
    def test_newdir_with_repeat_roots(self):
 
3127
        """If the file-id of the dir to be merged already exists a new ID will
 
3128
        be allocated to let the merge happen.
 
3129
        """
 
3130
        project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
 
3131
        root_id = project_wt.path2id('')
 
3132
        self.do_merge_into('lib1', 'project/lib1')
 
3133
        project_wt.lock_read()
 
3134
        self.addCleanup(project_wt.unlock)
 
3135
        # The r1-lib1 revision should be merged into this one
 
3136
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3137
        new_lib1_id = project_wt.path2id('lib1')
 
3138
        self.assertNotEqual(None, new_lib1_id)
 
3139
        self.assertTreeEntriesEqual(
 
3140
            [('', root_id),
 
3141
             ('README', 'project-README-id'),
 
3142
             ('dir', 'project-dir-id'),
 
3143
             ('lib1', new_lib1_id),
 
3144
             ('dir/file.c', 'project-file.c-id'),
 
3145
             ('lib1/Makefile', 'lib1-Makefile-id'),
 
3146
             ('lib1/README', 'lib1-README-id'),
 
3147
             ('lib1/foo.c', 'lib1-foo.c-id'),
 
3148
            ], project_wt)
 
3149
 
 
3150
    def test_name_conflict(self):
 
3151
        """When the target directory name already exists a conflict is
 
3152
        generated and the original directory is renamed to foo.moved.
 
3153
        """
 
3154
        dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
 
3155
        src_wt = self.setup_simple_branch('src', ['README'])
 
3156
        conflicts = self.do_merge_into('src', 'dest/dir')
 
3157
        self.assertEqual(1, conflicts)
 
3158
        dest_wt.lock_read()
 
3159
        self.addCleanup(dest_wt.unlock)
 
3160
        # The r1-lib1 revision should be merged into this one
 
3161
        self.assertEqual(['r1-dest', 'r1-src'], dest_wt.get_parent_ids())
 
3162
        self.assertTreeEntriesEqual(
 
3163
            [('', 'dest-root-id'),
 
3164
             ('dir', 'src-root-id'),
 
3165
             ('dir.moved', 'dest-dir-id'),
 
3166
             ('dir/README', 'src-README-id'),
 
3167
             ('dir.moved/file.txt', 'dest-file.txt-id'),
 
3168
            ], dest_wt)
 
3169
 
 
3170
    def test_file_id_conflict(self):
 
3171
        """A conflict is generated if the merge-into adds a file (or other
 
3172
        inventory entry) with a file-id that already exists in the target tree.
 
3173
        """
 
3174
        dest_wt = self.setup_simple_branch('dest', ['file.txt'])
 
3175
        # Make a second tree with a file-id that will clash with file.txt in
 
3176
        # dest.
 
3177
        src_wt = self.make_branch_and_tree('src')
 
3178
        self.build_tree(['src/README'])
 
3179
        src_wt.add(['README'], ids=['dest-file.txt-id'])
 
3180
        src_wt.commit("Rev 1 of src.", rev_id='r1-src')
 
3181
        conflicts = self.do_merge_into('src', 'dest/dir')
 
3182
        # This is an edge case that shouldn't happen to users very often.  So
 
3183
        # we don't care really about the exact presentation of the conflict,
 
3184
        # just that there is one.
 
3185
        self.assertEqual(1, conflicts)
 
3186
 
 
3187
    def test_only_subdir(self):
 
3188
        """When the location points to just part of a tree, merge just that
 
3189
        subtree.
 
3190
        """
 
3191
        dest_wt = self.setup_simple_branch('dest')
 
3192
        src_wt = self.setup_simple_branch(
 
3193
            'src', ['hello.txt', 'dir/', 'dir/foo.c'])
 
3194
        conflicts = self.do_merge_into('src/dir', 'dest/dir')
 
3195
        dest_wt.lock_read()
 
3196
        self.addCleanup(dest_wt.unlock)
 
3197
        # The r1-lib1 revision should NOT be merged into this one (this is a
 
3198
        # partial merge).
 
3199
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3200
        self.assertTreeEntriesEqual(
 
3201
            [('', 'dest-root-id'),
 
3202
             ('dir', 'src-dir-id'),
 
3203
             ('dir/foo.c', 'src-foo.c-id'),
 
3204
            ], dest_wt)
 
3205
 
 
3206
    def test_only_file(self):
 
3207
        """An edge case: merge just one file, not a whole dir."""
 
3208
        dest_wt = self.setup_simple_branch('dest')
 
3209
        two_file_wt = self.setup_simple_branch(
 
3210
            'two-file', ['file1.txt', 'file2.txt'])
 
3211
        conflicts = self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
 
3212
        dest_wt.lock_read()
 
3213
        self.addCleanup(dest_wt.unlock)
 
3214
        # The r1-lib1 revision should NOT be merged into this one
 
3215
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3216
        self.assertTreeEntriesEqual(
 
3217
            [('', 'dest-root-id'), ('file1.txt', 'two-file-file1.txt-id')],
 
3218
            dest_wt)
 
3219
 
 
3220
    def test_no_such_source_path(self):
 
3221
        """PathNotInTree is raised if the specified path in the source tree
 
3222
        does not exist.
 
3223
        """
 
3224
        dest_wt = self.setup_simple_branch('dest')
 
3225
        two_file_wt = self.setup_simple_branch('src', ['dir/'])
 
3226
        self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
 
3227
            'src/no-such-dir', 'dest/foo')
 
3228
        dest_wt.lock_read()
 
3229
        self.addCleanup(dest_wt.unlock)
 
3230
        # The dest tree is unmodified.
 
3231
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3232
        self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)
 
3233
 
 
3234
    def test_no_such_target_path(self):
 
3235
        """PathNotInTree is also raised if the specified path in the target
 
3236
        tree does not exist.
 
3237
        """
 
3238
        dest_wt = self.setup_simple_branch('dest')
 
3239
        two_file_wt = self.setup_simple_branch('src', ['file.txt'])
 
3240
        self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
 
3241
            'src', 'dest/no-such-dir/foo')
 
3242
        dest_wt.lock_read()
 
3243
        self.addCleanup(dest_wt.unlock)
 
3244
        # The dest tree is unmodified.
 
3245
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3246
        self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)