1# Copyright (C) 2005-2012, 2016 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17import os
18import sys
19
20import breezy
21from .. import (
22    controldir,
23    errors,
24    merge_directive,
25    osutils,
26    )
27from ..bzr.conflicts import (
28    ContentsConflict,
29    TextConflict,
30    PathConflict,
31    )
32from ..merge import (
33    Merge3Merger,
34    Diff3Merger,
35    WeaveMerger,
36    Merger,
37    )
38from ..bzr import (
39    generate_ids,
40    )
41from ..osutils import getcwd, pathjoin
42from . import TestCaseWithTransport, TestSkipped
43from ..workingtree import WorkingTree
44
45
46class MergeBuilder(object):
47
48    def __init__(self, dir=None):
49        self.dir = osutils.mkdtemp(prefix="merge-test", dir=dir)
50        self.tree_root = generate_ids.gen_root_id()
51
52        def wt(name):
53            path = pathjoin(self.dir, name)
54            os.mkdir(path)
55            wt = controldir.ControlDir.create_standalone_workingtree(path)
56            # the tests perform pulls, so need a branch that is writeable.
57            wt.lock_write()
58            wt.set_root_id(self.tree_root)
59            wt.flush()
60            tt = wt.transform()
61            return wt, tt
62        self.base, self.base_tt = wt('base')
63        self.this, self.this_tt = wt('this')
64        self.other, self.other_tt = wt('other')
65
66    def get_cset_path(self, parent, name):
67        if name is None:
68            if parent is not None:
69                raise AssertionError()
70            return None
71        return pathjoin(self.cset.entries[parent].path, name)
72
73    def add_file(self, id, parent, name, contents, executable, this=True,
74                 base=True, other=True):
75        def new_file(tt):
76            parent_id = tt.trans_id_file_id(parent)
77            tt.new_file(name, parent_id, [contents], id, executable)
78        for option, tt in self.selected_transforms(this, base, other):
79            if option is True:
80                new_file(tt)
81
82    def merge(self, merge_type=Merge3Merger, interesting_files=None, **kwargs):
83        merger = self.make_merger(merge_type, interesting_files, **kwargs)
84        merger.do_merge()
85        return merger.cooked_conflicts
86
87    def make_preview_transform(self):
88        merger = self.make_merger(Merge3Merger, None, this_revision_tree=True)
89        return merger.make_preview_transform()
90
91    def make_merger(self, merge_type, interesting_files,
92                    this_revision_tree=False, **kwargs):
93        self.base_tt.apply()
94        self.base.commit('base commit')
95        for tt, wt in ((self.this_tt, self.this), (self.other_tt, self.other)):
96            # why does this not do wt.pull() ?
97            wt.branch.pull(self.base.branch)
98            wt.set_parent_ids([wt.branch.last_revision()])
99            wt.flush()
100            # We maintain a write lock, so make sure changes are flushed to
101            # disk first
102            tt.apply()
103            wt.commit('branch commit')
104            wt.flush()
105            if wt.branch.last_revision_info()[0] != 2:
106                raise AssertionError()
107        self.this.branch.fetch(self.other.branch)
108        other_basis = self.other.branch.basis_tree()
109        if this_revision_tree:
110            self.this.commit('message')
111            this_tree = self.this.basis_tree()
112        else:
113            this_tree = self.this
114        merger = merge_type(this_tree, self.this, self.base, other_basis,
115                            interesting_files=interesting_files, do_merge=False,
116                            this_branch=self.this.branch, **kwargs)
117        return merger
118
119    def list_transforms(self):
120        return [self.this_tt, self.base_tt, self.other_tt]
121
122    def selected_transforms(self, this, base, other):
123        pairs = [(this, self.this_tt), (base, self.base_tt),
124                 (other, self.other_tt)]
125        return [(v, tt) for (v, tt) in pairs if v is not None]
126
127    def add_symlink(self, id, parent, name, contents):
128        for tt in self.list_transforms():
129            parent_id = tt.trans_id_file_id(parent)
130            tt.new_symlink(name, parent_id, contents, id)
131
132    def remove_file(self, file_id, base=False, this=False, other=False):
133        for option, tt in self.selected_transforms(this, base, other):
134            if option is True:
135                trans_id = tt.trans_id_file_id(file_id)
136                tt.cancel_creation(trans_id)
137                tt.cancel_versioning(trans_id)
138                tt.set_executability(None, trans_id)
139
140    def add_dir(self, file_id, parent, name, this=True, base=True, other=True):
141        for option, tt in self.selected_transforms(this, base, other):
142            if option is True:
143                parent_id = tt.trans_id_file_id(parent)
144                tt.new_directory(name, parent_id, file_id)
145
146    def change_name(self, id, base=None, this=None, other=None):
147        for val, tt in ((base, self.base_tt), (this, self.this_tt),
148                        (other, self.other_tt)):
149            if val is None:
150                continue
151            trans_id = tt.trans_id_file_id(id)
152            parent_id = tt.final_parent(trans_id)
153            tt.adjust_path(val, parent_id, trans_id)
154
155    def change_parent(self, file_id, base=None, this=None, other=None):
156        for parent, tt in self.selected_transforms(this, base, other):
157            trans_id = tt.trans_id_file_id(file_id)
158            parent_id = tt.trans_id_file_id(parent)
159            tt.adjust_path(tt.final_name(trans_id), parent_id, trans_id)
160
161    def change_contents(self, file_id, base=None, this=None, other=None):
162        for contents, tt in self.selected_transforms(this, base, other):
163            trans_id = tt.trans_id_file_id(file_id)
164            tt.cancel_creation(trans_id)
165            tt.create_file([contents], trans_id)
166
167    def change_target(self, id, base=None, this=None, other=None):
168        for target, tt in self.selected_transforms(this, base, other):
169            trans_id = tt.trans_id_file_id(id)
170            tt.cancel_creation(trans_id)
171            tt.create_symlink(target, trans_id)
172
173    def change_perms(self, id, base=None, this=None, other=None):
174        for executability, tt in self.selected_transforms(this, base, other):
175            trans_id = tt.trans_id_file_id(id)
176            tt.set_executability(None, trans_id)
177            tt.set_executability(executability, trans_id)
178
179    def change_perms_tree(self, id, tree, mode):
180        os.chmod(tree.full_path(id), mode)
181
182    def apply_inv_change(self, inventory_change, orig_inventory):
183        orig_inventory_by_path = {}
184        for file_id, path in orig_inventory.items():
185            orig_inventory_by_path[path] = file_id
186
187        def parent_id(file_id):
188            try:
189                parent_dir = os.path.dirname(orig_inventory[file_id])
190            except:
191                print(file_id)
192                raise
193            if parent_dir == "":
194                return None
195            return orig_inventory_by_path[parent_dir]
196
197        def new_path(file_id):
198            if fild_id in inventory_change:
199                return inventory_change[file_id]
200            else:
201                parent = parent_id(file_id)
202                if parent is None:
203                    return orig_inventory[file_id]
204                dirname = new_path(parent)
205                return pathjoin(dirname, os.path.basename(orig_inventory[file_id]))
206
207        new_inventory = {}
208        for file_id in orig_inventory:
209            path = new_path(file_id)
210            if path is None:
211                continue
212            new_inventory[file_id] = path
213
214        for file_id, path in inventory_change.items():
215            if file_id in orig_inventory:
216                continue
217            new_inventory[file_id] = path
218        return new_inventory
219
220    def unlock(self):
221        self.base.unlock()
222        self.this.unlock()
223        self.other.unlock()
224
225    def cleanup(self):
226        self.unlock()
227        osutils.rmtree(self.dir)
228
229
230class MergeTest(TestCaseWithTransport):
231
232    def test_change_name(self):
233        """Test renames"""
234        builder = MergeBuilder(getcwd())
235        builder.add_file(b"1", builder.tree_root, "name1", b"hello1", True)
236        builder.change_name(b"1", other="name2")
237        builder.add_file(b"2", builder.tree_root, "name3", b"hello2", True)
238        builder.change_name(b"2", base="name4")
239        builder.add_file(b"3", builder.tree_root, "name5", b"hello3", True)
240        builder.change_name(b"3", this="name6")
241        builder.merge()
242        builder.cleanup()
243        builder = MergeBuilder(getcwd())
244        builder.add_file(b"1", builder.tree_root, "name1", b"hello1", False)
245        builder.change_name(b"1", other="name2", this="name3")
246        conflicts = builder.merge()
247        self.assertEqual(conflicts, [PathConflict('name3', 'name2', b'1')])
248        builder.cleanup()
249
250    def test_merge_one(self):
251        builder = MergeBuilder(getcwd())
252        builder.add_file(b"1", builder.tree_root, "name1", b"hello1", True)
253        builder.change_contents(b"1", other=b"text4")
254        builder.add_file(b"2", builder.tree_root, "name2", b"hello1", True)
255        builder.change_contents(b"2", other=b"text4")
256        builder.merge(interesting_files=["name1"])
257        self.assertEqual(builder.this.get_file("name1").read(), b"text4")
258        self.assertEqual(builder.this.get_file("name2").read(), b"hello1")
259        builder.cleanup()
260
261    def test_file_moves(self):
262        """Test moves"""
263        builder = MergeBuilder(getcwd())
264        builder.add_dir(b"1", builder.tree_root, "dir1")
265        builder.add_dir(b"2", builder.tree_root, "dir2")
266        builder.add_file(b"3", b"1", "file1", b"hello1", True)
267        builder.add_file(b"4", b"1", "file2", b"hello2", True)
268        builder.add_file(b"5", b"1", "file3", b"hello3", True)
269        builder.change_parent(b"3", other=b"2")
270        builder.change_parent(b"4", this=b"2")
271        builder.change_parent(b"5", base=b"2")
272        builder.merge()
273        builder.cleanup()
274
275        builder = MergeBuilder(getcwd())
276        builder.add_dir(b"1", builder.tree_root, "dir1")
277        builder.add_dir(b"2", builder.tree_root, "dir2")
278        builder.add_dir(b"3", builder.tree_root, "dir3")
279        builder.add_file(b"4", b"1", "file1", b"hello1", False)
280        builder.change_parent(b"4", other=b"2", this=b"3")
281        conflicts = builder.merge()
282        path2 = pathjoin('dir2', 'file1')
283        path3 = pathjoin('dir3', 'file1')
284        self.assertEqual(conflicts, [PathConflict(path3, path2, b'4')])
285        builder.cleanup()
286
287    def test_contents_merge(self):
288        """Test merge3 merging"""
289        self.do_contents_test(Merge3Merger)
290
291    def test_contents_merge2(self):
292        """Test diff3 merging"""
293        if sys.platform == 'win32':
294            raise TestSkipped("diff3 does not have --binary flag"
295                              " and therefore always fails on win32")
296        try:
297            self.do_contents_test(Diff3Merger)
298        except errors.NoDiff3:
299            raise TestSkipped("diff3 not available")
300
301    def test_contents_merge3(self):
302        """Test diff3 merging"""
303        self.do_contents_test(WeaveMerger)
304
305    def test_reprocess_weave(self):
306        # Reprocess works on weaves, and behaves as expected
307        builder = MergeBuilder(getcwd())
308        builder.add_file(b'a', builder.tree_root, 'blah', b'a', False)
309        builder.change_contents(
310            b'a', this=b'b\nc\nd\ne\n', other=b'z\nc\nd\ny\n')
311        builder.merge(WeaveMerger, reprocess=True)
312        expected = b"""<<<<<<< TREE
313b
314=======
315z
316>>>>>>> MERGE-SOURCE
317c
318d
319<<<<<<< TREE
320e
321=======
322y
323>>>>>>> MERGE-SOURCE
324"""
325        self.assertEqualDiff(
326            builder.this.get_file(builder.this.id2path(b"a")).read(),
327            expected)
328        builder.cleanup()
329
330    def do_contents_test(self, merge_factory):
331        """Test merging with specified ContentsChange factory"""
332        builder = self.contents_test_success(merge_factory)
333        builder.cleanup()
334        self.contents_test_conflicts(merge_factory)
335
336    def contents_test_success(self, merge_factory):
337        builder = MergeBuilder(getcwd())
338        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
339        builder.change_contents(b"1", other=b"text4")
340        builder.add_file(b"2", builder.tree_root, "name3", b"text2", False)
341        builder.change_contents(b"2", base=b"text5")
342        builder.add_file(b"3", builder.tree_root, "name5", b"text3", True)
343        builder.add_file(b"4", builder.tree_root, "name6", b"text4", True)
344        builder.remove_file(b"4", base=True)
345        builder.add_file(b"5", builder.tree_root, "name7", b"a\nb\nc\nd\ne\nf\n",
346                         True)
347        builder.change_contents(b"5", other=b"a\nz\nc\nd\ne\nf\n",
348                                this=b"a\nb\nc\nd\ne\nz\n")
349        conflicts = builder.merge(merge_factory)
350        try:
351            self.assertEqual([], conflicts)
352            self.assertEqual(b"text4", builder.this.get_file("name1").read())
353            self.assertEqual(b"text2", builder.this.get_file("name3").read())
354            self.assertEqual(b"a\nz\nc\nd\ne\nz\n",
355                             builder.this.get_file("name7").read())
356            self.assertTrue(builder.this.is_executable("name1"))
357            self.assertFalse(builder.this.is_executable("name3"))
358            self.assertTrue(builder.this.is_executable("name5"))
359        except:
360            builder.unlock()
361            raise
362        return builder
363
364    def contents_test_conflicts(self, merge_factory):
365        builder = MergeBuilder(getcwd())
366        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
367        builder.change_contents(b"1", other=b"text4", this=b"text3")
368        builder.add_file(b"2", builder.tree_root, "name2", b"text1", True)
369        builder.change_contents(b"2", other=b"\x00", this=b"text3")
370        builder.add_file(b"3", builder.tree_root, "name3", b"text5", False)
371        builder.change_perms(b"3", this=True)
372        builder.change_contents(b'3', this=b'moretext')
373        builder.remove_file(b'3', other=True)
374        conflicts = builder.merge(merge_factory)
375        self.assertEqual(conflicts, [TextConflict('name1', file_id=b'1'),
376                                     ContentsConflict('name2', file_id=b'2'),
377                                     ContentsConflict('name3', file_id=b'3')])
378        with builder.this.get_file(builder.this.id2path(b'2')) as f:
379            self.assertEqual(f.read(), b'\x00')
380        builder.cleanup()
381
382    def test_symlink_conflicts(self):
383        if sys.platform != "win32":
384            builder = MergeBuilder(getcwd())
385            builder.add_symlink(b"2", builder.tree_root, "name2", "target1")
386            builder.change_target(b"2", other="target4", base="text3")
387            conflicts = builder.merge()
388            self.assertEqual(conflicts, [ContentsConflict('name2',
389                                                          file_id=b'2')])
390            builder.cleanup()
391
392    def test_symlink_merge(self):
393        if sys.platform != "win32":
394            builder = MergeBuilder(getcwd())
395            builder.add_symlink(b"1", builder.tree_root, "name1", "target1")
396            builder.add_symlink(b"2", builder.tree_root, "name2", "target1")
397            builder.add_symlink(b"3", builder.tree_root, "name3", "target1")
398            builder.change_target(b"1", this=b"target2")
399            builder.change_target(b"2", base=b"target2")
400            builder.change_target(b"3", other=b"target2")
401            builder.merge()
402            self.assertEqual(
403                builder.this.get_symlink_target("name1"), "target2")
404            self.assertEqual(
405                builder.this.get_symlink_target("name2"), "target1")
406            self.assertEqual(
407                builder.this.get_symlink_target("name3"), "target2")
408            builder.cleanup()
409
410    def test_no_passive_add(self):
411        builder = MergeBuilder(getcwd())
412        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
413        builder.remove_file(b"1", this=True)
414        builder.merge()
415        builder.cleanup()
416
417    def test_perms_merge(self):
418        builder = MergeBuilder(getcwd())
419        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
420        builder.change_perms(b"1", other=False)
421        builder.add_file(b"2", builder.tree_root, "name2", b"text2", True)
422        builder.change_perms(b"2", base=False)
423        builder.add_file(b"3", builder.tree_root, "name3", b"text3", True)
424        builder.change_perms(b"3", this=False)
425        builder.add_file(b'4', builder.tree_root, 'name4', b'text4', False)
426        builder.change_perms(b'4', this=True)
427        builder.remove_file(b'4', base=True)
428        builder.merge()
429        self.assertIs(builder.this.is_executable("name1"), False)
430        self.assertIs(builder.this.is_executable("name2"), True)
431        self.assertIs(builder.this.is_executable("name3"), False)
432        builder.cleanup()
433
434    def test_new_suffix(self):
435        builder = MergeBuilder(getcwd())
436        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
437        builder.change_contents(b"1", other=b"text3")
438        builder.add_file(b"2", builder.tree_root, "name1.new", b"text2", True)
439        builder.merge()
440        os.lstat(builder.this.abspath(builder.this.id2path(b"2")))
441        builder.cleanup()
442
443    def test_spurious_conflict(self):
444        builder = MergeBuilder(getcwd())
445        builder.add_file(b"1", builder.tree_root, "name1", b"text1", False)
446        builder.remove_file(b"1", other=True)
447        builder.add_file(b"2", builder.tree_root, "name1", b"text1", False,
448                         this=False, base=False)
449        conflicts = builder.merge()
450        self.assertEqual(conflicts, [])
451        builder.cleanup()
452
453    def test_merge_one_renamed(self):
454        builder = MergeBuilder(getcwd())
455        builder.add_file(b'1', builder.tree_root, 'name1', b'text1a', False)
456        builder.change_name(b'1', this='name2')
457        builder.change_contents(b'1', other=b'text2')
458        builder.merge(interesting_files=['name2'])
459        self.assertEqual(b'text2', builder.this.get_file('name2').read())
460        builder.cleanup()
461
462
463class FunctionalMergeTest(TestCaseWithTransport):
464
465    def test_trivial_star_merge(self):
466        """Test that merges in a star shape Just Work."""
467        # John starts a branch
468        self.build_tree(("original/", "original/file1", "original/file2"))
469        tree = self.make_branch_and_tree('original')
470        branch = tree.branch
471        tree.smart_add(["original"])
472        tree.commit("start branch.", verbose=False)
473        # Mary branches it.
474        self.build_tree(("mary/",))
475        branch.controldir.clone("mary")
476        # Now John commits a change
477        with open("original/file1", "wt") as f:
478            f.write("John\n")
479        tree.commit("change file1")
480        # Mary does too
481        mary_tree = WorkingTree.open('mary')
482        mary_branch = mary_tree.branch
483        with open("mary/file2", "wt") as f:
484            f.write("Mary\n")
485        mary_tree.commit("change file2")
486        # john should be able to merge with no conflicts.
487        base = [None, None]
488        other = ("mary", -1)
489        tree.merge_from_branch(mary_tree.branch)
490        with open("original/file1", "rt") as f:
491            self.assertEqual("John\n", f.read())
492        with open("original/file2", "rt") as f:
493            self.assertEqual("Mary\n", f.read())
494
495    def test_conflicts(self):
496        wta = self.make_branch_and_tree('a')
497        self.build_tree_contents([('a/file', b'contents\n')])
498        wta.add('file')
499        wta.commit('base revision', allow_pointless=False)
500        d_b = wta.branch.controldir.clone('b')
501        self.build_tree_contents([('a/file', b'other contents\n')])
502        wta.commit('other revision', allow_pointless=False)
503        self.build_tree_contents([('b/file', b'this contents contents\n')])
504        wtb = d_b.open_workingtree()
505        wtb.commit('this revision', allow_pointless=False)
506        self.assertEqual(1, len(wtb.merge_from_branch(wta.branch)))
507        self.assertPathExists('b/file.THIS')
508        self.assertPathExists('b/file.BASE')
509        self.assertPathExists('b/file.OTHER')
510        wtb.revert()
511        self.assertEqual(1, len(wtb.merge_from_branch(wta.branch,
512                                                  merge_type=WeaveMerger)))
513        self.assertPathExists('b/file')
514        self.assertPathExists('b/file.THIS')
515        self.assertPathExists('b/file.BASE')
516        self.assertPathExists('b/file.OTHER')
517
518    def test_weave_conflicts_not_in_base(self):
519        builder = self.make_branch_builder('source')
520        builder.start_series()
521        # See bug #494197
522        #  A        base revision (before criss-cross)
523        #  |\
524        #  B C      B does nothing, C adds 'foo'
525        #  |X|
526        #  D E      D and E modify foo in incompatible ways
527        #
528        # Merging will conflict, with C as a clean base text. However, the
529        # current code uses A as the global base and 'foo' doesn't exist there.
530        # It isn't trivial to create foo.BASE because it tries to look up
531        # attributes like 'executable' in A.
532        builder.build_snapshot(None, [
533            ('add', ('', b'TREE_ROOT', 'directory', None))],
534            revision_id=b'A-id')
535        builder.build_snapshot([b'A-id'], [], revision_id=b'B-id')
536        builder.build_snapshot([b'A-id'], [
537            ('add', ('foo', b'foo-id', 'file', b'orig\ncontents\n'))],
538            revision_id=b'C-id')
539        builder.build_snapshot([b'B-id', b'C-id'], [
540            ('add', ('foo', b'foo-id', 'file', b'orig\ncontents\nand D\n'))],
541            revision_id=b'D-id')
542        builder.build_snapshot([b'C-id', b'B-id'], [
543            ('modify', ('foo', b'orig\ncontents\nand E\n'))],
544            revision_id=b'E-id')
545        builder.finish_series()
546        tree = builder.get_branch().create_checkout('tree', lightweight=True)
547        self.assertEqual(1, len(tree.merge_from_branch(tree.branch,
548                                                   to_revision=b'D-id',
549                                                   merge_type=WeaveMerger)))
550        self.assertPathExists('tree/foo.THIS')
551        self.assertPathExists('tree/foo.OTHER')
552        self.expectFailure('fail to create .BASE in some criss-cross merges',
553                           self.assertPathExists, 'tree/foo.BASE')
554        self.assertPathExists('tree/foo.BASE')
555
556    def test_merge_unrelated(self):
557        """Sucessfully merges unrelated branches with no common names"""
558        wta = self.make_branch_and_tree('a')
559        a = wta.branch
560        with open('a/a_file', 'wb') as f:
561            f.write(b'contents\n')
562        wta.add('a_file')
563        wta.commit('a_revision', allow_pointless=False)
564        wtb = self.make_branch_and_tree('b')
565        b = wtb.branch
566        with open('b/b_file', 'wb') as f:
567            f.write(b'contents\n')
568        wtb.add('b_file')
569        b_rev = wtb.commit('b_revision', allow_pointless=False)
570        wta.merge_from_branch(wtb.branch, b_rev, b'null:')
571        self.assertTrue(os.path.lexists('a/b_file'))
572        self.assertEqual([b_rev], wta.get_parent_ids()[1:])
573
574    def test_merge_unrelated_conflicting(self):
575        """Sucessfully merges unrelated branches with common names"""
576        wta = self.make_branch_and_tree('a')
577        a = wta.branch
578        with open('a/file', 'wb') as f:
579            f.write(b'contents\n')
580        wta.add('file')
581        wta.commit('a_revision', allow_pointless=False)
582        wtb = self.make_branch_and_tree('b')
583        b = wtb.branch
584        with open('b/file', 'wb') as f:
585            f.write(b'contents\n')
586        wtb.add('file')
587        b_rev = wtb.commit('b_revision', allow_pointless=False)
588        wta.merge_from_branch(wtb.branch, b_rev, b'null:')
589        self.assertTrue(os.path.lexists('a/file'))
590        self.assertTrue(os.path.lexists('a/file.moved'))
591        self.assertEqual([b_rev], wta.get_parent_ids()[1:])
592
593    def test_merge_deleted_conflicts(self):
594        wta = self.make_branch_and_tree('a')
595        with open('a/file', 'wb') as f:
596            f.write(b'contents\n')
597        wta.add('file')
598        wta.commit('a_revision', allow_pointless=False)
599        self.run_bzr('branch a b')
600        os.remove('a/file')
601        wta.commit('removed file', allow_pointless=False)
602        with open('b/file', 'wb') as f:
603            f.write(b'changed contents\n')
604        wtb = WorkingTree.open('b')
605        wtb.commit('changed file', allow_pointless=False)
606        wtb.merge_from_branch(wta.branch, wta.branch.last_revision(),
607                              wta.branch.get_rev_id(1))
608        self.assertFalse(os.path.lexists('b/file'))
609
610    def test_merge_metadata_vs_deletion(self):
611        """Conflict deletion vs metadata change"""
612        a_wt = self.make_branch_and_tree('a')
613        with open('a/file', 'wb') as f:
614            f.write(b'contents\n')
615        a_wt.add('file')
616        a_wt.commit('r0')
617        self.run_bzr('branch a b')
618        b_wt = WorkingTree.open('b')
619        os.chmod('b/file', 0o755)
620        os.remove('a/file')
621        a_wt.commit('removed a')
622        self.assertEqual(a_wt.branch.revno(), 2)
623        self.assertFalse(os.path.exists('a/file'))
624        b_wt.commit('exec a')
625        a_wt.merge_from_branch(b_wt.branch, b_wt.last_revision(), b'null:')
626        self.assertTrue(os.path.exists('a/file'))
627
628    def test_merge_swapping_renames(self):
629        a_wt = self.make_branch_and_tree('a')
630        with open('a/un', 'wb') as f:
631            f.write(b'UN')
632        with open('a/deux', 'wb') as f:
633            f.write(b'DEUX')
634        a_wt.add('un', b'un-id')
635        a_wt.add('deux', b'deux-id')
636        a_wt.commit('r0', rev_id=b'r0')
637        self.run_bzr('branch a b')
638        b_wt = WorkingTree.open('b')
639        b_wt.rename_one('un', 'tmp')
640        b_wt.rename_one('deux', 'un')
641        b_wt.rename_one('tmp', 'deux')
642        b_wt.commit('r1', rev_id=b'r1')
643        self.assertEqual(
644            0, len(a_wt.merge_from_branch(
645                b_wt.branch, b_wt.branch.last_revision(),
646                b_wt.branch.get_rev_id(1))))
647        self.assertPathExists('a/un')
648        self.assertTrue('a/deux')
649        self.assertFalse(os.path.exists('a/tmp'))
650        with open('a/un', 'r') as f:
651            self.assertEqual(f.read(), 'DEUX')
652        with open('a/deux', 'r') as f:
653            self.assertEqual(f.read(), 'UN')
654
655    def test_merge_delete_and_add_same(self):
656        a_wt = self.make_branch_and_tree('a')
657        with open('a/file', 'wb') as f:
658            f.write(b'THIS')
659        a_wt.add('file')
660        a_wt.commit('r0')
661        self.run_bzr('branch a b')
662        b_wt = WorkingTree.open('b')
663        os.remove('b/file')
664        b_wt.commit('r1')
665        with open('b/file', 'wb') as f:
666            f.write(b'THAT')
667        b_wt.add('file')
668        b_wt.commit('r2')
669        a_wt.merge_from_branch(b_wt.branch, b_wt.branch.last_revision(),
670                               b_wt.branch.get_rev_id(1))
671        self.assertTrue(os.path.exists('a/file'))
672        with open('a/file', 'r') as f:
673            self.assertEqual(f.read(), 'THAT')
674
675    def test_merge_rename_before_create(self):
676        """rename before create
677
678        This case requires that you must not do creates
679        before move-into-place:
680
681        $ touch foo
682        $ bzr add foo
683        $ bzr commit
684        $ bzr mv foo bar
685        $ touch foo
686        $ bzr add foo
687        $ bzr commit
688        """
689        a_wt = self.make_branch_and_tree('a')
690        with open('a/foo', 'wb') as f:
691            f.write(b'A/FOO')
692        a_wt.add('foo')
693        a_wt.commit('added foo')
694        self.run_bzr('branch a b')
695        b_wt = WorkingTree.open('b')
696        b_wt.rename_one('foo', 'bar')
697        with open('b/foo', 'wb') as f:
698            f.write(b'B/FOO')
699        b_wt.add('foo')
700        b_wt.commit('moved foo to bar, added new foo')
701        a_wt.merge_from_branch(b_wt.branch, b_wt.branch.last_revision(),
702                               b_wt.branch.get_rev_id(1))
703
704    def test_merge_create_before_rename(self):
705        """create before rename, target parents before children
706
707        This case requires that you must not do move-into-place
708        before creates, and that you must not do children after
709        parents:
710
711        $ touch foo
712        $ bzr add foo
713        $ bzr commit
714        $ bzr mkdir bar
715        $ bzr add bar
716        $ bzr mv foo bar/foo
717        $ bzr commit
718        """
719        os.mkdir('a')
720        a_wt = self.make_branch_and_tree('a')
721        with open('a/foo', 'wb') as f:
722            f.write(b'A/FOO')
723        a_wt.add('foo')
724        a_wt.commit('added foo')
725        self.run_bzr('branch a b')
726        b_wt = WorkingTree.open('b')
727        os.mkdir('b/bar')
728        b_wt.add('bar')
729        b_wt.rename_one('foo', 'bar/foo')
730        b_wt.commit('created bar dir, moved foo into bar')
731        a_wt.merge_from_branch(b_wt.branch, b_wt.branch.last_revision(),
732                               b_wt.branch.get_rev_id(1))
733
734    def test_merge_rename_to_temp_before_delete(self):
735        """rename to temp before delete, source children before parents
736
737        This case requires that you must not do deletes before
738        move-out-of-the-way, and that you must not do children
739        after parents:
740
741        $ mkdir foo
742        $ touch foo/bar
743        $ bzr add foo/bar
744        $ bzr commit
745        $ bzr mv foo/bar bar
746        $ rmdir foo
747        $ bzr commit
748        """
749        a_wt = self.make_branch_and_tree('a')
750        os.mkdir('a/foo')
751        with open('a/foo/bar', 'wb') as f:
752            f.write(b'A/FOO/BAR')
753        a_wt.add('foo')
754        a_wt.add('foo/bar')
755        a_wt.commit('added foo/bar')
756        self.run_bzr('branch a b')
757        b_wt = WorkingTree.open('b')
758        b_wt.rename_one('foo/bar', 'bar')
759        os.rmdir('b/foo')
760        b_wt.remove('foo')
761        b_wt.commit('moved foo/bar to bar, deleted foo')
762        a_wt.merge_from_branch(b_wt.branch, b_wt.branch.last_revision(),
763                               b_wt.branch.get_rev_id(1))
764
765    def test_merge_delete_before_rename_to_temp(self):
766        """delete before rename to temp
767
768        This case requires that you must not do
769        move-out-of-the-way before deletes:
770
771        $ touch foo
772        $ touch bar
773        $ bzr add foo bar
774        $ bzr commit
775        $ rm foo
776        $ bzr rm foo
777        $ bzr mv bar foo
778        $ bzr commit
779        """
780        a_wt = self.make_branch_and_tree('a')
781        with open('a/foo', 'wb') as f:
782            f.write(b'A/FOO')
783        with open('a/bar', 'wb') as f:
784            f.write(b'A/BAR')
785        a_wt.add('foo')
786        a_wt.add('bar')
787        a_wt.commit('added foo and bar')
788        self.run_bzr('branch a b')
789        b_wt = WorkingTree.open('b')
790        os.unlink('b/foo')
791        b_wt.remove('foo')
792        b_wt.rename_one('bar', 'foo')
793        b_wt.commit('deleted foo, renamed bar to foo')
794        a_wt.merge_from_branch(b_wt.branch, b_wt.branch.last_revision(),
795                               b_wt.branch.get_rev_id(1))
796
797
798class TestMerger(TestCaseWithTransport):
799
800    def set_up_trees(self):
801        this = self.make_branch_and_tree('this')
802        this.commit('rev1', rev_id=b'rev1')
803        other = this.controldir.sprout('other').open_workingtree()
804        this.commit('rev2a', rev_id=b'rev2a')
805        other.commit('rev2b', rev_id=b'rev2b')
806        return this, other
807
808    def test_from_revision_ids(self):
809        this, other = self.set_up_trees()
810        self.assertRaises(errors.NoSuchRevision, Merger.from_revision_ids,
811                          this, b'rev2b')
812        this.lock_write()
813        self.addCleanup(this.unlock)
814        merger = Merger.from_revision_ids(this,
815                                          b'rev2b', other_branch=other.branch)
816        self.assertEqual(b'rev2b', merger.other_rev_id)
817        self.assertEqual(b'rev1', merger.base_rev_id)
818        merger = Merger.from_revision_ids(this,
819                                          b'rev2b', b'rev2a', other_branch=other.branch)
820        self.assertEqual(b'rev2a', merger.base_rev_id)
821
822    def test_from_uncommitted(self):
823        this, other = self.set_up_trees()
824        merger = Merger.from_uncommitted(this, other, None)
825        self.assertIs(other, merger.other_tree)
826        self.assertIs(None, merger.other_rev_id)
827        self.assertEqual(b'rev2b', merger.base_rev_id)
828
829    def prepare_for_merging(self):
830        this, other = self.set_up_trees()
831        other.commit('rev3', rev_id=b'rev3')
832        this.lock_write()
833        self.addCleanup(this.unlock)
834        return this, other
835
836    def test_from_mergeable(self):
837        this, other = self.prepare_for_merging()
838        md = merge_directive.MergeDirective2.from_objects(
839            other.branch.repository, b'rev3', 0, 0, 'this')
840        other.lock_read()
841        self.addCleanup(other.unlock)
842        merger, verified = Merger.from_mergeable(this, md)
843        md.patch = None
844        merger, verified = Merger.from_mergeable(this, md)
845        self.assertEqual('inapplicable', verified)
846        self.assertEqual(b'rev3', merger.other_rev_id)
847        self.assertEqual(b'rev1', merger.base_rev_id)
848        md.base_revision_id = b'rev2b'
849        merger, verified = Merger.from_mergeable(this, md)
850        self.assertEqual(b'rev2b', merger.base_rev_id)
851
852    def test_from_mergeable_old_merge_directive(self):
853        this, other = self.prepare_for_merging()
854        other.lock_write()
855        self.addCleanup(other.unlock)
856        md = merge_directive.MergeDirective.from_objects(
857            other.branch.repository, b'rev3', 0, 0, 'this')
858        merger, verified = Merger.from_mergeable(this, md)
859        self.assertEqual(b'rev3', merger.other_rev_id)
860        self.assertEqual(b'rev1', merger.base_rev_id)
861