~bzr-pqm/bzr/bzr.dev

4634.149.1 by Vincent Ladeuil
Fix imports.
1
# Copyright (C) 2006-2010 Canonical Ltd
1979.2.1 by Robert Collins
(robertc) adds a convenience method "merge_from_branch" to WorkingTree.
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1979.2.1 by Robert Collins
(robertc) adds a convenience method "merge_from_branch" to WorkingTree.
17
18
"""Tests for the WorkingTree.merge_from_branch api."""
19
1551.10.31 by Aaron Bentley
Fix WorkingTree4._iter_changes with pending merges and deleted files
20
import os
21
1551.15.69 by Aaron Bentley
Add merge_type to merge_from_branch
22
from bzrlib import (
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
23
    conflicts,
1551.15.69 by Aaron Bentley
Add merge_type to merge_from_branch
24
    errors,
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
25
    merge,
1551.15.69 by Aaron Bentley
Add merge_type to merge_from_branch
26
    )
4634.149.1 by Vincent Ladeuil
Fix imports.
27
from bzrlib.tests import per_workingtree
28
29
30
class TestMergeFromBranch(per_workingtree.TestCaseWithWorkingTree):
1979.2.1 by Robert Collins
(robertc) adds a convenience method "merge_from_branch" to WorkingTree.
31
32
    def create_two_trees_for_merging(self):
33
        """Create two trees that can be merged from.
34
35
        This sets self.tree_from, self.first_rev, self.tree_to, self.second_rev
36
        and self.to_second_rev.
37
        """
38
        self.tree_from = self.make_branch_and_tree('from')
39
        self.first_rev = self.tree_from.commit('first post')
40
        self.tree_to = self.tree_from.bzrdir.sprout('to').open_workingtree()
41
        self.second_rev = self.tree_from.commit('second rev', allow_pointless=True)
42
        self.to_second_rev = self.tree_to.commit('second rev', allow_pointless=True)
43
44
    def test_smoking_merge(self):
45
        """Smoke test of merge_from_branch."""
46
        self.create_two_trees_for_merging()
47
        self.tree_to.merge_from_branch(self.tree_from.branch)
48
        self.assertEqual([self.to_second_rev, self.second_rev],
49
            self.tree_to.get_parent_ids())
50
51
    def test_merge_to_revision(self):
52
        """Merge from a branch to a revision that is not the tip."""
53
        self.create_two_trees_for_merging()
54
        self.third_rev = self.tree_from.commit('real_tip')
55
        self.tree_to.merge_from_branch(self.tree_from.branch,
56
            to_revision=self.second_rev)
57
        self.assertEqual([self.to_second_rev, self.second_rev],
58
            self.tree_to.get_parent_ids())
1551.10.31 by Aaron Bentley
Fix WorkingTree4._iter_changes with pending merges and deleted files
59
60
    def test_compare_after_merge(self):
61
        tree_a = self.make_branch_and_tree('tree_a')
62
        self.build_tree_contents([('tree_a/file', 'text-a')])
63
        tree_a.add('file')
64
        tree_a.commit('added file')
65
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
66
        os.unlink('tree_a/file')
67
        tree_a.commit('deleted file')
68
        self.build_tree_contents([('tree_b/file', 'text-b')])
69
        tree_b.commit('changed file')
70
        tree_a.merge_from_branch(tree_b.branch)
71
        tree_a.lock_read()
72
        self.addCleanup(tree_a.unlock)
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
73
        list(tree_a.iter_changes(tree_a.basis_tree()))
2490.2.28 by Aaron Bentley
Fix handling of null revision
74
75
    def test_merge_empty(self):
76
        tree_a = self.make_branch_and_tree('tree_a')
77
        self.build_tree_contents([('tree_a/file', 'text-a')])
78
        tree_a.add('file')
79
        tree_a.commit('added file')
80
        tree_b = self.make_branch_and_tree('treeb')
81
        self.assertRaises(errors.NoCommits, tree_a.merge_from_branch,
82
                          tree_b.branch)
83
        tree_b.merge_from_branch(tree_a.branch)
1551.15.68 by Aaron Bentley
Add support for base to merge_from_branch
84
85
    def test_merge_base(self):
86
        tree_a = self.make_branch_and_tree('tree_a')
87
        self.build_tree_contents([('tree_a/file', 'text-a')])
88
        tree_a.add('file')
89
        tree_a.commit('added file', rev_id='rev_1')
90
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
91
        os.unlink('tree_a/file')
92
        tree_a.commit('deleted file')
93
        self.build_tree_contents([('tree_b/file', 'text-b')])
94
        tree_b.commit('changed file')
95
        self.assertRaises(errors.PointlessMerge, tree_a.merge_from_branch,
96
            tree_b.branch, from_revision=tree_b.branch.last_revision())
97
        tree_a.merge_from_branch(tree_b.branch, from_revision='rev_1')
98
        tree_a.lock_read()
99
        self.addCleanup(tree_a.unlock)
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
100
        changes = list(tree_a.iter_changes(tree_a.basis_tree()))
1551.15.68 by Aaron Bentley
Add support for base to merge_from_branch
101
        self.assertEqual(1, len(changes))
1551.15.69 by Aaron Bentley
Add merge_type to merge_from_branch
102
103
    def test_merge_type(self):
104
        this = self.make_branch_and_tree('this')
105
        self.build_tree_contents([('this/foo', 'foo')])
106
        this.add('foo', 'foo-id')
107
        this.commit('added foo')
108
        other = this.bzrdir.sprout('other').open_workingtree()
109
        self.build_tree_contents([('other/foo', 'bar')])
110
        other.commit('content -> bar')
111
        self.build_tree_contents([('this/foo', 'baz')])
112
        this.commit('content -> baz')
113
        class QuxMerge(merge.Merge3Merger):
114
            def text_merge(self, file_id, trans_id):
115
                self.tt.create_file('qux', trans_id)
116
        this.merge_from_branch(other.branch, merge_type=QuxMerge)
117
        self.assertEqual('qux', this.get_file_text('foo-id'))
4634.149.2 by Vincent Ladeuil
Reproduce bug #373898.
118
119
120
class TestMergedBranch(per_workingtree.TestCaseWithWorkingTree):
121
122
    def make_inner_branch(self):
123
        bld_inner = self.make_branch_builder('inner')
124
        bld_inner.start_series()
125
        bld_inner.build_snapshot(
126
            '1', None,
127
            [('add', ('', 'inner-root-id', 'directory', '')),
128
             ('add', ('dir', 'dir-id', 'directory', '')),
129
             ('add', ('dir/file1', 'file1-id', 'file', 'file1 content\n')),
130
             ('add', ('file3', 'file3-id', 'file', 'file3 content\n')),
131
             ])
132
        bld_inner.build_snapshot(
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
133
            '4', ['1'],
134
            [('add', ('file4', 'file4-id', 'file', 'file4 content\n'))
135
             ])
136
        bld_inner.build_snapshot(
137
            '5', ['4'], [('rename', ('file4', 'dir/file4'))])
138
        bld_inner.build_snapshot(
4634.149.2 by Vincent Ladeuil
Reproduce bug #373898.
139
            '3', ['1'], [('modify', ('file3-id', 'new file3 contents\n')),])
140
        bld_inner.build_snapshot(
141
            '2', ['1'],
142
            [('add', ('dir/file2', 'file2-id', 'file', 'file2 content\n')),
143
             ])
144
        bld_inner.finish_series()
145
        br = bld_inner.get_branch()
146
        return br
147
148
    def assertTreeLayout(self, expected, tree):
6072.2.5 by Jelmer Vernooij
Don't use for test_merge_from_branch.
149
        tree.lock_read()
150
        try:
151
            actual = [e[0] for e in tree.list_files()]
152
            # list_files doesn't guarantee order
153
            actual = sorted(actual)
154
            self.assertEqual(expected, actual)
155
        finally:
156
            tree.unlock()
4634.149.2 by Vincent Ladeuil
Reproduce bug #373898.
157
158
    def make_outer_tree(self):
159
        outer = self.make_branch_and_tree('outer')
160
        self.build_tree_contents([('outer/foo', 'foo')])
161
        outer.add('foo', 'foo-id')
162
        outer.commit('added foo')
163
        inner = self.make_inner_branch()
164
        outer.merge_from_branch(inner, to_revision='1', from_revision='null:')
5954.4.9 by Aaron Bentley
Fix tests that assumed root was retained in a new-root conflict.
165
        #retain original root id.
166
        outer.set_root_id(outer.basis_tree().get_root_id())
4634.149.2 by Vincent Ladeuil
Reproduce bug #373898.
167
        outer.commit('merge inner branch')
168
        outer.mkdir('dir-outer', 'dir-outer-id')
169
        outer.move(['dir', 'file3'], to_dir='dir-outer')
170
        outer.commit('rename imported dir and file3 to dir-outer')
171
        return outer, inner
172
173
    def test_file1_deleted_in_dir(self):
174
        outer, inner = self.make_outer_tree()
175
        outer.remove(['dir-outer/dir/file1'], keep_files=False)
176
        outer.commit('delete file1')
177
        outer.merge_from_branch(inner)
178
        outer.commit('merge the rest')
179
        self.assertTreeLayout(['dir-outer',
180
                               'dir-outer/dir',
181
                               'dir-outer/dir/file2',
182
                               'dir-outer/file3',
183
                               'foo'],
184
                              outer)
185
186
    def test_file3_deleted_in_root(self):
187
        # Reproduce bug #375898
188
        outer, inner = self.make_outer_tree()
189
        outer.remove(['dir-outer/file3'], keep_files=False)
190
        outer.commit('delete file3')
191
        outer.merge_from_branch(inner)
192
        outer.commit('merge the rest')
193
        self.assertTreeLayout(['dir-outer',
194
                               'dir-outer/dir',
195
                               'dir-outer/dir/file1',
196
                               'dir-outer/dir/file2',
197
                               'foo'],
198
                              outer)
199
200
201
    def test_file3_in_root_conflicted(self):
202
        outer, inner = self.make_outer_tree()
203
        outer.remove(['dir-outer/file3'], keep_files=False)
204
        outer.commit('delete file3')
205
        nb_conflicts = outer.merge_from_branch(inner, to_revision='3')
5954.4.9 by Aaron Bentley
Fix tests that assumed root was retained in a new-root conflict.
206
        self.assertEqual(4, nb_conflicts)
4634.149.2 by Vincent Ladeuil
Reproduce bug #373898.
207
        self.assertTreeLayout(['dir-outer',
208
                               'dir-outer/dir',
209
                               'dir-outer/dir/file1',
210
                               # Ideally th conflict helpers should be in
211
                               # dir-outer/dir but since we can't easily find
212
                               # back the file3 -> outer-dir/dir rename, root
213
                               # is good enough -- vila 20100401
214
                               'file3.BASE',
215
                               'file3.OTHER',
216
                               'foo'],
217
                              outer)
218
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
219
    def test_file4_added_in_root(self):
220
        outer, inner = self.make_outer_tree()
221
        nb_conflicts = outer.merge_from_branch(inner, to_revision='4')
5954.4.9 by Aaron Bentley
Fix tests that assumed root was retained in a new-root conflict.
222
        # file4 could not be added to its original root, so it gets added to
223
        # the new root with a conflict.
224
        self.assertEqual(1, nb_conflicts)
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
225
        self.assertTreeLayout(['dir-outer',
226
                               'dir-outer/dir',
227
                               'dir-outer/dir/file1',
228
                               'dir-outer/file3',
229
                               'file4',
230
                               'foo'],
231
                              outer)
232
233
    def test_file4_added_then_renamed(self):
234
        outer, inner = self.make_outer_tree()
5954.4.9 by Aaron Bentley
Fix tests that assumed root was retained in a new-root conflict.
235
        # 1 conflict, because file4 can't be put into the old root
236
        self.assertEqual(1, outer.merge_from_branch(inner, to_revision='4'))
237
        try:
238
            outer.set_conflicts(conflicts.ConflictList())
239
        except errors.UnsupportedOperation:
240
            # WT2 doesn't have a separate list of conflicts to clear. It
241
            # actually says there is a conflict, but happily forgets all about
242
            # it.
243
            pass
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
244
        outer.commit('added file4')
245
        # And now file4 gets renamed into an existing dir
246
        nb_conflicts = outer.merge_from_branch(inner, to_revision='5')
5954.4.9 by Aaron Bentley
Fix tests that assumed root was retained in a new-root conflict.
247
        self.assertEqual(1, nb_conflicts)
5783.2.1 by John Arbash Meinel
Remove the 'include_unchanged=True' from iter_changes.
248
        self.assertTreeLayout(['dir-outer',
249
                               'dir-outer/dir',
250
                               'dir-outer/dir/file1',
251
                               'dir-outer/dir/file4',
252
                               'dir-outer/file3',
253
                               'foo'],
254
                              outer)