~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: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
from StringIO import StringIO
19
19
 
20
20
from bzrlib import (
 
21
    branch as _mod_branch,
 
22
    cleanup,
21
23
    conflicts,
22
24
    errors,
 
25
    inventory,
23
26
    knit,
 
27
    memorytree,
24
28
    merge as _mod_merge,
25
29
    option,
26
 
    progress,
 
30
    revision as _mod_revision,
 
31
    tests,
27
32
    transform,
28
33
    versionedfile,
29
34
    )
30
 
from bzrlib.branch import Branch
31
35
from bzrlib.conflicts import ConflictList, TextConflict
32
 
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
 
36
from bzrlib.errors import UnrelatedBranches, NoCommits
33
37
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
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)
 
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
    )
37
45
from bzrlib.workingtree import WorkingTree
38
46
 
39
47
 
83
91
        os.chdir('branch2')
84
92
        self.run_bzr('merge ../branch1/baz', retcode=3)
85
93
        self.run_bzr('merge ../branch1/foo')
86
 
        self.failUnlessExists('foo')
87
 
        self.failIfExists('bar')
 
94
        self.assertPathExists('foo')
 
95
        self.assertPathDoesNotExist('bar')
88
96
        wt2 = WorkingTree.open('.') # opens branch2
89
97
        self.assertEqual([tip], wt2.get_parent_ids())
90
 
        
 
98
 
91
99
    def test_pending_with_null(self):
92
100
        """When base is forced to revno 0, parent_ids are set"""
93
101
        wt2 = self.test_unrelated()
94
102
        wt1 = WorkingTree.open('.')
95
103
        br1 = wt1.branch
96
104
        br1.fetch(wt2.branch)
97
 
        # merge all of branch 2 into branch 1 even though they 
 
105
        # merge all of branch 2 into branch 1 even though they
98
106
        # are not related.
99
107
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
100
108
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
114
122
        finally:
115
123
            wt1.unlock()
116
124
 
 
125
    def test_merge_into_null_tree(self):
 
126
        wt = self.make_branch_and_tree('tree')
 
127
        null_tree = wt.basis_tree()
 
128
        self.build_tree(['tree/file'])
 
129
        wt.add('file')
 
130
        wt.commit('tree with root')
 
131
        merger = _mod_merge.Merge3Merger(null_tree, null_tree, null_tree, wt,
 
132
                                         this_branch=wt.branch,
 
133
                                         do_merge=False)
 
134
        with merger.make_preview_transform() as tt:
 
135
            self.assertEqual([], tt.find_conflicts())
 
136
            preview = tt.get_preview_tree()
 
137
            self.assertEqual(wt.get_root_id(), preview.get_root_id())
 
138
 
 
139
    def test_merge_unrelated_retains_root(self):
 
140
        wt = self.make_branch_and_tree('tree')
 
141
        other_tree = self.make_branch_and_tree('other')
 
142
        self.addCleanup(other_tree.lock_read().unlock)
 
143
        merger = _mod_merge.Merge3Merger(wt, wt, wt.basis_tree(), other_tree,
 
144
                                         this_branch=wt.branch,
 
145
                                         do_merge=False)
 
146
        with transform.TransformPreview(wt) as merger.tt:
 
147
            merger._compute_transform()
 
148
            new_root_id = merger.tt.final_file_id(merger.tt.root)
 
149
            self.assertEqual(wt.get_root_id(), new_root_id)
 
150
 
117
151
    def test_create_rename(self):
118
152
        """Rename an inventory entry while creating the file"""
119
153
        tree =self.make_branch_and_tree('.')
148
182
        self.addCleanup(tree_b.unlock)
149
183
        tree_a.commit(message="hello again")
150
184
        log = StringIO()
151
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
185
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
152
186
                    this_tree=tree_b, ignore_zero=True)
153
 
        log = self._get_log(keep_log_file=True)
154
 
        self.failUnless('All changes applied successfully.\n' not in log)
 
187
        self.assertTrue('All changes applied successfully.\n' not in
 
188
            self.get_log())
155
189
        tree_b.revert()
156
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
190
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
157
191
                    this_tree=tree_b, ignore_zero=False)
158
 
        log = self._get_log(keep_log_file=True)
159
 
        self.failUnless('All changes applied successfully.\n' in log)
 
192
        self.assertTrue('All changes applied successfully.\n' in self.get_log())
160
193
 
161
194
    def test_merge_inner_conflicts(self):
162
195
        tree_a = self.make_branch_and_tree('a')
217
250
        tree_a.add('file')
218
251
        tree_a.commit('commit base')
219
252
        # basis_tree() is only guaranteed to be valid as long as it is actually
220
 
        # the basis tree. This mutates the tree after grabbing basis, so go to
221
 
        # the repository.
 
253
        # the basis tree. This test commits to the tree after grabbing basis,
 
254
        # so we go to the repository.
222
255
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
223
256
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
224
257
        self.build_tree_contents([('tree_a/file', 'content_2')])
225
258
        tree_a.commit('commit other')
226
259
        other_tree = tree_a.basis_tree()
 
260
        # 'file' is now missing but isn't altered in any commit in b so no
 
261
        # change should be applied.
227
262
        os.unlink('tree_b/file')
228
263
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
229
264
 
246
281
        self.assertEqual(tree_b.conflicts(),
247
282
                         [conflicts.ContentsConflict('file',
248
283
                          file_id='file-id')])
249
 
    
 
284
 
250
285
    def test_merge_type_registry(self):
251
286
        merge_type_option = option.Option.OPTIONS['merge-type']
252
 
        self.assertFalse('merge4' in [x[0] for x in 
 
287
        self.assertFalse('merge4' in [x[0] for x in
253
288
                        merge_type_option.iter_switches()])
254
289
        registry = _mod_merge.get_merge_type_registry()
255
290
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
256
291
                               'time-travelling merge')
257
 
        self.assertTrue('merge4' in [x[0] for x in 
 
292
        self.assertTrue('merge4' in [x[0] for x in
258
293
                        merge_type_option.iter_switches()])
259
294
        registry.remove('merge4')
260
 
        self.assertFalse('merge4' in [x[0] for x in 
 
295
        self.assertFalse('merge4' in [x[0] for x in
261
296
                        merge_type_option.iter_switches()])
262
297
 
263
298
    def test_merge_other_moves_we_deleted(self):
290
325
        tree_a.commit('commit 2')
291
326
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
292
327
        tree_b.rename_one('file_1', 'renamed')
293
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
294
 
                                                    progress.DummyProgress())
 
328
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
295
329
        merger.merge_type = _mod_merge.Merge3Merger
296
330
        merger.do_merge()
297
331
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
305
339
        tree_a.commit('commit 2')
306
340
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
307
341
        tree_b.rename_one('file_1', 'renamed')
308
 
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
309
 
                                                    progress.DummyProgress())
 
342
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b)
310
343
        merger.merge_type = _mod_merge.WeaveMerger
311
344
        merger.do_merge()
312
345
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
337
370
 
338
371
    def test_weave_cherrypick(self):
339
372
        this_tree, other_tree = self.prepare_cherrypick()
340
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
373
        merger = _mod_merge.Merger.from_revision_ids(None,
341
374
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
342
375
        merger.merge_type = _mod_merge.WeaveMerger
343
376
        merger.do_merge()
345
378
 
346
379
    def test_weave_cannot_reverse_cherrypick(self):
347
380
        this_tree, other_tree = self.prepare_cherrypick()
348
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
381
        merger = _mod_merge.Merger.from_revision_ids(None,
349
382
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
350
383
        merger.merge_type = _mod_merge.WeaveMerger
351
384
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
352
385
 
353
386
    def test_merge3_can_reverse_cherrypick(self):
354
387
        this_tree, other_tree = self.prepare_cherrypick()
355
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
388
        merger = _mod_merge.Merger.from_revision_ids(None,
356
389
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
357
390
        merger.merge_type = _mod_merge.Merge3Merger
358
391
        merger.do_merge()
370
403
        this_tree.lock_write()
371
404
        self.addCleanup(this_tree.unlock)
372
405
 
373
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
406
        merger = _mod_merge.Merger.from_revision_ids(None,
374
407
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
375
408
        merger.merge_type = _mod_merge.Merge3Merger
376
409
        merger.do_merge()
381
414
                             '>>>>>>> MERGE-SOURCE\n',
382
415
                             'this/file')
383
416
 
 
417
    def test_merge_reverse_revision_range(self):
 
418
        tree = self.make_branch_and_tree(".")
 
419
        tree.lock_write()
 
420
        self.addCleanup(tree.unlock)
 
421
        self.build_tree(['a'])
 
422
        tree.add('a')
 
423
        tree.commit("added a")
 
424
        first_rev = tree.branch.revision_history()[0]
 
425
        merger = _mod_merge.Merger.from_revision_ids(None, tree,
 
426
                                          _mod_revision.NULL_REVISION,
 
427
                                          first_rev)
 
428
        merger.merge_type = _mod_merge.Merge3Merger
 
429
        merger.interesting_files = 'a'
 
430
        conflict_count = merger.do_merge()
 
431
        self.assertEqual(0, conflict_count)
 
432
 
 
433
        self.assertPathDoesNotExist("a")
 
434
        tree.revert()
 
435
        self.assertPathExists("a")
 
436
 
384
437
    def test_make_merger(self):
385
438
        this_tree = self.make_branch_and_tree('this')
386
439
        this_tree.commit('rev1', rev_id='rev1')
389
442
        other_tree.commit('rev2', rev_id='rev2b')
390
443
        this_tree.lock_write()
391
444
        self.addCleanup(this_tree.unlock)
392
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
445
        merger = _mod_merge.Merger.from_revision_ids(None,
393
446
            this_tree, 'rev2b', other_branch=other_tree.branch)
394
447
        merger.merge_type = _mod_merge.Merge3Merger
395
448
        tree_merger = merger.make_merger()
409
462
        other_tree.commit('rev2', rev_id='rev2b')
410
463
        this_tree.lock_write()
411
464
        self.addCleanup(this_tree.unlock)
412
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
465
        merger = _mod_merge.Merger.from_revision_ids(None,
413
466
            this_tree, 'rev2b', other_branch=other_tree.branch)
414
467
        merger.merge_type = _mod_merge.Merge3Merger
415
468
        tree_merger = merger.make_merger()
439
492
        other_tree.commit('rev2', rev_id='rev2b')
440
493
        this_tree.lock_write()
441
494
        self.addCleanup(this_tree.unlock)
442
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
495
        merger = _mod_merge.Merger.from_revision_ids(None,
443
496
            this_tree, 'rev2b', other_branch=other_tree.branch)
444
497
        merger.merge_type = _mod_merge.Merge3Merger
445
498
        tree_merger = merger.make_merger()
450
503
        finally:
451
504
            tree_file.close()
452
505
 
 
506
    def test_merge_require_tree_root(self):
 
507
        tree = self.make_branch_and_tree(".")
 
508
        tree.lock_write()
 
509
        self.addCleanup(tree.unlock)
 
510
        self.build_tree(['a'])
 
511
        tree.add('a')
 
512
        tree.commit("added a")
 
513
        old_root_id = tree.get_root_id()
 
514
        first_rev = tree.branch.revision_history()[0]
 
515
        merger = _mod_merge.Merger.from_revision_ids(None, tree,
 
516
                                          _mod_revision.NULL_REVISION,
 
517
                                          first_rev)
 
518
        merger.merge_type = _mod_merge.Merge3Merger
 
519
        conflict_count = merger.do_merge()
 
520
        self.assertEqual(0, conflict_count)
 
521
        self.assertEquals(set([old_root_id]), tree.all_file_ids())
 
522
        tree.set_parent_ids([])
 
523
 
453
524
    def test_merge_add_into_deleted_root(self):
454
525
        # Yes, people actually do this.  And report bugs if it breaks.
455
526
        source = self.make_branch_and_tree('source', format='rich-root-pack')
516
587
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
517
588
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
518
589
 
 
590
    def test_base_from_plan(self):
 
591
        self.setup_plan_merge()
 
592
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
593
        pwm = versionedfile.PlanWeaveMerge(plan)
 
594
        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
 
595
 
519
596
    def test_unique_lines(self):
520
597
        plan = self.setup_plan_merge()
521
598
        self.assertEqual(plan._unique_lines(
716
793
 
717
794
    def test_plan_merge_insert_order(self):
718
795
        """Weave merges are sensitive to the order of insertion.
719
 
        
 
796
 
720
797
        Specifically for overlapping regions, it effects which region gets put
721
798
        'first'. And when a user resolves an overlapping merge, if they use the
722
799
        same ordering, then the lines match the parents, if they don't only
819
896
                          ('unchanged', 'f\n'),
820
897
                          ('unchanged', 'g\n')],
821
898
                         list(plan))
 
899
        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
 
900
        # This is one of the main differences between plan_merge and
 
901
        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
 
902
        # because 'x' was not present in one of the bases. However, in this
 
903
        # case it is spurious because 'x' does not exist in the global base A.
 
904
        self.assertEqual([
 
905
                          ('unchanged', 'h\n'),
 
906
                          ('unchanged', 'a\n'),
 
907
                          ('conflicted-a', 'x\n'),
 
908
                          ('new-b', 'z\n'),
 
909
                          ('unchanged', 'c\n'),
 
910
                          ('unchanged', 'd\n'),
 
911
                          ('unchanged', 'y\n'),
 
912
                          ('unchanged', 'f\n'),
 
913
                          ('unchanged', 'g\n')],
 
914
                         list(plan))
 
915
 
 
916
    def test_criss_cross_flip_flop(self):
 
917
        # This is specificly trying to trigger problems when using limited
 
918
        # ancestry and weaves. The ancestry graph looks like:
 
919
        #       XX      unused ancestor, should not show up in the weave
 
920
        #       |
 
921
        #       A       Unique LCA
 
922
        #      / \  
 
923
        #     B   C     B & C both introduce a new line
 
924
        #     |\ /|  
 
925
        #     | X |  
 
926
        #     |/ \| 
 
927
        #     D   E     B & C are both merged, so both are common ancestors
 
928
        #               In the process of merging, both sides order the new
 
929
        #               lines differently
 
930
        #
 
931
        self.add_rev('root', 'XX', [], 'qrs')
 
932
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
933
        self.add_rev('root', 'B', ['A'], 'abcdgef')
 
934
        self.add_rev('root', 'C', ['A'], 'abcdhef')
 
935
        self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
 
936
        self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
 
937
        plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
 
938
        self.assertEqual([
 
939
                          ('unchanged', 'a\n'),
 
940
                          ('unchanged', 'b\n'),
 
941
                          ('unchanged', 'c\n'),
 
942
                          ('unchanged', 'd\n'),
 
943
                          ('new-b', 'h\n'),
 
944
                          ('unchanged', 'g\n'),
 
945
                          ('killed-b', 'h\n'),
 
946
                          ('unchanged', 'e\n'),
 
947
                          ('unchanged', 'f\n'),
 
948
                         ], plan)
 
949
        pwm = versionedfile.PlanWeaveMerge(plan)
 
950
        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
 
951
                             ''.join(pwm.base_from_plan()))
 
952
        # Reversing the order reverses the merge plan, and final order of 'hg'
 
953
        # => 'gh'
 
954
        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
 
955
        self.assertEqual([
 
956
                          ('unchanged', 'a\n'),
 
957
                          ('unchanged', 'b\n'),
 
958
                          ('unchanged', 'c\n'),
 
959
                          ('unchanged', 'd\n'),
 
960
                          ('new-b', 'g\n'),
 
961
                          ('unchanged', 'h\n'),
 
962
                          ('killed-b', 'g\n'),
 
963
                          ('unchanged', 'e\n'),
 
964
                          ('unchanged', 'f\n'),
 
965
                         ], plan)
 
966
        pwm = versionedfile.PlanWeaveMerge(plan)
 
967
        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
 
968
                             ''.join(pwm.base_from_plan()))
 
969
        # This is where lca differs, in that it (fairly correctly) determines
 
970
        # that there is a conflict because both sides resolved the merge
 
971
        # differently
 
972
        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
 
973
        self.assertEqual([
 
974
                          ('unchanged', 'a\n'),
 
975
                          ('unchanged', 'b\n'),
 
976
                          ('unchanged', 'c\n'),
 
977
                          ('unchanged', 'd\n'),
 
978
                          ('conflicted-b', 'h\n'),
 
979
                          ('unchanged', 'g\n'),
 
980
                          ('conflicted-a', 'h\n'),
 
981
                          ('unchanged', 'e\n'),
 
982
                          ('unchanged', 'f\n'),
 
983
                         ], plan)
 
984
        pwm = versionedfile.PlanWeaveMerge(plan)
 
985
        self.assertEqualDiff('\n'.join('abcdgef') + '\n',
 
986
                             ''.join(pwm.base_from_plan()))
 
987
        # Reversing it changes what line is doubled, but still gives a
 
988
        # double-conflict
 
989
        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
 
990
        self.assertEqual([
 
991
                          ('unchanged', 'a\n'),
 
992
                          ('unchanged', 'b\n'),
 
993
                          ('unchanged', 'c\n'),
 
994
                          ('unchanged', 'd\n'),
 
995
                          ('conflicted-b', 'g\n'),
 
996
                          ('unchanged', 'h\n'),
 
997
                          ('conflicted-a', 'g\n'),
 
998
                          ('unchanged', 'e\n'),
 
999
                          ('unchanged', 'f\n'),
 
1000
                         ], plan)
 
1001
        pwm = versionedfile.PlanWeaveMerge(plan)
 
1002
        self.assertEqualDiff('\n'.join('abcdhef') + '\n',
 
1003
                             ''.join(pwm.base_from_plan()))
822
1004
 
823
1005
    def assertRemoveExternalReferences(self, filtered_parent_map,
824
1006
                                       child_map, tails, parent_map):
1024
1206
                         ], list(plan))
1025
1207
 
1026
1208
 
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)
 
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)