~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Robert Collins
  • Date: 2007-11-09 17:50:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2988.
  • Revision ID: robertc@robertcollins.net-20071109175031-agaiy6530rvbprmb
Change (without backwards compatibility) the
iter_lines_added_or_present_in_versions VersionedFile API to yield the
text version that each line is being returned from. This is useful for
reconcile in determining what inventories reference what texts.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
import os
 
18
from StringIO import StringIO
 
19
 
 
20
from bzrlib import (
 
21
    conflicts,
 
22
    errors,
 
23
    merge as _mod_merge,
 
24
    option,
 
25
    progress,
 
26
    )
 
27
from bzrlib.branch import Branch
 
28
from bzrlib.conflicts import ConflictList, TextConflict
 
29
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
 
30
from bzrlib.merge import transform_tree, merge_inner
 
31
from bzrlib.osutils import pathjoin, file_kind
 
32
from bzrlib.revision import common_ancestor
 
33
from bzrlib.tests import TestCaseWithTransport
 
34
from bzrlib.trace import (enable_test_log, disable_test_log)
 
35
from bzrlib.workingtree import WorkingTree
 
36
 
 
37
 
 
38
class TestMerge(TestCaseWithTransport):
 
39
    """Test appending more than one revision"""
 
40
 
 
41
    def test_pending(self):
 
42
        wt = self.make_branch_and_tree('.')
 
43
        rev_a = wt.commit("lala!")
 
44
        self.assertEqual([rev_a], wt.get_parent_ids())
 
45
        self.assertRaises(errors.PointlessMerge, wt.merge_from_branch,
 
46
                          wt.branch)
 
47
        self.assertEqual([rev_a], wt.get_parent_ids())
 
48
        return wt
 
49
 
 
50
    def test_undo(self):
 
51
        wt = self.make_branch_and_tree('.')
 
52
        wt.commit("lala!")
 
53
        wt.commit("haha!")
 
54
        wt.commit("blabla!")
 
55
        wt.merge_from_branch(wt.branch, wt.branch.get_rev_id(2),
 
56
                             wt.branch.get_rev_id(1))
 
57
 
 
58
    def test_nocommits(self):
 
59
        wt = self.test_pending()
 
60
        wt2 = self.make_branch_and_tree('branch2')
 
61
        self.assertRaises(NoCommits, wt.merge_from_branch, wt2.branch)
 
62
        return wt, wt2
 
63
 
 
64
    def test_unrelated(self):
 
65
        wt, wt2 = self.test_nocommits()
 
66
        wt2.commit("blah")
 
67
        self.assertRaises(UnrelatedBranches, wt.merge_from_branch, wt2.branch)
 
68
        return wt2
 
69
 
 
70
    def test_merge_one_file(self):
 
71
        """Do a partial merge of a tree which should not affect tree parents."""
 
72
        wt1 = self.make_branch_and_tree('branch1')
 
73
        tip = wt1.commit('empty commit')
 
74
        wt2 = self.make_branch_and_tree('branch2')
 
75
        wt2.pull(wt1.branch)
 
76
        file('branch1/foo', 'wb').write('foo')
 
77
        file('branch1/bar', 'wb').write('bar')
 
78
        wt1.add('foo')
 
79
        wt1.add('bar')
 
80
        wt1.commit('add foobar')
 
81
        os.chdir('branch2')
 
82
        self.run_bzr('merge ../branch1/baz', retcode=3)
 
83
        self.run_bzr('merge ../branch1/foo')
 
84
        self.failUnlessExists('foo')
 
85
        self.failIfExists('bar')
 
86
        wt2 = WorkingTree.open('.') # opens branch2
 
87
        self.assertEqual([tip], wt2.get_parent_ids())
 
88
        
 
89
    def test_pending_with_null(self):
 
90
        """When base is forced to revno 0, parent_ids are set"""
 
91
        wt2 = self.test_unrelated()
 
92
        wt1 = WorkingTree.open('.')
 
93
        br1 = wt1.branch
 
94
        br1.fetch(wt2.branch)
 
95
        # merge all of branch 2 into branch 1 even though they 
 
96
        # are not related.
 
97
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
 
98
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
 
99
            wt1.get_parent_ids())
 
100
        return (wt1, wt2.branch)
 
101
 
 
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()
 
105
        wt1.commit("blah")
 
106
        last = wt1.branch.last_revision()
 
107
        self.assertEqual(common_ancestor(last, last, wt1.branch.repository), last)
 
108
 
 
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')
 
113
        tree.add('name1')
 
114
        tree.commit(message="hello")
 
115
        tree.rename_one('name1', 'name2')
 
116
        os.unlink('name2')
 
117
        transform_tree(tree, tree.branch.basis_tree())
 
118
 
 
119
    def test_layered_rename(self):
 
120
        """Rename both child and parent at same time"""
 
121
        tree =self.make_branch_and_tree('.')
 
122
        os.mkdir('dirname1')
 
123
        tree.add('dirname1')
 
124
        filename = pathjoin('dirname1', 'name1')
 
125
        file(filename, 'wb').write('Hello')
 
126
        tree.add(filename)
 
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())
 
132
 
 
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")
 
140
        log = StringIO()
 
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)
 
145
        tree_b.revert()
 
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)
 
150
 
 
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()))
 
156
 
 
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
 
164
        # the repository.
 
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'])
 
168
        tree_a.add('b/c')
 
169
        tree_a.commit('added c')
 
170
        os.rmdir('z/b')
 
171
        tree_z.commit('removed b')
 
172
        merge_inner(tree_z.branch, tree_a, base_tree, this_tree=tree_z)
 
173
        self.assertEqual([
 
174
            conflicts.MissingParent('Created directory', 'b', 'b-id'),
 
175
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
 
176
            tree_z.conflicts())
 
177
        merge_inner(tree_a.branch, tree_z.basis_tree(), base_tree,
 
178
                    this_tree=tree_a)
 
179
        self.assertEqual([
 
180
            conflicts.DeletingParent('Not deleting', 'b', 'b-id'),
 
181
            conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
 
182
            tree_a.conflicts())
 
183
 
 
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')])
 
191
        sub_tree.add('file')
 
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
 
199
        # tree
 
200
        tree2.commit('changed file text')
 
201
        tree.merge_from_branch(tree2.branch)
 
202
        self.assertFileEqual('text2', 'tree/sub-tree/file')
 
203
 
 
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')])
 
207
        tree_a.add('file')
 
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
 
211
        # the repository.
 
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)
 
219
 
 
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'))
 
231
        tree_b.revert()
 
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',
 
238
                          file_id='file-id')])
 
239
    
 
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()])
 
252
 
 
253
    def test_merge_other_moves_we_deleted(self):
 
254
        tree_a = self.make_branch_and_tree('A')
 
255
        tree_a.lock_write()
 
256
        self.addCleanup(tree_a.unlock)
 
257
        self.build_tree(['A/a'])
 
258
        tree_a.add('a')
 
259
        tree_a.commit('1', rev_id='rev-1')
 
260
        tree_a.flush()
 
261
        tree_a.rename_one('a', 'b')
 
262
        tree_a.commit('2')
 
263
        bzrdir_b = tree_a.bzrdir.sprout('B', revision_id='rev-1')
 
264
        tree_b = bzrdir_b.open_workingtree()
 
265
        tree_b.lock_write()
 
266
        self.addCleanup(tree_b.unlock)
 
267
        os.unlink('B/a')
 
268
        tree_b.commit('3')
 
269
        try:
 
270
            tree_b.merge_from_branch(tree_a.branch)
 
271
        except AttributeError:
 
272
            self.fail('tried to join a path when name was None')
 
273
 
 
274
    def test_merge_uncommitted_otherbasis_ancestor_of_thisbasis(self):
 
275
        tree_a = self.make_branch_and_tree('a')
 
276
        self.build_tree(['a/file_1', 'a/file_2'])
 
277
        tree_a.add(['file_1'])
 
278
        tree_a.commit('commit 1')
 
279
        tree_a.add(['file_2'])
 
280
        tree_a.commit('commit 2')
 
281
        tree_b = tree_a.bzrdir.sprout('b').open_workingtree()
 
282
        tree_b.rename_one('file_1', 'renamed')
 
283
        merger = _mod_merge.Merger.from_uncommitted(tree_a, tree_b,
 
284
                                                    progress.DummyProgress())
 
285
        merger.merge_type = _mod_merge.Merge3Merger
 
286
        merger.do_merge()
 
287
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])