~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: 2008-09-11 06:10:59 UTC
  • mfrom: (3702.1.1 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20080911061059-svzqfejar17ui4zw
(mbp) KnitVersionedFiles repr

Show diffs side-by-side

added added

removed removed

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