/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 breezy/tests/test_merge.py

  • Committer: Jelmer Vernooij
  • Date: 2018-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

Show diffs side-by-side

added added

removed removed

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