/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

First attempt to merge .dev and resolve the conflicts (but tests are 
failing)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 by Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
import os
18
18
from StringIO import StringIO
19
19
 
20
 
from bzrlib import conflicts
 
20
from bzrlib import (
 
21
    conflicts,
 
22
    errors,
 
23
    knit,
 
24
    merge as _mod_merge,
 
25
    option,
 
26
    progress,
 
27
    transform,
 
28
    versionedfile,
 
29
    )
21
30
from bzrlib.branch import Branch
22
 
from bzrlib.builtins import merge
23
31
from bzrlib.conflicts import ConflictList, TextConflict
24
32
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
25
 
from bzrlib.merge import transform_tree, merge_inner
26
 
from bzrlib.osutils import pathjoin
27
 
from bzrlib.revision import common_ancestor
28
 
from bzrlib.tests import TestCaseWithTransport
 
33
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
 
34
from bzrlib.osutils import pathjoin, file_kind
 
35
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
29
36
from bzrlib.trace import (enable_test_log, disable_test_log)
30
37
from bzrlib.workingtree import WorkingTree
31
38
 
37
44
        wt = self.make_branch_and_tree('.')
38
45
        rev_a = wt.commit("lala!")
39
46
        self.assertEqual([rev_a], wt.get_parent_ids())
40
 
        merge([u'.', -1], [None, None])
 
47
        self.assertRaises(errors.PointlessMerge, wt.merge_from_branch,
 
48
                          wt.branch)
41
49
        self.assertEqual([rev_a], wt.get_parent_ids())
 
50
        return wt
42
51
 
43
52
    def test_undo(self):
44
53
        wt = self.make_branch_and_tree('.')
45
54
        wt.commit("lala!")
46
55
        wt.commit("haha!")
47
56
        wt.commit("blabla!")
48
 
        merge([u'.', 2], [u'.', 1])
 
57
        wt.merge_from_branch(wt.branch, wt.branch.get_rev_id(2),
 
58
                             wt.branch.get_rev_id(1))
49
59
 
50
60
    def test_nocommits(self):
51
 
        self.test_pending()
 
61
        wt = self.test_pending()
52
62
        wt2 = self.make_branch_and_tree('branch2')
53
 
        self.assertRaises(NoCommits, merge, ['branch2', -1], 
54
 
                          [None, None])
55
 
        return wt2
 
63
        self.assertRaises(NoCommits, wt.merge_from_branch, wt2.branch)
 
64
        return wt, wt2
56
65
 
57
66
    def test_unrelated(self):
58
 
        wt2 = self.test_nocommits()
 
67
        wt, wt2 = self.test_nocommits()
59
68
        wt2.commit("blah")
60
 
        self.assertRaises(UnrelatedBranches, merge, ['branch2', -1], 
61
 
                          [None, None])
 
69
        self.assertRaises(UnrelatedBranches, wt.merge_from_branch, wt2.branch)
62
70
        return wt2
63
71
 
64
72
    def test_merge_one_file(self):
73
81
        wt1.add('bar')
74
82
        wt1.commit('add foobar')
75
83
        os.chdir('branch2')
76
 
        self.run_bzr('merge', '../branch1/baz', retcode=3)
77
 
        self.run_bzr('merge', '../branch1/foo')
 
84
        self.run_bzr('merge ../branch1/baz', retcode=3)
 
85
        self.run_bzr('merge ../branch1/foo')
78
86
        self.failUnlessExists('foo')
79
87
        self.failIfExists('bar')
80
88
        wt2 = WorkingTree.open('.') # opens branch2
81
89
        self.assertEqual([tip], wt2.get_parent_ids())
82
90
        
83
91
    def test_pending_with_null(self):
84
 
        """When base is forced to revno 0, pending_merges is set"""
 
92
        """When base is forced to revno 0, parent_ids are set"""
85
93
        wt2 = self.test_unrelated()
86
94
        wt1 = WorkingTree.open('.')
87
95
        br1 = wt1.branch
88
96
        br1.fetch(wt2.branch)
89
97
        # merge all of branch 2 into branch 1 even though they 
90
98
        # are not related.
91
 
        self.assertRaises(BzrCommandError, merge, ['branch2', -1],
92
 
                          ['branch2', 0], reprocess=True, show_base=True)
93
 
        merge(['branch2', -1], ['branch2', 0], reprocess=True)
 
99
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
94
100
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
95
101
            wt1.get_parent_ids())
96
102
        return (wt1, wt2.branch)
99
105
        """Merge base is sane when two unrelated branches are merged"""
100
106
        wt1, br2 = self.test_pending_with_null()
101
107
        wt1.commit("blah")
102
 
        last = wt1.branch.last_revision()
103
 
        self.assertEqual(common_ancestor(last, last, wt1.branch.repository), last)
 
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()
104
116
 
105
117
    def test_create_rename(self):
106
118
        """Rename an inventory entry while creating the file"""
132
144
        tree_a.commit(message="hello")
133
145
        dir_b = tree_a.bzrdir.sprout('b')
134
146
        tree_b = dir_b.open_workingtree()
 
147
        tree_b.lock_write()
 
148
        self.addCleanup(tree_b.unlock)
135
149
        tree_a.commit(message="hello again")
136
150
        log = StringIO()
137
151
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
138
152
                    this_tree=tree_b, ignore_zero=True)
139
 
        log = self._get_log()
 
153
        log = self._get_log(keep_log_file=True)
140
154
        self.failUnless('All changes applied successfully.\n' not in log)
141
 
        tree_b.revert([])
 
155
        tree_b.revert()
142
156
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
143
157
                    this_tree=tree_b, ignore_zero=False)
144
 
        log = self._get_log()
 
158
        log = self._get_log(keep_log_file=True)
145
159
        self.failUnless('All changes applied successfully.\n' in log)
146
160
 
147
161
    def test_merge_inner_conflicts(self):
155
169
        self.build_tree(['a/b/'])
156
170
        tree_a.add('b', 'b-id')
157
171
        tree_a.commit('added b')
158
 
        base_tree = tree_a.basis_tree()
 
172
        # basis_tree() is only guaranteed to be valid as long as it is actually
 
173
        # the basis tree. This mutates the tree after grabbing basis, so go to
 
174
        # the repository.
 
175
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
159
176
        tree_z = tree_a.bzrdir.sprout('z').open_workingtree()
160
177
        self.build_tree(['a/b/c'])
161
178
        tree_a.add('b/c')
167
184
            conflicts.MissingParent('Created directory', 'b', 'b-id'),
168
185
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
169
186
            tree_z.conflicts())
170
 
        merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree, 
 
187
        merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree,
171
188
                    this_tree=tree_a)
172
189
        self.assertEqual([
173
190
            conflicts.DeletingParent('Not deleting', 'b', 'b-id'),
174
191
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
175
192
            tree_a.conflicts())
 
193
 
 
194
    def test_nested_merge(self):
 
195
        tree = self.make_branch_and_tree('tree',
 
196
            format='dirstate-with-subtree')
 
197
        sub_tree = self.make_branch_and_tree('tree/sub-tree',
 
198
            format='dirstate-with-subtree')
 
199
        sub_tree.set_root_id('sub-tree-root')
 
200
        self.build_tree_contents([('tree/sub-tree/file', 'text1')])
 
201
        sub_tree.add('file')
 
202
        sub_tree.commit('foo')
 
203
        tree.add_reference(sub_tree)
 
204
        tree.commit('set text to 1')
 
205
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
206
        # modify the file in the subtree
 
207
        self.build_tree_contents([('tree2/sub-tree/file', 'text2')])
 
208
        # and merge the changes from the diverged subtree into the containing
 
209
        # tree
 
210
        tree2.commit('changed file text')
 
211
        tree.merge_from_branch(tree2.branch)
 
212
        self.assertFileEqual('text2', 'tree/sub-tree/file')
 
213
 
 
214
    def test_merge_with_missing(self):
 
215
        tree_a = self.make_branch_and_tree('tree_a')
 
216
        self.build_tree_contents([('tree_a/file', 'content_1')])
 
217
        tree_a.add('file')
 
218
        tree_a.commit('commit base')
 
219
        # basis_tree() is only guaranteed to be valid as long as it is actually
 
220
        # the basis tree. This mutates the tree after grabbing basis, so go to
 
221
        # the repository.
 
222
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
 
223
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
 
224
        self.build_tree_contents([('tree_a/file', 'content_2')])
 
225
        tree_a.commit('commit other')
 
226
        other_tree = tree_a.basis_tree()
 
227
        os.unlink('tree_b/file')
 
228
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
 
229
 
 
230
    def test_merge_kind_change(self):
 
231
        tree_a = self.make_branch_and_tree('tree_a')
 
232
        self.build_tree_contents([('tree_a/file', 'content_1')])
 
233
        tree_a.add('file', 'file-id')
 
234
        tree_a.commit('added file')
 
235
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
 
236
        os.unlink('tree_a/file')
 
237
        self.build_tree(['tree_a/file/'])
 
238
        tree_a.commit('changed file to directory')
 
239
        tree_b.merge_from_branch(tree_a.branch)
 
240
        self.assertEqual('directory', file_kind('tree_b/file'))
 
241
        tree_b.revert()
 
242
        self.assertEqual('file', file_kind('tree_b/file'))
 
243
        self.build_tree_contents([('tree_b/file', 'content_2')])
 
244
        tree_b.commit('content change')
 
245
        tree_b.merge_from_branch(tree_a.branch)
 
246
        self.assertEqual(tree_b.conflicts(),
 
247
                         [conflicts.ContentsConflict('file',
 
248
                          file_id='file-id')])
 
249
    
 
250
    def test_merge_type_registry(self):
 
251
        merge_type_option = option.Option.OPTIONS['merge-type']
 
252
        self.assertFalse('merge4' in [x[0] for x in 
 
253
                        merge_type_option.iter_switches()])
 
254
        registry = _mod_merge.get_merge_type_registry()
 
255
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
 
256
                               'time-travelling merge')
 
257
        self.assertTrue('merge4' in [x[0] for x in 
 
258
                        merge_type_option.iter_switches()])
 
259
        registry.remove('merge4')
 
260
        self.assertFalse('merge4' in [x[0] for x in 
 
261
                        merge_type_option.iter_switches()])
 
262
 
 
263
    def test_merge_other_moves_we_deleted(self):
 
264
        tree_a = self.make_branch_and_tree('A')
 
265
        tree_a.lock_write()
 
266
        self.addCleanup(tree_a.unlock)
 
267
        self.build_tree(['A/a'])
 
268
        tree_a.add('a')
 
269
        tree_a.commit('1', rev_id='rev-1')
 
270
        tree_a.flush()
 
271
        tree_a.rename_one('a', 'b')
 
272
        tree_a.commit('2')
 
273
        bzrdir_b = tree_a.bzrdir.sprout('B', revision_id='rev-1')
 
274
        tree_b = bzrdir_b.open_workingtree()
 
275
        tree_b.lock_write()
 
276
        self.addCleanup(tree_b.unlock)
 
277
        os.unlink('B/a')
 
278
        tree_b.commit('3')
 
279
        try:
 
280
            tree_b.merge_from_branch(tree_a.branch)
 
281
        except AttributeError:
 
282
            self.fail('tried to join a path when name was None')
 
283
 
 
284
    def test_merge_uncommitted_otherbasis_ancestor_of_thisbasis(self):
 
285
        tree_a = self.make_branch_and_tree('a')
 
286
        self.build_tree(['a/file_1', 'a/file_2'])
 
287
        tree_a.add(['file_1'])
 
288
        tree_a.commit('commit 1')
 
289
        tree_a.add(['file_2'])
 
290
        tree_a.commit('commit 2')
 
291
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
292
        tree_b.rename_one('file_1', 'renamed')
 
293
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
294
                                                    progress.DummyProgress())
 
295
        merger.merge_type = _mod_merge.Merge3Merger
 
296
        merger.do_merge()
 
297
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
 
298
 
 
299
    def test_merge_uncommitted_otherbasis_ancestor_of_thisbasis_weave(self):
 
300
        tree_a = self.make_branch_and_tree('a')
 
301
        self.build_tree(['a/file_1', 'a/file_2'])
 
302
        tree_a.add(['file_1'])
 
303
        tree_a.commit('commit 1')
 
304
        tree_a.add(['file_2'])
 
305
        tree_a.commit('commit 2')
 
306
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
307
        tree_b.rename_one('file_1', 'renamed')
 
308
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
309
                                                    progress.DummyProgress())
 
310
        merger.merge_type = _mod_merge.WeaveMerger
 
311
        merger.do_merge()
 
312
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
 
313
 
 
314
    def prepare_cherrypick(self):
 
315
        """Prepare a pair of trees for cherrypicking tests.
 
316
 
 
317
        Both trees have a file, 'file'.
 
318
        rev1 sets content to 'a'.
 
319
        rev2b adds 'b'.
 
320
        rev3b adds 'c'.
 
321
        A full merge of rev2b and rev3b into this_tree would add both 'b' and
 
322
        'c'.  A successful cherrypick of rev2b-rev3b into this_tree will add
 
323
        'c', but not 'b'.
 
324
        """
 
325
        this_tree = self.make_branch_and_tree('this')
 
326
        self.build_tree_contents([('this/file', "a\n")])
 
327
        this_tree.add('file')
 
328
        this_tree.commit('rev1')
 
329
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
330
        self.build_tree_contents([('other/file', "a\nb\n")])
 
331
        other_tree.commit('rev2b', rev_id='rev2b')
 
332
        self.build_tree_contents([('other/file', "c\na\nb\n")])
 
333
        other_tree.commit('rev3b', rev_id='rev3b')
 
334
        this_tree.lock_write()
 
335
        self.addCleanup(this_tree.unlock)
 
336
        return this_tree, other_tree
 
337
 
 
338
    def test_weave_cherrypick(self):
 
339
        this_tree, other_tree = self.prepare_cherrypick()
 
340
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
341
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
 
342
        merger.merge_type = _mod_merge.WeaveMerger
 
343
        merger.do_merge()
 
344
        self.assertFileEqual('c\na\n', 'this/file')
 
345
 
 
346
    def test_weave_cannot_reverse_cherrypick(self):
 
347
        this_tree, other_tree = self.prepare_cherrypick()
 
348
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
349
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
 
350
        merger.merge_type = _mod_merge.WeaveMerger
 
351
        self.assertRaises(errors.CannotReverseCherrypick, merger.do_merge)
 
352
 
 
353
    def test_merge3_can_reverse_cherrypick(self):
 
354
        this_tree, other_tree = self.prepare_cherrypick()
 
355
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
356
            this_tree, 'rev2b', 'rev3b', other_tree.branch)
 
357
        merger.merge_type = _mod_merge.Merge3Merger
 
358
        merger.do_merge()
 
359
 
 
360
    def test_merge3_will_detect_cherrypick(self):
 
361
        this_tree = self.make_branch_and_tree('this')
 
362
        self.build_tree_contents([('this/file', "a\n")])
 
363
        this_tree.add('file')
 
364
        this_tree.commit('rev1')
 
365
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
366
        self.build_tree_contents([('other/file', "a\nb\n")])
 
367
        other_tree.commit('rev2b', rev_id='rev2b')
 
368
        self.build_tree_contents([('other/file', "a\nb\nc\n")])
 
369
        other_tree.commit('rev3b', rev_id='rev3b')
 
370
        this_tree.lock_write()
 
371
        self.addCleanup(this_tree.unlock)
 
372
 
 
373
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
374
            this_tree, 'rev3b', 'rev2b', other_tree.branch)
 
375
        merger.merge_type = _mod_merge.Merge3Merger
 
376
        merger.do_merge()
 
377
        self.assertFileEqual('a\n'
 
378
                             '<<<<<<< TREE\n'
 
379
                             '=======\n'
 
380
                             'c\n'
 
381
                             '>>>>>>> MERGE-SOURCE\n',
 
382
                             'this/file')
 
383
 
 
384
    def test_make_merger(self):
 
385
        this_tree = self.make_branch_and_tree('this')
 
386
        this_tree.commit('rev1', rev_id='rev1')
 
387
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
388
        this_tree.commit('rev2', rev_id='rev2a')
 
389
        other_tree.commit('rev2', rev_id='rev2b')
 
390
        this_tree.lock_write()
 
391
        self.addCleanup(this_tree.unlock)
 
392
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress,
 
393
            this_tree, 'rev2b', other_branch=other_tree.branch)
 
394
        merger.merge_type = _mod_merge.Merge3Merger
 
395
        tree_merger = merger.make_merger()
 
396
        self.assertIs(_mod_merge.Merge3Merger, tree_merger.__class__)
 
397
        self.assertEqual('rev2b', tree_merger.other_tree.get_revision_id())
 
398
        self.assertEqual('rev1', tree_merger.base_tree.get_revision_id())
 
399
 
 
400
    def test_make_preview_transform(self):
 
401
        this_tree = self.make_branch_and_tree('this')
 
402
        self.build_tree_contents([('this/file', '1\n')])
 
403
        this_tree.add('file', 'file-id')
 
404
        this_tree.commit('rev1', rev_id='rev1')
 
405
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
406
        self.build_tree_contents([('this/file', '1\n2a\n')])
 
407
        this_tree.commit('rev2', rev_id='rev2a')
 
408
        self.build_tree_contents([('other/file', '2b\n1\n')])
 
409
        other_tree.commit('rev2', rev_id='rev2b')
 
410
        this_tree.lock_write()
 
411
        self.addCleanup(this_tree.unlock)
 
412
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
413
            this_tree, 'rev2b', other_branch=other_tree.branch)
 
414
        merger.merge_type = _mod_merge.Merge3Merger
 
415
        tree_merger = merger.make_merger()
 
416
        tt = tree_merger.make_preview_transform()
 
417
        self.addCleanup(tt.finalize)
 
418
        preview_tree = tt.get_preview_tree()
 
419
        tree_file = this_tree.get_file('file-id')
 
420
        try:
 
421
            self.assertEqual('1\n2a\n', tree_file.read())
 
422
        finally:
 
423
            tree_file.close()
 
424
        preview_file = preview_tree.get_file('file-id')
 
425
        try:
 
426
            self.assertEqual('2b\n1\n2a\n', preview_file.read())
 
427
        finally:
 
428
            preview_file.close()
 
429
 
 
430
    def test_do_merge(self):
 
431
        this_tree = self.make_branch_and_tree('this')
 
432
        self.build_tree_contents([('this/file', '1\n')])
 
433
        this_tree.add('file', 'file-id')
 
434
        this_tree.commit('rev1', rev_id='rev1')
 
435
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
436
        self.build_tree_contents([('this/file', '1\n2a\n')])
 
437
        this_tree.commit('rev2', rev_id='rev2a')
 
438
        self.build_tree_contents([('other/file', '2b\n1\n')])
 
439
        other_tree.commit('rev2', rev_id='rev2b')
 
440
        this_tree.lock_write()
 
441
        self.addCleanup(this_tree.unlock)
 
442
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
443
            this_tree, 'rev2b', other_branch=other_tree.branch)
 
444
        merger.merge_type = _mod_merge.Merge3Merger
 
445
        tree_merger = merger.make_merger()
 
446
        tt = tree_merger.do_merge()
 
447
        tree_file = this_tree.get_file('file-id')
 
448
        try:
 
449
            self.assertEqual('2b\n1\n2a\n', tree_file.read())
 
450
        finally:
 
451
            tree_file.close()
 
452
 
 
453
    def test_merge_add_into_deleted_root(self):
 
454
        # Yes, people actually do this.  And report bugs if it breaks.
 
455
        source = self.make_branch_and_tree('source', format='rich-root-pack')
 
456
        self.build_tree(['source/foo/'])
 
457
        source.add('foo', 'foo-id')
 
458
        source.commit('Add foo')
 
459
        target = source.bzrdir.sprout('target').open_workingtree()
 
460
        subtree = target.extract('foo-id')
 
461
        subtree.commit('Delete root')
 
462
        self.build_tree(['source/bar'])
 
463
        source.add('bar', 'bar-id')
 
464
        source.commit('Add bar')
 
465
        subtree.merge_from_branch(source.branch)
 
466
 
 
467
 
 
468
class TestPlanMerge(TestCaseWithMemoryTransport):
 
469
 
 
470
    def setUp(self):
 
471
        TestCaseWithMemoryTransport.setUp(self)
 
472
        mapper = versionedfile.PrefixMapper()
 
473
        factory = knit.make_file_factory(True, mapper)
 
474
        self.vf = factory(self.get_transport())
 
475
        self.plan_merge_vf = versionedfile._PlanMergeVersionedFile('root')
 
476
        self.plan_merge_vf.fallback_versionedfiles.append(self.vf)
 
477
 
 
478
    def add_version(self, key, parents, text):
 
479
        self.vf.add_lines(key, parents, [c+'\n' for c in text])
 
480
 
 
481
    def add_rev(self, prefix, revision_id, parents, text):
 
482
        self.add_version((prefix, revision_id), [(prefix, p) for p in parents],
 
483
                         text)
 
484
 
 
485
    def add_uncommitted_version(self, key, parents, text):
 
486
        self.plan_merge_vf.add_lines(key, parents,
 
487
                                     [c+'\n' for c in text])
 
488
 
 
489
    def setup_plan_merge(self):
 
490
        self.add_rev('root', 'A', [], 'abc')
 
491
        self.add_rev('root', 'B', ['A'], 'acehg')
 
492
        self.add_rev('root', 'C', ['A'], 'fabg')
 
493
        return _PlanMerge('B', 'C', self.plan_merge_vf, ('root',))
 
494
 
 
495
    def setup_plan_merge_uncommitted(self):
 
496
        self.add_version(('root', 'A'), [], 'abc')
 
497
        self.add_uncommitted_version(('root', 'B:'), [('root', 'A')], 'acehg')
 
498
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
 
499
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
 
500
 
 
501
    def test_unique_lines(self):
 
502
        plan = self.setup_plan_merge()
 
503
        self.assertEqual(plan._unique_lines(
 
504
            plan._get_matching_blocks('B', 'C')),
 
505
            ([1, 2, 3], [0, 2]))
 
506
 
 
507
    def test_plan_merge(self):
 
508
        self.setup_plan_merge()
 
509
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
510
        self.assertEqual([
 
511
                          ('new-b', 'f\n'),
 
512
                          ('unchanged', 'a\n'),
 
513
                          ('killed-a', 'b\n'),
 
514
                          ('killed-b', 'c\n'),
 
515
                          ('new-a', 'e\n'),
 
516
                          ('new-a', 'h\n'),
 
517
                          ('new-a', 'g\n'),
 
518
                          ('new-b', 'g\n')],
 
519
                         list(plan))
 
520
 
 
521
    def test_plan_merge_cherrypick(self):
 
522
        self.add_rev('root', 'A', [], 'abc')
 
523
        self.add_rev('root', 'B', ['A'], 'abcde')
 
524
        self.add_rev('root', 'C', ['A'], 'abcefg')
 
525
        self.add_rev('root', 'D', ['A', 'B', 'C'], 'abcdegh')
 
526
        my_plan = _PlanMerge('B', 'D', self.plan_merge_vf, ('root',))
 
527
        # We shortcut when one text supersedes the other in the per-file graph.
 
528
        # We don't actually need to compare the texts at this point.
 
529
        self.assertEqual([
 
530
                          ('new-b', 'a\n'),
 
531
                          ('new-b', 'b\n'),
 
532
                          ('new-b', 'c\n'),
 
533
                          ('new-b', 'd\n'),
 
534
                          ('new-b', 'e\n'),
 
535
                          ('new-b', 'g\n'),
 
536
                          ('new-b', 'h\n')],
 
537
                          list(my_plan.plan_merge()))
 
538
 
 
539
    def test_plan_merge_no_common_ancestor(self):
 
540
        self.add_rev('root', 'A', [], 'abc')
 
541
        self.add_rev('root', 'B', [], 'xyz')
 
542
        my_plan = _PlanMerge('A', 'B', self.plan_merge_vf, ('root',))
 
543
        self.assertEqual([
 
544
                          ('new-a', 'a\n'),
 
545
                          ('new-a', 'b\n'),
 
546
                          ('new-a', 'c\n'),
 
547
                          ('new-b', 'x\n'),
 
548
                          ('new-b', 'y\n'),
 
549
                          ('new-b', 'z\n')],
 
550
                          list(my_plan.plan_merge()))
 
551
 
 
552
    def test_plan_merge_tail_ancestors(self):
 
553
        # The graph looks like this:
 
554
        #       A       # Common to all ancestors
 
555
        #      / \
 
556
        #     B   C     # Ancestors of E, only common to one side
 
557
        #     |\ /|
 
558
        #     D E F     # D, F are unique to G, H respectively
 
559
        #     |/ \|     # E is the LCA for G & H, and the unique LCA for
 
560
        #     G   H     # I, J
 
561
        #     |\ /|
 
562
        #     | X |
 
563
        #     |/ \|
 
564
        #     I   J     # criss-cross merge of G, H
 
565
        #
 
566
        # In this situation, a simple pruning of ancestors of E will leave D &
 
567
        # F "dangling", which looks like they introduce lines different from
 
568
        # the ones in E, but in actuality C&B introduced the lines, and they
 
569
        # are already present in E
 
570
 
 
571
        # Introduce the base text
 
572
        self.add_rev('root', 'A', [], 'abc')
 
573
        # Introduces a new line B
 
574
        self.add_rev('root', 'B', ['A'], 'aBbc')
 
575
        # Introduces a new line C
 
576
        self.add_rev('root', 'C', ['A'], 'abCc')
 
577
        # Introduce new line D
 
578
        self.add_rev('root', 'D', ['B'], 'DaBbc')
 
579
        # Merges B and C by just incorporating both
 
580
        self.add_rev('root', 'E', ['B', 'C'], 'aBbCc')
 
581
        # Introduce new line F
 
582
        self.add_rev('root', 'F', ['C'], 'abCcF')
 
583
        # Merge D & E by just combining the texts
 
584
        self.add_rev('root', 'G', ['D', 'E'], 'DaBbCc')
 
585
        # Merge F & E by just combining the texts
 
586
        self.add_rev('root', 'H', ['F', 'E'], 'aBbCcF')
 
587
        # Merge G & H by just combining texts
 
588
        self.add_rev('root', 'I', ['G', 'H'], 'DaBbCcF')
 
589
        # Merge G & H but supersede an old line in B
 
590
        self.add_rev('root', 'J', ['H', 'G'], 'DaJbCcF')
 
591
        plan = self.plan_merge_vf.plan_merge('I', 'J')
 
592
        self.assertEqual([
 
593
                          ('unchanged', 'D\n'),
 
594
                          ('unchanged', 'a\n'),
 
595
                          ('killed-b', 'B\n'),
 
596
                          ('new-b', 'J\n'),
 
597
                          ('unchanged', 'b\n'),
 
598
                          ('unchanged', 'C\n'),
 
599
                          ('unchanged', 'c\n'),
 
600
                          ('unchanged', 'F\n')],
 
601
                         list(plan))
 
602
 
 
603
    def test_plan_merge_tail_triple_ancestors(self):
 
604
        # The graph looks like this:
 
605
        #       A       # Common to all ancestors
 
606
        #      / \
 
607
        #     B   C     # Ancestors of E, only common to one side
 
608
        #     |\ /|
 
609
        #     D E F     # D, F are unique to G, H respectively
 
610
        #     |/|\|     # E is the LCA for G & H, and the unique LCA for
 
611
        #     G Q H     # I, J
 
612
        #     |\ /|     # Q is just an extra node which is merged into both
 
613
        #     | X |     # I and J
 
614
        #     |/ \|
 
615
        #     I   J     # criss-cross merge of G, H
 
616
        #
 
617
        # This is the same as the test_plan_merge_tail_ancestors, except we add
 
618
        # a third LCA that doesn't add new lines, but will trigger our more
 
619
        # involved ancestry logic
 
620
 
 
621
        self.add_rev('root', 'A', [], 'abc')
 
622
        self.add_rev('root', 'B', ['A'], 'aBbc')
 
623
        self.add_rev('root', 'C', ['A'], 'abCc')
 
624
        self.add_rev('root', 'D', ['B'], 'DaBbc')
 
625
        self.add_rev('root', 'E', ['B', 'C'], 'aBbCc')
 
626
        self.add_rev('root', 'F', ['C'], 'abCcF')
 
627
        self.add_rev('root', 'G', ['D', 'E'], 'DaBbCc')
 
628
        self.add_rev('root', 'H', ['F', 'E'], 'aBbCcF')
 
629
        self.add_rev('root', 'Q', ['E'], 'aBbCc')
 
630
        self.add_rev('root', 'I', ['G', 'Q', 'H'], 'DaBbCcF')
 
631
        # Merge G & H but supersede an old line in B
 
632
        self.add_rev('root', 'J', ['H', 'Q', 'G'], 'DaJbCcF')
 
633
        plan = self.plan_merge_vf.plan_merge('I', 'J')
 
634
        self.assertEqual([
 
635
                          ('unchanged', 'D\n'),
 
636
                          ('unchanged', 'a\n'),
 
637
                          ('killed-b', 'B\n'),
 
638
                          ('new-b', 'J\n'),
 
639
                          ('unchanged', 'b\n'),
 
640
                          ('unchanged', 'C\n'),
 
641
                          ('unchanged', 'c\n'),
 
642
                          ('unchanged', 'F\n')],
 
643
                         list(plan))
 
644
 
 
645
    def test_plan_merge_2_tail_triple_ancestors(self):
 
646
        # The graph looks like this:
 
647
        #     A   B     # 2 tails going back to NULL
 
648
        #     |\ /|
 
649
        #     D E F     # D, is unique to G, F to H
 
650
        #     |/|\|     # E is the LCA for G & H, and the unique LCA for
 
651
        #     G Q H     # I, J
 
652
        #     |\ /|     # Q is just an extra node which is merged into both
 
653
        #     | X |     # I and J
 
654
        #     |/ \|
 
655
        #     I   J     # criss-cross merge of G, H (and Q)
 
656
        #
 
657
 
 
658
        # This is meant to test after hitting a 3-way LCA, and multiple tail
 
659
        # ancestors (only have NULL_REVISION in common)
 
660
 
 
661
        self.add_rev('root', 'A', [], 'abc')
 
662
        self.add_rev('root', 'B', [], 'def')
 
663
        self.add_rev('root', 'D', ['A'], 'Dabc')
 
664
        self.add_rev('root', 'E', ['A', 'B'], 'abcdef')
 
665
        self.add_rev('root', 'F', ['B'], 'defF')
 
666
        self.add_rev('root', 'G', ['D', 'E'], 'Dabcdef')
 
667
        self.add_rev('root', 'H', ['F', 'E'], 'abcdefF')
 
668
        self.add_rev('root', 'Q', ['E'], 'abcdef')
 
669
        self.add_rev('root', 'I', ['G', 'Q', 'H'], 'DabcdefF')
 
670
        # Merge G & H but supersede an old line in B
 
671
        self.add_rev('root', 'J', ['H', 'Q', 'G'], 'DabcdJfF')
 
672
        plan = self.plan_merge_vf.plan_merge('I', 'J')
 
673
        self.assertEqual([
 
674
                          ('unchanged', 'D\n'),
 
675
                          ('unchanged', 'a\n'),
 
676
                          ('unchanged', 'b\n'),
 
677
                          ('unchanged', 'c\n'),
 
678
                          ('unchanged', 'd\n'),
 
679
                          ('killed-b', 'e\n'),
 
680
                          ('new-b', 'J\n'),
 
681
                          ('unchanged', 'f\n'),
 
682
                          ('unchanged', 'F\n')],
 
683
                         list(plan))
 
684
 
 
685
    def test_plan_merge_uncommitted_files(self):
 
686
        self.setup_plan_merge_uncommitted()
 
687
        plan = self.plan_merge_vf.plan_merge('B:', 'C:')
 
688
        self.assertEqual([
 
689
                          ('new-b', 'f\n'),
 
690
                          ('unchanged', 'a\n'),
 
691
                          ('killed-a', 'b\n'),
 
692
                          ('killed-b', 'c\n'),
 
693
                          ('new-a', 'e\n'),
 
694
                          ('new-a', 'h\n'),
 
695
                          ('new-a', 'g\n'),
 
696
                          ('new-b', 'g\n')],
 
697
                         list(plan))
 
698
 
 
699
    def test_plan_merge_insert_order(self):
 
700
        """Weave merges are sensitive to the order of insertion.
 
701
        
 
702
        Specifically for overlapping regions, it effects which region gets put
 
703
        'first'. And when a user resolves an overlapping merge, if they use the
 
704
        same ordering, then the lines match the parents, if they don't only
 
705
        *some* of the lines match.
 
706
        """
 
707
        self.add_rev('root', 'A', [], 'abcdef')
 
708
        self.add_rev('root', 'B', ['A'], 'abwxcdef')
 
709
        self.add_rev('root', 'C', ['A'], 'abyzcdef')
 
710
        # Merge, and resolve the conflict by adding *both* sets of lines
 
711
        # If we get the ordering wrong, these will look like new lines in D,
 
712
        # rather than carried over from B, C
 
713
        self.add_rev('root', 'D', ['B', 'C'],
 
714
                         'abwxyzcdef')
 
715
        # Supersede the lines in B and delete the lines in C, which will
 
716
        # conflict if they are treated as being in D
 
717
        self.add_rev('root', 'E', ['C', 'B'],
 
718
                         'abnocdef')
 
719
        # Same thing for the lines in C
 
720
        self.add_rev('root', 'F', ['C'], 'abpqcdef')
 
721
        plan = self.plan_merge_vf.plan_merge('D', 'E')
 
722
        self.assertEqual([
 
723
                          ('unchanged', 'a\n'),
 
724
                          ('unchanged', 'b\n'),
 
725
                          ('killed-b', 'w\n'),
 
726
                          ('killed-b', 'x\n'),
 
727
                          ('killed-b', 'y\n'),
 
728
                          ('killed-b', 'z\n'),
 
729
                          ('new-b', 'n\n'),
 
730
                          ('new-b', 'o\n'),
 
731
                          ('unchanged', 'c\n'),
 
732
                          ('unchanged', 'd\n'),
 
733
                          ('unchanged', 'e\n'),
 
734
                          ('unchanged', 'f\n')],
 
735
                         list(plan))
 
736
        plan = self.plan_merge_vf.plan_merge('E', 'D')
 
737
        # Going in the opposite direction shows the effect of the opposite plan
 
738
        self.assertEqual([
 
739
                          ('unchanged', 'a\n'),
 
740
                          ('unchanged', 'b\n'),
 
741
                          ('new-b', 'w\n'),
 
742
                          ('new-b', 'x\n'),
 
743
                          ('killed-a', 'y\n'),
 
744
                          ('killed-a', 'z\n'),
 
745
                          ('killed-both', 'w\n'),
 
746
                          ('killed-both', 'x\n'),
 
747
                          ('new-a', 'n\n'),
 
748
                          ('new-a', 'o\n'),
 
749
                          ('unchanged', 'c\n'),
 
750
                          ('unchanged', 'd\n'),
 
751
                          ('unchanged', 'e\n'),
 
752
                          ('unchanged', 'f\n')],
 
753
                         list(plan))
 
754
 
 
755
    def test_plan_merge_criss_cross(self):
 
756
        # This is specificly trying to trigger problems when using limited
 
757
        # ancestry and weaves. The ancestry graph looks like:
 
758
        #       XX      unused ancestor, should not show up in the weave
 
759
        #       |
 
760
        #       A       Unique LCA
 
761
        #       |\
 
762
        #       B \     Introduces a line 'foo'
 
763
        #      / \ \
 
764
        #     C   D E   C & D both have 'foo', E has different changes
 
765
        #     |\ /| |
 
766
        #     | X | |
 
767
        #     |/ \|/
 
768
        #     F   G      All of C, D, E are merged into F and G, so they are
 
769
        #                all common ancestors.
 
770
        #
 
771
        # The specific issue with weaves:
 
772
        #   B introduced a text ('foo') that is present in both C and D.
 
773
        #   If we do not include B (because it isn't an ancestor of E), then
 
774
        #   the A=>C and A=>D look like both sides independently introduce the
 
775
        #   text ('foo'). If F does not modify the text, it would still appear
 
776
        #   to have deleted on of the versions from C or D. If G then modifies
 
777
        #   'foo', it should appear as superseding the value in F (since it
 
778
        #   came from B), rather than conflict because of the resolution during
 
779
        #   C & D.
 
780
        self.add_rev('root', 'XX', [], 'qrs')
 
781
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
782
        self.add_rev('root', 'B', ['A'], 'axcdef')
 
783
        self.add_rev('root', 'C', ['B'], 'axcdefg')
 
784
        self.add_rev('root', 'D', ['B'], 'haxcdef')
 
785
        self.add_rev('root', 'E', ['A'], 'abcdyf')
 
786
        # Simple combining of all texts
 
787
        self.add_rev('root', 'F', ['C', 'D', 'E'], 'haxcdyfg')
 
788
        # combine and supersede 'x'
 
789
        self.add_rev('root', 'G', ['C', 'D', 'E'], 'hazcdyfg')
 
790
        plan = self.plan_merge_vf.plan_merge('F', 'G')
 
791
        self.assertEqual([
 
792
                          ('unchanged', 'h\n'),
 
793
                          ('unchanged', 'a\n'),
 
794
                          ('killed-base', 'b\n'),
 
795
                          ('killed-b', 'x\n'),
 
796
                          ('new-b', 'z\n'),
 
797
                          ('unchanged', 'c\n'),
 
798
                          ('unchanged', 'd\n'),
 
799
                          ('killed-base', 'e\n'),
 
800
                          ('unchanged', 'y\n'),
 
801
                          ('unchanged', 'f\n'),
 
802
                          ('unchanged', 'g\n')],
 
803
                         list(plan))
 
804
 
 
805
    def assertRemoveExternalReferences(self, filtered_parent_map,
 
806
                                       child_map, tails, parent_map):
 
807
        """Assert results for _PlanMerge._remove_external_references."""
 
808
        (act_filtered_parent_map, act_child_map,
 
809
         act_tails) = _PlanMerge._remove_external_references(parent_map)
 
810
 
 
811
        # The parent map *should* preserve ordering, but the ordering of
 
812
        # children is not strictly defined
 
813
        # child_map = dict((k, sorted(children))
 
814
        #                  for k, children in child_map.iteritems())
 
815
        # act_child_map = dict(k, sorted(children)
 
816
        #                      for k, children in act_child_map.iteritems())
 
817
        self.assertEqual(filtered_parent_map, act_filtered_parent_map)
 
818
        self.assertEqual(child_map, act_child_map)
 
819
        self.assertEqual(sorted(tails), sorted(act_tails))
 
820
 
 
821
    def test__remove_external_references(self):
 
822
        # First, nothing to remove
 
823
        self.assertRemoveExternalReferences({3: [2], 2: [1], 1: []},
 
824
            {1: [2], 2: [3], 3: []}, [1], {3: [2], 2: [1], 1: []})
 
825
        # The reverse direction
 
826
        self.assertRemoveExternalReferences({1: [2], 2: [3], 3: []},
 
827
            {3: [2], 2: [1], 1: []}, [3], {1: [2], 2: [3], 3: []})
 
828
        # Extra references
 
829
        self.assertRemoveExternalReferences({3: [2], 2: [1], 1: []},
 
830
            {1: [2], 2: [3], 3: []}, [1], {3: [2, 4], 2: [1, 5], 1: [6]})
 
831
        # Multiple tails
 
832
        self.assertRemoveExternalReferences(
 
833
            {4: [2, 3], 3: [], 2: [1], 1: []},
 
834
            {1: [2], 2: [4], 3: [4], 4: []},
 
835
            [1, 3],
 
836
            {4: [2, 3], 3: [5], 2: [1], 1: [6]})
 
837
        # Multiple children
 
838
        self.assertRemoveExternalReferences(
 
839
            {1: [3], 2: [3, 4], 3: [], 4: []},
 
840
            {1: [], 2: [], 3: [1, 2], 4: [2]},
 
841
            [3, 4],
 
842
            {1: [3], 2: [3, 4], 3: [5], 4: []})
 
843
 
 
844
    def assertPruneTails(self, pruned_map, tails, parent_map):
 
845
        child_map = {}
 
846
        for key, parent_keys in parent_map.iteritems():
 
847
            child_map.setdefault(key, [])
 
848
            for pkey in parent_keys:
 
849
                child_map.setdefault(pkey, []).append(key)
 
850
        _PlanMerge._prune_tails(parent_map, child_map, tails)
 
851
        self.assertEqual(pruned_map, parent_map)
 
852
 
 
853
    def test__prune_tails(self):
 
854
        # Nothing requested to prune
 
855
        self.assertPruneTails({1: [], 2: [], 3: []}, [],
 
856
                              {1: [], 2: [], 3: []})
 
857
        # Prune a single entry
 
858
        self.assertPruneTails({1: [], 3: []}, [2],
 
859
                              {1: [], 2: [], 3: []})
 
860
        # Prune a chain
 
861
        self.assertPruneTails({1: []}, [3],
 
862
                              {1: [], 2: [3], 3: []})
 
863
        # Prune a chain with a diamond
 
864
        self.assertPruneTails({1: []}, [5],
 
865
                              {1: [], 2: [3, 4], 3: [5], 4: [5], 5: []})
 
866
        # Prune a partial chain
 
867
        self.assertPruneTails({1: [6], 6:[]}, [5],
 
868
                              {1: [2, 6], 2: [3, 4], 3: [5], 4: [5], 5: [],
 
869
                               6: []})
 
870
        # Prune a chain with multiple tips, that pulls out intermediates
 
871
        self.assertPruneTails({1:[3], 3:[]}, [4, 5],
 
872
                              {1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
 
873
        self.assertPruneTails({1:[3], 3:[]}, [5, 4],
 
874
                              {1: [2, 3], 2: [4, 5], 3: [], 4:[], 5:[]})
 
875
 
 
876
    def test_subtract_plans(self):
 
877
        old_plan = [
 
878
        ('unchanged', 'a\n'),
 
879
        ('new-a', 'b\n'),
 
880
        ('killed-a', 'c\n'),
 
881
        ('new-b', 'd\n'),
 
882
        ('new-b', 'e\n'),
 
883
        ('killed-b', 'f\n'),
 
884
        ('killed-b', 'g\n'),
 
885
        ]
 
886
        new_plan = [
 
887
        ('unchanged', 'a\n'),
 
888
        ('new-a', 'b\n'),
 
889
        ('killed-a', 'c\n'),
 
890
        ('new-b', 'd\n'),
 
891
        ('new-b', 'h\n'),
 
892
        ('killed-b', 'f\n'),
 
893
        ('killed-b', 'i\n'),
 
894
        ]
 
895
        subtracted_plan = [
 
896
        ('unchanged', 'a\n'),
 
897
        ('new-a', 'b\n'),
 
898
        ('killed-a', 'c\n'),
 
899
        ('new-b', 'h\n'),
 
900
        ('unchanged', 'f\n'),
 
901
        ('killed-b', 'i\n'),
 
902
        ]
 
903
        self.assertEqual(subtracted_plan,
 
904
            list(_PlanMerge._subtract_plans(old_plan, new_plan)))
 
905
 
 
906
    def setup_merge_with_base(self):
 
907
        self.add_rev('root', 'COMMON', [], 'abc')
 
908
        self.add_rev('root', 'THIS', ['COMMON'], 'abcd')
 
909
        self.add_rev('root', 'BASE', ['COMMON'], 'eabc')
 
910
        self.add_rev('root', 'OTHER', ['BASE'], 'eafb')
 
911
 
 
912
    def test_plan_merge_with_base(self):
 
913
        self.setup_merge_with_base()
 
914
        plan = self.plan_merge_vf.plan_merge('THIS', 'OTHER', 'BASE')
 
915
        self.assertEqual([('unchanged', 'a\n'),
 
916
                          ('new-b', 'f\n'),
 
917
                          ('unchanged', 'b\n'),
 
918
                          ('killed-b', 'c\n'),
 
919
                          ('new-a', 'd\n')
 
920
                         ], list(plan))
 
921
 
 
922
    def test_plan_lca_merge(self):
 
923
        self.setup_plan_merge()
 
924
        plan = self.plan_merge_vf.plan_lca_merge('B', 'C')
 
925
        self.assertEqual([
 
926
                          ('new-b', 'f\n'),
 
927
                          ('unchanged', 'a\n'),
 
928
                          ('killed-b', 'c\n'),
 
929
                          ('new-a', 'e\n'),
 
930
                          ('new-a', 'h\n'),
 
931
                          ('killed-a', 'b\n'),
 
932
                          ('unchanged', 'g\n')],
 
933
                         list(plan))
 
934
 
 
935
    def test_plan_lca_merge_uncommitted_files(self):
 
936
        self.setup_plan_merge_uncommitted()
 
937
        plan = self.plan_merge_vf.plan_lca_merge('B:', 'C:')
 
938
        self.assertEqual([
 
939
                          ('new-b', 'f\n'),
 
940
                          ('unchanged', 'a\n'),
 
941
                          ('killed-b', 'c\n'),
 
942
                          ('new-a', 'e\n'),
 
943
                          ('new-a', 'h\n'),
 
944
                          ('killed-a', 'b\n'),
 
945
                          ('unchanged', 'g\n')],
 
946
                         list(plan))
 
947
 
 
948
    def test_plan_lca_merge_with_base(self):
 
949
        self.setup_merge_with_base()
 
950
        plan = self.plan_merge_vf.plan_lca_merge('THIS', 'OTHER', 'BASE')
 
951
        self.assertEqual([('unchanged', 'a\n'),
 
952
                          ('new-b', 'f\n'),
 
953
                          ('unchanged', 'b\n'),
 
954
                          ('killed-b', 'c\n'),
 
955
                          ('new-a', 'd\n')
 
956
                         ], list(plan))
 
957
 
 
958
    def test_plan_lca_merge_with_criss_cross(self):
 
959
        self.add_version(('root', 'ROOT'), [], 'abc')
 
960
        # each side makes a change
 
961
        self.add_version(('root', 'REV1'), [('root', 'ROOT')], 'abcd')
 
962
        self.add_version(('root', 'REV2'), [('root', 'ROOT')], 'abce')
 
963
        # both sides merge, discarding others' changes
 
964
        self.add_version(('root', 'LCA1'),
 
965
            [('root', 'REV1'), ('root', 'REV2')], 'abcd')
 
966
        self.add_version(('root', 'LCA2'),
 
967
            [('root', 'REV1'), ('root', 'REV2')], 'fabce')
 
968
        plan = self.plan_merge_vf.plan_lca_merge('LCA1', 'LCA2')
 
969
        self.assertEqual([('new-b', 'f\n'),
 
970
                          ('unchanged', 'a\n'),
 
971
                          ('unchanged', 'b\n'),
 
972
                          ('unchanged', 'c\n'),
 
973
                          ('conflicted-a', 'd\n'),
 
974
                          ('conflicted-b', 'e\n'),
 
975
                         ], list(plan))
 
976
 
 
977
    def test_plan_lca_merge_with_null(self):
 
978
        self.add_version(('root', 'A'), [], 'ab')
 
979
        self.add_version(('root', 'B'), [], 'bc')
 
980
        plan = self.plan_merge_vf.plan_lca_merge('A', 'B')
 
981
        self.assertEqual([('new-a', 'a\n'),
 
982
                          ('unchanged', 'b\n'),
 
983
                          ('new-b', 'c\n'),
 
984
                         ], list(plan))
 
985
 
 
986
    def test_plan_merge_with_delete_and_change(self):
 
987
        self.add_rev('root', 'C', [], 'a')
 
988
        self.add_rev('root', 'A', ['C'], 'b')
 
989
        self.add_rev('root', 'B', ['C'], '')
 
990
        plan = self.plan_merge_vf.plan_merge('A', 'B')
 
991
        self.assertEqual([('killed-both', 'a\n'),
 
992
                          ('new-a', 'b\n'),
 
993
                         ], list(plan))
 
994
 
 
995
    def test_plan_merge_with_move_and_change(self):
 
996
        self.add_rev('root', 'C', [], 'abcd')
 
997
        self.add_rev('root', 'A', ['C'], 'acbd')
 
998
        self.add_rev('root', 'B', ['C'], 'aBcd')
 
999
        plan = self.plan_merge_vf.plan_merge('A', 'B')
 
1000
        self.assertEqual([('unchanged', 'a\n'),
 
1001
                          ('new-a', 'c\n'),
 
1002
                          ('killed-b', 'b\n'),
 
1003
                          ('new-b', 'B\n'),
 
1004
                          ('killed-a', 'c\n'),
 
1005
                          ('unchanged', 'd\n'),
 
1006
                         ], list(plan))
 
1007
 
 
1008
 
 
1009
class TestMergeImplementation(object):
 
1010
 
 
1011
    def do_merge(self, target_tree, source_tree, **kwargs):
 
1012
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1013
            target_tree, source_tree.last_revision(),
 
1014
            other_branch=source_tree.branch)
 
1015
        merger.merge_type=self.merge_type
 
1016
        for name, value in kwargs.items():
 
1017
            setattr(merger, name, value)
 
1018
        merger.do_merge()
 
1019
 
 
1020
    def test_merge_specific_file(self):
 
1021
        this_tree = self.make_branch_and_tree('this')
 
1022
        this_tree.lock_write()
 
1023
        self.addCleanup(this_tree.unlock)
 
1024
        self.build_tree_contents([
 
1025
            ('this/file1', 'a\nb\n'),
 
1026
            ('this/file2', 'a\nb\n')
 
1027
        ])
 
1028
        this_tree.add(['file1', 'file2'])
 
1029
        this_tree.commit('Added files')
 
1030
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
1031
        self.build_tree_contents([
 
1032
            ('other/file1', 'a\nb\nc\n'),
 
1033
            ('other/file2', 'a\nb\nc\n')
 
1034
        ])
 
1035
        other_tree.commit('modified both')
 
1036
        self.build_tree_contents([
 
1037
            ('this/file1', 'd\na\nb\n'),
 
1038
            ('this/file2', 'd\na\nb\n')
 
1039
        ])
 
1040
        this_tree.commit('modified both')
 
1041
        self.do_merge(this_tree, other_tree, interesting_files=['file1'])
 
1042
        self.assertFileEqual('d\na\nb\nc\n', 'this/file1')
 
1043
        self.assertFileEqual('d\na\nb\n', 'this/file2')
 
1044
 
 
1045
    def test_merge_move_and_change(self):
 
1046
        this_tree = self.make_branch_and_tree('this')
 
1047
        this_tree.lock_write()
 
1048
        self.addCleanup(this_tree.unlock)
 
1049
        self.build_tree_contents([
 
1050
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
 
1051
        ])
 
1052
        this_tree.add('file1',)
 
1053
        this_tree.commit('Added file')
 
1054
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
1055
        self.build_tree_contents([
 
1056
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
 
1057
        ])
 
1058
        other_tree.commit('Changed 2 to 2.1')
 
1059
        self.build_tree_contents([
 
1060
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
 
1061
        ])
 
1062
        this_tree.commit('Swapped 2 & 3')
 
1063
        self.do_merge(this_tree, other_tree)
 
1064
        self.assertFileEqual('line 1\n'
 
1065
            '<<<<<<< TREE\n'
 
1066
            'line 3\n'
 
1067
            'line 2\n'
 
1068
            '=======\n'
 
1069
            'line 2 to 2.1\n'
 
1070
            'line 3\n'
 
1071
            '>>>>>>> MERGE-SOURCE\n'
 
1072
            'line 4\n', 'this/file1')
 
1073
 
 
1074
 
 
1075
class TestMerge3Merge(TestCaseWithTransport, TestMergeImplementation):
 
1076
 
 
1077
    merge_type = _mod_merge.Merge3Merger
 
1078
 
 
1079
 
 
1080
class TestWeaveMerge(TestCaseWithTransport, TestMergeImplementation):
 
1081
 
 
1082
    merge_type = _mod_merge.WeaveMerger
 
1083
 
 
1084
 
 
1085
class TestLCAMerge(TestCaseWithTransport, TestMergeImplementation):
 
1086
 
 
1087
    merge_type = _mod_merge.LCAMerger
 
1088
 
 
1089
    def test_merge_move_and_change(self):
 
1090
        self.expectFailure("lca merge doesn't conflict for move and change",
 
1091
            super(TestLCAMerge, self).test_merge_move_and_change)