/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

Show diffs side-by-side

added added

removed removed

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