1
# Copyright (C) 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
17
# Author: Aaron Bentley <aaron.bentley@utoronto.ca>
19
"""Black-box tests for bzr merge.
24
from bzrlib import merge_directive
25
from bzrlib.branch import Branch
26
from bzrlib.bzrdir import BzrDir
27
from bzrlib.conflicts import ConflictList, ContentsConflict
28
from bzrlib.osutils import abspath, file_kind
29
from bzrlib.tests.blackbox import ExternalBase
30
import bzrlib.urlutils as urlutils
31
from bzrlib.workingtree import WorkingTree
34
class TestMerge(ExternalBase):
36
def example_branch(test):
38
file('hello', 'wt').write('foo')
39
test.run_bzr('add hello')
40
test.run_bzr('commit -m setup hello')
41
file('goodbye', 'wt').write('baz')
42
test.run_bzr('add goodbye')
43
test.run_bzr('commit -m setup goodbye')
45
def test_merge_reprocess(self):
46
d = BzrDir.create_standalone_workingtree('.')
48
self.run_bzr('merge . --reprocess --merge-type weave')
51
from bzrlib.branch import Branch
56
ancestor = Branch.open('.').revno()
58
self.run_bzr('branch a b')
60
file('goodbye', 'wt').write('quux')
61
self.run_bzr(['commit', '-m', "more u's are always good"])
64
file('hello', 'wt').write('quuux')
65
# We can't merge when there are in-tree changes
66
self.run_bzr('merge ../b', retcode=3)
67
a = WorkingTree.open('.')
68
a_tip = a.commit("Like an epidemic of u's")
69
self.run_bzr('merge ../b -r last:1..last:1 --merge-type blooof',
71
self.run_bzr('merge ../b -r last:1..last:1 --merge-type merge3')
72
self.run_bzr('revert --no-backup')
73
self.run_bzr('merge ../b -r last:1..last:1 --merge-type weave')
74
self.run_bzr('revert --no-backup')
75
self.run_bzr_error(['Show-base is not supported for this merge type'],
76
'merge ../b -r last:1..last:1 --merge-type weave'
78
self.run_bzr('revert --no-backup')
79
self.run_bzr('merge ../b -r last:1..last:1 --reprocess')
80
self.run_bzr('revert --no-backup')
81
self.run_bzr('merge ../b -r last:1')
82
self.check_file_contents('goodbye', 'quux')
83
# Merging a branch pulls its revision into the tree
84
b = Branch.open('../b')
85
b_tip = b.last_revision()
86
self.failUnless(a.branch.repository.has_revision(b_tip))
87
self.assertEqual([a_tip, b_tip], a.get_parent_ids())
88
self.run_bzr('revert --no-backup')
89
out, err = self.run_bzr('merge -r revno:1:./hello', retcode=3)
90
self.assertTrue("Not a branch" in err)
91
self.run_bzr('merge -r revno:%d:./..revno:%d:../b'
92
%(ancestor,b.revno()))
93
self.assertEquals(a.get_parent_ids(),
94
[a.branch.last_revision(), b.last_revision()])
95
self.check_file_contents('goodbye', 'quux')
96
self.run_bzr('revert --no-backup')
97
self.run_bzr('merge -r revno:%d:../b'%b.revno())
98
self.assertEquals(a.get_parent_ids(),
99
[a.branch.last_revision(), b.last_revision()])
100
a_tip = a.commit('merged')
101
self.run_bzr('merge ../b -r last:1')
102
self.assertEqual([a_tip], a.get_parent_ids())
104
def test_merge_with_missing_file(self):
105
"""Merge handles missing file conflicts"""
109
print >> file('sub/a.txt', 'wb'), "hello"
110
print >> file('b.txt', 'wb'), "hello"
111
print >> file('sub/c.txt', 'wb'), "hello"
114
self.run_bzr(['commit', '-m', 'added a'])
115
self.run_bzr('branch . ../b')
116
print >> file('sub/a.txt', 'ab'), "there"
117
print >> file('b.txt', 'ab'), "there"
118
print >> file('sub/c.txt', 'ab'), "there"
119
self.run_bzr(['commit', '-m', 'Added there'])
120
os.unlink('sub/a.txt')
121
os.unlink('sub/c.txt')
124
self.run_bzr(['commit', '-m', 'Removed a.txt'])
126
print >> file('sub/a.txt', 'ab'), "something"
127
print >> file('b.txt', 'ab'), "something"
128
print >> file('sub/c.txt', 'ab'), "something"
129
self.run_bzr(['commit', '-m', 'Modified a.txt'])
130
self.run_bzr('merge ../a/', retcode=1)
131
self.assert_(os.path.exists('sub/a.txt.THIS'))
132
self.assert_(os.path.exists('sub/a.txt.BASE'))
134
self.run_bzr('merge ../b/', retcode=1)
135
self.assert_(os.path.exists('sub/a.txt.OTHER'))
136
self.assert_(os.path.exists('sub/a.txt.BASE'))
138
def test_merge_remember(self):
139
"""Merge changes from one branch to another and test parent location."""
140
tree_a = self.make_branch_and_tree('branch_a')
141
branch_a = tree_a.branch
142
self.build_tree(['branch_a/a'])
144
tree_a.commit('commit a')
145
branch_b = branch_a.bzrdir.sprout('branch_b').open_branch()
146
tree_b = branch_b.bzrdir.open_workingtree()
147
branch_c = branch_a.bzrdir.sprout('branch_c').open_branch()
148
tree_c = branch_c.bzrdir.open_workingtree()
149
self.build_tree(['branch_a/b'])
151
tree_a.commit('commit b')
152
self.build_tree(['branch_c/c'])
154
tree_c.commit('commit c')
156
parent = branch_b.get_parent()
157
branch_b.set_parent(None)
158
self.assertEqual(None, branch_b.get_parent())
159
# test merge for failure without parent set
161
out = self.run_bzr('merge', retcode=3)
162
self.assertEquals(out,
163
('','bzr: ERROR: No location specified or remembered\n'))
164
# test implicit --remember when no parent set, this merge conflicts
165
self.build_tree(['d'])
167
out = self.run_bzr('merge ../branch_a', retcode=3)
168
self.assertEquals(out,
169
('','bzr: ERROR: Working tree has uncommitted changes.\n'))
170
self.assertEquals(abspath(branch_b.get_parent()), abspath(parent))
171
# test implicit --remember after resolving conflict
172
tree_b.commit('commit d')
173
out, err = self.run_bzr('merge')
175
base = urlutils.local_path_from_url(branch_a.base)
176
self.assertEquals(out, 'Merging from remembered location %s\n' % (base,))
177
self.assertEquals(err, '+N b\nAll changes applied successfully.\n')
178
self.assertEquals(abspath(branch_b.get_parent()), abspath(parent))
179
# re-open tree as external run_bzr modified it
180
tree_b = branch_b.bzrdir.open_workingtree()
181
tree_b.commit('merge branch_a')
182
# test explicit --remember
183
out, err = self.run_bzr('merge ../branch_c --remember')
184
self.assertEquals(out, '')
185
self.assertEquals(err, '+N c\nAll changes applied successfully.\n')
186
self.assertEquals(abspath(branch_b.get_parent()),
187
abspath(branch_c.bzrdir.root_transport.base))
188
# re-open tree as external run_bzr modified it
189
tree_b = branch_b.bzrdir.open_workingtree()
190
tree_b.commit('merge branch_c')
192
def test_merge_bundle(self):
193
from bzrlib.testament import Testament
194
tree_a = self.make_branch_and_tree('branch_a')
195
f = file('branch_a/a', 'wb')
199
tree_a.commit('message')
201
tree_b = tree_a.bzrdir.sprout('branch_b').open_workingtree()
202
f = file('branch_a/a', 'wb')
205
tree_a.commit('message')
207
f = file('branch_b/a', 'wb')
210
tree_b.commit('message')
212
self.run_bzr('bundle ../branch_a -o ../bundle')
213
os.chdir('../branch_a')
214
self.run_bzr('merge ../bundle', retcode=1)
215
testament_a = Testament.from_revision(tree_a.branch.repository,
216
tree_b.get_parent_ids()[0])
217
testament_b = Testament.from_revision(tree_b.branch.repository,
218
tree_b.get_parent_ids()[0])
219
self.assertEqualDiff(testament_a.as_text(),
220
testament_b.as_text())
221
tree_a.set_conflicts(ConflictList())
222
tree_a.commit('message')
223
# it is legal to attempt to merge an already-merged bundle
224
output = self.run_bzr('merge ../bundle')[1]
225
# but it does nothing
226
self.assertFalse(tree_a.changes_from(tree_a.basis_tree()).has_changed())
227
self.assertEqual('Nothing to do.\n', output)
229
def test_merge_uncommitted(self):
230
"""Check that merge --uncommitted behaves properly"""
231
tree_a = self.make_branch_and_tree('a')
232
self.build_tree(['a/file_1', 'a/file_2'])
233
tree_a.add(['file_1', 'file_2'])
234
tree_a.commit('commit 1')
235
tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
236
self.failUnlessExists('b/file_1')
237
tree_a.rename_one('file_1', 'file_i')
238
tree_a.commit('commit 2')
239
tree_a.rename_one('file_2', 'file_ii')
241
self.run_bzr('merge a --uncommitted -d b')
242
self.failUnlessExists('b/file_1')
243
self.failUnlessExists('b/file_ii')
245
self.run_bzr_error(('Cannot use --uncommitted and --revision',),
246
'merge /a --uncommitted -r1 -d b')
248
def pullable_branch(self):
251
self.example_branch()
253
self.run_bzr('branch a b')
255
file('goodbye', 'wt').write('quux')
256
self.run_bzr(['commit', '-m', "mode u's are always good"])
259
def pullable_branch(self):
260
tree_a = self.make_branch_and_tree('a')
261
self.build_tree(['a/file'])
263
self.id1 = tree_a.commit('commit 1')
265
tree_b = self.make_branch_and_tree('b')
266
tree_b.pull(tree_a.branch)
267
file('b/file', 'wb').write('foo')
268
self.id2 = tree_b.commit('commit 2')
270
def test_merge_pull(self):
271
self.pullable_branch()
273
(out, err) = self.run_bzr('merge --pull ../b')
274
self.assertContainsRe(out, 'Now on revision 2\\.')
275
tree_a = WorkingTree.open('.')
276
self.assertEqual([self.id2], tree_a.get_parent_ids())
278
def test_merge_kind_change(self):
279
tree_a = self.make_branch_and_tree('tree_a')
280
self.build_tree_contents([('tree_a/file', 'content_1')])
281
tree_a.add('file', 'file-id')
282
tree_a.commit('added file')
283
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
284
os.unlink('tree_a/file')
285
self.build_tree(['tree_a/file/'])
286
tree_a.commit('changed file to directory')
288
self.run_bzr('merge ../tree_a')
289
self.assertEqual('directory', file_kind('file'))
291
self.assertEqual('file', file_kind('file'))
292
self.build_tree_contents([('file', 'content_2')])
293
tree_b.commit('content change')
294
self.run_bzr('merge ../tree_a', retcode=1)
295
self.assertEqual(tree_b.conflicts(),
296
[ContentsConflict('file', file_id='file-id')])
298
def test_directive_cherrypick(self):
299
source = self.make_branch_and_tree('source')
300
self.build_tree(['source/a'])
302
source.commit('Added a', rev_id='rev1')
303
self.build_tree(['source/b'])
305
source.commit('Added b', rev_id='rev2')
306
target = self.make_branch_and_tree('target')
307
target.commit('empty commit')
308
self.write_directive('directive', source.branch, 'target', 'rev2',
310
self.run_bzr('merge -d target directive')
311
self.failIfExists('target/a')
312
self.failUnlessExists('target/b')
314
def write_directive(self, filename, source, target, revision_id,
315
base_revision_id=None, mangle_patch=False):
316
md = merge_directive.MergeDirective2.from_objects(
317
source.repository, revision_id, 0, 0, target,
318
base_revision_id=base_revision_id)
321
self.build_tree_contents([(filename, ''.join(md.to_lines()))])
323
def test_directive_verify_warning(self):
324
source = self.make_branch_and_tree('source')
325
self.build_tree(['source/a'])
327
source.commit('Added a', rev_id='rev1')
328
target = self.make_branch_and_tree('target')
329
target.commit('empty commit')
330
self.write_directive('directive', source.branch, 'target', 'rev1')
331
err = self.run_bzr('merge -d target directive')[1]
332
self.assertNotContainsRe(err, 'Preview patch does not match changes')
334
self.write_directive('directive', source.branch, 'target', 'rev1',
336
err = self.run_bzr('merge -d target directive')[1]
337
self.assertContainsRe(err, 'Preview patch does not match changes')
339
def test_merge_arbitrary(self):
340
target = self.make_branch_and_tree('target')
341
target.commit('empty')
342
# We need a revision that has no integer revno
343
branch_a = target.bzrdir.sprout('branch_a').open_workingtree()
344
self.build_tree(['branch_a/file1'])
345
branch_a.add('file1')
346
branch_a.commit('added file1', rev_id='rev2a')
347
branch_b = target.bzrdir.sprout('branch_b').open_workingtree()
348
self.build_tree(['branch_b/file2'])
349
branch_b.add('file2')
350
branch_b.commit('added file2', rev_id='rev2b')
351
branch_b.merge_from_branch(branch_a.branch)
352
self.failUnlessExists('branch_b/file1')
353
branch_b.commit('merged branch_a', rev_id='rev3b')
355
# It works if the revid has an interger revno
356
self.run_bzr('merge -d target -r revid:rev2a branch_a')
357
self.failUnlessExists('target/file1')
358
self.failIfExists('target/file2')
361
# It should work if the revid has no integer revno
362
self.run_bzr('merge -d target -r revid:rev2a branch_b')
363
self.failUnlessExists('target/file1')
364
self.failIfExists('target/file2')