/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: John Arbash Meinel
  • Date: 2010-01-12 22:51:31 UTC
  • mto: This revision was merged to the branch mainline in revision 4955.
  • Revision ID: john@arbash-meinel.com-20100112225131-he8h411p6aeeb947
Delay grabbing an output stream until we actually go to show a diff.

This makes the test suite happy, but it also seems to be reasonable.
If we aren't going to write anything, we don't need to hold an
output stream open.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import os
 
18
from StringIO import StringIO
 
19
 
 
20
from bzrlib import (
 
21
    conflicts,
 
22
    errors,
 
23
    knit,
 
24
    memorytree,
 
25
    merge as _mod_merge,
 
26
    option,
 
27
    progress,
 
28
    tests,
 
29
    transform,
 
30
    versionedfile,
 
31
    )
 
32
from bzrlib.conflicts import ConflictList, TextConflict
 
33
from bzrlib.errors import UnrelatedBranches, NoCommits
 
34
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
 
35
from bzrlib.osutils import pathjoin, file_kind
 
36
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
 
37
from bzrlib.workingtree import WorkingTree
 
38
 
 
39
 
 
40
class TestMerge(TestCaseWithTransport):
 
41
    """Test appending more than one revision"""
 
42
 
 
43
    def test_pending(self):
 
44
        wt = self.make_branch_and_tree('.')
 
45
        rev_a = wt.commit("lala!")
 
46
        self.assertEqual([rev_a], wt.get_parent_ids())
 
47
        self.assertRaises(errors.PointlessMerge, wt.merge_from_branch,
 
48
                          wt.branch)
 
49
        self.assertEqual([rev_a], wt.get_parent_ids())
 
50
        return wt
 
51
 
 
52
    def test_undo(self):
 
53
        wt = self.make_branch_and_tree('.')
 
54
        wt.commit("lala!")
 
55
        wt.commit("haha!")
 
56
        wt.commit("blabla!")
 
57
        wt.merge_from_branch(wt.branch, wt.branch.get_rev_id(2),
 
58
                             wt.branch.get_rev_id(1))
 
59
 
 
60
    def test_nocommits(self):
 
61
        wt = self.test_pending()
 
62
        wt2 = self.make_branch_and_tree('branch2')
 
63
        self.assertRaises(NoCommits, wt.merge_from_branch, wt2.branch)
 
64
        return wt, wt2
 
65
 
 
66
    def test_unrelated(self):
 
67
        wt, wt2 = self.test_nocommits()
 
68
        wt2.commit("blah")
 
69
        self.assertRaises(UnrelatedBranches, wt.merge_from_branch, wt2.branch)
 
70
        return wt2
 
71
 
 
72
    def test_merge_one_file(self):
 
73
        """Do a partial merge of a tree which should not affect tree parents."""
 
74
        wt1 = self.make_branch_and_tree('branch1')
 
75
        tip = wt1.commit('empty commit')
 
76
        wt2 = self.make_branch_and_tree('branch2')
 
77
        wt2.pull(wt1.branch)
 
78
        file('branch1/foo', 'wb').write('foo')
 
79
        file('branch1/bar', 'wb').write('bar')
 
80
        wt1.add('foo')
 
81
        wt1.add('bar')
 
82
        wt1.commit('add foobar')
 
83
        os.chdir('branch2')
 
84
        self.run_bzr('merge ../branch1/baz', retcode=3)
 
85
        self.run_bzr('merge ../branch1/foo')
 
86
        self.failUnlessExists('foo')
 
87
        self.failIfExists('bar')
 
88
        wt2 = WorkingTree.open('.') # opens branch2
 
89
        self.assertEqual([tip], wt2.get_parent_ids())
 
90
 
 
91
    def test_pending_with_null(self):
 
92
        """When base is forced to revno 0, parent_ids are set"""
 
93
        wt2 = self.test_unrelated()
 
94
        wt1 = WorkingTree.open('.')
 
95
        br1 = wt1.branch
 
96
        br1.fetch(wt2.branch)
 
97
        # merge all of branch 2 into branch 1 even though they
 
98
        # are not related.
 
99
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
 
100
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
 
101
            wt1.get_parent_ids())
 
102
        return (wt1, wt2.branch)
 
103
 
 
104
    def test_two_roots(self):
 
105
        """Merge base is sane when two unrelated branches are merged"""
 
106
        wt1, br2 = self.test_pending_with_null()
 
107
        wt1.commit("blah")
 
108
        wt1.lock_read()
 
109
        try:
 
110
            last = wt1.branch.last_revision()
 
111
            last2 = br2.last_revision()
 
112
            graph = wt1.branch.repository.get_graph()
 
113
            self.assertEqual(last2, graph.find_unique_lca(last, last2))
 
114
        finally:
 
115
            wt1.unlock()
 
116
 
 
117
    def test_create_rename(self):
 
118
        """Rename an inventory entry while creating the file"""
 
119
        tree =self.make_branch_and_tree('.')
 
120
        file('name1', 'wb').write('Hello')
 
121
        tree.add('name1')
 
122
        tree.commit(message="hello")
 
123
        tree.rename_one('name1', 'name2')
 
124
        os.unlink('name2')
 
125
        transform_tree(tree, tree.branch.basis_tree())
 
126
 
 
127
    def test_layered_rename(self):
 
128
        """Rename both child and parent at same time"""
 
129
        tree =self.make_branch_and_tree('.')
 
130
        os.mkdir('dirname1')
 
131
        tree.add('dirname1')
 
132
        filename = pathjoin('dirname1', 'name1')
 
133
        file(filename, 'wb').write('Hello')
 
134
        tree.add(filename)
 
135
        tree.commit(message="hello")
 
136
        filename2 = pathjoin('dirname1', 'name2')
 
137
        tree.rename_one(filename, filename2)
 
138
        tree.rename_one('dirname1', 'dirname2')
 
139
        transform_tree(tree, tree.branch.basis_tree())
 
140
 
 
141
    def test_ignore_zero_merge_inner(self):
 
142
        # Test that merge_inner's ignore zero parameter is effective
 
143
        tree_a =self.make_branch_and_tree('a')
 
144
        tree_a.commit(message="hello")
 
145
        dir_b = tree_a.bzrdir.sprout('b')
 
146
        tree_b = dir_b.open_workingtree()
 
147
        tree_b.lock_write()
 
148
        self.addCleanup(tree_b.unlock)
 
149
        tree_a.commit(message="hello again")
 
150
        log = StringIO()
 
151
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
 
152
                    this_tree=tree_b, ignore_zero=True)
 
153
        self.failUnless('All changes applied successfully.\n' not in
 
154
            self.get_log())
 
155
        tree_b.revert()
 
156
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
 
157
                    this_tree=tree_b, ignore_zero=False)
 
158
        self.failUnless('All changes applied successfully.\n' in self.get_log())
 
159
 
 
160
    def test_merge_inner_conflicts(self):
 
161
        tree_a = self.make_branch_and_tree('a')
 
162
        tree_a.set_conflicts(ConflictList([TextConflict('patha')]))
 
163
        merge_inner(tree_a.branch, tree_a, tree_a, this_tree=tree_a)
 
164
        self.assertEqual(1, len(tree_a.conflicts()))
 
165
 
 
166
    def test_rmdir_conflict(self):
 
167
        tree_a = self.make_branch_and_tree('a')
 
168
        self.build_tree(['a/b/'])
 
169
        tree_a.add('b', 'b-id')
 
170
        tree_a.commit('added b')
 
171
        # basis_tree() is only guaranteed to be valid as long as it is actually
 
172
        # the basis tree. This mutates the tree after grabbing basis, so go to
 
173
        # the repository.
 
174
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
 
175
        tree_z = tree_a.bzrdir.sprout('z').open_workingtree()
 
176
        self.build_tree(['a/b/c'])
 
177
        tree_a.add('b/c')
 
178
        tree_a.commit('added c')
 
179
        os.rmdir('z/b')
 
180
        tree_z.commit('removed b')
 
181
        merge_inner(tree_z.branch, tree_a, base_tree, this_tree=tree_z)
 
182
        self.assertEqual([
 
183
            conflicts.MissingParent('Created directory', 'b', 'b-id'),
 
184
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
 
185
            tree_z.conflicts())
 
186
        merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree,
 
187
                    this_tree=tree_a)
 
188
        self.assertEqual([
 
189
            conflicts.DeletingParent('Not deleting', 'b', 'b-id'),
 
190
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
 
191
            tree_a.conflicts())
 
192
 
 
193
    def test_nested_merge(self):
 
194
        tree = self.make_branch_and_tree('tree',
 
195
            format='dirstate-with-subtree')
 
196
        sub_tree = self.make_branch_and_tree('tree/sub-tree',
 
197
            format='dirstate-with-subtree')
 
198
        sub_tree.set_root_id('sub-tree-root')
 
199
        self.build_tree_contents([('tree/sub-tree/file', 'text1')])
 
200
        sub_tree.add('file')
 
201
        sub_tree.commit('foo')
 
202
        tree.add_reference(sub_tree)
 
203
        tree.commit('set text to 1')
 
204
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
205
        # modify the file in the subtree
 
206
        self.build_tree_contents([('tree2/sub-tree/file', 'text2')])
 
207
        # and merge the changes from the diverged subtree into the containing
 
208
        # tree
 
209
        tree2.commit('changed file text')
 
210
        tree.merge_from_branch(tree2.branch)
 
211
        self.assertFileEqual('text2', 'tree/sub-tree/file')
 
212
 
 
213
    def test_merge_with_missing(self):
 
214
        tree_a = self.make_branch_and_tree('tree_a')
 
215
        self.build_tree_contents([('tree_a/file', 'content_1')])
 
216
        tree_a.add('file')
 
217
        tree_a.commit('commit base')
 
218
        # basis_tree() is only guaranteed to be valid as long as it is actually
 
219
        # the basis tree. This test commits to the tree after grabbing basis,
 
220
        # so we go to the repository.
 
221
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
 
222
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
 
223
        self.build_tree_contents([('tree_a/file', 'content_2')])
 
224
        tree_a.commit('commit other')
 
225
        other_tree = tree_a.basis_tree()
 
226
        # 'file' is now missing but isn't altered in any commit in b so no
 
227
        # change should be applied.
 
228
        os.unlink('tree_b/file')
 
229
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
 
230
 
 
231
    def test_merge_kind_change(self):
 
232
        tree_a = self.make_branch_and_tree('tree_a')
 
233
        self.build_tree_contents([('tree_a/file', 'content_1')])
 
234
        tree_a.add('file', 'file-id')
 
235
        tree_a.commit('added file')
 
236
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
 
237
        os.unlink('tree_a/file')
 
238
        self.build_tree(['tree_a/file/'])
 
239
        tree_a.commit('changed file to directory')
 
240
        tree_b.merge_from_branch(tree_a.branch)
 
241
        self.assertEqual('directory', file_kind('tree_b/file'))
 
242
        tree_b.revert()
 
243
        self.assertEqual('file', file_kind('tree_b/file'))
 
244
        self.build_tree_contents([('tree_b/file', 'content_2')])
 
245
        tree_b.commit('content change')
 
246
        tree_b.merge_from_branch(tree_a.branch)
 
247
        self.assertEqual(tree_b.conflicts(),
 
248
                         [conflicts.ContentsConflict('file',
 
249
                          file_id='file-id')])
 
250
 
 
251
    def test_merge_type_registry(self):
 
252
        merge_type_option = option.Option.OPTIONS['merge-type']
 
253
        self.assertFalse('merge4' in [x[0] for x in
 
254
                        merge_type_option.iter_switches()])
 
255
        registry = _mod_merge.get_merge_type_registry()
 
256
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
 
257
                               'time-travelling merge')
 
258
        self.assertTrue('merge4' in [x[0] for x in
 
259
                        merge_type_option.iter_switches()])
 
260
        registry.remove('merge4')
 
261
        self.assertFalse('merge4' in [x[0] for x in
 
262
                        merge_type_option.iter_switches()])
 
263
 
 
264
    def test_merge_other_moves_we_deleted(self):
 
265
        tree_a = self.make_branch_and_tree('A')
 
266
        tree_a.lock_write()
 
267
        self.addCleanup(tree_a.unlock)
 
268
        self.build_tree(['A/a'])
 
269
        tree_a.add('a')
 
270
        tree_a.commit('1', rev_id='rev-1')
 
271
        tree_a.flush()
 
272
        tree_a.rename_one('a', 'b')
 
273
        tree_a.commit('2')
 
274
        bzrdir_b = tree_a.bzrdir.sprout('B', revision_id='rev-1')
 
275
        tree_b = bzrdir_b.open_workingtree()
 
276
        tree_b.lock_write()
 
277
        self.addCleanup(tree_b.unlock)
 
278
        os.unlink('B/a')
 
279
        tree_b.commit('3')
 
280
        try:
 
281
            tree_b.merge_from_branch(tree_a.branch)
 
282
        except AttributeError:
 
283
            self.fail('tried to join a path when name was None')
 
284
 
 
285
    def test_merge_uncommitted_otherbasis_ancestor_of_thisbasis(self):
 
286
        tree_a = self.make_branch_and_tree('a')
 
287
        self.build_tree(['a/file_1', 'a/file_2'])
 
288
        tree_a.add(['file_1'])
 
289
        tree_a.commit('commit 1')
 
290
        tree_a.add(['file_2'])
 
291
        tree_a.commit('commit 2')
 
292
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
293
        tree_b.rename_one('file_1', 'renamed')
 
294
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
295
                                                    progress.DummyProgress())
 
296
        merger.merge_type = _mod_merge.Merge3Merger
 
297
        merger.do_merge()
 
298
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
 
299
 
 
300
    def test_merge_uncommitted_otherbasis_ancestor_of_thisbasis_weave(self):
 
301
        tree_a = self.make_branch_and_tree('a')
 
302
        self.build_tree(['a/file_1', 'a/file_2'])
 
303
        tree_a.add(['file_1'])
 
304
        tree_a.commit('commit 1')
 
305
        tree_a.add(['file_2'])
 
306
        tree_a.commit('commit 2')
 
307
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
308
        tree_b.rename_one('file_1', 'renamed')
 
309
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
310
                                                    progress.DummyProgress())
 
311
        merger.merge_type = _mod_merge.WeaveMerger
 
312
        merger.do_merge()
 
313
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
 
314
 
 
315
    def test_Merger_defaults_to_DummyProgress(self):
 
316
        branch = self.make_branch('branch')
 
317
        merger = _mod_merge.Merger(branch, pb=None)
 
318
        self.assertIsInstance(merger._pb, progress.DummyProgress)
 
319
 
 
320
    def prepare_cherrypick(self):
 
321
        """Prepare a pair of trees for cherrypicking tests.
 
322
 
 
323
        Both trees have a file, 'file'.
 
324
        rev1 sets content to 'a'.
 
325
        rev2b adds 'b'.
 
326
        rev3b adds 'c'.
 
327
        A full merge of rev2b and rev3b into this_tree would add both 'b' and
 
328
        'c'.  A successful cherrypick of rev2b-rev3b into this_tree will add
 
329
        'c', but not 'b'.
 
330
        """
 
331
        this_tree = self.make_branch_and_tree('this')
 
332
        self.build_tree_contents([('this/file', "a\n")])
 
333
        this_tree.add('file')
 
334
        this_tree.commit('rev1')
 
335
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
336
        self.build_tree_contents([('other/file', "a\nb\n")])
 
337
        other_tree.commit('rev2b', rev_id='rev2b')
 
338
        self.build_tree_contents([('other/file', "c\na\nb\n")])
 
339
        other_tree.commit('rev3b', rev_id='rev3b')
 
340
        this_tree.lock_write()
 
341
        self.addCleanup(this_tree.unlock)
 
342
        return this_tree, other_tree
 
343
 
 
344
    def test_weave_cherrypick(self):
 
345
        this_tree, other_tree = self.prepare_cherrypick()
 
346
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
347
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
 
348
        merger.merge_type = _mod_merge.WeaveMerger
 
349
        merger.do_merge()
 
350
        self.assertFileEqual('c\na\n', 'this/file')
 
351
 
 
352
    def test_weave_cannot_reverse_cherrypick(self):
 
353
        this_tree, other_tree = self.prepare_cherrypick()
 
354
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
355
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
 
356
        merger.merge_type = _mod_merge.WeaveMerger
 
357
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
 
358
 
 
359
    def test_merge3_can_reverse_cherrypick(self):
 
360
        this_tree, other_tree = self.prepare_cherrypick()
 
361
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
362
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
 
363
        merger.merge_type = _mod_merge.Merge3Merger
 
364
        merger.do_merge()
 
365
 
 
366
    def test_merge3_will_detect_cherrypick(self):
 
367
        this_tree = self.make_branch_and_tree('this')
 
368
        self.build_tree_contents([('this/file', "a\n")])
 
369
        this_tree.add('file')
 
370
        this_tree.commit('rev1')
 
371
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
372
        self.build_tree_contents([('other/file', "a\nb\n")])
 
373
        other_tree.commit('rev2b', rev_id='rev2b')
 
374
        self.build_tree_contents([('other/file', "a\nb\nc\n")])
 
375
        other_tree.commit('rev3b', rev_id='rev3b')
 
376
        this_tree.lock_write()
 
377
        self.addCleanup(this_tree.unlock)
 
378
 
 
379
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
380
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
 
381
        merger.merge_type = _mod_merge.Merge3Merger
 
382
        merger.do_merge()
 
383
        self.assertFileEqual('a\n'
 
384
                             '<<<<<<< TREE\n'
 
385
                             '=======\n'
 
386
                             'c\n'
 
387
                             '>>>>>>> MERGE-SOURCE\n',
 
388
                             'this/file')
 
389
 
 
390
    def test_make_merger(self):
 
391
        this_tree = self.make_branch_and_tree('this')
 
392
        this_tree.commit('rev1', rev_id='rev1')
 
393
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
394
        this_tree.commit('rev2', rev_id='rev2a')
 
395
        other_tree.commit('rev2', rev_id='rev2b')
 
396
        this_tree.lock_write()
 
397
        self.addCleanup(this_tree.unlock)
 
398
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
399
            this_tree, 'rev2b', other_branch=other_tree.branch)
 
400
        merger.merge_type = _mod_merge.Merge3Merger
 
401
        tree_merger = merger.make_merger()
 
402
        self.assertIs(_mod_merge.Merge3Merger, tree_merger.__class__)
 
403
        self.assertEqual('rev2b', tree_merger.other_tree.get_revision_id())
 
404
        self.assertEqual('rev1', tree_merger.base_tree.get_revision_id())
 
405
 
 
406
    def test_make_preview_transform(self):
 
407
        this_tree = self.make_branch_and_tree('this')
 
408
        self.build_tree_contents([('this/file', '1\n')])
 
409
        this_tree.add('file', 'file-id')
 
410
        this_tree.commit('rev1', rev_id='rev1')
 
411
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
412
        self.build_tree_contents([('this/file', '1\n2a\n')])
 
413
        this_tree.commit('rev2', rev_id='rev2a')
 
414
        self.build_tree_contents([('other/file', '2b\n1\n')])
 
415
        other_tree.commit('rev2', rev_id='rev2b')
 
416
        this_tree.lock_write()
 
417
        self.addCleanup(this_tree.unlock)
 
418
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
419
            this_tree, 'rev2b', other_branch=other_tree.branch)
 
420
        merger.merge_type = _mod_merge.Merge3Merger
 
421
        tree_merger = merger.make_merger()
 
422
        tt = tree_merger.make_preview_transform()
 
423
        self.addCleanup(tt.finalize)
 
424
        preview_tree = tt.get_preview_tree()
 
425
        tree_file = this_tree.get_file('file-id')
 
426
        try:
 
427
            self.assertEqual('1\n2a\n', tree_file.read())
 
428
        finally:
 
429
            tree_file.close()
 
430
        preview_file = preview_tree.get_file('file-id')
 
431
        try:
 
432
            self.assertEqual('2b\n1\n2a\n', preview_file.read())
 
433
        finally:
 
434
            preview_file.close()
 
435
 
 
436
    def test_do_merge(self):
 
437
        this_tree = self.make_branch_and_tree('this')
 
438
        self.build_tree_contents([('this/file', '1\n')])
 
439
        this_tree.add('file', 'file-id')
 
440
        this_tree.commit('rev1', rev_id='rev1')
 
441
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
442
        self.build_tree_contents([('this/file', '1\n2a\n')])
 
443
        this_tree.commit('rev2', rev_id='rev2a')
 
444
        self.build_tree_contents([('other/file', '2b\n1\n')])
 
445
        other_tree.commit('rev2', rev_id='rev2b')
 
446
        this_tree.lock_write()
 
447
        self.addCleanup(this_tree.unlock)
 
448
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
449
            this_tree, 'rev2b', other_branch=other_tree.branch)
 
450
        merger.merge_type = _mod_merge.Merge3Merger
 
451
        tree_merger = merger.make_merger()
 
452
        tt = tree_merger.do_merge()
 
453
        tree_file = this_tree.get_file('file-id')
 
454
        try:
 
455
            self.assertEqual('2b\n1\n2a\n', tree_file.read())
 
456
        finally:
 
457
            tree_file.close()
 
458
 
 
459
    def test_merge_add_into_deleted_root(self):
 
460
        # Yes, people actually do this.  And report bugs if it breaks.
 
461
        source = self.make_branch_and_tree('source', format='rich-root-pack')
 
462
        self.build_tree(['source/foo/'])
 
463
        source.add('foo', 'foo-id')
 
464
        source.commit('Add foo')
 
465
        target = source.bzrdir.sprout('target').open_workingtree()
 
466
        subtree = target.extract('foo-id')
 
467
        subtree.commit('Delete root')
 
468
        self.build_tree(['source/bar'])
 
469
        source.add('bar', 'bar-id')
 
470
        source.commit('Add bar')
 
471
        subtree.merge_from_branch(source.branch)
 
472
 
 
473
    def test_merge_joined_branch(self):
 
474
        source = self.make_branch_and_tree('source', format='rich-root-pack')
 
475
        self.build_tree(['source/foo'])
 
476
        source.add('foo')
 
477
        source.commit('Add foo')
 
478
        target = self.make_branch_and_tree('target', format='rich-root-pack')
 
479
        self.build_tree(['target/bla'])
 
480
        target.add('bla')
 
481
        target.commit('Add bla')
 
482
        nested = source.bzrdir.sprout('target/subtree').open_workingtree()
 
483
        target.subsume(nested)
 
484
        target.commit('Join nested')
 
485
        self.build_tree(['source/bar'])
 
486
        source.add('bar')
 
487
        source.commit('Add bar')
 
488
        target.merge_from_branch(source.branch)
 
489
        target.commit('Merge source')
 
490
 
 
491
 
 
492
class TestPlanMerge(TestCaseWithMemoryTransport):
 
493
 
 
494
    def setUp(self):
 
495
        TestCaseWithMemoryTransport.setUp(self)
 
496
        mapper = versionedfile.PrefixMapper()
 
497
        factory = knit.make_file_factory(True, mapper)
 
498
        self.vf = factory(self.get_transport())
 
499
        self.plan_merge_vf = versionedfile._PlanMergeVersionedFile('root')
 
500
        self.plan_merge_vf.fallback_versionedfiles.append(self.vf)
 
501
 
 
502
    def add_version(self, key, parents, text):
 
503
        self.vf.add_lines(key, parents, [c+'\n' for c in text])
 
504
 
 
505
    def add_rev(self, prefix, revision_id, parents, text):
 
506
        self.add_version((prefix, revision_id), [(prefix, p) for p in parents],
 
507
                         text)
 
508
 
 
509
    def add_uncommitted_version(self, key, parents, text):
 
510
        self.plan_merge_vf.add_lines(key, parents,
 
511
                                     [c+'\n' for c in text])
 
512
 
 
513
    def setup_plan_merge(self):
 
514
        self.add_rev('root', 'A', [], 'abc')
 
515
        self.add_rev('root', 'B', ['A'], 'acehg')
 
516
        self.add_rev('root', 'C', ['A'], 'fabg')
 
517
        return _PlanMerge('B', 'C', self.plan_merge_vf, ('root',))
 
518
 
 
519
    def setup_plan_merge_uncommitted(self):
 
520
        self.add_version(('root', 'A'), [], 'abc')
 
521
        self.add_uncommitted_version(('root', 'B:'), [('root', 'A')], 'acehg')
 
522
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
 
523
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
 
524
 
 
525
    def test_base_from_plan(self):
 
526
        self.setup_plan_merge()
 
527
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
528
        pwm = versionedfile.PlanWeaveMerge(plan)
 
529
        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
 
530
 
 
531
    def test_unique_lines(self):
 
532
        plan = self.setup_plan_merge()
 
533
        self.assertEqual(plan._unique_lines(
 
534
            plan._get_matching_blocks('B', 'C')),
 
535
            ([1, 2, 3], [0, 2]))
 
536
 
 
537
    def test_plan_merge(self):
 
538
        self.setup_plan_merge()
 
539
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
540
        self.assertEqual([
 
541
                          ('new-b', 'f\n'),
 
542
                          ('unchanged', 'a\n'),
 
543
                          ('killed-a', 'b\n'),
 
544
                          ('killed-b', 'c\n'),
 
545
                          ('new-a', 'e\n'),
 
546
                          ('new-a', 'h\n'),
 
547
                          ('new-a', 'g\n'),
 
548
                          ('new-b', 'g\n')],
 
549
                         list(plan))
 
550
 
 
551
    def test_plan_merge_cherrypick(self):
 
552
        self.add_rev('root', 'A', [], 'abc')
 
553
        self.add_rev('root', 'B', ['A'], 'abcde')
 
554
        self.add_rev('root', 'C', ['A'], 'abcefg')
 
555
        self.add_rev('root', 'D', ['A', 'B', 'C'], 'abcdegh')
 
556
        my_plan = _PlanMerge('B', 'D', self.plan_merge_vf, ('root',))
 
557
        # We shortcut when one text supersedes the other in the per-file graph.
 
558
        # We don't actually need to compare the texts at this point.
 
559
        self.assertEqual([
 
560
                          ('new-b', 'a\n'),
 
561
                          ('new-b', 'b\n'),
 
562
                          ('new-b', 'c\n'),
 
563
                          ('new-b', 'd\n'),
 
564
                          ('new-b', 'e\n'),
 
565
                          ('new-b', 'g\n'),
 
566
                          ('new-b', 'h\n')],
 
567
                          list(my_plan.plan_merge()))
 
568
 
 
569
    def test_plan_merge_no_common_ancestor(self):
 
570
        self.add_rev('root', 'A', [], 'abc')
 
571
        self.add_rev('root', 'B', [], 'xyz')
 
572
        my_plan = _PlanMerge('A', 'B', self.plan_merge_vf, ('root',))
 
573
        self.assertEqual([
 
574
                          ('new-a', 'a\n'),
 
575
                          ('new-a', 'b\n'),
 
576
                          ('new-a', 'c\n'),
 
577
                          ('new-b', 'x\n'),
 
578
                          ('new-b', 'y\n'),
 
579
                          ('new-b', 'z\n')],
 
580
                          list(my_plan.plan_merge()))
 
581
 
 
582
    def test_plan_merge_tail_ancestors(self):
 
583
        # The graph looks like this:
 
584
        #       A       # Common to all ancestors
 
585
        #      / \
 
586
        #     B   C     # Ancestors of E, only common to one side
 
587
        #     |\ /|
 
588
        #     D E F     # D, F are unique to G, H respectively
 
589
        #     |/ \|     # E is the LCA for G & H, and the unique LCA for
 
590
        #     G   H     # I, J
 
591
        #     |\ /|
 
592
        #     | X |
 
593
        #     |/ \|
 
594
        #     I   J     # criss-cross merge of G, H
 
595
        #
 
596
        # In this situation, a simple pruning of ancestors of E will leave D &
 
597
        # F "dangling", which looks like they introduce lines different from
 
598
        # the ones in E, but in actuality C&B introduced the lines, and they
 
599
        # are already present in E
 
600
 
 
601
        # Introduce the base text
 
602
        self.add_rev('root', 'A', [], 'abc')
 
603
        # Introduces a new line B
 
604
        self.add_rev('root', 'B', ['A'], 'aBbc')
 
605
        # Introduces a new line C
 
606
        self.add_rev('root', 'C', ['A'], 'abCc')
 
607
        # Introduce new line D
 
608
        self.add_rev('root', 'D', ['B'], 'DaBbc')
 
609
        # Merges B and C by just incorporating both
 
610
        self.add_rev('root', 'E', ['B', 'C'], 'aBbCc')
 
611
        # Introduce new line F
 
612
        self.add_rev('root', 'F', ['C'], 'abCcF')
 
613
        # Merge D & E by just combining the texts
 
614
        self.add_rev('root', 'G', ['D', 'E'], 'DaBbCc')
 
615
        # Merge F & E by just combining the texts
 
616
        self.add_rev('root', 'H', ['F', 'E'], 'aBbCcF')
 
617
        # Merge G & H by just combining texts
 
618
        self.add_rev('root', 'I', ['G', 'H'], 'DaBbCcF')
 
619
        # Merge G & H but supersede an old line in B
 
620
        self.add_rev('root', 'J', ['H', 'G'], 'DaJbCcF')
 
621
        plan = self.plan_merge_vf.plan_merge('I', 'J')
 
622
        self.assertEqual([
 
623
                          ('unchanged', 'D\n'),
 
624
                          ('unchanged', 'a\n'),
 
625
                          ('killed-b', 'B\n'),
 
626
                          ('new-b', 'J\n'),
 
627
                          ('unchanged', 'b\n'),
 
628
                          ('unchanged', 'C\n'),
 
629
                          ('unchanged', 'c\n'),
 
630
                          ('unchanged', 'F\n')],
 
631
                         list(plan))
 
632
 
 
633
    def test_plan_merge_tail_triple_ancestors(self):
 
634
        # The graph looks like this:
 
635
        #       A       # Common to all ancestors
 
636
        #      / \
 
637
        #     B   C     # Ancestors of E, only common to one side
 
638
        #     |\ /|
 
639
        #     D E F     # D, F are unique to G, H respectively
 
640
        #     |/|\|     # E is the LCA for G & H, and the unique LCA for
 
641
        #     G Q H     # I, J
 
642
        #     |\ /|     # Q is just an extra node which is merged into both
 
643
        #     | X |     # I and J
 
644
        #     |/ \|
 
645
        #     I   J     # criss-cross merge of G, H
 
646
        #
 
647
        # This is the same as the test_plan_merge_tail_ancestors, except we add
 
648
        # a third LCA that doesn't add new lines, but will trigger our more
 
649
        # involved ancestry logic
 
650
 
 
651
        self.add_rev('root', 'A', [], 'abc')
 
652
        self.add_rev('root', 'B', ['A'], 'aBbc')
 
653
        self.add_rev('root', 'C', ['A'], 'abCc')
 
654
        self.add_rev('root', 'D', ['B'], 'DaBbc')
 
655
        self.add_rev('root', 'E', ['B', 'C'], 'aBbCc')
 
656
        self.add_rev('root', 'F', ['C'], 'abCcF')
 
657
        self.add_rev('root', 'G', ['D', 'E'], 'DaBbCc')
 
658
        self.add_rev('root', 'H', ['F', 'E'], 'aBbCcF')
 
659
        self.add_rev('root', 'Q', ['E'], 'aBbCc')
 
660
        self.add_rev('root', 'I', ['G', 'Q', 'H'], 'DaBbCcF')
 
661
        # Merge G & H but supersede an old line in B
 
662
        self.add_rev('root', 'J', ['H', 'Q', 'G'], 'DaJbCcF')
 
663
        plan = self.plan_merge_vf.plan_merge('I', 'J')
 
664
        self.assertEqual([
 
665
                          ('unchanged', 'D\n'),
 
666
                          ('unchanged', 'a\n'),
 
667
                          ('killed-b', 'B\n'),
 
668
                          ('new-b', 'J\n'),
 
669
                          ('unchanged', 'b\n'),
 
670
                          ('unchanged', 'C\n'),
 
671
                          ('unchanged', 'c\n'),
 
672
                          ('unchanged', 'F\n')],
 
673
                         list(plan))
 
674
 
 
675
    def test_plan_merge_2_tail_triple_ancestors(self):
 
676
        # The graph looks like this:
 
677
        #     A   B     # 2 tails going back to NULL
 
678
        #     |\ /|
 
679
        #     D E F     # D, is unique to G, F to H
 
680
        #     |/|\|     # E is the LCA for G & H, and the unique LCA for
 
681
        #     G Q H     # I, J
 
682
        #     |\ /|     # Q is just an extra node which is merged into both
 
683
        #     | X |     # I and J
 
684
        #     |/ \|
 
685
        #     I   J     # criss-cross merge of G, H (and Q)
 
686
        #
 
687
 
 
688
        # This is meant to test after hitting a 3-way LCA, and multiple tail
 
689
        # ancestors (only have NULL_REVISION in common)
 
690
 
 
691
        self.add_rev('root', 'A', [], 'abc')
 
692
        self.add_rev('root', 'B', [], 'def')
 
693
        self.add_rev('root', 'D', ['A'], 'Dabc')
 
694
        self.add_rev('root', 'E', ['A', 'B'], 'abcdef')
 
695
        self.add_rev('root', 'F', ['B'], 'defF')
 
696
        self.add_rev('root', 'G', ['D', 'E'], 'Dabcdef')
 
697
        self.add_rev('root', 'H', ['F', 'E'], 'abcdefF')
 
698
        self.add_rev('root', 'Q', ['E'], 'abcdef')
 
699
        self.add_rev('root', 'I', ['G', 'Q', 'H'], 'DabcdefF')
 
700
        # Merge G & H but supersede an old line in B
 
701
        self.add_rev('root', 'J', ['H', 'Q', 'G'], 'DabcdJfF')
 
702
        plan = self.plan_merge_vf.plan_merge('I', 'J')
 
703
        self.assertEqual([
 
704
                          ('unchanged', 'D\n'),
 
705
                          ('unchanged', 'a\n'),
 
706
                          ('unchanged', 'b\n'),
 
707
                          ('unchanged', 'c\n'),
 
708
                          ('unchanged', 'd\n'),
 
709
                          ('killed-b', 'e\n'),
 
710
                          ('new-b', 'J\n'),
 
711
                          ('unchanged', 'f\n'),
 
712
                          ('unchanged', 'F\n')],
 
713
                         list(plan))
 
714
 
 
715
    def test_plan_merge_uncommitted_files(self):
 
716
        self.setup_plan_merge_uncommitted()
 
717
        plan = self.plan_merge_vf.plan_merge('B:', 'C:')
 
718
        self.assertEqual([
 
719
                          ('new-b', 'f\n'),
 
720
                          ('unchanged', 'a\n'),
 
721
                          ('killed-a', 'b\n'),
 
722
                          ('killed-b', 'c\n'),
 
723
                          ('new-a', 'e\n'),
 
724
                          ('new-a', 'h\n'),
 
725
                          ('new-a', 'g\n'),
 
726
                          ('new-b', 'g\n')],
 
727
                         list(plan))
 
728
 
 
729
    def test_plan_merge_insert_order(self):
 
730
        """Weave merges are sensitive to the order of insertion.
 
731
 
 
732
        Specifically for overlapping regions, it effects which region gets put
 
733
        'first'. And when a user resolves an overlapping merge, if they use the
 
734
        same ordering, then the lines match the parents, if they don't only
 
735
        *some* of the lines match.
 
736
        """
 
737
        self.add_rev('root', 'A', [], 'abcdef')
 
738
        self.add_rev('root', 'B', ['A'], 'abwxcdef')
 
739
        self.add_rev('root', 'C', ['A'], 'abyzcdef')
 
740
        # Merge, and resolve the conflict by adding *both* sets of lines
 
741
        # If we get the ordering wrong, these will look like new lines in D,
 
742
        # rather than carried over from B, C
 
743
        self.add_rev('root', 'D', ['B', 'C'],
 
744
                         'abwxyzcdef')
 
745
        # Supersede the lines in B and delete the lines in C, which will
 
746
        # conflict if they are treated as being in D
 
747
        self.add_rev('root', 'E', ['C', 'B'],
 
748
                         'abnocdef')
 
749
        # Same thing for the lines in C
 
750
        self.add_rev('root', 'F', ['C'], 'abpqcdef')
 
751
        plan = self.plan_merge_vf.plan_merge('D', 'E')
 
752
        self.assertEqual([
 
753
                          ('unchanged', 'a\n'),
 
754
                          ('unchanged', 'b\n'),
 
755
                          ('killed-b', 'w\n'),
 
756
                          ('killed-b', 'x\n'),
 
757
                          ('killed-b', 'y\n'),
 
758
                          ('killed-b', 'z\n'),
 
759
                          ('new-b', 'n\n'),
 
760
                          ('new-b', 'o\n'),
 
761
                          ('unchanged', 'c\n'),
 
762
                          ('unchanged', 'd\n'),
 
763
                          ('unchanged', 'e\n'),
 
764
                          ('unchanged', 'f\n')],
 
765
                         list(plan))
 
766
        plan = self.plan_merge_vf.plan_merge('E', 'D')
 
767
        # Going in the opposite direction shows the effect of the opposite plan
 
768
        self.assertEqual([
 
769
                          ('unchanged', 'a\n'),
 
770
                          ('unchanged', 'b\n'),
 
771
                          ('new-b', 'w\n'),
 
772
                          ('new-b', 'x\n'),
 
773
                          ('killed-a', 'y\n'),
 
774
                          ('killed-a', 'z\n'),
 
775
                          ('killed-both', 'w\n'),
 
776
                          ('killed-both', 'x\n'),
 
777
                          ('new-a', 'n\n'),
 
778
                          ('new-a', 'o\n'),
 
779
                          ('unchanged', 'c\n'),
 
780
                          ('unchanged', 'd\n'),
 
781
                          ('unchanged', 'e\n'),
 
782
                          ('unchanged', 'f\n')],
 
783
                         list(plan))
 
784
 
 
785
    def test_plan_merge_criss_cross(self):
 
786
        # This is specificly trying to trigger problems when using limited
 
787
        # ancestry and weaves. The ancestry graph looks like:
 
788
        #       XX      unused ancestor, should not show up in the weave
 
789
        #       |
 
790
        #       A       Unique LCA
 
791
        #       |\
 
792
        #       B \     Introduces a line 'foo'
 
793
        #      / \ \
 
794
        #     C   D E   C & D both have 'foo', E has different changes
 
795
        #     |\ /| |
 
796
        #     | X | |
 
797
        #     |/ \|/
 
798
        #     F   G      All of C, D, E are merged into F and G, so they are
 
799
        #                all common ancestors.
 
800
        #
 
801
        # The specific issue with weaves:
 
802
        #   B introduced a text ('foo') that is present in both C and D.
 
803
        #   If we do not include B (because it isn't an ancestor of E), then
 
804
        #   the A=>C and A=>D look like both sides independently introduce the
 
805
        #   text ('foo'). If F does not modify the text, it would still appear
 
806
        #   to have deleted on of the versions from C or D. If G then modifies
 
807
        #   'foo', it should appear as superseding the value in F (since it
 
808
        #   came from B), rather than conflict because of the resolution during
 
809
        #   C & D.
 
810
        self.add_rev('root', 'XX', [], 'qrs')
 
811
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
812
        self.add_rev('root', 'B', ['A'], 'axcdef')
 
813
        self.add_rev('root', 'C', ['B'], 'axcdefg')
 
814
        self.add_rev('root', 'D', ['B'], 'haxcdef')
 
815
        self.add_rev('root', 'E', ['A'], 'abcdyf')
 
816
        # Simple combining of all texts
 
817
        self.add_rev('root', 'F', ['C', 'D', 'E'], 'haxcdyfg')
 
818
        # combine and supersede 'x'
 
819
        self.add_rev('root', 'G', ['C', 'D', 'E'], 'hazcdyfg')
 
820
        plan = self.plan_merge_vf.plan_merge('F', 'G')
 
821
        self.assertEqual([
 
822
                          ('unchanged', 'h\n'),
 
823
                          ('unchanged', 'a\n'),
 
824
                          ('killed-base', 'b\n'),
 
825
                          ('killed-b', 'x\n'),
 
826
                          ('new-b', 'z\n'),
 
827
                          ('unchanged', 'c\n'),
 
828
                          ('unchanged', 'd\n'),
 
829
                          ('killed-base', 'e\n'),
 
830
                          ('unchanged', 'y\n'),
 
831
                          ('unchanged', 'f\n'),
 
832
                          ('unchanged', 'g\n')],
 
833
                         list(plan))
 
834
        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
 
835
        # This is one of the main differences between plan_merge and
 
836
        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
 
837
        # because 'x' was not present in one of the bases. However, in this
 
838
        # case it is spurious because 'x' does not exist in the global base A.
 
839
        self.assertEqual([
 
840
                          ('unchanged', 'h\n'),
 
841
                          ('unchanged', 'a\n'),
 
842
                          ('conflicted-a', 'x\n'),
 
843
                          ('new-b', 'z\n'),
 
844
                          ('unchanged', 'c\n'),
 
845
                          ('unchanged', 'd\n'),
 
846
                          ('unchanged', 'y\n'),
 
847
                          ('unchanged', 'f\n'),
 
848
                          ('unchanged', 'g\n')],
 
849
                         list(plan))
 
850
 
 
851
    def test_criss_cross_flip_flop(self):
 
852
        # This is specificly trying to trigger problems when using limited
 
853
        # ancestry and weaves. The ancestry graph looks like:
 
854
        #       XX      unused ancestor, should not show up in the weave
 
855
        #       |
 
856
        #       A       Unique LCA
 
857
        #      / \  
 
858
        #     B   C     B & C both introduce a new line
 
859
        #     |\ /|  
 
860
        #     | X |  
 
861
        #     |/ \| 
 
862
        #     D   E     B & C are both merged, so both are common ancestors
 
863
        #               In the process of merging, both sides order the new
 
864
        #               lines differently
 
865
        #
 
866
        self.add_rev('root', 'XX', [], 'qrs')
 
867
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
868
        self.add_rev('root', 'B', ['A'], 'abcdgef')
 
869
        self.add_rev('root', 'C', ['A'], 'abcdhef')
 
870
        self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
 
871
        self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
 
872
        plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
 
873
        self.assertEqual([
 
874
                          ('unchanged', 'a\n'),
 
875
                          ('unchanged', 'b\n'),
 
876
                          ('unchanged', 'c\n'),
 
877
                          ('unchanged', 'd\n'),
 
878
                          ('new-b', 'h\n'),
 
879
                          ('unchanged', 'g\n'),
 
880
                          ('killed-b', 'h\n'),
 
881
                          ('unchanged', 'e\n'),
 
882
                          ('unchanged', 'f\n'),
 
883
                         ], plan)
 
884
        pwm = versionedfile.PlanWeaveMerge(plan)
 
885
        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
 
886
                             ''.join(pwm.base_from_plan()))
 
887
        # Reversing the order reverses the merge plan, and final order of 'hg'
 
888
        # => 'gh'
 
889
        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
 
890
        self.assertEqual([
 
891
                          ('unchanged', 'a\n'),
 
892
                          ('unchanged', 'b\n'),
 
893
                          ('unchanged', 'c\n'),
 
894
                          ('unchanged', 'd\n'),
 
895
                          ('new-b', 'g\n'),
 
896
                          ('unchanged', 'h\n'),
 
897
                          ('killed-b', 'g\n'),
 
898
                          ('unchanged', 'e\n'),
 
899
                          ('unchanged', 'f\n'),
 
900
                         ], plan)
 
901
        pwm = versionedfile.PlanWeaveMerge(plan)
 
902
        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
 
903
                             ''.join(pwm.base_from_plan()))
 
904
        # This is where lca differs, in that it (fairly correctly) determines
 
905
        # that there is a conflict because both sides resolved the merge
 
906
        # differently
 
907
        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
 
908
        self.assertEqual([
 
909
                          ('unchanged', 'a\n'),
 
910
                          ('unchanged', 'b\n'),
 
911
                          ('unchanged', 'c\n'),
 
912
                          ('unchanged', 'd\n'),
 
913
                          ('conflicted-b', 'h\n'),
 
914
                          ('unchanged', 'g\n'),
 
915
                          ('conflicted-a', 'h\n'),
 
916
                          ('unchanged', 'e\n'),
 
917
                          ('unchanged', 'f\n'),
 
918
                         ], plan)
 
919
        pwm = versionedfile.PlanWeaveMerge(plan)
 
920
        self.assertEqualDiff('\n'.join('abcdgef') + '\n',
 
921
                             ''.join(pwm.base_from_plan()))
 
922
        # Reversing it changes what line is doubled, but still gives a
 
923
        # double-conflict
 
924
        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
 
925
        self.assertEqual([
 
926
                          ('unchanged', 'a\n'),
 
927
                          ('unchanged', 'b\n'),
 
928
                          ('unchanged', 'c\n'),
 
929
                          ('unchanged', 'd\n'),
 
930
                          ('conflicted-b', 'g\n'),
 
931
                          ('unchanged', 'h\n'),
 
932
                          ('conflicted-a', 'g\n'),
 
933
                          ('unchanged', 'e\n'),
 
934
                          ('unchanged', 'f\n'),
 
935
                         ], plan)
 
936
        pwm = versionedfile.PlanWeaveMerge(plan)
 
937
        self.assertEqualDiff('\n'.join('abcdhef') + '\n',
 
938
                             ''.join(pwm.base_from_plan()))
 
939
 
 
940
    def assertRemoveExternalReferences(self, filtered_parent_map,
 
941
                                       child_map, tails, parent_map):
 
942
        """Assert results for _PlanMerge._remove_external_references."""
 
943
        (act_filtered_parent_map, act_child_map,
 
944
         act_tails) = _PlanMerge._remove_external_references(parent_map)
 
945
 
 
946
        # The parent map *should* preserve ordering, but the ordering of
 
947
        # children is not strictly defined
 
948
        # child_map = dict((k, sorted(children))
 
949
        #                  for k, children in child_map.iteritems())
 
950
        # act_child_map = dict(k, sorted(children)
 
951
        #                      for k, children in act_child_map.iteritems())
 
952
        self.assertEqual(filtered_parent_map, act_filtered_parent_map)
 
953
        self.assertEqual(child_map, act_child_map)
 
954
        self.assertEqual(sorted(tails), sorted(act_tails))
 
955
 
 
956
    def test__remove_external_references(self):
 
957
        # First, nothing to remove
 
958
        self.assertRemoveExternalReferences({3: [2], 2: [1], 1: []},
 
959
            {1: [2], 2: [3], 3: []}, [1], {3: [2], 2: [1], 1: []})
 
960
        # The reverse direction
 
961
        self.assertRemoveExternalReferences({1: [2], 2: [3], 3: []},
 
962
            {3: [2], 2: [1], 1: []}, [3], {1: [2], 2: [3], 3: []})
 
963
        # Extra references
 
964
        self.assertRemoveExternalReferences({3: [2], 2: [1], 1: []},
 
965
            {1: [2], 2: [3], 3: []}, [1], {3: [2, 4], 2: [1, 5], 1: [6]})
 
966
        # Multiple tails
 
967
        self.assertRemoveExternalReferences(
 
968
            {4: [2, 3], 3: [], 2: [1], 1: []},
 
969
            {1: [2], 2: [4], 3: [4], 4: []},
 
970
            [1, 3],
 
971
            {4: [2, 3], 3: [5], 2: [1], 1: [6]})
 
972
        # Multiple children
 
973
        self.assertRemoveExternalReferences(
 
974
            {1: [3], 2: [3, 4], 3: [], 4: []},
 
975
            {1: [], 2: [], 3: [1, 2], 4: [2]},
 
976
            [3, 4],
 
977
            {1: [3], 2: [3, 4], 3: [5], 4: []})
 
978
 
 
979
    def assertPruneTails(self, pruned_map, tails, parent_map):
 
980
        child_map = {}
 
981
        for key, parent_keys in parent_map.iteritems():
 
982
            child_map.setdefault(key, [])
 
983
            for pkey in parent_keys:
 
984
                child_map.setdefault(pkey, []).append(key)
 
985
        _PlanMerge._prune_tails(parent_map, child_map, tails)
 
986
        self.assertEqual(pruned_map, parent_map)
 
987
 
 
988
    def test__prune_tails(self):
 
989
        # Nothing requested to prune
 
990
        self.assertPruneTails({1: [], 2: [], 3: []}, [],
 
991
                              {1: [], 2: [], 3: []})
 
992
        # Prune a single entry
 
993
        self.assertPruneTails({1: [], 3: []}, [2],
 
994
                              {1: [], 2: [], 3: []})
 
995
        # Prune a chain
 
996
        self.assertPruneTails({1: []}, [3],
 
997
                              {1: [], 2: [3], 3: []})
 
998
        # Prune a chain with a diamond
 
999
        self.assertPruneTails({1: []}, [5],
 
1000
                              {1: [], 2: [3, 4], 3: [5], 4: [5], 5: []})
 
1001
        # Prune a partial chain
 
1002
        self.assertPruneTails({1: [6], 6:[]}, [5],
 
1003
                              {1: [2, 6], 2: [3, 4], 3: [5], 4: [5], 5: [],
 
1004
                               6: []})
 
1005
        # Prune a chain with multiple tips, that pulls out intermediates
 
1006
        self.assertPruneTails({1:[3], 3:[]}, [4, 5],
 
1007
                              {1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
 
1008
        self.assertPruneTails({1:[3], 3:[]}, [5, 4],
 
1009
                              {1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
 
1010
 
 
1011
    def test_subtract_plans(self):
 
1012
        old_plan = [
 
1013
        ('unchanged', 'a\n'),
 
1014
        ('new-a', 'b\n'),
 
1015
        ('killed-a', 'c\n'),
 
1016
        ('new-b', 'd\n'),
 
1017
        ('new-b', 'e\n'),
 
1018
        ('killed-b', 'f\n'),
 
1019
        ('killed-b', 'g\n'),
 
1020
        ]
 
1021
        new_plan = [
 
1022
        ('unchanged', 'a\n'),
 
1023
        ('new-a', 'b\n'),
 
1024
        ('killed-a', 'c\n'),
 
1025
        ('new-b', 'd\n'),
 
1026
        ('new-b', 'h\n'),
 
1027
        ('killed-b', 'f\n'),
 
1028
        ('killed-b', 'i\n'),
 
1029
        ]
 
1030
        subtracted_plan = [
 
1031
        ('unchanged', 'a\n'),
 
1032
        ('new-a', 'b\n'),
 
1033
        ('killed-a', 'c\n'),
 
1034
        ('new-b', 'h\n'),
 
1035
        ('unchanged', 'f\n'),
 
1036
        ('killed-b', 'i\n'),
 
1037
        ]
 
1038
        self.assertEqual(subtracted_plan,
 
1039
            list(_PlanMerge._subtract_plans(old_plan, new_plan)))
 
1040
 
 
1041
    def setup_merge_with_base(self):
 
1042
        self.add_rev('root', 'COMMON', [], 'abc')
 
1043
        self.add_rev('root', 'THIS', ['COMMON'], 'abcd')
 
1044
        self.add_rev('root', 'BASE', ['COMMON'], 'eabc')
 
1045
        self.add_rev('root', 'OTHER', ['BASE'], 'eafb')
 
1046
 
 
1047
    def test_plan_merge_with_base(self):
 
1048
        self.setup_merge_with_base()
 
1049
        plan = self.plan_merge_vf.plan_merge('THIS', 'OTHER', 'BASE')
 
1050
        self.assertEqual([('unchanged', 'a\n'),
 
1051
                          ('new-b', 'f\n'),
 
1052
                          ('unchanged', 'b\n'),
 
1053
                          ('killed-b', 'c\n'),
 
1054
                          ('new-a', 'd\n')
 
1055
                         ], list(plan))
 
1056
 
 
1057
    def test_plan_lca_merge(self):
 
1058
        self.setup_plan_merge()
 
1059
        plan = self.plan_merge_vf.plan_lca_merge('B', 'C')
 
1060
        self.assertEqual([
 
1061
                          ('new-b', 'f\n'),
 
1062
                          ('unchanged', 'a\n'),
 
1063
                          ('killed-b', 'c\n'),
 
1064
                          ('new-a', 'e\n'),
 
1065
                          ('new-a', 'h\n'),
 
1066
                          ('killed-a', 'b\n'),
 
1067
                          ('unchanged', 'g\n')],
 
1068
                         list(plan))
 
1069
 
 
1070
    def test_plan_lca_merge_uncommitted_files(self):
 
1071
        self.setup_plan_merge_uncommitted()
 
1072
        plan = self.plan_merge_vf.plan_lca_merge('B:', 'C:')
 
1073
        self.assertEqual([
 
1074
                          ('new-b', 'f\n'),
 
1075
                          ('unchanged', 'a\n'),
 
1076
                          ('killed-b', 'c\n'),
 
1077
                          ('new-a', 'e\n'),
 
1078
                          ('new-a', 'h\n'),
 
1079
                          ('killed-a', 'b\n'),
 
1080
                          ('unchanged', 'g\n')],
 
1081
                         list(plan))
 
1082
 
 
1083
    def test_plan_lca_merge_with_base(self):
 
1084
        self.setup_merge_with_base()
 
1085
        plan = self.plan_merge_vf.plan_lca_merge('THIS', 'OTHER', 'BASE')
 
1086
        self.assertEqual([('unchanged', 'a\n'),
 
1087
                          ('new-b', 'f\n'),
 
1088
                          ('unchanged', 'b\n'),
 
1089
                          ('killed-b', 'c\n'),
 
1090
                          ('new-a', 'd\n')
 
1091
                         ], list(plan))
 
1092
 
 
1093
    def test_plan_lca_merge_with_criss_cross(self):
 
1094
        self.add_version(('root', 'ROOT'), [], 'abc')
 
1095
        # each side makes a change
 
1096
        self.add_version(('root', 'REV1'), [('root', 'ROOT')], 'abcd')
 
1097
        self.add_version(('root', 'REV2'), [('root', 'ROOT')], 'abce')
 
1098
        # both sides merge, discarding others' changes
 
1099
        self.add_version(('root', 'LCA1'),
 
1100
            [('root', 'REV1'), ('root', 'REV2')], 'abcd')
 
1101
        self.add_version(('root', 'LCA2'),
 
1102
            [('root', 'REV1'), ('root', 'REV2')], 'fabce')
 
1103
        plan = self.plan_merge_vf.plan_lca_merge('LCA1', 'LCA2')
 
1104
        self.assertEqual([('new-b', 'f\n'),
 
1105
                          ('unchanged', 'a\n'),
 
1106
                          ('unchanged', 'b\n'),
 
1107
                          ('unchanged', 'c\n'),
 
1108
                          ('conflicted-a', 'd\n'),
 
1109
                          ('conflicted-b', 'e\n'),
 
1110
                         ], list(plan))
 
1111
 
 
1112
    def test_plan_lca_merge_with_null(self):
 
1113
        self.add_version(('root', 'A'), [], 'ab')
 
1114
        self.add_version(('root', 'B'), [], 'bc')
 
1115
        plan = self.plan_merge_vf.plan_lca_merge('A', 'B')
 
1116
        self.assertEqual([('new-a', 'a\n'),
 
1117
                          ('unchanged', 'b\n'),
 
1118
                          ('new-b', 'c\n'),
 
1119
                         ], list(plan))
 
1120
 
 
1121
    def test_plan_merge_with_delete_and_change(self):
 
1122
        self.add_rev('root', 'C', [], 'a')
 
1123
        self.add_rev('root', 'A', ['C'], 'b')
 
1124
        self.add_rev('root', 'B', ['C'], '')
 
1125
        plan = self.plan_merge_vf.plan_merge('A', 'B')
 
1126
        self.assertEqual([('killed-both', 'a\n'),
 
1127
                          ('new-a', 'b\n'),
 
1128
                         ], list(plan))
 
1129
 
 
1130
    def test_plan_merge_with_move_and_change(self):
 
1131
        self.add_rev('root', 'C', [], 'abcd')
 
1132
        self.add_rev('root', 'A', ['C'], 'acbd')
 
1133
        self.add_rev('root', 'B', ['C'], 'aBcd')
 
1134
        plan = self.plan_merge_vf.plan_merge('A', 'B')
 
1135
        self.assertEqual([('unchanged', 'a\n'),
 
1136
                          ('new-a', 'c\n'),
 
1137
                          ('killed-b', 'b\n'),
 
1138
                          ('new-b', 'B\n'),
 
1139
                          ('killed-a', 'c\n'),
 
1140
                          ('unchanged', 'd\n'),
 
1141
                         ], list(plan))
 
1142
 
 
1143
 
 
1144
class LoggingMerger(object):
 
1145
    # These seem to be the required attributes
 
1146
    requires_base = False
 
1147
    supports_reprocess = False
 
1148
    supports_show_base = False
 
1149
    supports_cherrypick = False
 
1150
    # We intentionally do not define supports_lca_trees
 
1151
 
 
1152
    def __init__(self, *args, **kwargs):
 
1153
        self.args = args
 
1154
        self.kwargs = kwargs
 
1155
 
 
1156
 
 
1157
class TestMergerBase(TestCaseWithMemoryTransport):
 
1158
    """Common functionality for Merger tests that don't write to disk."""
 
1159
 
 
1160
    def get_builder(self):
 
1161
        builder = self.make_branch_builder('path')
 
1162
        builder.start_series()
 
1163
        self.addCleanup(builder.finish_series)
 
1164
        return builder
 
1165
 
 
1166
    def setup_simple_graph(self):
 
1167
        """Create a simple 3-node graph.
 
1168
 
 
1169
        :return: A BranchBuilder
 
1170
        """
 
1171
        #
 
1172
        #  A
 
1173
        #  |\
 
1174
        #  B C
 
1175
        #
 
1176
        builder = self.get_builder()
 
1177
        builder.build_snapshot('A-id', None,
 
1178
            [('add', ('', None, 'directory', None))])
 
1179
        builder.build_snapshot('C-id', ['A-id'], [])
 
1180
        builder.build_snapshot('B-id', ['A-id'], [])
 
1181
        return builder
 
1182
 
 
1183
    def setup_criss_cross_graph(self):
 
1184
        """Create a 5-node graph with a criss-cross.
 
1185
 
 
1186
        :return: A BranchBuilder
 
1187
        """
 
1188
        # A
 
1189
        # |\
 
1190
        # B C
 
1191
        # |X|
 
1192
        # D E
 
1193
        builder = self.setup_simple_graph()
 
1194
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1195
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1196
        return builder
 
1197
 
 
1198
    def make_Merger(self, builder, other_revision_id,
 
1199
                    interesting_files=None, interesting_ids=None):
 
1200
        """Make a Merger object from a branch builder"""
 
1201
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
 
1202
        mem_tree.lock_write()
 
1203
        self.addCleanup(mem_tree.unlock)
 
1204
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1205
            mem_tree, other_revision_id)
 
1206
        merger.set_interesting_files(interesting_files)
 
1207
        # It seems there is no matching function for set_interesting_ids
 
1208
        merger.interesting_ids = interesting_ids
 
1209
        merger.merge_type = _mod_merge.Merge3Merger
 
1210
        return merger
 
1211
 
 
1212
 
 
1213
class TestMergerInMemory(TestMergerBase):
 
1214
 
 
1215
    def test_cache_trees_with_revision_ids_None(self):
 
1216
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1217
        original_cache = dict(merger._cached_trees)
 
1218
        merger.cache_trees_with_revision_ids([None])
 
1219
        self.assertEqual(original_cache, merger._cached_trees)
 
1220
 
 
1221
    def test_cache_trees_with_revision_ids_no_revision_id(self):
 
1222
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1223
        original_cache = dict(merger._cached_trees)
 
1224
        tree = self.make_branch_and_memory_tree('tree')
 
1225
        merger.cache_trees_with_revision_ids([tree])
 
1226
        self.assertEqual(original_cache, merger._cached_trees)
 
1227
 
 
1228
    def test_cache_trees_with_revision_ids_having_revision_id(self):
 
1229
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1230
        original_cache = dict(merger._cached_trees)
 
1231
        tree = merger.this_branch.repository.revision_tree('B-id')
 
1232
        original_cache['B-id'] = tree
 
1233
        merger.cache_trees_with_revision_ids([tree])
 
1234
        self.assertEqual(original_cache, merger._cached_trees)
 
1235
 
 
1236
    def test_find_base(self):
 
1237
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1238
        self.assertEqual('A-id', merger.base_rev_id)
 
1239
        self.assertFalse(merger._is_criss_cross)
 
1240
        self.assertIs(None, merger._lca_trees)
 
1241
 
 
1242
    def test_find_base_criss_cross(self):
 
1243
        builder = self.setup_criss_cross_graph()
 
1244
        merger = self.make_Merger(builder, 'E-id')
 
1245
        self.assertEqual('A-id', merger.base_rev_id)
 
1246
        self.assertTrue(merger._is_criss_cross)
 
1247
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1248
                                            for t in merger._lca_trees])
 
1249
        # If we swap the order, we should get a different lca order
 
1250
        builder.build_snapshot('F-id', ['E-id'], [])
 
1251
        merger = self.make_Merger(builder, 'D-id')
 
1252
        self.assertEqual(['C-id', 'B-id'], [t.get_revision_id()
 
1253
                                            for t in merger._lca_trees])
 
1254
 
 
1255
    def test_find_base_triple_criss_cross(self):
 
1256
        #       A-.
 
1257
        #      / \ \
 
1258
        #     B   C F # F is merged into both branches
 
1259
        #     |\ /| |
 
1260
        #     | X | |\
 
1261
        #     |/ \| | :
 
1262
        #   : D   E |
 
1263
        #    \|   |/
 
1264
        #     G   H
 
1265
        builder = self.setup_criss_cross_graph()
 
1266
        builder.build_snapshot('F-id', ['A-id'], [])
 
1267
        builder.build_snapshot('H-id', ['E-id', 'F-id'], [])
 
1268
        builder.build_snapshot('G-id', ['D-id', 'F-id'], [])
 
1269
        merger = self.make_Merger(builder, 'H-id')
 
1270
        self.assertEqual(['B-id', 'C-id', 'F-id'],
 
1271
                         [t.get_revision_id() for t in merger._lca_trees])
 
1272
 
 
1273
    def test_no_criss_cross_passed_to_merge_type(self):
 
1274
        class LCATreesMerger(LoggingMerger):
 
1275
            supports_lca_trees = True
 
1276
 
 
1277
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1278
        merger.merge_type = LCATreesMerger
 
1279
        merge_obj = merger.make_merger()
 
1280
        self.assertIsInstance(merge_obj, LCATreesMerger)
 
1281
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1282
 
 
1283
    def test_criss_cross_passed_to_merge_type(self):
 
1284
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1285
        merger.merge_type = _mod_merge.Merge3Merger
 
1286
        merge_obj = merger.make_merger()
 
1287
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1288
                                            for t in merger._lca_trees])
 
1289
 
 
1290
    def test_criss_cross_not_supported_merge_type(self):
 
1291
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1292
        # We explicitly do not define supports_lca_trees
 
1293
        merger.merge_type = LoggingMerger
 
1294
        merge_obj = merger.make_merger()
 
1295
        self.assertIsInstance(merge_obj, LoggingMerger)
 
1296
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1297
 
 
1298
    def test_criss_cross_unsupported_merge_type(self):
 
1299
        class UnsupportedLCATreesMerger(LoggingMerger):
 
1300
            supports_lca_trees = False
 
1301
 
 
1302
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1303
        merger.merge_type = UnsupportedLCATreesMerger
 
1304
        merge_obj = merger.make_merger()
 
1305
        self.assertIsInstance(merge_obj, UnsupportedLCATreesMerger)
 
1306
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1307
 
 
1308
 
 
1309
class TestMergerEntriesLCA(TestMergerBase):
 
1310
 
 
1311
    def make_merge_obj(self, builder, other_revision_id,
 
1312
                       interesting_files=None, interesting_ids=None):
 
1313
        merger = self.make_Merger(builder, other_revision_id,
 
1314
            interesting_files=interesting_files,
 
1315
            interesting_ids=interesting_ids)
 
1316
        return merger.make_merger()
 
1317
 
 
1318
    def test_simple(self):
 
1319
        builder = self.get_builder()
 
1320
        builder.build_snapshot('A-id', None,
 
1321
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1322
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1323
        builder.build_snapshot('C-id', ['A-id'],
 
1324
            [('modify', ('a-id', 'a\nb\nC\nc\n'))])
 
1325
        builder.build_snapshot('B-id', ['A-id'],
 
1326
            [('modify', ('a-id', 'a\nB\nb\nc\n'))])
 
1327
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1328
            [('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
 
1329
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1330
            [('modify', ('a-id', 'a\nB\nb\nC\nc\n'))])
 
1331
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1332
 
 
1333
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1334
                                            for t in merge_obj._lca_trees])
 
1335
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1336
        entries = list(merge_obj._entries_lca())
 
1337
 
 
1338
        # (file_id, changed, parents, names, executable)
 
1339
        # BASE, lca1, lca2, OTHER, THIS
 
1340
        root_id = 'a-root-id'
 
1341
        self.assertEqual([('a-id', True,
 
1342
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1343
                           ((u'a', [u'a', u'a']), u'a', u'a'),
 
1344
                           ((False, [False, False]), False, False)),
 
1345
                         ], entries)
 
1346
 
 
1347
    def test_not_in_base(self):
 
1348
        # LCAs all have the same last-modified revision for the file, as do
 
1349
        # the tips, but the base has something different
 
1350
        #       A    base, doesn't have the file
 
1351
        #       |\
 
1352
        #       B C  B introduces 'foo', C introduces 'bar'
 
1353
        #       |X|
 
1354
        #       D E  D and E now both have 'foo' and 'bar'
 
1355
        #       |X|
 
1356
        #       F G  the files are now in F, G, D and E, but not in A
 
1357
        #            G modifies 'bar'
 
1358
 
 
1359
        builder = self.get_builder()
 
1360
        builder.build_snapshot('A-id', None,
 
1361
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1362
        builder.build_snapshot('B-id', ['A-id'],
 
1363
            [('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1364
        builder.build_snapshot('C-id', ['A-id'],
 
1365
            [('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
 
1366
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1367
            [('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
 
1368
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1369
            [('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1370
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1371
            [('modify', (u'bar-id', 'd\ne\nf\nG\n'))])
 
1372
        builder.build_snapshot('F-id', ['D-id', 'E-id'], [])
 
1373
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1374
 
 
1375
        self.assertEqual(['D-id', 'E-id'], [t.get_revision_id()
 
1376
                                            for t in merge_obj._lca_trees])
 
1377
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1378
        entries = list(merge_obj._entries_lca())
 
1379
        root_id = 'a-root-id'
 
1380
        self.assertEqual([('bar-id', True,
 
1381
                           ((None, [root_id, root_id]), root_id, root_id),
 
1382
                           ((None, [u'bar', u'bar']), u'bar', u'bar'),
 
1383
                           ((None, [False, False]), False, False)),
 
1384
                         ], entries)
 
1385
 
 
1386
    def test_not_in_this(self):
 
1387
        builder = self.get_builder()
 
1388
        builder.build_snapshot('A-id', None,
 
1389
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1390
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1391
        builder.build_snapshot('B-id', ['A-id'],
 
1392
            [('modify', ('a-id', 'a\nB\nb\nc\n'))])
 
1393
        builder.build_snapshot('C-id', ['A-id'],
 
1394
            [('modify', ('a-id', 'a\nb\nC\nc\n'))])
 
1395
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1396
            [('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
 
1397
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1398
            [('unversion', 'a-id')])
 
1399
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1400
 
 
1401
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1402
                                            for t in merge_obj._lca_trees])
 
1403
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1404
 
 
1405
        entries = list(merge_obj._entries_lca())
 
1406
        root_id = 'a-root-id'
 
1407
        self.assertEqual([('a-id', True,
 
1408
                           ((root_id, [root_id, root_id]), root_id, None),
 
1409
                           ((u'a', [u'a', u'a']), u'a', None),
 
1410
                           ((False, [False, False]), False, None)),
 
1411
                         ], entries)
 
1412
 
 
1413
    def test_file_not_in_one_lca(self):
 
1414
        #   A   # just root
 
1415
        #   |\
 
1416
        #   B C # B no file, C introduces a file
 
1417
        #   |X|
 
1418
        #   D E # D and E both have the file, unchanged from C
 
1419
        builder = self.get_builder()
 
1420
        builder.build_snapshot('A-id', None,
 
1421
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1422
        builder.build_snapshot('B-id', ['A-id'], [])
 
1423
        builder.build_snapshot('C-id', ['A-id'],
 
1424
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1425
        builder.build_snapshot('E-id', ['C-id', 'B-id'], []) # Inherited from C
 
1426
        builder.build_snapshot('D-id', ['B-id', 'C-id'], # Merged from C
 
1427
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1428
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1429
 
 
1430
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1431
                                            for t in merge_obj._lca_trees])
 
1432
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1433
 
 
1434
        entries = list(merge_obj._entries_lca())
 
1435
        self.assertEqual([], entries)
 
1436
 
 
1437
    def test_not_in_other(self):
 
1438
        builder = self.get_builder()
 
1439
        builder.build_snapshot('A-id', None,
 
1440
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1441
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1442
        builder.build_snapshot('B-id', ['A-id'], [])
 
1443
        builder.build_snapshot('C-id', ['A-id'], [])
 
1444
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1445
            [('unversion', 'a-id')])
 
1446
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1447
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1448
 
 
1449
        entries = list(merge_obj._entries_lca())
 
1450
        root_id = 'a-root-id'
 
1451
        self.assertEqual([('a-id', True,
 
1452
                           ((root_id, [root_id, root_id]), None, root_id),
 
1453
                           ((u'a', [u'a', u'a']), None, u'a'),
 
1454
                           ((False, [False, False]), None, False)),
 
1455
                         ], entries)
 
1456
 
 
1457
    def test_not_in_other_or_lca(self):
 
1458
        #       A    base, introduces 'foo'
 
1459
        #       |\
 
1460
        #       B C  B nothing, C deletes foo
 
1461
        #       |X|
 
1462
        #       D E  D restores foo (same as B), E leaves it deleted
 
1463
        # Analysis:
 
1464
        #   A => B, no changes
 
1465
        #   A => C, delete foo (C should supersede B)
 
1466
        #   C => D, restore foo
 
1467
        #   C => E, no changes
 
1468
        # D would then win 'cleanly' and no record would be given
 
1469
        builder = self.get_builder()
 
1470
        builder.build_snapshot('A-id', None,
 
1471
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1472
             ('add', (u'foo', 'foo-id', 'file', 'content\n'))])
 
1473
        builder.build_snapshot('B-id', ['A-id'], [])
 
1474
        builder.build_snapshot('C-id', ['A-id'],
 
1475
            [('unversion', 'foo-id')])
 
1476
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1477
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1478
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1479
 
 
1480
        entries = list(merge_obj._entries_lca())
 
1481
        self.assertEqual([], entries)
 
1482
 
 
1483
    def test_not_in_other_mod_in_lca1_not_in_lca2(self):
 
1484
        #       A    base, introduces 'foo'
 
1485
        #       |\
 
1486
        #       B C  B changes 'foo', C deletes foo
 
1487
        #       |X|
 
1488
        #       D E  D restores foo (same as B), E leaves it deleted (as C)
 
1489
        # Analysis:
 
1490
        #   A => B, modified foo
 
1491
        #   A => C, delete foo, C does not supersede B
 
1492
        #   B => D, no changes
 
1493
        #   C => D, resolve in favor of B
 
1494
        #   B => E, resolve in favor of E
 
1495
        #   C => E, no changes
 
1496
        # In this case, we have a conflict of how the changes were resolved. E
 
1497
        # picked C and D picked B, so we should issue a conflict
 
1498
        builder = self.get_builder()
 
1499
        builder.build_snapshot('A-id', None,
 
1500
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1501
             ('add', (u'foo', 'foo-id', 'file', 'content\n'))])
 
1502
        builder.build_snapshot('B-id', ['A-id'], [
 
1503
            ('modify', ('foo-id', 'new-content\n'))])
 
1504
        builder.build_snapshot('C-id', ['A-id'],
 
1505
            [('unversion', 'foo-id')])
 
1506
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1507
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1508
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1509
 
 
1510
        entries = list(merge_obj._entries_lca())
 
1511
        root_id = 'a-root-id'
 
1512
        self.assertEqual([('foo-id', True,
 
1513
                           ((root_id, [root_id, None]), None, root_id),
 
1514
                           ((u'foo', [u'foo', None]), None, 'foo'),
 
1515
                           ((False, [False, None]), None, False)),
 
1516
                         ], entries)
 
1517
 
 
1518
    def test_only_in_one_lca(self):
 
1519
        #   A   add only root
 
1520
        #   |\
 
1521
        #   B C B nothing, C add file
 
1522
        #   |X|
 
1523
        #   D E D still has nothing, E removes file
 
1524
        # Analysis:
 
1525
        #   B => D, no change
 
1526
        #   C => D, removed the file
 
1527
        #   B => E, no change
 
1528
        #   C => E, removed the file
 
1529
        # Thus D & E have identical changes, and this is a no-op
 
1530
        # Alternatively:
 
1531
        #   A => B, no change
 
1532
        #   A => C, add file, thus C supersedes B
 
1533
        #   w/ C=BASE, D=THIS, E=OTHER we have 'happy convergence'
 
1534
        builder = self.get_builder()
 
1535
        builder.build_snapshot('A-id', None,
 
1536
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1537
        builder.build_snapshot('B-id', ['A-id'], [])
 
1538
        builder.build_snapshot('C-id', ['A-id'],
 
1539
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1540
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1541
            [('unversion', 'a-id')])
 
1542
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1543
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1544
 
 
1545
        entries = list(merge_obj._entries_lca())
 
1546
        self.assertEqual([], entries)
 
1547
 
 
1548
    def test_only_in_other(self):
 
1549
        builder = self.get_builder()
 
1550
        builder.build_snapshot('A-id', None,
 
1551
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1552
        builder.build_snapshot('B-id', ['A-id'], [])
 
1553
        builder.build_snapshot('C-id', ['A-id'], [])
 
1554
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1555
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1556
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1557
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1558
 
 
1559
        entries = list(merge_obj._entries_lca())
 
1560
        root_id = 'a-root-id'
 
1561
        self.assertEqual([('a-id', True,
 
1562
                           ((None, [None, None]), root_id, None),
 
1563
                           ((None, [None, None]), u'a', None),
 
1564
                           ((None, [None, None]), False, None)),
 
1565
                         ], entries)
 
1566
 
 
1567
    def test_one_lca_supersedes(self):
 
1568
        # One LCA supersedes the other LCAs last modified value, but the
 
1569
        # value is not the same as BASE.
 
1570
        #       A    base, introduces 'foo', last mod A
 
1571
        #       |\
 
1572
        #       B C  B modifies 'foo' (mod B), C does nothing (mod A)
 
1573
        #       |X|
 
1574
        #       D E  D does nothing (mod B), E updates 'foo' (mod E)
 
1575
        #       |X|
 
1576
        #       F G  F updates 'foo' (mod F). G does nothing (mod E)
 
1577
        #
 
1578
        #   At this point, G should not be considered to modify 'foo', even
 
1579
        #   though its LCAs disagree. This is because the modification in E
 
1580
        #   completely supersedes the value in D.
 
1581
        builder = self.get_builder()
 
1582
        builder.build_snapshot('A-id', None,
 
1583
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1584
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1585
        builder.build_snapshot('C-id', ['A-id'], [])
 
1586
        builder.build_snapshot('B-id', ['A-id'],
 
1587
            [('modify', ('foo-id', 'B content\n'))])
 
1588
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1589
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1590
            [('modify', ('foo-id', 'E content\n'))])
 
1591
        builder.build_snapshot('G-id', ['E-id', 'D-id'], [])
 
1592
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1593
            [('modify', ('foo-id', 'F content\n'))])
 
1594
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1595
 
 
1596
        self.assertEqual([], list(merge_obj._entries_lca()))
 
1597
 
 
1598
    def test_one_lca_supersedes_path(self):
 
1599
        # Double-criss-cross merge, the ultimate base value is different from
 
1600
        # the intermediate.
 
1601
        #   A    value 'foo'
 
1602
        #   |\
 
1603
        #   B C  B value 'bar', C = 'foo'
 
1604
        #   |X|
 
1605
        #   D E  D = 'bar', E supersedes to 'bing'
 
1606
        #   |X|
 
1607
        #   F G  F = 'bing', G supersedes to 'barry'
 
1608
        #
 
1609
        # In this case, we technically should not care about the value 'bar' for
 
1610
        # D, because it was clearly superseded by E's 'bing'. The
 
1611
        # per-file/attribute graph would actually look like:
 
1612
        #   A
 
1613
        #   |
 
1614
        #   B
 
1615
        #   |
 
1616
        #   E
 
1617
        #   |
 
1618
        #   G
 
1619
        #
 
1620
        # Because the other side of the merge never modifies the value, it just
 
1621
        # takes the value from the merge.
 
1622
        #
 
1623
        # ATM this fails because we will prune 'foo' from the LCAs, but we
 
1624
        # won't prune 'bar'. This is getting far off into edge-case land, so we
 
1625
        # aren't supporting it yet.
 
1626
        #
 
1627
        builder = self.get_builder()
 
1628
        builder.build_snapshot('A-id', None,
 
1629
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1630
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1631
        builder.build_snapshot('C-id', ['A-id'], [])
 
1632
        builder.build_snapshot('B-id', ['A-id'],
 
1633
            [('rename', ('foo', 'bar'))])
 
1634
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1635
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1636
            [('rename', ('foo', 'bing'))]) # override to bing
 
1637
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1638
            [('rename', ('bing', 'barry'))]) # override to barry
 
1639
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1640
            [('rename', ('bar', 'bing'))]) # Merge in E's change
 
1641
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1642
 
 
1643
        self.expectFailure("We don't do an actual heads() check on lca values,"
 
1644
            " or use the per-attribute graph",
 
1645
            self.assertEqual, [], list(merge_obj._entries_lca()))
 
1646
 
 
1647
    def test_one_lca_accidentally_pruned(self):
 
1648
        # Another incorrect resolution from the same basic flaw:
 
1649
        #   A    value 'foo'
 
1650
        #   |\
 
1651
        #   B C  B value 'bar', C = 'foo'
 
1652
        #   |X|
 
1653
        #   D E  D = 'bar', E reverts to 'foo'
 
1654
        #   |X|
 
1655
        #   F G  F = 'bing', G switches to 'bar'
 
1656
        #
 
1657
        # 'bar' will not be seen as an interesting change, because 'foo' will
 
1658
        # be pruned from the LCAs, even though it was newly introduced by E
 
1659
        # (superseding B).
 
1660
        builder = self.get_builder()
 
1661
        builder.build_snapshot('A-id', None,
 
1662
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1663
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1664
        builder.build_snapshot('C-id', ['A-id'], [])
 
1665
        builder.build_snapshot('B-id', ['A-id'],
 
1666
            [('rename', ('foo', 'bar'))])
 
1667
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1668
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1669
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1670
            [('rename', ('foo', 'bar'))])
 
1671
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1672
            [('rename', ('bar', 'bing'))]) # should end up conflicting
 
1673
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1674
 
 
1675
        entries = list(merge_obj._entries_lca())
 
1676
        root_id = 'a-root-id'
 
1677
        self.expectFailure("We prune values from BASE even when relevant.",
 
1678
            self.assertEqual,
 
1679
                [('foo-id', False,
 
1680
                  ((root_id, [root_id, root_id]), root_id, root_id),
 
1681
                  ((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
 
1682
                  ((False, [False, False]), False, False)),
 
1683
                ], entries)
 
1684
 
 
1685
    def test_both_sides_revert(self):
 
1686
        # Both sides of a criss-cross revert the text to the lca
 
1687
        #       A    base, introduces 'foo'
 
1688
        #       |\
 
1689
        #       B C  B modifies 'foo', C modifies 'foo'
 
1690
        #       |X|
 
1691
        #       D E  D reverts to B, E reverts to C
 
1692
        # This should conflict
 
1693
        builder = self.get_builder()
 
1694
        builder.build_snapshot('A-id', None,
 
1695
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1696
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1697
        builder.build_snapshot('B-id', ['A-id'],
 
1698
            [('modify', ('foo-id', 'B content\n'))])
 
1699
        builder.build_snapshot('C-id', ['A-id'],
 
1700
            [('modify', ('foo-id', 'C content\n'))])
 
1701
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1702
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1703
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1704
 
 
1705
        entries = list(merge_obj._entries_lca())
 
1706
        root_id = 'a-root-id'
 
1707
        self.assertEqual([('foo-id', True,
 
1708
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1709
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1710
                           ((False, [False, False]), False, False)),
 
1711
                         ], entries)
 
1712
 
 
1713
    def test_different_lca_resolve_one_side_updates_content(self):
 
1714
        # Both sides converge, but then one side updates the text.
 
1715
        #       A    base, introduces 'foo'
 
1716
        #       |\
 
1717
        #       B C  B modifies 'foo', C modifies 'foo'
 
1718
        #       |X|
 
1719
        #       D E  D reverts to B, E reverts to C
 
1720
        #       |
 
1721
        #       F    F updates to a new value
 
1722
        # We need to emit an entry for 'foo', because D & E differed on the
 
1723
        # merge resolution
 
1724
        builder = self.get_builder()
 
1725
        builder.build_snapshot('A-id', None,
 
1726
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1727
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1728
        builder.build_snapshot('B-id', ['A-id'],
 
1729
            [('modify', ('foo-id', 'B content\n'))])
 
1730
        builder.build_snapshot('C-id', ['A-id'],
 
1731
            [('modify', ('foo-id', 'C content\n'))])
 
1732
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1733
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1734
        builder.build_snapshot('F-id', ['D-id'],
 
1735
            [('modify', ('foo-id', 'F content\n'))])
 
1736
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1737
 
 
1738
        entries = list(merge_obj._entries_lca())
 
1739
        root_id = 'a-root-id'
 
1740
        self.assertEqual([('foo-id', True,
 
1741
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1742
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1743
                           ((False, [False, False]), False, False)),
 
1744
                         ], entries)
 
1745
 
 
1746
    def test_same_lca_resolution_one_side_updates_content(self):
 
1747
        # Both sides converge, but then one side updates the text.
 
1748
        #       A    base, introduces 'foo'
 
1749
        #       |\
 
1750
        #       B C  B modifies 'foo', C modifies 'foo'
 
1751
        #       |X|
 
1752
        #       D E  D and E use C's value
 
1753
        #       |
 
1754
        #       F    F updates to a new value
 
1755
        # I think it is a bug that this conflicts, but we don't have a way to
 
1756
        # detect otherwise. And because of:
 
1757
        #   test_different_lca_resolve_one_side_updates_content
 
1758
        # We need to conflict.
 
1759
 
 
1760
        builder = self.get_builder()
 
1761
        builder.build_snapshot('A-id', None,
 
1762
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1763
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1764
        builder.build_snapshot('B-id', ['A-id'],
 
1765
            [('modify', ('foo-id', 'B content\n'))])
 
1766
        builder.build_snapshot('C-id', ['A-id'],
 
1767
            [('modify', ('foo-id', 'C content\n'))])
 
1768
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1769
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1770
            [('modify', ('foo-id', 'C content\n'))]) # Same as E
 
1771
        builder.build_snapshot('F-id', ['D-id'],
 
1772
            [('modify', ('foo-id', 'F content\n'))])
 
1773
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1774
 
 
1775
        entries = list(merge_obj._entries_lca())
 
1776
        self.expectFailure("We don't detect that LCA resolution was the"
 
1777
                           " same on both sides",
 
1778
            self.assertEqual, [], entries)
 
1779
 
 
1780
    def test_only_path_changed(self):
 
1781
        builder = self.get_builder()
 
1782
        builder.build_snapshot('A-id', None,
 
1783
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1784
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1785
        builder.build_snapshot('B-id', ['A-id'], [])
 
1786
        builder.build_snapshot('C-id', ['A-id'], [])
 
1787
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1788
            [('rename', (u'a', u'b'))])
 
1789
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1790
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1791
        entries = list(merge_obj._entries_lca())
 
1792
        root_id = 'a-root-id'
 
1793
        # The content was not changed, only the path
 
1794
        self.assertEqual([('a-id', False,
 
1795
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1796
                           ((u'a', [u'a', u'a']), u'b', u'a'),
 
1797
                           ((False, [False, False]), False, False)),
 
1798
                         ], entries)
 
1799
 
 
1800
    def test_kind_changed(self):
 
1801
        # Identical content, except 'D' changes a-id into a directory
 
1802
        builder = self.get_builder()
 
1803
        builder.build_snapshot('A-id', None,
 
1804
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1805
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1806
        builder.build_snapshot('B-id', ['A-id'], [])
 
1807
        builder.build_snapshot('C-id', ['A-id'], [])
 
1808
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1809
            [('unversion', 'a-id'),
 
1810
             ('add', (u'a', 'a-id', 'directory', None))])
 
1811
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1812
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1813
        entries = list(merge_obj._entries_lca())
 
1814
        root_id = 'a-root-id'
 
1815
        # Only the kind was changed (content)
 
1816
        self.assertEqual([('a-id', True,
 
1817
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1818
                           ((u'a', [u'a', u'a']), u'a', u'a'),
 
1819
                           ((False, [False, False]), False, False)),
 
1820
                         ], entries)
 
1821
 
 
1822
    def test_this_changed_kind(self):
 
1823
        # Identical content, but THIS changes a file to a directory
 
1824
        builder = self.get_builder()
 
1825
        builder.build_snapshot('A-id', None,
 
1826
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1827
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1828
        builder.build_snapshot('B-id', ['A-id'], [])
 
1829
        builder.build_snapshot('C-id', ['A-id'], [])
 
1830
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1831
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1832
            [('unversion', 'a-id'),
 
1833
             ('add', (u'a', 'a-id', 'directory', None))])
 
1834
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1835
        entries = list(merge_obj._entries_lca())
 
1836
        # Only the kind was changed (content)
 
1837
        self.assertEqual([], entries)
 
1838
 
 
1839
    def test_interesting_files(self):
 
1840
        # Two files modified, but we should filter one of them
 
1841
        builder = self.get_builder()
 
1842
        builder.build_snapshot('A-id', None,
 
1843
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1844
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1845
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1846
        builder.build_snapshot('B-id', ['A-id'], [])
 
1847
        builder.build_snapshot('C-id', ['A-id'], [])
 
1848
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1849
            [('modify', ('a-id', 'new-content\n')),
 
1850
             ('modify', ('b-id', 'new-content\n'))])
 
1851
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1852
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1853
                                        interesting_files=['b'])
 
1854
        entries = list(merge_obj._entries_lca())
 
1855
        root_id = 'a-root-id'
 
1856
        self.assertEqual([('b-id', True,
 
1857
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1858
                           ((u'b', [u'b', u'b']), u'b', u'b'),
 
1859
                           ((False, [False, False]), False, False)),
 
1860
                         ], entries)
 
1861
 
 
1862
    def test_interesting_file_in_this(self):
 
1863
        # This renamed the file, but it should still match the entry in other
 
1864
        builder = self.get_builder()
 
1865
        builder.build_snapshot('A-id', None,
 
1866
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1867
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1868
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1869
        builder.build_snapshot('B-id', ['A-id'], [])
 
1870
        builder.build_snapshot('C-id', ['A-id'], [])
 
1871
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1872
            [('modify', ('a-id', 'new-content\n')),
 
1873
             ('modify', ('b-id', 'new-content\n'))])
 
1874
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1875
            [('rename', ('b', 'c'))])
 
1876
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1877
                                        interesting_files=['c'])
 
1878
        entries = list(merge_obj._entries_lca())
 
1879
        root_id = 'a-root-id'
 
1880
        self.assertEqual([('b-id', True,
 
1881
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1882
                           ((u'b', [u'b', u'b']), u'b', u'c'),
 
1883
                           ((False, [False, False]), False, False)),
 
1884
                         ], entries)
 
1885
 
 
1886
    def test_interesting_file_in_base(self):
 
1887
        # This renamed the file, but it should still match the entry in BASE
 
1888
        builder = self.get_builder()
 
1889
        builder.build_snapshot('A-id', None,
 
1890
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1891
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1892
             ('add', (u'c', 'c-id', 'file', 'content\n'))])
 
1893
        builder.build_snapshot('B-id', ['A-id'],
 
1894
            [('rename', ('c', 'b'))])
 
1895
        builder.build_snapshot('C-id', ['A-id'],
 
1896
            [('rename', ('c', 'b'))])
 
1897
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1898
            [('modify', ('a-id', 'new-content\n')),
 
1899
             ('modify', ('c-id', 'new-content\n'))])
 
1900
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1901
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1902
                                        interesting_files=['c'])
 
1903
        entries = list(merge_obj._entries_lca())
 
1904
        root_id = 'a-root-id'
 
1905
        self.assertEqual([('c-id', True,
 
1906
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1907
                           ((u'c', [u'b', u'b']), u'b', u'b'),
 
1908
                           ((False, [False, False]), False, False)),
 
1909
                         ], entries)
 
1910
 
 
1911
    def test_interesting_file_in_lca(self):
 
1912
        # This renamed the file, but it should still match the entry in LCA
 
1913
        builder = self.get_builder()
 
1914
        builder.build_snapshot('A-id', None,
 
1915
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1916
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1917
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1918
        builder.build_snapshot('B-id', ['A-id'],
 
1919
            [('rename', ('b', 'c'))])
 
1920
        builder.build_snapshot('C-id', ['A-id'], [])
 
1921
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1922
            [('modify', ('a-id', 'new-content\n')),
 
1923
             ('modify', ('b-id', 'new-content\n'))])
 
1924
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1925
            [('rename', ('c', 'b'))])
 
1926
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1927
                                        interesting_files=['c'])
 
1928
        entries = list(merge_obj._entries_lca())
 
1929
        root_id = 'a-root-id'
 
1930
        self.assertEqual([('b-id', True,
 
1931
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1932
                           ((u'b', [u'c', u'b']), u'b', u'b'),
 
1933
                           ((False, [False, False]), False, False)),
 
1934
                         ], entries)
 
1935
 
 
1936
    def test_interesting_ids(self):
 
1937
        # Two files modified, but we should filter one of them
 
1938
        builder = self.get_builder()
 
1939
        builder.build_snapshot('A-id', None,
 
1940
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1941
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1942
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1943
        builder.build_snapshot('B-id', ['A-id'], [])
 
1944
        builder.build_snapshot('C-id', ['A-id'], [])
 
1945
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1946
            [('modify', ('a-id', 'new-content\n')),
 
1947
             ('modify', ('b-id', 'new-content\n'))])
 
1948
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1949
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1950
                                        interesting_ids=['b-id'])
 
1951
        entries = list(merge_obj._entries_lca())
 
1952
        root_id = 'a-root-id'
 
1953
        self.assertEqual([('b-id', True,
 
1954
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1955
                           ((u'b', [u'b', u'b']), u'b', u'b'),
 
1956
                           ((False, [False, False]), False, False)),
 
1957
                         ], entries)
 
1958
 
 
1959
 
 
1960
 
 
1961
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
 
1962
 
 
1963
    def get_builder(self):
 
1964
        builder = self.make_branch_builder('path')
 
1965
        builder.start_series()
 
1966
        self.addCleanup(builder.finish_series)
 
1967
        return builder
 
1968
 
 
1969
    def get_wt_from_builder(self, builder):
 
1970
        """Get a real WorkingTree from the builder."""
 
1971
        the_branch = builder.get_branch()
 
1972
        wt = the_branch.bzrdir.create_workingtree()
 
1973
        # Note: This is a little bit ugly, but we are holding the branch
 
1974
        #       write-locked as part of the build process, and we would like to
 
1975
        #       maintain that. So we just force the WT to re-use the same
 
1976
        #       branch object.
 
1977
        wt._branch = the_branch
 
1978
        wt.lock_write()
 
1979
        self.addCleanup(wt.unlock)
 
1980
        return wt
 
1981
 
 
1982
    def do_merge(self, builder, other_revision_id):
 
1983
        wt = self.get_wt_from_builder(builder)
 
1984
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1985
            wt, other_revision_id)
 
1986
        merger.merge_type = _mod_merge.Merge3Merger
 
1987
        return wt, merger.do_merge()
 
1988
 
 
1989
    def test_simple_lca(self):
 
1990
        builder = self.get_builder()
 
1991
        builder.build_snapshot('A-id', None,
 
1992
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1993
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1994
        builder.build_snapshot('C-id', ['A-id'], [])
 
1995
        builder.build_snapshot('B-id', ['A-id'], [])
 
1996
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1997
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1998
            [('modify', ('a-id', 'a\nb\nc\nd\ne\nf\n'))])
 
1999
        wt, conflicts = self.do_merge(builder, 'E-id')
 
2000
        self.assertEqual(0, conflicts)
 
2001
        # The merge should have simply update the contents of 'a'
 
2002
        self.assertEqual('a\nb\nc\nd\ne\nf\n', wt.get_file_text('a-id'))
 
2003
 
 
2004
    def test_conflict_without_lca(self):
 
2005
        # This test would cause a merge conflict, unless we use the lca trees
 
2006
        # to determine the real ancestry
 
2007
        #   A       Path at 'foo'
 
2008
        #  / \
 
2009
        # B   C     Path renamed to 'bar' in B
 
2010
        # |\ /|
 
2011
        # | X |
 
2012
        # |/ \|
 
2013
        # D   E     Path at 'bar' in D and E
 
2014
        #     |
 
2015
        #     F     Path at 'baz' in F, which supersedes 'bar' and 'foo'
 
2016
        builder = self.get_builder()
 
2017
        builder.build_snapshot('A-id', None,
 
2018
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2019
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2020
        builder.build_snapshot('C-id', ['A-id'], [])
 
2021
        builder.build_snapshot('B-id', ['A-id'],
 
2022
            [('rename', ('foo', 'bar'))])
 
2023
        builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
 
2024
            [('rename', ('foo', 'bar'))])
 
2025
        builder.build_snapshot('F-id', ['E-id'],
 
2026
            [('rename', ('bar', 'baz'))])
 
2027
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2028
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2029
        self.assertEqual(0, conflicts)
 
2030
        # The merge should simply recognize that the final rename takes
 
2031
        # precedence
 
2032
        self.assertEqual('baz', wt.id2path('foo-id'))
 
2033
 
 
2034
    def test_other_deletes_lca_renames(self):
 
2035
        # This test would cause a merge conflict, unless we use the lca trees
 
2036
        # to determine the real ancestry
 
2037
        #   A       Path at 'foo'
 
2038
        #  / \
 
2039
        # B   C     Path renamed to 'bar' in B
 
2040
        # |\ /|
 
2041
        # | X |
 
2042
        # |/ \|
 
2043
        # D   E     Path at 'bar' in D and E
 
2044
        #     |
 
2045
        #     F     F deletes 'bar'
 
2046
        builder = self.get_builder()
 
2047
        builder.build_snapshot('A-id', None,
 
2048
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2049
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2050
        builder.build_snapshot('C-id', ['A-id'], [])
 
2051
        builder.build_snapshot('B-id', ['A-id'],
 
2052
            [('rename', ('foo', 'bar'))])
 
2053
        builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
 
2054
            [('rename', ('foo', 'bar'))])
 
2055
        builder.build_snapshot('F-id', ['E-id'],
 
2056
            [('unversion', 'foo-id')])
 
2057
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2058
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2059
        self.assertEqual(0, conflicts)
 
2060
        self.assertRaises(errors.NoSuchId, wt.id2path, 'foo-id')
 
2061
 
 
2062
    def test_executable_changes(self):
 
2063
        #   A       Path at 'foo'
 
2064
        #  / \
 
2065
        # B   C
 
2066
        # |\ /|
 
2067
        # | X |
 
2068
        # |/ \|
 
2069
        # D   E
 
2070
        #     |
 
2071
        #     F     Executable bit changed
 
2072
        builder = self.get_builder()
 
2073
        builder.build_snapshot('A-id', None,
 
2074
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2075
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2076
        builder.build_snapshot('C-id', ['A-id'], [])
 
2077
        builder.build_snapshot('B-id', ['A-id'], [])
 
2078
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2079
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2080
        # Have to use a real WT, because BranchBuilder doesn't support exec bit
 
2081
        wt = self.get_wt_from_builder(builder)
 
2082
        tt = transform.TreeTransform(wt)
 
2083
        try:
 
2084
            tt.set_executability(True, tt.trans_id_tree_file_id('foo-id'))
 
2085
            tt.apply()
 
2086
        except:
 
2087
            tt.finalize()
 
2088
            raise
 
2089
        self.assertTrue(wt.is_executable('foo-id'))
 
2090
        wt.commit('F-id', rev_id='F-id')
 
2091
        # Reset to D, so that we can merge F
 
2092
        wt.set_parent_ids(['D-id'])
 
2093
        wt.branch.set_last_revision_info(3, 'D-id')
 
2094
        wt.revert()
 
2095
        self.assertFalse(wt.is_executable('foo-id'))
 
2096
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2097
        self.assertEqual(0, conflicts)
 
2098
        self.assertTrue(wt.is_executable('foo-id'))
 
2099
 
 
2100
    def test_create_symlink(self):
 
2101
        self.requireFeature(tests.SymlinkFeature)
 
2102
        #   A
 
2103
        #  / \
 
2104
        # B   C
 
2105
        # |\ /|
 
2106
        # | X |
 
2107
        # |/ \|
 
2108
        # D   E
 
2109
        #     |
 
2110
        #     F     Add a symlink 'foo' => 'bar'
 
2111
        # Have to use a real WT, because BranchBuilder and MemoryTree don't
 
2112
        # have symlink support
 
2113
        builder = self.get_builder()
 
2114
        builder.build_snapshot('A-id', None,
 
2115
            [('add', (u'', 'a-root-id', 'directory', None))])
 
2116
        builder.build_snapshot('C-id', ['A-id'], [])
 
2117
        builder.build_snapshot('B-id', ['A-id'], [])
 
2118
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2119
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2120
        # Have to use a real WT, because BranchBuilder doesn't support exec bit
 
2121
        wt = self.get_wt_from_builder(builder)
 
2122
        os.symlink('bar', 'path/foo')
 
2123
        wt.add(['foo'], ['foo-id'])
 
2124
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2125
        wt.commit('add symlink', rev_id='F-id')
 
2126
        # Reset to D, so that we can merge F
 
2127
        wt.set_parent_ids(['D-id'])
 
2128
        wt.branch.set_last_revision_info(3, 'D-id')
 
2129
        wt.revert()
 
2130
        self.assertIs(None, wt.path2id('foo'))
 
2131
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2132
        self.assertEqual(0, conflicts)
 
2133
        self.assertEqual('foo-id', wt.path2id('foo'))
 
2134
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2135
 
 
2136
    def test_both_sides_revert(self):
 
2137
        # Both sides of a criss-cross revert the text to the lca
 
2138
        #       A    base, introduces 'foo'
 
2139
        #       |\
 
2140
        #       B C  B modifies 'foo', C modifies 'foo'
 
2141
        #       |X|
 
2142
        #       D E  D reverts to B, E reverts to C
 
2143
        # This should conflict
 
2144
        # This must be done with a real WorkingTree, because normally their
 
2145
        # inventory contains "None" rather than a real sha1
 
2146
        builder = self.get_builder()
 
2147
        builder.build_snapshot('A-id', None,
 
2148
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2149
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
2150
        builder.build_snapshot('B-id', ['A-id'],
 
2151
            [('modify', ('foo-id', 'B content\n'))])
 
2152
        builder.build_snapshot('C-id', ['A-id'],
 
2153
            [('modify', ('foo-id', 'C content\n'))])
 
2154
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2155
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2156
        wt, conflicts = self.do_merge(builder, 'E-id')
 
2157
        self.assertEqual(1, conflicts)
 
2158
        self.assertEqualDiff('<<<<<<< TREE\n'
 
2159
                             'B content\n'
 
2160
                             '=======\n'
 
2161
                             'C content\n'
 
2162
                             '>>>>>>> MERGE-SOURCE\n',
 
2163
                             wt.get_file_text('foo-id'))
 
2164
 
 
2165
    def test_modified_symlink(self):
 
2166
        self.requireFeature(tests.SymlinkFeature)
 
2167
        #   A       Create symlink foo => bar
 
2168
        #  / \
 
2169
        # B   C     B relinks foo => baz
 
2170
        # |\ /|
 
2171
        # | X |
 
2172
        # |/ \|
 
2173
        # D   E     D & E have foo => baz
 
2174
        #     |
 
2175
        #     F     F changes it to bing
 
2176
        #
 
2177
        # Merging D & F should result in F cleanly overriding D, because D's
 
2178
        # value actually comes from B
 
2179
 
 
2180
        # Have to use a real WT, because BranchBuilder and MemoryTree don't
 
2181
        # have symlink support
 
2182
        wt = self.make_branch_and_tree('path')
 
2183
        wt.lock_write()
 
2184
        self.addCleanup(wt.unlock)
 
2185
        os.symlink('bar', 'path/foo')
 
2186
        wt.add(['foo'], ['foo-id'])
 
2187
        wt.commit('add symlink', rev_id='A-id')
 
2188
        os.remove('path/foo')
 
2189
        os.symlink('baz', 'path/foo')
 
2190
        wt.commit('foo => baz', rev_id='B-id')
 
2191
        wt.set_last_revision('A-id')
 
2192
        wt.branch.set_last_revision_info(1, 'A-id')
 
2193
        wt.revert()
 
2194
        wt.commit('C', rev_id='C-id')
 
2195
        wt.merge_from_branch(wt.branch, 'B-id')
 
2196
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2197
        wt.commit('E merges C & B', rev_id='E-id')
 
2198
        os.remove('path/foo')
 
2199
        os.symlink('bing', 'path/foo')
 
2200
        wt.commit('F foo => bing', rev_id='F-id')
 
2201
        wt.set_last_revision('B-id')
 
2202
        wt.branch.set_last_revision_info(2, 'B-id')
 
2203
        wt.revert()
 
2204
        wt.merge_from_branch(wt.branch, 'C-id')
 
2205
        wt.commit('D merges B & C', rev_id='D-id')
 
2206
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2207
        self.assertEqual(0, conflicts)
 
2208
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
 
2209
 
 
2210
    def test_renamed_symlink(self):
 
2211
        self.requireFeature(tests.SymlinkFeature)
 
2212
        #   A       Create symlink foo => bar
 
2213
        #  / \
 
2214
        # B   C     B renames foo => barry
 
2215
        # |\ /|
 
2216
        # | X |
 
2217
        # |/ \|
 
2218
        # D   E     D & E have barry
 
2219
        #     |
 
2220
        #     F     F renames barry to blah
 
2221
        #
 
2222
        # Merging D & F should result in F cleanly overriding D, because D's
 
2223
        # value actually comes from B
 
2224
 
 
2225
        wt = self.make_branch_and_tree('path')
 
2226
        wt.lock_write()
 
2227
        self.addCleanup(wt.unlock)
 
2228
        os.symlink('bar', 'path/foo')
 
2229
        wt.add(['foo'], ['foo-id'])
 
2230
        wt.commit('A add symlink', rev_id='A-id')
 
2231
        wt.rename_one('foo', 'barry')
 
2232
        wt.commit('B foo => barry', rev_id='B-id')
 
2233
        wt.set_last_revision('A-id')
 
2234
        wt.branch.set_last_revision_info(1, 'A-id')
 
2235
        wt.revert()
 
2236
        wt.commit('C', rev_id='C-id')
 
2237
        wt.merge_from_branch(wt.branch, 'B-id')
 
2238
        self.assertEqual('barry', wt.id2path('foo-id'))
 
2239
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2240
        wt.commit('E merges C & B', rev_id='E-id')
 
2241
        wt.rename_one('barry', 'blah')
 
2242
        wt.commit('F barry => blah', rev_id='F-id')
 
2243
        wt.set_last_revision('B-id')
 
2244
        wt.branch.set_last_revision_info(2, 'B-id')
 
2245
        wt.revert()
 
2246
        wt.merge_from_branch(wt.branch, 'C-id')
 
2247
        wt.commit('D merges B & C', rev_id='D-id')
 
2248
        self.assertEqual('barry', wt.id2path('foo-id'))
 
2249
        # Check the output of the Merger object directly
 
2250
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2251
            wt, 'F-id')
 
2252
        merger.merge_type = _mod_merge.Merge3Merger
 
2253
        merge_obj = merger.make_merger()
 
2254
        root_id = wt.path2id('')
 
2255
        entries = list(merge_obj._entries_lca())
 
2256
        # No content change, just a path change
 
2257
        self.assertEqual([('foo-id', False,
 
2258
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2259
                           ((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
 
2260
                           ((False, [False, False]), False, False)),
 
2261
                         ], entries)
 
2262
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2263
        self.assertEqual(0, conflicts)
 
2264
        self.assertEqual('blah', wt.id2path('foo-id'))
 
2265
 
 
2266
    def test_symlink_no_content_change(self):
 
2267
        self.requireFeature(tests.SymlinkFeature)
 
2268
        #   A       Create symlink foo => bar
 
2269
        #  / \
 
2270
        # B   C     B relinks foo => baz
 
2271
        # |\ /|
 
2272
        # | X |
 
2273
        # |/ \|
 
2274
        # D   E     D & E have foo => baz
 
2275
        # |
 
2276
        # F         F has foo => bing
 
2277
        #
 
2278
        # Merging E into F should not cause a conflict, because E doesn't have
 
2279
        # a content change relative to the LCAs (it does relative to A)
 
2280
        wt = self.make_branch_and_tree('path')
 
2281
        wt.lock_write()
 
2282
        self.addCleanup(wt.unlock)
 
2283
        os.symlink('bar', 'path/foo')
 
2284
        wt.add(['foo'], ['foo-id'])
 
2285
        wt.commit('add symlink', rev_id='A-id')
 
2286
        os.remove('path/foo')
 
2287
        os.symlink('baz', 'path/foo')
 
2288
        wt.commit('foo => baz', rev_id='B-id')
 
2289
        wt.set_last_revision('A-id')
 
2290
        wt.branch.set_last_revision_info(1, 'A-id')
 
2291
        wt.revert()
 
2292
        wt.commit('C', rev_id='C-id')
 
2293
        wt.merge_from_branch(wt.branch, 'B-id')
 
2294
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2295
        wt.commit('E merges C & B', rev_id='E-id')
 
2296
        wt.set_last_revision('B-id')
 
2297
        wt.branch.set_last_revision_info(2, 'B-id')
 
2298
        wt.revert()
 
2299
        wt.merge_from_branch(wt.branch, 'C-id')
 
2300
        wt.commit('D merges B & C', rev_id='D-id')
 
2301
        os.remove('path/foo')
 
2302
        os.symlink('bing', 'path/foo')
 
2303
        wt.commit('F foo => bing', rev_id='F-id')
 
2304
 
 
2305
        # Check the output of the Merger object directly
 
2306
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2307
            wt, 'E-id')
 
2308
        merger.merge_type = _mod_merge.Merge3Merger
 
2309
        merge_obj = merger.make_merger()
 
2310
        # Nothing interesting happened in OTHER relative to BASE
 
2311
        self.assertEqual([], list(merge_obj._entries_lca()))
 
2312
        # Now do a real merge, just to test the rest of the stack
 
2313
        conflicts = wt.merge_from_branch(wt.branch, to_revision='E-id')
 
2314
        self.assertEqual(0, conflicts)
 
2315
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
 
2316
 
 
2317
    def test_symlink_this_changed_kind(self):
 
2318
        self.requireFeature(tests.SymlinkFeature)
 
2319
        #   A       Nothing
 
2320
        #  / \
 
2321
        # B   C     B creates symlink foo => bar
 
2322
        # |\ /|
 
2323
        # | X |
 
2324
        # |/ \|
 
2325
        # D   E     D changes foo into a file, E has foo => bing
 
2326
        #
 
2327
        # Mostly, this is trying to test that we don't try to os.readlink() on
 
2328
        # a file, or when there is nothing there
 
2329
        wt = self.make_branch_and_tree('path')
 
2330
        wt.lock_write()
 
2331
        self.addCleanup(wt.unlock)
 
2332
        wt.commit('base', rev_id='A-id')
 
2333
        os.symlink('bar', 'path/foo')
 
2334
        wt.add(['foo'], ['foo-id'])
 
2335
        wt.commit('add symlink foo => bar', rev_id='B-id')
 
2336
        wt.set_last_revision('A-id')
 
2337
        wt.branch.set_last_revision_info(1, 'A-id')
 
2338
        wt.revert()
 
2339
        wt.commit('C', rev_id='C-id')
 
2340
        wt.merge_from_branch(wt.branch, 'B-id')
 
2341
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2342
        os.remove('path/foo')
 
2343
        # We have to change the link in E, or it won't try to do a comparison
 
2344
        os.symlink('bing', 'path/foo')
 
2345
        wt.commit('E merges C & B, overrides to bing', rev_id='E-id')
 
2346
        wt.set_last_revision('B-id')
 
2347
        wt.branch.set_last_revision_info(2, 'B-id')
 
2348
        wt.revert()
 
2349
        wt.merge_from_branch(wt.branch, 'C-id')
 
2350
        os.remove('path/foo')
 
2351
        self.build_tree_contents([('path/foo', 'file content\n')])
 
2352
        # XXX: workaround, WT doesn't detect kind changes unless you do
 
2353
        # iter_changes()
 
2354
        list(wt.iter_changes(wt.basis_tree()))
 
2355
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
 
2356
 
 
2357
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2358
            wt, 'E-id')
 
2359
        merger.merge_type = _mod_merge.Merge3Merger
 
2360
        merge_obj = merger.make_merger()
 
2361
        entries = list(merge_obj._entries_lca())
 
2362
        root_id = wt.path2id('')
 
2363
        self.assertEqual([('foo-id', True,
 
2364
                           ((None, [root_id, None]), root_id, root_id),
 
2365
                           ((None, [u'foo', None]), u'foo', u'foo'),
 
2366
                           ((None, [False, None]), False, False)),
 
2367
                         ], entries)
 
2368
 
 
2369
    def test_symlink_all_wt(self):
 
2370
        """Check behavior if all trees are Working Trees."""
 
2371
        self.requireFeature(tests.SymlinkFeature)
 
2372
        # The big issue is that entry.symlink_target is None for WorkingTrees.
 
2373
        # So we need to make sure we handle that case correctly.
 
2374
        #   A   foo => bar
 
2375
        #   |\
 
2376
        #   B C B relinks foo => baz
 
2377
        #   |X|
 
2378
        #   D E D & E have foo => baz
 
2379
        #     |
 
2380
        #     F F changes it to bing
 
2381
        # Merging D & F should result in F cleanly overriding D, because D's
 
2382
        # value actually comes from B
 
2383
 
 
2384
        wt = self.make_branch_and_tree('path')
 
2385
        wt.lock_write()
 
2386
        self.addCleanup(wt.unlock)
 
2387
        os.symlink('bar', 'path/foo')
 
2388
        wt.add(['foo'], ['foo-id'])
 
2389
        wt.commit('add symlink', rev_id='A-id')
 
2390
        os.remove('path/foo')
 
2391
        os.symlink('baz', 'path/foo')
 
2392
        wt.commit('foo => baz', rev_id='B-id')
 
2393
        wt.set_last_revision('A-id')
 
2394
        wt.branch.set_last_revision_info(1, 'A-id')
 
2395
        wt.revert()
 
2396
        wt.commit('C', rev_id='C-id')
 
2397
        wt.merge_from_branch(wt.branch, 'B-id')
 
2398
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2399
        wt.commit('E merges C & B', rev_id='E-id')
 
2400
        os.remove('path/foo')
 
2401
        os.symlink('bing', 'path/foo')
 
2402
        wt.commit('F foo => bing', rev_id='F-id')
 
2403
        wt.set_last_revision('B-id')
 
2404
        wt.branch.set_last_revision_info(2, 'B-id')
 
2405
        wt.revert()
 
2406
        wt.merge_from_branch(wt.branch, 'C-id')
 
2407
        wt.commit('D merges B & C', rev_id='D-id')
 
2408
        wt_base = wt.bzrdir.sprout('base', 'A-id').open_workingtree()
 
2409
        wt_base.lock_read()
 
2410
        self.addCleanup(wt_base.unlock)
 
2411
        wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
 
2412
        wt_lca1.lock_read()
 
2413
        self.addCleanup(wt_lca1.unlock)
 
2414
        wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
 
2415
        wt_lca2.lock_read()
 
2416
        self.addCleanup(wt_lca2.unlock)
 
2417
        wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
 
2418
        wt_other.lock_read()
 
2419
        self.addCleanup(wt_other.unlock)
 
2420
        merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
 
2421
            wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
 
2422
        entries = list(merge_obj._entries_lca())
 
2423
        root_id = wt.path2id('')
 
2424
        self.assertEqual([('foo-id', True,
 
2425
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2426
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
2427
                           ((False, [False, False]), False, False)),
 
2428
                         ], entries)
 
2429
 
 
2430
    def test_other_reverted_path_to_base(self):
 
2431
        #   A       Path at 'foo'
 
2432
        #  / \
 
2433
        # B   C     Path at 'bar' in B
 
2434
        # |\ /|
 
2435
        # | X |
 
2436
        # |/ \|
 
2437
        # D   E     Path at 'bar'
 
2438
        #     |
 
2439
        #     F     Path at 'foo'
 
2440
        builder = self.get_builder()
 
2441
        builder.build_snapshot('A-id', None,
 
2442
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2443
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2444
        builder.build_snapshot('C-id', ['A-id'], [])
 
2445
        builder.build_snapshot('B-id', ['A-id'],
 
2446
            [('rename', ('foo', 'bar'))])
 
2447
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2448
            [('rename', ('foo', 'bar'))]) # merge the rename
 
2449
        builder.build_snapshot('F-id', ['E-id'],
 
2450
            [('rename', ('bar', 'foo'))]) # Rename back to BASE
 
2451
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2452
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2453
        self.assertEqual(0, conflicts)
 
2454
        self.assertEqual('foo', wt.id2path('foo-id'))
 
2455
 
 
2456
    def test_other_reverted_content_to_base(self):
 
2457
        builder = self.get_builder()
 
2458
        builder.build_snapshot('A-id', None,
 
2459
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2460
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2461
        builder.build_snapshot('C-id', ['A-id'], [])
 
2462
        builder.build_snapshot('B-id', ['A-id'],
 
2463
            [('modify', ('foo-id', 'B content\n'))])
 
2464
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2465
            [('modify', ('foo-id', 'B content\n'))]) # merge the content
 
2466
        builder.build_snapshot('F-id', ['E-id'],
 
2467
            [('modify', ('foo-id', 'base content\n'))]) # Revert back to BASE
 
2468
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2469
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2470
        self.assertEqual(0, conflicts)
 
2471
        # TODO: We need to use the per-file graph to properly select a BASE
 
2472
        #       before this will work. Or at least use the LCA trees to find
 
2473
        #       the appropriate content base. (which is B, not A).
 
2474
        self.assertEqual('base content\n', wt.get_file_text('foo-id'))
 
2475
 
 
2476
    def test_other_modified_content(self):
 
2477
        builder = self.get_builder()
 
2478
        builder.build_snapshot('A-id', None,
 
2479
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2480
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2481
        builder.build_snapshot('C-id', ['A-id'], [])
 
2482
        builder.build_snapshot('B-id', ['A-id'],
 
2483
            [('modify', ('foo-id', 'B content\n'))])
 
2484
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2485
            [('modify', ('foo-id', 'B content\n'))]) # merge the content
 
2486
        builder.build_snapshot('F-id', ['E-id'],
 
2487
            [('modify', ('foo-id', 'F content\n'))]) # Override B content
 
2488
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2489
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2490
        self.assertEqual(0, conflicts)
 
2491
        self.assertEqual('F content\n', wt.get_file_text('foo-id'))
 
2492
 
 
2493
    def test_all_wt(self):
 
2494
        """Check behavior if all trees are Working Trees."""
 
2495
        # The big issue is that entry.revision is None for WorkingTrees. (as is
 
2496
        # entry.text_sha1, etc. So we need to make sure we handle that case
 
2497
        # correctly.
 
2498
        #   A   Content of 'foo', path of 'a'
 
2499
        #   |\
 
2500
        #   B C B modifies content, C renames 'a' => 'b'
 
2501
        #   |X|
 
2502
        #   D E E updates content, renames 'b' => 'c'
 
2503
        builder = self.get_builder()
 
2504
        builder.build_snapshot('A-id', None,
 
2505
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2506
             ('add', (u'a', 'a-id', 'file', 'base content\n')),
 
2507
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2508
        builder.build_snapshot('B-id', ['A-id'],
 
2509
            [('modify', ('foo-id', 'B content\n'))])
 
2510
        builder.build_snapshot('C-id', ['A-id'],
 
2511
            [('rename', ('a', 'b'))])
 
2512
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2513
            [('rename', ('b', 'c')),
 
2514
             ('modify', ('foo-id', 'E content\n'))])
 
2515
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
2516
            [('rename', ('a', 'b'))]) # merged change
 
2517
        wt_this = self.get_wt_from_builder(builder)
 
2518
        wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
 
2519
        wt_base.lock_read()
 
2520
        self.addCleanup(wt_base.unlock)
 
2521
        wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
 
2522
        wt_lca1.lock_read()
 
2523
        self.addCleanup(wt_lca1.unlock)
 
2524
        wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
 
2525
        wt_lca2.lock_read()
 
2526
        self.addCleanup(wt_lca2.unlock)
 
2527
        wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
 
2528
        wt_other.lock_read()
 
2529
        self.addCleanup(wt_other.unlock)
 
2530
        merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
 
2531
            wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
 
2532
        entries = list(merge_obj._entries_lca())
 
2533
        root_id = 'a-root-id'
 
2534
        self.assertEqual([('a-id', False,
 
2535
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2536
                           ((u'a', [u'a', u'b']), u'c', u'b'),
 
2537
                           ((False, [False, False]), False, False)),
 
2538
                          ('foo-id', True,
 
2539
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2540
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
2541
                           ((False, [False, False]), False, False)),
 
2542
                         ], entries)
 
2543
 
 
2544
    def test_nested_tree_unmodified(self):
 
2545
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2546
        # 'tree-reference'
 
2547
        wt = self.make_branch_and_tree('tree',
 
2548
            format='dirstate-with-subtree')
 
2549
        wt.lock_write()
 
2550
        self.addCleanup(wt.unlock)
 
2551
        sub_tree = self.make_branch_and_tree('tree/sub-tree',
 
2552
            format='dirstate-with-subtree')
 
2553
        wt.set_root_id('a-root-id')
 
2554
        sub_tree.set_root_id('sub-tree-root')
 
2555
        self.build_tree_contents([('tree/sub-tree/file', 'text1')])
 
2556
        sub_tree.add('file')
 
2557
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2558
        wt.add_reference(sub_tree)
 
2559
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2560
        # Now create a criss-cross merge in the parent, without modifying the
 
2561
        # subtree
 
2562
        wt.commit('B', rev_id='B-id', recursive=None)
 
2563
        wt.set_last_revision('A-id')
 
2564
        wt.branch.set_last_revision_info(1, 'A-id')
 
2565
        wt.commit('C', rev_id='C-id', recursive=None)
 
2566
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2567
        wt.commit('E', rev_id='E-id', recursive=None)
 
2568
        wt.set_parent_ids(['B-id', 'C-id'])
 
2569
        wt.branch.set_last_revision_info(2, 'B-id')
 
2570
        wt.commit('D', rev_id='D-id', recursive=None)
 
2571
 
 
2572
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2573
            wt, 'E-id')
 
2574
        merger.merge_type = _mod_merge.Merge3Merger
 
2575
        merge_obj = merger.make_merger()
 
2576
        entries = list(merge_obj._entries_lca())
 
2577
        self.assertEqual([], entries)
 
2578
 
 
2579
    def test_nested_tree_subtree_modified(self):
 
2580
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2581
        # 'tree-reference'
 
2582
        wt = self.make_branch_and_tree('tree',
 
2583
            format='dirstate-with-subtree')
 
2584
        wt.lock_write()
 
2585
        self.addCleanup(wt.unlock)
 
2586
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2587
            format='dirstate-with-subtree')
 
2588
        wt.set_root_id('a-root-id')
 
2589
        sub_tree.set_root_id('sub-tree-root')
 
2590
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2591
        sub_tree.add('file')
 
2592
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2593
        wt.add_reference(sub_tree)
 
2594
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2595
        # Now create a criss-cross merge in the parent, without modifying the
 
2596
        # subtree
 
2597
        wt.commit('B', rev_id='B-id', recursive=None)
 
2598
        wt.set_last_revision('A-id')
 
2599
        wt.branch.set_last_revision_info(1, 'A-id')
 
2600
        wt.commit('C', rev_id='C-id', recursive=None)
 
2601
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2602
        self.build_tree_contents([('tree/sub/file', 'text2')])
 
2603
        sub_tree.commit('modify contents', rev_id='sub-B-id')
 
2604
        wt.commit('E', rev_id='E-id', recursive=None)
 
2605
        wt.set_parent_ids(['B-id', 'C-id'])
 
2606
        wt.branch.set_last_revision_info(2, 'B-id')
 
2607
        wt.commit('D', rev_id='D-id', recursive=None)
 
2608
 
 
2609
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2610
            wt, 'E-id')
 
2611
        merger.merge_type = _mod_merge.Merge3Merger
 
2612
        merge_obj = merger.make_merger()
 
2613
        entries = list(merge_obj._entries_lca())
 
2614
        # Nothing interesting about this sub-tree, because content changes are
 
2615
        # computed at a higher level
 
2616
        self.assertEqual([], entries)
 
2617
 
 
2618
    def test_nested_tree_subtree_renamed(self):
 
2619
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2620
        # 'tree-reference'
 
2621
        wt = self.make_branch_and_tree('tree',
 
2622
            format='dirstate-with-subtree')
 
2623
        wt.lock_write()
 
2624
        self.addCleanup(wt.unlock)
 
2625
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2626
            format='dirstate-with-subtree')
 
2627
        wt.set_root_id('a-root-id')
 
2628
        sub_tree.set_root_id('sub-tree-root')
 
2629
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2630
        sub_tree.add('file')
 
2631
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2632
        wt.add_reference(sub_tree)
 
2633
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2634
        # Now create a criss-cross merge in the parent, without modifying the
 
2635
        # subtree
 
2636
        wt.commit('B', rev_id='B-id', recursive=None)
 
2637
        wt.set_last_revision('A-id')
 
2638
        wt.branch.set_last_revision_info(1, 'A-id')
 
2639
        wt.commit('C', rev_id='C-id', recursive=None)
 
2640
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2641
        wt.rename_one('sub', 'alt_sub')
 
2642
        wt.commit('E', rev_id='E-id', recursive=None)
 
2643
        wt.set_last_revision('B-id')
 
2644
        wt.revert()
 
2645
        wt.set_parent_ids(['B-id', 'C-id'])
 
2646
        wt.branch.set_last_revision_info(2, 'B-id')
 
2647
        wt.commit('D', rev_id='D-id', recursive=None)
 
2648
 
 
2649
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2650
            wt, 'E-id')
 
2651
        merger.merge_type = _mod_merge.Merge3Merger
 
2652
        merge_obj = merger.make_merger()
 
2653
        entries = list(merge_obj._entries_lca())
 
2654
        root_id = 'a-root-id'
 
2655
        self.assertEqual([('sub-tree-root', False,
 
2656
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2657
                           ((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
 
2658
                           ((False, [False, False]), False, False)),
 
2659
                         ], entries)
 
2660
 
 
2661
    def test_nested_tree_subtree_renamed_and_modified(self):
 
2662
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2663
        # 'tree-reference'
 
2664
        wt = self.make_branch_and_tree('tree',
 
2665
            format='dirstate-with-subtree')
 
2666
        wt.lock_write()
 
2667
        self.addCleanup(wt.unlock)
 
2668
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2669
            format='dirstate-with-subtree')
 
2670
        wt.set_root_id('a-root-id')
 
2671
        sub_tree.set_root_id('sub-tree-root')
 
2672
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2673
        sub_tree.add('file')
 
2674
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2675
        wt.add_reference(sub_tree)
 
2676
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2677
        # Now create a criss-cross merge in the parent, without modifying the
 
2678
        # subtree
 
2679
        wt.commit('B', rev_id='B-id', recursive=None)
 
2680
        wt.set_last_revision('A-id')
 
2681
        wt.branch.set_last_revision_info(1, 'A-id')
 
2682
        wt.commit('C', rev_id='C-id', recursive=None)
 
2683
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2684
        self.build_tree_contents([('tree/sub/file', 'text2')])
 
2685
        sub_tree.commit('modify contents', rev_id='sub-B-id')
 
2686
        wt.rename_one('sub', 'alt_sub')
 
2687
        wt.commit('E', rev_id='E-id', recursive=None)
 
2688
        wt.set_last_revision('B-id')
 
2689
        wt.revert()
 
2690
        wt.set_parent_ids(['B-id', 'C-id'])
 
2691
        wt.branch.set_last_revision_info(2, 'B-id')
 
2692
        wt.commit('D', rev_id='D-id', recursive=None)
 
2693
 
 
2694
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2695
            wt, 'E-id')
 
2696
        merger.merge_type = _mod_merge.Merge3Merger
 
2697
        merge_obj = merger.make_merger()
 
2698
        entries = list(merge_obj._entries_lca())
 
2699
        root_id = 'a-root-id'
 
2700
        self.assertEqual([('sub-tree-root', False,
 
2701
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2702
                           ((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
 
2703
                           ((False, [False, False]), False, False)),
 
2704
                         ], entries)
 
2705
 
 
2706
 
 
2707
class TestLCAMultiWay(tests.TestCase):
 
2708
 
 
2709
    def assertLCAMultiWay(self, expected, base, lcas, other, this,
 
2710
                          allow_overriding_lca=True):
 
2711
        self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
 
2712
                                (base, lcas), other, this,
 
2713
                                allow_overriding_lca=allow_overriding_lca))
 
2714
 
 
2715
    def test_other_equal_equal_lcas(self):
 
2716
        """Test when OTHER=LCA and all LCAs are identical."""
 
2717
        self.assertLCAMultiWay('this',
 
2718
            'bval', ['bval', 'bval'], 'bval', 'bval')
 
2719
        self.assertLCAMultiWay('this',
 
2720
            'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
 
2721
        self.assertLCAMultiWay('this',
 
2722
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
 
2723
        self.assertLCAMultiWay('this',
 
2724
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
 
2725
        self.assertLCAMultiWay('this',
 
2726
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
 
2727
 
 
2728
    def test_other_equal_this(self):
 
2729
        """Test when other and this are identical."""
 
2730
        self.assertLCAMultiWay('this',
 
2731
            'bval', ['bval', 'bval'], 'oval', 'oval')
 
2732
        self.assertLCAMultiWay('this',
 
2733
            'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
 
2734
        self.assertLCAMultiWay('this',
 
2735
            'bval', ['cval', 'dval'], 'oval', 'oval')
 
2736
        self.assertLCAMultiWay('this',
 
2737
            'bval', [None, 'lcaval'], 'oval', 'oval')
 
2738
        self.assertLCAMultiWay('this',
 
2739
            None, [None, 'lcaval'], 'oval', 'oval')
 
2740
        self.assertLCAMultiWay('this',
 
2741
            None, ['lcaval', 'lcaval'], 'oval', 'oval')
 
2742
        self.assertLCAMultiWay('this',
 
2743
            None, ['cval', 'dval'], 'oval', 'oval')
 
2744
        self.assertLCAMultiWay('this',
 
2745
            None, ['cval', 'dval'], None, None)
 
2746
        self.assertLCAMultiWay('this',
 
2747
            None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
 
2748
 
 
2749
    def test_no_lcas(self):
 
2750
        self.assertLCAMultiWay('this',
 
2751
            'bval', [], 'bval', 'tval')
 
2752
        self.assertLCAMultiWay('other',
 
2753
            'bval', [], 'oval', 'bval')
 
2754
        self.assertLCAMultiWay('conflict',
 
2755
            'bval', [], 'oval', 'tval')
 
2756
        self.assertLCAMultiWay('this',
 
2757
            'bval', [], 'oval', 'oval')
 
2758
 
 
2759
    def test_lca_supersedes_other_lca(self):
 
2760
        """If one lca == base, the other lca takes precedence"""
 
2761
        self.assertLCAMultiWay('this',
 
2762
            'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
 
2763
        self.assertLCAMultiWay('this',
 
2764
            'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
 
2765
        # This is actually considered a 'revert' because the 'lcaval' in LCAS
 
2766
        # supersedes the BASE val (in the other LCA) but then OTHER reverts it
 
2767
        # back to bval.
 
2768
        self.assertLCAMultiWay('other',
 
2769
            'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
 
2770
        self.assertLCAMultiWay('conflict',
 
2771
            'bval', ['bval', 'lcaval'], 'bval', 'tval')
 
2772
 
 
2773
    def test_other_and_this_pick_different_lca(self):
 
2774
        # OTHER and THIS resolve the lca conflict in different ways
 
2775
        self.assertLCAMultiWay('conflict',
 
2776
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
 
2777
        self.assertLCAMultiWay('conflict',
 
2778
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
 
2779
        self.assertLCAMultiWay('conflict',
 
2780
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
 
2781
 
 
2782
    def test_other_in_lca(self):
 
2783
        # OTHER takes a value of one of the LCAs, THIS takes a new value, which
 
2784
        # theoretically supersedes both LCA values and 'wins'
 
2785
        self.assertLCAMultiWay('this',
 
2786
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
 
2787
        self.assertLCAMultiWay('this',
 
2788
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval')
 
2789
        self.assertLCAMultiWay('conflict',
 
2790
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval',
 
2791
            allow_overriding_lca=False)
 
2792
        self.assertLCAMultiWay('conflict',
 
2793
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval',
 
2794
            allow_overriding_lca=False)
 
2795
        # THIS reverted back to BASE, but that is an explicit supersede of all
 
2796
        # LCAs
 
2797
        self.assertLCAMultiWay('this',
 
2798
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval')
 
2799
        self.assertLCAMultiWay('this',
 
2800
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
 
2801
        self.assertLCAMultiWay('conflict',
 
2802
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval',
 
2803
            allow_overriding_lca=False)
 
2804
        self.assertLCAMultiWay('conflict',
 
2805
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval',
 
2806
            allow_overriding_lca=False)
 
2807
 
 
2808
    def test_this_in_lca(self):
 
2809
        # THIS takes a value of one of the LCAs, OTHER takes a new value, which
 
2810
        # theoretically supersedes both LCA values and 'wins'
 
2811
        self.assertLCAMultiWay('other',
 
2812
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
 
2813
        self.assertLCAMultiWay('other',
 
2814
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
 
2815
        self.assertLCAMultiWay('conflict',
 
2816
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val',
 
2817
            allow_overriding_lca=False)
 
2818
        self.assertLCAMultiWay('conflict',
 
2819
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val',
 
2820
            allow_overriding_lca=False)
 
2821
        # OTHER reverted back to BASE, but that is an explicit supersede of all
 
2822
        # LCAs
 
2823
        self.assertLCAMultiWay('other',
 
2824
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val')
 
2825
        self.assertLCAMultiWay('conflict',
 
2826
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val',
 
2827
            allow_overriding_lca=False)
 
2828
 
 
2829
    def test_all_differ(self):
 
2830
        self.assertLCAMultiWay('conflict',
 
2831
            'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
 
2832
        self.assertLCAMultiWay('conflict',
 
2833
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
 
2834
        self.assertLCAMultiWay('conflict',
 
2835
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')