1
# Copyright (C) 2005, 2006 Canonical Ltd
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from StringIO import StringIO
25
from bzrlib.branch import Branch
26
from bzrlib.builtins import merge
27
from bzrlib.conflicts import ConflictList, TextConflict
28
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
29
from bzrlib.merge import transform_tree, merge_inner
30
from bzrlib.osutils import pathjoin, file_kind
31
from bzrlib.revision import common_ancestor
32
from bzrlib.tests import TestCaseWithTransport
33
from bzrlib.trace import (enable_test_log, disable_test_log)
34
from bzrlib.workingtree import WorkingTree
37
class TestMerge(TestCaseWithTransport):
38
"""Test appending more than one revision"""
40
def test_pending(self):
41
wt = self.make_branch_and_tree('.')
42
rev_a = wt.commit("lala!")
43
self.assertEqual([rev_a], wt.get_parent_ids())
44
merge([u'.', -1], [None, None])
45
self.assertEqual([rev_a], wt.get_parent_ids())
48
wt = self.make_branch_and_tree('.')
52
merge([u'.', 2], [u'.', 1])
54
def test_nocommits(self):
56
wt2 = self.make_branch_and_tree('branch2')
57
self.assertRaises(NoCommits, merge, ['branch2', -1],
61
def test_unrelated(self):
62
wt2 = self.test_nocommits()
64
self.assertRaises(UnrelatedBranches, merge, ['branch2', -1],
68
def test_merge_one_file(self):
69
"""Do a partial merge of a tree which should not affect tree parents."""
70
wt1 = self.make_branch_and_tree('branch1')
71
tip = wt1.commit('empty commit')
72
wt2 = self.make_branch_and_tree('branch2')
74
file('branch1/foo', 'wb').write('foo')
75
file('branch1/bar', 'wb').write('bar')
78
wt1.commit('add foobar')
80
self.run_bzr('merge', '../branch1/baz', retcode=3)
81
self.run_bzr('merge', '../branch1/foo')
82
self.failUnlessExists('foo')
83
self.failIfExists('bar')
84
wt2 = WorkingTree.open('.') # opens branch2
85
self.assertEqual([tip], wt2.get_parent_ids())
87
def test_pending_with_null(self):
88
"""When base is forced to revno 0, parent_ids are set"""
89
wt2 = self.test_unrelated()
90
wt1 = WorkingTree.open('.')
93
# merge all of branch 2 into branch 1 even though they
95
self.assertRaises(BzrCommandError, merge, ['branch2', -1],
96
['branch2', 0], reprocess=True, show_base=True)
97
merge(['branch2', -1], ['branch2', 0], reprocess=True)
98
self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
100
return (wt1, wt2.branch)
102
def test_two_roots(self):
103
"""Merge base is sane when two unrelated branches are merged"""
104
wt1, br2 = self.test_pending_with_null()
106
last = wt1.branch.last_revision()
107
self.assertEqual(common_ancestor(last, last, wt1.branch.repository), last)
109
def test_create_rename(self):
110
"""Rename an inventory entry while creating the file"""
111
tree =self.make_branch_and_tree('.')
112
file('name1', 'wb').write('Hello')
114
tree.commit(message="hello")
115
tree.rename_one('name1', 'name2')
117
transform_tree(tree, tree.branch.basis_tree())
119
def test_layered_rename(self):
120
"""Rename both child and parent at same time"""
121
tree =self.make_branch_and_tree('.')
124
filename = pathjoin('dirname1', 'name1')
125
file(filename, 'wb').write('Hello')
127
tree.commit(message="hello")
128
filename2 = pathjoin('dirname1', 'name2')
129
tree.rename_one(filename, filename2)
130
tree.rename_one('dirname1', 'dirname2')
131
transform_tree(tree, tree.branch.basis_tree())
133
def test_ignore_zero_merge_inner(self):
134
# Test that merge_inner's ignore zero parameter is effective
135
tree_a =self.make_branch_and_tree('a')
136
tree_a.commit(message="hello")
137
dir_b = tree_a.bzrdir.sprout('b')
138
tree_b = dir_b.open_workingtree()
139
tree_a.commit(message="hello again")
141
merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
142
this_tree=tree_b, ignore_zero=True)
143
log = self._get_log(keep_log_file=True)
144
self.failUnless('All changes applied successfully.\n' not in log)
146
merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
147
this_tree=tree_b, ignore_zero=False)
148
log = self._get_log(keep_log_file=True)
149
self.failUnless('All changes applied successfully.\n' in log)
151
def test_merge_inner_conflicts(self):
152
tree_a = self.make_branch_and_tree('a')
153
tree_a.set_conflicts(ConflictList([TextConflict('patha')]))
154
merge_inner(tree_a.branch, tree_a, tree_a, this_tree=tree_a)
155
self.assertEqual(1, len(tree_a.conflicts()))
157
def test_rmdir_conflict(self):
158
tree_a = self.make_branch_and_tree('a')
159
self.build_tree(['a/b/'])
160
tree_a.add('b', 'b-id')
161
tree_a.commit('added b')
162
# basis_tree() is only guaranteed to be valid as long as it is actually
163
# the basis tree. This mutates the tree after grabbing basis, so go to
165
base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
166
tree_z = tree_a.bzrdir.sprout('z').open_workingtree()
167
self.build_tree(['a/b/c'])
169
tree_a.commit('added c')
171
tree_z.commit('removed b')
172
merge_inner(tree_z.branch, tree_a, base_tree, this_tree=tree_z)
174
conflicts.MissingParent('Created directory', 'b', 'b-id'),
175
conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
177
merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree,
180
conflicts.DeletingParent('Not deleting', 'b', 'b-id'),
181
conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
184
def test_nested_merge(self):
185
tree = self.make_branch_and_tree('tree', format='experimental-reference-dirstate')
186
sub_tree = self.make_branch_and_tree('tree/sub-tree',
187
format='experimental-reference-dirstate')
188
sub_tree.set_root_id('sub-tree-root')
189
self.build_tree_contents([('tree/sub-tree/file', 'text1')])
191
sub_tree.commit('foo')
192
tree.add_reference(sub_tree)
193
tree.commit('set text to 1')
194
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
195
self.build_tree_contents([('tree2/sub-tree/file', 'text2')])
198
subtree2 = tree2.get_nested_tree(tree2.inventory['sub-tree-root'],
200
tree2.commit('changed file text')
203
tree.merge_from_branch(tree2.branch)
204
self.assertFileEqual('text2', 'tree/sub-tree/file')
206
def test_merge_with_missing(self):
207
tree_a = self.make_branch_and_tree('tree_a')
208
self.build_tree_contents([('tree_a/file', 'content_1')])
210
tree_a.commit('commit base')
211
# basis_tree() is only guaranteed to be valid as long as it is actually
212
# the basis tree. This mutates the tree after grabbing basis, so go to
214
base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
215
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
216
self.build_tree_contents([('tree_a/file', 'content_2')])
217
tree_a.commit('commit other')
218
other_tree = tree_a.basis_tree()
219
os.unlink('tree_b/file')
220
merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
222
def test_merge_kind_change(self):
223
tree_a = self.make_branch_and_tree('tree_a')
224
self.build_tree_contents([('tree_a/file', 'content_1')])
225
tree_a.add('file', 'file-id')
226
tree_a.commit('added file')
227
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
228
os.unlink('tree_a/file')
229
self.build_tree(['tree_a/file/'])
230
tree_a.commit('changed file to directory')
231
tree_b.merge_from_branch(tree_a.branch)
232
self.assertEqual('directory', file_kind('tree_b/file'))
234
self.assertEqual('file', file_kind('tree_b/file'))
235
self.build_tree_contents([('tree_b/file', 'content_2')])
236
tree_b.commit('content change')
237
tree_b.merge_from_branch(tree_a.branch)
238
self.assertEqual(tree_b.conflicts(),
239
[conflicts.ContentsConflict('file',
242
def test_merge_type_registry(self):
243
merge_type_option = option.Option.OPTIONS['merge-type']
244
self.assertFalse('merge4' in [x[0] for x in
245
merge_type_option.iter_switches()])
246
registry = _mod_merge.get_merge_type_registry()
247
registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
248
'time-travelling merge')
249
self.assertTrue('merge4' in [x[0] for x in
250
merge_type_option.iter_switches()])
251
registry.remove('merge4')
252
self.assertFalse('merge4' in [x[0] for x in
253
merge_type_option.iter_switches()])