~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/blackbox/test_merge.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
#
17
17
# Author: Aaron Bentley <aaron.bentley@utoronto.ca>
18
18
 
21
21
 
22
22
import os
23
23
 
 
24
from bzrlib import merge_directive
24
25
from bzrlib.branch import Branch
25
26
from bzrlib.bzrdir import BzrDir
26
 
from bzrlib.conflicts import ConflictList
27
 
from bzrlib.osutils import abspath
 
27
from bzrlib.conflicts import ConflictList, ContentsConflict
 
28
from bzrlib.osutils import abspath, file_kind, pathjoin
28
29
from bzrlib.tests.blackbox import ExternalBase
29
30
import bzrlib.urlutils as urlutils
30
31
from bzrlib.workingtree import WorkingTree
32
33
 
33
34
class TestMerge(ExternalBase):
34
35
 
35
 
    def example_branch(test):
36
 
        test.runbzr('init')
37
 
        file('hello', 'wt').write('foo')
38
 
        test.runbzr('add hello')
39
 
        test.runbzr('commit -m setup hello')
40
 
        file('goodbye', 'wt').write('baz')
41
 
        test.runbzr('add goodbye')
42
 
        test.runbzr('commit -m setup goodbye')
 
36
    def example_branch(self, path='.'):
 
37
        tree = self.make_branch_and_tree(path)
 
38
        self.build_tree_contents([
 
39
            (pathjoin(path, 'hello'), 'foo'),
 
40
            (pathjoin(path, 'goodbye'), 'baz')])
 
41
        tree.add('hello')
 
42
        tree.commit(message='setup')
 
43
        tree.add('goodbye')
 
44
        tree.commit(message='setup')
 
45
        return tree
 
46
 
 
47
    def create_conflicting_branches(self):
 
48
        """Create two branches which have overlapping modifications.
 
49
 
 
50
        :return: (tree, other_branch) Where merging other_branch causes a file
 
51
            conflict.
 
52
        """
 
53
        builder = self.make_branch_builder('branch')
 
54
        builder.build_snapshot('rev1', None,
 
55
            [('add', ('', 'root-id', 'directory', None)),
 
56
             ('add', ('fname', 'f-id', 'file', 'a\nb\nc\n'))])
 
57
        builder.build_snapshot('rev2other', ['rev1'],
 
58
            [('modify', ('f-id', 'a\nB\nD\n'))])
 
59
        other = builder.get_branch().bzrdir.sprout('other').open_branch()
 
60
        builder.build_snapshot('rev2this', ['rev1'],
 
61
            [('modify', ('f-id', 'a\nB\nC\n'))])
 
62
        tree = builder.get_branch().create_checkout('tree', lightweight=True)
 
63
        return tree, other
43
64
 
44
65
    def test_merge_reprocess(self):
45
66
        d = BzrDir.create_standalone_workingtree('.')
46
67
        d.commit('h')
47
 
        self.run_bzr('merge', '.', '--reprocess', '--merge-type', 'weave')
 
68
        self.run_bzr('merge . --reprocess --merge-type weave')
48
69
 
49
70
    def test_merge(self):
50
71
        from bzrlib.branch import Branch
51
 
        
52
 
        os.mkdir('a')
53
 
        os.chdir('a')
54
 
        self.example_branch()
55
 
        os.chdir('..')
56
 
        self.runbzr('branch a b')
57
 
        os.chdir('b')
58
 
        file('goodbye', 'wt').write('quux')
59
 
        self.runbzr(['commit',  '-m',  "more u's are always good"])
60
 
 
61
 
        os.chdir('../a')
62
 
        file('hello', 'wt').write('quuux')
 
72
 
 
73
        a_tree = self.example_branch('a')
 
74
        ancestor = a_tree.branch.revno()
 
75
        b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
 
76
        self.build_tree_contents([('b/goodbye', 'quux')])
 
77
        b_tree.commit(message="more u's are always good")
 
78
 
 
79
        self.build_tree_contents([('a/hello', 'quuux')])
63
80
        # We can't merge when there are in-tree changes
64
 
        self.runbzr('merge ../b', retcode=3)
65
 
        self.runbzr(['commit', '-m', "Like an epidemic of u's"])
66
 
        self.runbzr('merge ../b -r last:1..last:1 --merge-type blooof',
 
81
        os.chdir('a')
 
82
        self.run_bzr('merge ../b', retcode=3)
 
83
        a = WorkingTree.open('.')
 
84
        a_tip = a.commit("Like an epidemic of u's")
 
85
        self.run_bzr('merge ../b -r last:1..last:1 --merge-type blooof',
67
86
                    retcode=3)
68
 
        self.runbzr('merge ../b -r last:1..last:1 --merge-type merge3')
69
 
        self.runbzr('revert --no-backup')
70
 
        self.runbzr('merge ../b -r last:1..last:1 --merge-type weave')
71
 
        self.runbzr('revert --no-backup')
72
 
        self.runbzr('merge ../b -r last:1..last:1 --reprocess')
73
 
        self.runbzr('revert --no-backup')
74
 
        self.runbzr('merge ../b -r last:1')
 
87
        self.run_bzr('merge ../b -r last:1..last:1 --merge-type merge3')
 
88
        a_tree.revert(backups=False)
 
89
        self.run_bzr('merge ../b -r last:1..last:1 --merge-type weave')
 
90
        a_tree.revert(backups=False)
 
91
        self.run_bzr('merge ../b -r last:1..last:1 --merge-type lca')
 
92
        a_tree.revert(backups=False)
 
93
        self.run_bzr_error(['Show-base is not supported for this merge type'],
 
94
                           'merge ../b -r last:1..last:1 --merge-type weave'
 
95
                           ' --show-base')
 
96
        a_tree.revert(backups=False)
 
97
        self.run_bzr('merge ../b -r last:1..last:1 --reprocess')
 
98
        a_tree.revert(backups=False)
 
99
        self.run_bzr('merge ../b -r last:1')
75
100
        self.check_file_contents('goodbye', 'quux')
76
101
        # Merging a branch pulls its revision into the tree
77
 
        a = WorkingTree.open('.')
78
102
        b = Branch.open('../b')
79
 
        a.branch.repository.get_revision_xml(b.last_revision())
80
 
        self.log('pending merges: %s', a.pending_merges())
81
 
        self.assertEquals(a.pending_merges(),
82
 
                          [b.last_revision()])
83
 
        self.runbzr('commit -m merged')
84
 
        self.runbzr('merge ../b -r last:1')
85
 
        self.assertEqual(a.pending_merges(), [])
 
103
        b_tip = b.last_revision()
 
104
        self.failUnless(a.branch.repository.has_revision(b_tip))
 
105
        self.assertEqual([a_tip, b_tip], a.get_parent_ids())
 
106
        a_tree.revert(backups=False)
 
107
        out, err = self.run_bzr('merge -r revno:1:./hello', retcode=3)
 
108
        self.assertTrue("Not a branch" in err)
 
109
        self.run_bzr('merge -r revno:%d:./..revno:%d:../b'
 
110
                    %(ancestor,b.revno()))
 
111
        self.assertEquals(a.get_parent_ids(),
 
112
                          [a.branch.last_revision(), b.last_revision()])
 
113
        self.check_file_contents('goodbye', 'quux')
 
114
        a_tree.revert(backups=False)
 
115
        self.run_bzr('merge -r revno:%d:../b'%b.revno())
 
116
        self.assertEquals(a.get_parent_ids(),
 
117
                          [a.branch.last_revision(), b.last_revision()])
 
118
        a_tip = a.commit('merged')
 
119
        self.run_bzr('merge ../b -r last:1')
 
120
        self.assertEqual([a_tip], a.get_parent_ids())
 
121
 
 
122
    def test_merge_defaults_to_reprocess(self):
 
123
        tree, other = self.create_conflicting_branches()
 
124
        # The default merge algorithm should enable 'reprocess' because
 
125
        # 'show-base' is not set
 
126
        self.run_bzr('merge ../other', working_dir='tree',
 
127
                     retcode=1)
 
128
        self.assertEqualDiff('a\n'
 
129
                             'B\n'
 
130
                             '<<<<<<< TREE\n'
 
131
                             'C\n'
 
132
                             '=======\n'
 
133
                             'D\n'
 
134
                             '>>>>>>> MERGE-SOURCE\n',
 
135
                             tree.get_file_text('f-id'))
 
136
 
 
137
    def test_merge_explicit_reprocess_show_base(self):
 
138
        tree, other = self.create_conflicting_branches()
 
139
        # Explicitly setting --reprocess, and --show-base is an error
 
140
        self.run_bzr_error(['Cannot do conflict reduction and show base'],
 
141
                           'merge ../other --reprocess --show-base',
 
142
                           working_dir='tree')
 
143
 
 
144
    def test_merge_override_reprocess(self):
 
145
        tree, other = self.create_conflicting_branches()
 
146
        # Explicitly disable reprocess
 
147
        self.run_bzr('merge ../other --no-reprocess', working_dir='tree',
 
148
                     retcode=1)
 
149
        self.assertEqualDiff('a\n'
 
150
                             '<<<<<<< TREE\n'
 
151
                             'B\n'
 
152
                             'C\n'
 
153
                             '=======\n'
 
154
                             'B\n'
 
155
                             'D\n'
 
156
                             '>>>>>>> MERGE-SOURCE\n',
 
157
                             tree.get_file_text('f-id'))
 
158
 
 
159
    def test_merge_override_show_base(self):
 
160
        tree, other = self.create_conflicting_branches()
 
161
        # Setting '--show-base' will auto-disable '--reprocess'
 
162
        self.run_bzr('merge ../other --show-base', working_dir='tree',
 
163
                     retcode=1)
 
164
        self.assertEqualDiff('a\n'
 
165
                             '<<<<<<< TREE\n'
 
166
                             'B\n'
 
167
                             'C\n'
 
168
                             '||||||| BASE-REVISION\n'
 
169
                             'b\n'
 
170
                             'c\n'
 
171
                             '=======\n'
 
172
                             'B\n'
 
173
                             'D\n'
 
174
                             '>>>>>>> MERGE-SOURCE\n',
 
175
                             tree.get_file_text('f-id'))
86
176
 
87
177
    def test_merge_with_missing_file(self):
88
178
        """Merge handles missing file conflicts"""
89
 
        os.mkdir('a')
90
 
        os.chdir('a')
91
 
        os.mkdir('sub')
92
 
        print >> file('sub/a.txt', 'wb'), "hello"
93
 
        print >> file('b.txt', 'wb'), "hello"
94
 
        print >> file('sub/c.txt', 'wb'), "hello"
95
 
        self.runbzr('init')
96
 
        self.runbzr('add')
97
 
        self.runbzr(('commit', '-m', 'added a'))
98
 
        self.runbzr('branch . ../b')
99
 
        print >> file('sub/a.txt', 'ab'), "there"
100
 
        print >> file('b.txt', 'ab'), "there"
101
 
        print >> file('sub/c.txt', 'ab'), "there"
102
 
        self.runbzr(('commit', '-m', 'Added there'))
103
 
        os.unlink('sub/a.txt')
104
 
        os.unlink('sub/c.txt')
105
 
        os.rmdir('sub')
106
 
        os.unlink('b.txt')
107
 
        self.runbzr(('commit', '-m', 'Removed a.txt'))
108
 
        os.chdir('../b')
109
 
        print >> file('sub/a.txt', 'ab'), "something"
110
 
        print >> file('b.txt', 'ab'), "something"
111
 
        print >> file('sub/c.txt', 'ab'), "something"
112
 
        self.runbzr(('commit', '-m', 'Modified a.txt'))
113
 
        self.runbzr('merge ../a/', retcode=1)
114
 
        self.assert_(os.path.exists('sub/a.txt.THIS'))
115
 
        self.assert_(os.path.exists('sub/a.txt.BASE'))
 
179
        self.build_tree_contents([
 
180
            ('a/',),
 
181
            ('a/sub/',),
 
182
            ('a/sub/a.txt', 'hello\n'),
 
183
            ('a/b.txt', 'hello\n'),
 
184
            ('a/sub/c.txt', 'hello\n')])
 
185
        a_tree = self.make_branch_and_tree('a')
 
186
        a_tree.add(['sub', 'b.txt', 'sub/c.txt', 'sub/a.txt'])
 
187
        a_tree.commit(message='added a')
 
188
        b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
 
189
        self.build_tree_contents([
 
190
            ('a/sub/a.txt', 'hello\nthere\n'),
 
191
            ('a/b.txt', 'hello\nthere\n'),
 
192
            ('a/sub/c.txt', 'hello\nthere\n')])
 
193
        a_tree.commit(message='Added there')
 
194
        os.remove('a/sub/a.txt')
 
195
        os.remove('a/sub/c.txt')
 
196
        os.rmdir('a/sub')
 
197
        os.remove('a/b.txt')
 
198
        a_tree.commit(message='Removed a.txt')
 
199
        self.build_tree_contents([
 
200
            ('b/sub/a.txt', 'hello\nsomething\n'),
 
201
            ('b/b.txt', 'hello\nsomething\n'),
 
202
            ('b/sub/c.txt', 'hello\nsomething\n')])
 
203
        b_tree.commit(message='Modified a.txt')
 
204
        os.chdir('b')
 
205
        self.run_bzr('merge ../a/', retcode=1)
 
206
        self.failUnlessExists('sub/a.txt.THIS')
 
207
        self.failUnlessExists('sub/a.txt.BASE')
116
208
        os.chdir('../a')
117
 
        self.runbzr('merge ../b/', retcode=1)
118
 
        self.assert_(os.path.exists('sub/a.txt.OTHER'))
119
 
        self.assert_(os.path.exists('sub/a.txt.BASE'))
 
209
        self.run_bzr('merge ../b/', retcode=1)
 
210
        self.failUnlessExists('sub/a.txt.OTHER')
 
211
        self.failUnlessExists('sub/a.txt.BASE')
120
212
 
121
213
    def test_merge_remember(self):
122
 
        """Merge changes from one branch to another and test parent location."""
 
214
        """Merge changes from one branch to another, test submit location."""
123
215
        tree_a = self.make_branch_and_tree('branch_a')
124
216
        branch_a = tree_a.branch
125
217
        self.build_tree(['branch_a/a'])
141
233
        self.assertEqual(None, branch_b.get_parent())
142
234
        # test merge for failure without parent set
143
235
        os.chdir('branch_b')
144
 
        out = self.runbzr('merge', retcode=3)
 
236
        out = self.run_bzr('merge', retcode=3)
145
237
        self.assertEquals(out,
146
238
                ('','bzr: ERROR: No location specified or remembered\n'))
147
 
        # test implicit --remember when no parent set, this merge conflicts
 
239
 
 
240
        # test uncommitted changes
148
241
        self.build_tree(['d'])
149
242
        tree_b.add('d')
150
 
        out = self.runbzr('merge ../branch_a', retcode=3)
151
 
        self.assertEquals(out,
152
 
                ('','bzr: ERROR: Working tree has uncommitted changes.\n'))
153
 
        self.assertEquals(abspath(branch_b.get_parent()), abspath(parent))
154
 
        # test implicit --remember after resolving conflict
 
243
        self.run_bzr_error(['Working tree ".*" has uncommitted changes'],
 
244
                           'merge')
 
245
 
 
246
        # merge should now pass and implicitly remember merge location
155
247
        tree_b.commit('commit d')
156
 
        out, err = self.runbzr('merge')
157
 
        
 
248
        out, err = self.run_bzr('merge ../branch_a')
 
249
 
158
250
        base = urlutils.local_path_from_url(branch_a.base)
159
 
        self.assertEquals(out, 'Merging from remembered location %s\n' % (base,))
160
 
        self.assertEquals(err, 'All changes applied successfully.\n')
161
 
        self.assertEquals(abspath(branch_b.get_parent()), abspath(parent))
162
 
        # re-open tree as external runbzr modified it
 
251
        self.assertEndsWith(err, '+N  b\nAll changes applied successfully.\n')
 
252
        self.assertEquals(abspath(branch_b.get_submit_branch()),
 
253
                          abspath(parent))
 
254
        # test implicit --remember when committing new file
 
255
        self.build_tree(['e'])
 
256
        tree_b.add('e')
 
257
        tree_b.commit('commit e')
 
258
        out, err = self.run_bzr('merge')
 
259
        self.assertStartsWith(err,
 
260
                          'Merging from remembered submit location %s\n' % (base,))
 
261
        # re-open tree as external run_bzr modified it
163
262
        tree_b = branch_b.bzrdir.open_workingtree()
164
263
        tree_b.commit('merge branch_a')
165
264
        # test explicit --remember
166
 
        out, err = self.runbzr('merge ../branch_c --remember')
 
265
        out, err = self.run_bzr('merge ../branch_c --remember')
167
266
        self.assertEquals(out, '')
168
 
        self.assertEquals(err, 'All changes applied successfully.\n')
169
 
        self.assertEquals(abspath(branch_b.get_parent()),
 
267
        self.assertEquals(err, '+N  c\nAll changes applied successfully.\n')
 
268
        self.assertEquals(abspath(branch_b.get_submit_branch()),
170
269
                          abspath(branch_c.bzrdir.root_transport.base))
171
 
        # re-open tree as external runbzr modified it
 
270
        # re-open tree as external run_bzr modified it
172
271
        tree_b = branch_b.bzrdir.open_workingtree()
173
272
        tree_b.commit('merge branch_c')
174
273
 
175
274
    def test_merge_bundle(self):
176
275
        from bzrlib.testament import Testament
177
276
        tree_a = self.make_branch_and_tree('branch_a')
178
 
        f = file('branch_a/a', 'wb')
179
 
        f.write('hello')
180
 
        f.close()
 
277
        self.build_tree_contents([('branch_a/a', 'hello')])
181
278
        tree_a.add('a')
182
279
        tree_a.commit('message')
183
280
 
184
281
        tree_b = tree_a.bzrdir.sprout('branch_b').open_workingtree()
185
 
        f = file('branch_a/a', 'wb')
186
 
        f.write('hey there')
187
 
        f.close()
 
282
        self.build_tree_contents([('branch_a/a', 'hey there')])
188
283
        tree_a.commit('message')
189
284
 
190
 
        f = file('branch_b/a', 'wb')
191
 
        f.write('goodbye')
192
 
        f.close()
 
285
        self.build_tree_contents([('branch_b/a', 'goodbye')])
193
286
        tree_b.commit('message')
194
287
        os.chdir('branch_b')
195
 
        file('../bundle', 'wb').write(self.runbzr('bundle ../branch_a')[0])
 
288
        self.run_bzr('bundle ../branch_a -o ../bundle')
196
289
        os.chdir('../branch_a')
197
 
        self.runbzr('merge ../bundle', retcode=1)
198
 
        testament_a = Testament.from_revision(tree_a.branch.repository, 
199
 
                                              tree_b.last_revision())
 
290
        self.run_bzr('merge ../bundle', retcode=1)
 
291
        testament_a = Testament.from_revision(tree_a.branch.repository,
 
292
                                              tree_b.get_parent_ids()[0])
200
293
        testament_b = Testament.from_revision(tree_b.branch.repository,
201
 
                                              tree_b.last_revision())
 
294
                                              tree_b.get_parent_ids()[0])
202
295
        self.assertEqualDiff(testament_a.as_text(),
203
296
                         testament_b.as_text())
204
297
        tree_a.set_conflicts(ConflictList())
205
298
        tree_a.commit('message')
206
299
        # it is legal to attempt to merge an already-merged bundle
207
 
        output = self.runbzr('merge ../bundle')[1]
 
300
        output = self.run_bzr('merge ../bundle')[1]
208
301
        # but it does nothing
209
302
        self.assertFalse(tree_a.changes_from(tree_a.basis_tree()).has_changed())
210
303
        self.assertEqual('Nothing to do.\n', output)
220
313
        tree_a.rename_one('file_1', 'file_i')
221
314
        tree_a.commit('commit 2')
222
315
        tree_a.rename_one('file_2', 'file_ii')
223
 
        os.chdir('b')
224
 
        self.run_bzr('merge', '../a', '--uncommitted')
225
 
        self.failUnlessExists('file_1')
226
 
        self.failUnlessExists('file_ii')
227
 
        tree_b.revert([])
228
 
        self.run_bzr_error(('Cannot use --uncommitted and --revision',), 
229
 
                           'merge', '../a', '--uncommitted', '-r1')
 
316
        ## os.chdir('b')
 
317
        self.run_bzr('merge a --uncommitted -d b')
 
318
        self.failUnlessExists('b/file_1')
 
319
        self.failUnlessExists('b/file_ii')
 
320
        tree_b.revert()
 
321
        self.run_bzr_error(('Cannot use --uncommitted and --revision',),
 
322
                           'merge /a --uncommitted -r1 -d b')
 
323
 
 
324
    def test_merge_uncommitted_file(self):
 
325
        """It should be possible to merge changes from a single file."""
 
326
        tree_a = self.make_branch_and_tree('tree_a')
 
327
        tree_a.commit('initial commit')
 
328
        tree_a.bzrdir.sprout('tree_b')
 
329
        self.build_tree(['tree_a/file1', 'tree_a/file2'])
 
330
        tree_a.add(['file1', 'file2'])
 
331
        os.chdir('tree_b')
 
332
        self.run_bzr(['merge', '--uncommitted', '../tree_a/file1'])
 
333
        self.failUnlessExists('file1')
 
334
        self.failIfExists('file2')
 
335
 
 
336
    def pullable_branch(self):
 
337
        tree_a = self.make_branch_and_tree('a')
 
338
        self.build_tree(['a/file'])
 
339
        tree_a.add(['file'])
 
340
        self.id1 = tree_a.commit('commit 1')
 
341
 
 
342
        tree_b = self.make_branch_and_tree('b')
 
343
        tree_b.pull(tree_a.branch)
 
344
        file('b/file', 'wb').write('foo')
 
345
        self.id2 = tree_b.commit('commit 2')
 
346
 
 
347
    def test_merge_pull(self):
 
348
        self.pullable_branch()
 
349
        os.chdir('a')
 
350
        (out, err) = self.run_bzr('merge --pull ../b')
 
351
        self.assertContainsRe(out, 'Now on revision 2\\.')
 
352
        tree_a = WorkingTree.open('.')
 
353
        self.assertEqual([self.id2], tree_a.get_parent_ids())
 
354
 
 
355
    def test_merge_kind_change(self):
 
356
        tree_a = self.make_branch_and_tree('tree_a')
 
357
        self.build_tree_contents([('tree_a/file', 'content_1')])
 
358
        tree_a.add('file', 'file-id')
 
359
        tree_a.commit('added file')
 
360
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
 
361
        os.unlink('tree_a/file')
 
362
        self.build_tree(['tree_a/file/'])
 
363
        tree_a.commit('changed file to directory')
 
364
        os.chdir('tree_b')
 
365
        self.run_bzr('merge ../tree_a')
 
366
        self.assertEqual('directory', file_kind('file'))
 
367
        tree_b.revert()
 
368
        self.assertEqual('file', file_kind('file'))
 
369
        self.build_tree_contents([('file', 'content_2')])
 
370
        tree_b.commit('content change')
 
371
        self.run_bzr('merge ../tree_a', retcode=1)
 
372
        self.assertEqual(tree_b.conflicts(),
 
373
                         [ContentsConflict('file', file_id='file-id')])
 
374
 
 
375
    def test_directive_cherrypick(self):
 
376
        source = self.make_branch_and_tree('source')
 
377
        source.commit("nothing")
 
378
        # see https://bugs.edge.launchpad.net/bzr/+bug/409688 - trying to
 
379
        # cherrypick from one branch into another unrelated branch with a
 
380
        # different root id will give shape conflicts.  as a workaround we
 
381
        # make sure they share the same root id.
 
382
        target = source.bzrdir.sprout('target').open_workingtree()
 
383
        self.build_tree(['source/a'])
 
384
        source.add('a')
 
385
        source.commit('Added a', rev_id='rev1')
 
386
        self.build_tree(['source/b'])
 
387
        source.add('b')
 
388
        source.commit('Added b', rev_id='rev2')
 
389
        target.commit('empty commit')
 
390
        self.write_directive('directive', source.branch, 'target', 'rev2',
 
391
                             'rev1')
 
392
        out, err = self.run_bzr('merge -d target directive')
 
393
        self.failIfExists('target/a')
 
394
        self.failUnlessExists('target/b')
 
395
        self.assertContainsRe(err, 'Performing cherrypick')
 
396
 
 
397
    def write_directive(self, filename, source, target, revision_id,
 
398
                        base_revision_id=None, mangle_patch=False):
 
399
        md = merge_directive.MergeDirective2.from_objects(
 
400
            source.repository, revision_id, 0, 0, target,
 
401
            base_revision_id=base_revision_id)
 
402
        if mangle_patch:
 
403
            md.patch = 'asdf\n'
 
404
        self.build_tree_contents([(filename, ''.join(md.to_lines()))])
 
405
 
 
406
    def test_directive_verify_warning(self):
 
407
        source = self.make_branch_and_tree('source')
 
408
        self.build_tree(['source/a'])
 
409
        source.add('a')
 
410
        source.commit('Added a', rev_id='rev1')
 
411
        target = self.make_branch_and_tree('target')
 
412
        target.commit('empty commit')
 
413
        self.write_directive('directive', source.branch, 'target', 'rev1')
 
414
        err = self.run_bzr('merge -d target directive')[1]
 
415
        self.assertNotContainsRe(err, 'Preview patch does not match changes')
 
416
        target.revert()
 
417
        self.write_directive('directive', source.branch, 'target', 'rev1',
 
418
                             mangle_patch=True)
 
419
        err = self.run_bzr('merge -d target directive')[1]
 
420
        self.assertContainsRe(err, 'Preview patch does not match changes')
 
421
 
 
422
    def test_merge_arbitrary(self):
 
423
        target = self.make_branch_and_tree('target')
 
424
        target.commit('empty')
 
425
        # We need a revision that has no integer revno
 
426
        branch_a = target.bzrdir.sprout('branch_a').open_workingtree()
 
427
        self.build_tree(['branch_a/file1'])
 
428
        branch_a.add('file1')
 
429
        branch_a.commit('added file1', rev_id='rev2a')
 
430
        branch_b = target.bzrdir.sprout('branch_b').open_workingtree()
 
431
        self.build_tree(['branch_b/file2'])
 
432
        branch_b.add('file2')
 
433
        branch_b.commit('added file2', rev_id='rev2b')
 
434
        branch_b.merge_from_branch(branch_a.branch)
 
435
        self.failUnlessExists('branch_b/file1')
 
436
        branch_b.commit('merged branch_a', rev_id='rev3b')
 
437
 
 
438
        # It works if the revid has an interger revno
 
439
        self.run_bzr('merge -d target -r revid:rev2a branch_a')
 
440
        self.failUnlessExists('target/file1')
 
441
        self.failIfExists('target/file2')
 
442
        target.revert()
 
443
 
 
444
        # It should work if the revid has no integer revno
 
445
        self.run_bzr('merge -d target -r revid:rev2a branch_b')
 
446
        self.failUnlessExists('target/file1')
 
447
        self.failIfExists('target/file2')
 
448
 
 
449
    def assertDirectoryContent(self, directory, entries, message=''):
 
450
        """Assert whether entries (file or directories) exist in a directory.
 
451
 
 
452
        It also checks that there are no extra entries.
 
453
        """
 
454
        ondisk = os.listdir(directory)
 
455
        if set(ondisk) == set(entries):
 
456
            return
 
457
        if message:
 
458
            message += '\n'
 
459
        raise AssertionError(
 
460
            '%s"%s" directory content is different:\na = %s\nb = %s\n'
 
461
            % (message, directory, sorted(entries), sorted(ondisk)))
 
462
 
 
463
    def test_cherrypicking_merge(self):
 
464
        # make source branch
 
465
        source = self.make_branch_and_tree('source')
 
466
        for f in ('a', 'b', 'c', 'd'):
 
467
            self.build_tree(['source/'+f])
 
468
            source.add(f)
 
469
            source.commit('added '+f, rev_id='rev_'+f)
 
470
        # target branch
 
471
        target = source.bzrdir.sprout('target', 'rev_a').open_workingtree()
 
472
        self.assertDirectoryContent('target', ['.bzr', 'a'])
 
473
        # pick 1 revision
 
474
        self.run_bzr('merge -d target -r revid:rev_b..revid:rev_c source')
 
475
        self.assertDirectoryContent('target', ['.bzr', 'a', 'c'])
 
476
        target.revert()
 
477
        # pick 2 revisions
 
478
        self.run_bzr('merge -d target -r revid:rev_b..revid:rev_d source')
 
479
        self.assertDirectoryContent('target', ['.bzr', 'a', 'c', 'd'])
 
480
        target.revert()
 
481
        # pick 1 revision with option --changes
 
482
        self.run_bzr('merge -d target -c revid:rev_d source')
 
483
        self.assertDirectoryContent('target', ['.bzr', 'a', 'd'])
 
484
 
 
485
    def test_merge_criss_cross(self):
 
486
        tree_a = self.make_branch_and_tree('a')
 
487
        tree_a.commit('', rev_id='rev1')
 
488
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
489
        tree_a.commit('', rev_id='rev2a')
 
490
        tree_b.commit('', rev_id='rev2b')
 
491
        tree_a.merge_from_branch(tree_b.branch)
 
492
        tree_b.merge_from_branch(tree_a.branch)
 
493
        tree_a.commit('', rev_id='rev3a')
 
494
        tree_b.commit('', rev_id='rev3b')
 
495
        graph = tree_a.branch.repository.get_graph(tree_b.branch.repository)
 
496
        out, err = self.run_bzr(['merge', '-d', 'a', 'b'])
 
497
        self.assertContainsRe(err, 'Warning: criss-cross merge encountered.')
 
498
 
 
499
    def test_merge_force(self):
 
500
        tree_a = self.make_branch_and_tree('a')
 
501
        self.build_tree(['a/foo'])
 
502
        tree_a.add(['foo'])
 
503
        tree_a.commit('add file')
 
504
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
505
        self.build_tree_contents([('a/foo', 'change 1')])
 
506
        tree_a.commit('change file')
 
507
        tree_b.merge_from_branch(tree_a.branch)
 
508
        tree_a.commit('empty change to allow merge to run')
 
509
        self.run_bzr(['merge', '../a', '--force'], working_dir='b')
 
510
 
 
511
    def test_merge_from_submit(self):
 
512
        tree_a = self.make_branch_and_tree('a')
 
513
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
514
        tree_c = tree_a.bzrdir.sprout('c').open_workingtree()
 
515
        out, err = self.run_bzr(['merge', '-d', 'c'])
 
516
        self.assertContainsRe(err, 'Merging from remembered parent location .*a\/')
 
517
        tree_c.branch.set_submit_branch(tree_b.bzrdir.root_transport.base)
 
518
        out, err = self.run_bzr(['merge', '-d', 'c'])
 
519
        self.assertContainsRe(err, 'Merging from remembered submit location .*b\/')
 
520
 
 
521
    def test_remember_sets_submit(self):
 
522
        tree_a = self.make_branch_and_tree('a')
 
523
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
524
        self.assertIs(tree_b.branch.get_submit_branch(), None)
 
525
 
 
526
        # Remember should not happen if using default from parent
 
527
        out, err = self.run_bzr(['merge', '-d', 'b'])
 
528
        self.assertIs(tree_b.branch.get_submit_branch(), None)
 
529
 
 
530
        # Remember should happen if user supplies location
 
531
        out, err = self.run_bzr(['merge', '-d', 'b', 'a'])
 
532
        self.assertEqual(tree_b.branch.get_submit_branch(),
 
533
                         tree_a.bzrdir.root_transport.base)
 
534
 
 
535
    def test_weave_cherrypick(self):
 
536
        this_tree = self.make_branch_and_tree('this')
 
537
        self.build_tree_contents([('this/file', "a\n")])
 
538
        this_tree.add('file')
 
539
        this_tree.commit('rev1')
 
540
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
541
        self.build_tree_contents([('other/file', "a\nb\n")])
 
542
        other_tree.commit('rev2b')
 
543
        self.build_tree_contents([('other/file', "c\na\nb\n")])
 
544
        other_tree.commit('rev3b')
 
545
        self.run_bzr('merge --weave -d this other -r -2..-1')
 
546
        self.assertFileEqual('c\na\n', 'this/file')
 
547
 
 
548
    def test_lca_merge_criss_cross(self):
 
549
        tree_a = self.make_branch_and_tree('a')
 
550
        self.build_tree_contents([('a/file', 'base-contents\n')])
 
551
        tree_a.add('file')
 
552
        tree_a.commit('', rev_id='rev1')
 
553
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
554
        self.build_tree_contents([('a/file',
 
555
                                   'base-contents\nthis-contents\n')])
 
556
        tree_a.commit('', rev_id='rev2a')
 
557
        self.build_tree_contents([('b/file',
 
558
                                   'base-contents\nother-contents\n')])
 
559
        tree_b.commit('', rev_id='rev2b')
 
560
        tree_a.merge_from_branch(tree_b.branch)
 
561
        self.build_tree_contents([('a/file',
 
562
                                   'base-contents\nthis-contents\n')])
 
563
        tree_a.set_conflicts(ConflictList())
 
564
        tree_b.merge_from_branch(tree_a.branch)
 
565
        self.build_tree_contents([('b/file',
 
566
                                   'base-contents\nother-contents\n')])
 
567
        tree_b.set_conflicts(ConflictList())
 
568
        tree_a.commit('', rev_id='rev3a')
 
569
        tree_b.commit('', rev_id='rev3b')
 
570
        out, err = self.run_bzr(['merge', '-d', 'a', 'b', '--lca'], retcode=1)
 
571
        self.assertFileEqual('base-contents\n<<<<<<< TREE\nthis-contents\n'
 
572
                             '=======\nother-contents\n>>>>>>> MERGE-SOURCE\n',
 
573
                             'a/file')
 
574
 
 
575
    def test_merge_preview(self):
 
576
        this_tree = self.make_branch_and_tree('this')
 
577
        this_tree.commit('rev1')
 
578
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
579
        self.build_tree_contents([('other/file', 'new line')])
 
580
        other_tree.add('file')
 
581
        other_tree.commit('rev2a')
 
582
        this_tree.commit('rev2b')
 
583
        out, err = self.run_bzr(['merge', '-d', 'this', 'other', '--preview'])
 
584
        self.assertContainsRe(out, '\+new line')
 
585
        self.assertNotContainsRe(err, '\+N  file\n')
 
586
        this_tree.lock_read()
 
587
        self.addCleanup(this_tree.unlock)
 
588
        self.assertEqual([],
 
589
                         list(this_tree.iter_changes(this_tree.basis_tree())))
 
590
 
 
591
    def test_merge_missing_second_revision_spec(self):
 
592
        """Merge uses branch basis when the second revision is unspecified."""
 
593
        this = self.make_branch_and_tree('this')
 
594
        this.commit('rev1')
 
595
        other = self.make_branch_and_tree('other')
 
596
        self.build_tree(['other/other_file'])
 
597
        other.add('other_file')
 
598
        other.commit('rev1b')
 
599
        self.run_bzr('merge -d this other -r0..')
 
600
        self.failUnlessExists('this/other_file')