~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-05-04 12:10:51 UTC
  • mfrom: (5819.1.4 777007-developer-doc)
  • Revision ID: pqm@pqm.ubuntu.com-20110504121051-aovlsmqiivjmc4fc
(jelmer) Small fixes to developer documentation. (Jonathan Riddell)

Show diffs side-by-side

added added

removed removed

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