1
# Copyright (C) 2005, 2006, 2007 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',
186
format='dirstate-with-subtree')
187
sub_tree = self.make_branch_and_tree('tree/sub-tree',
188
format='dirstate-with-subtree')
189
sub_tree.set_root_id('sub-tree-root')
190
self.build_tree_contents([('tree/sub-tree/file', 'text1')])
192
sub_tree.commit('foo')
193
tree.add_reference(sub_tree)
194
tree.commit('set text to 1')
195
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
196
# modify the file in the subtree
197
self.build_tree_contents([('tree2/sub-tree/file', 'text2')])
198
# and merge the changes from the diverged subtree into the containing
200
tree2.commit('changed file text')
201
tree.merge_from_branch(tree2.branch)
202
self.assertFileEqual('text2', 'tree/sub-tree/file')
204
def test_merge_with_missing(self):
205
tree_a = self.make_branch_and_tree('tree_a')
206
self.build_tree_contents([('tree_a/file', 'content_1')])
208
tree_a.commit('commit base')
209
# basis_tree() is only guaranteed to be valid as long as it is actually
210
# the basis tree. This mutates the tree after grabbing basis, so go to
212
base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
213
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
214
self.build_tree_contents([('tree_a/file', 'content_2')])
215
tree_a.commit('commit other')
216
other_tree = tree_a.basis_tree()
217
os.unlink('tree_b/file')
218
merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
220
def test_merge_kind_change(self):
221
tree_a = self.make_branch_and_tree('tree_a')
222
self.build_tree_contents([('tree_a/file', 'content_1')])
223
tree_a.add('file', 'file-id')
224
tree_a.commit('added file')
225
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
226
os.unlink('tree_a/file')
227
self.build_tree(['tree_a/file/'])
228
tree_a.commit('changed file to directory')
229
tree_b.merge_from_branch(tree_a.branch)
230
self.assertEqual('directory', file_kind('tree_b/file'))
232
self.assertEqual('file', file_kind('tree_b/file'))
233
self.build_tree_contents([('tree_b/file', 'content_2')])
234
tree_b.commit('content change')
235
tree_b.merge_from_branch(tree_a.branch)
236
self.assertEqual(tree_b.conflicts(),
237
[conflicts.ContentsConflict('file',
240
def test_merge_type_registry(self):
241
merge_type_option = option.Option.OPTIONS['merge-type']
242
self.assertFalse('merge4' in [x[0] for x in
243
merge_type_option.iter_switches()])
244
registry = _mod_merge.get_merge_type_registry()
245
registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
246
'time-travelling merge')
247
self.assertTrue('merge4' in [x[0] for x in
248
merge_type_option.iter_switches()])
249
registry.remove('merge4')
250
self.assertFalse('merge4' in [x[0] for x in
251
merge_type_option.iter_switches()])
253
def test_merge_other_moves_we_deleted(self):
254
tree_a = self.make_branch_and_tree('A')
256
self.addCleanup(tree_a.unlock)
257
self.build_tree(['A/a'])
259
tree_a.commit('1', rev_id='rev-1')
261
tree_a.rename_one('a', 'b')
263
bzrdir_b = tree_a.bzrdir.sprout('B', revision_id='rev-1')
264
tree_b = bzrdir_b.open_workingtree()
266
self.addCleanup(tree_b.unlock)
270
tree_b.merge_from_branch(tree_a.branch)
271
except AttributeError:
272
self.fail('tried to join a path when name was None')