~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 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 (
21
 
    conflicts,
22
 
    errors,
23
 
    knit,
24
 
    merge as _mod_merge,
25
 
    option,
26
 
    progress,
27
 
    transform,
28
 
    versionedfile,
29
 
    )
 
20
from bzrlib import conflicts
30
21
from bzrlib.branch import Branch
 
22
from bzrlib.builtins import merge
31
23
from bzrlib.conflicts import ConflictList, TextConflict
32
24
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
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
 
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
36
29
from bzrlib.trace import (enable_test_log, disable_test_log)
37
30
from bzrlib.workingtree import WorkingTree
38
31
 
44
37
        wt = self.make_branch_and_tree('.')
45
38
        rev_a = wt.commit("lala!")
46
39
        self.assertEqual([rev_a], wt.get_parent_ids())
47
 
        self.assertRaises(errors.PointlessMerge, wt.merge_from_branch,
48
 
                          wt.branch)
 
40
        merge([u'.', -1], [None, None])
49
41
        self.assertEqual([rev_a], wt.get_parent_ids())
50
 
        return wt
51
42
 
52
43
    def test_undo(self):
53
44
        wt = self.make_branch_and_tree('.')
54
45
        wt.commit("lala!")
55
46
        wt.commit("haha!")
56
47
        wt.commit("blabla!")
57
 
        wt.merge_from_branch(wt.branch, wt.branch.get_rev_id(2),
58
 
                             wt.branch.get_rev_id(1))
 
48
        merge([u'.', 2], [u'.', 1])
59
49
 
60
50
    def test_nocommits(self):
61
 
        wt = self.test_pending()
 
51
        self.test_pending()
62
52
        wt2 = self.make_branch_and_tree('branch2')
63
 
        self.assertRaises(NoCommits, wt.merge_from_branch, wt2.branch)
64
 
        return wt, wt2
 
53
        self.assertRaises(NoCommits, merge, ['branch2', -1], 
 
54
                          [None, None])
 
55
        return wt2
65
56
 
66
57
    def test_unrelated(self):
67
 
        wt, wt2 = self.test_nocommits()
 
58
        wt2 = self.test_nocommits()
68
59
        wt2.commit("blah")
69
 
        self.assertRaises(UnrelatedBranches, wt.merge_from_branch, wt2.branch)
 
60
        self.assertRaises(UnrelatedBranches, merge, ['branch2', -1], 
 
61
                          [None, None])
70
62
        return wt2
71
63
 
72
64
    def test_merge_one_file(self):
81
73
        wt1.add('bar')
82
74
        wt1.commit('add foobar')
83
75
        os.chdir('branch2')
84
 
        self.run_bzr('merge ../branch1/baz', retcode=3)
85
 
        self.run_bzr('merge ../branch1/foo')
 
76
        self.run_bzr('merge', '../branch1/baz', retcode=3)
 
77
        self.run_bzr('merge', '../branch1/foo')
86
78
        self.failUnlessExists('foo')
87
79
        self.failIfExists('bar')
88
80
        wt2 = WorkingTree.open('.') # opens branch2
96
88
        br1.fetch(wt2.branch)
97
89
        # merge all of branch 2 into branch 1 even though they 
98
90
        # are not related.
99
 
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
 
91
        self.assertRaises(BzrCommandError, merge, ['branch2', -1],
 
92
                          ['branch2', 0], reprocess=True, show_base=True)
 
93
        merge(['branch2', -1], ['branch2', 0], reprocess=True)
100
94
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
101
95
            wt1.get_parent_ids())
102
96
        return (wt1, wt2.branch)
105
99
        """Merge base is sane when two unrelated branches are merged"""
106
100
        wt1, br2 = self.test_pending_with_null()
107
101
        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()
 
102
        last = wt1.branch.last_revision()
 
103
        self.assertEqual(common_ancestor(last, last, wt1.branch.repository), last)
116
104
 
117
105
    def test_create_rename(self):
118
106
        """Rename an inventory entry while creating the file"""
144
132
        tree_a.commit(message="hello")
145
133
        dir_b = tree_a.bzrdir.sprout('b')
146
134
        tree_b = dir_b.open_workingtree()
147
 
        tree_b.lock_write()
148
 
        self.addCleanup(tree_b.unlock)
149
135
        tree_a.commit(message="hello again")
150
136
        log = StringIO()
151
137
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
152
138
                    this_tree=tree_b, ignore_zero=True)
153
139
        log = self._get_log(keep_log_file=True)
154
140
        self.failUnless('All changes applied successfully.\n' not in log)
155
 
        tree_b.revert()
 
141
        tree_b.revert([])
156
142
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
157
143
                    this_tree=tree_b, ignore_zero=False)
158
144
        log = self._get_log(keep_log_file=True)
169
155
        self.build_tree(['a/b/'])
170
156
        tree_a.add('b', 'b-id')
171
157
        tree_a.commit('added b')
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())
 
158
        base_tree = tree_a.basis_tree()
176
159
        tree_z = tree_a.bzrdir.sprout('z').open_workingtree()
177
160
        self.build_tree(['a/b/c'])
178
161
        tree_a.add('b/c')
184
167
            conflicts.MissingParent('Created directory', 'b', 'b-id'),
185
168
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
186
169
            tree_z.conflicts())
187
 
        merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree,
 
170
        merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree, 
188
171
                    this_tree=tree_a)
189
172
        self.assertEqual([
190
173
            conflicts.DeletingParent('Not deleting', 'b', 'b-id'),
191
174
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
192
175
            tree_a.conflicts())
193
176
 
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
177
    def test_merge_with_missing(self):
215
178
        tree_a = self.make_branch_and_tree('tree_a')
216
179
        self.build_tree_contents([('tree_a/file', 'content_1')])
217
180
        tree_a.add('file')
218
181
        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())
 
182
        base_tree = tree_a.basis_tree()
223
183
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
224
184
        self.build_tree_contents([('tree_a/file', 'content_2')])
225
185
        tree_a.commit('commit other')
226
186
        other_tree = tree_a.basis_tree()
227
187
        os.unlink('tree_b/file')
228
188
        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)