~bzr-pqm/bzr/bzr.dev

2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
1
# Copyright (C) 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
"""Tests for the BranchBuilder class."""
18
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
19
from bzrlib import (
20
    branch as _mod_branch,
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
21
    errors,
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
22
    revision as _mod_revision,
23
    tests,
24
    )
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
25
from bzrlib.branchbuilder import BranchBuilder
26
27
28
class TestBranchBuilder(tests.TestCaseWithMemoryTransport):
29
    
30
    def test_create(self):
31
        """Test the constructor api."""
32
        builder = BranchBuilder(self.get_transport().clone('foo'))
33
        # we dont care if the branch has been built or not at this point.
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
34
35
    def test_get_branch(self):
36
        """get_branch returns the created branch."""
37
        builder = BranchBuilder(self.get_transport().clone('foo'))
38
        branch = builder.get_branch()
39
        self.assertIsInstance(branch, _mod_branch.Branch)
40
        self.assertEqual(self.get_transport().clone('foo').base,
41
            branch.base)
42
        self.assertEqual(
43
            (0, _mod_revision.NULL_REVISION),
44
            branch.last_revision_info())
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
45
2466.7.10 by Robert Collins
Add a format parameter to BranchBuilder.
46
    def test_format(self):
47
        """Making a BranchBuilder with a format option sets the branch type."""
48
        builder = BranchBuilder(self.get_transport(), format='dirstate-tags')
49
        branch = builder.get_branch()
50
        self.assertIsInstance(branch, _mod_branch.BzrBranch6)
51
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
52
    def test_build_one_commit(self):
53
        """doing build_commit causes a commit to happen."""
54
        builder = BranchBuilder(self.get_transport().clone('foo'))
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
55
        rev_id = builder.build_commit()
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
56
        branch = builder.get_branch()
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
57
        self.assertEqual((1, rev_id), branch.last_revision_info())
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
58
        self.assertEqual(
59
            'commit 1',
60
            branch.repository.get_revision(branch.last_revision()).message)
61
62
    def test_build_two_commits(self):
63
        """The second commit has the right parents and message."""
64
        builder = BranchBuilder(self.get_transport().clone('foo'))
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
65
        rev_id1 = builder.build_commit()
66
        rev_id2 = builder.build_commit()
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
67
        branch = builder.get_branch()
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
68
        self.assertEqual((2, rev_id2), branch.last_revision_info())
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
69
        self.assertEqual(
70
            'commit 2',
71
            branch.repository.get_revision(branch.last_revision()).message)
72
        self.assertEqual(
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
73
            [rev_id1],
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
74
            branch.repository.get_revision(branch.last_revision()).parent_ids)
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
75
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
76
77
class TestBranchBuilderBuildSnapshot(tests.TestCaseWithMemoryTransport):
78
79
    def assertTreeShape(self, expected_shape, tree):
80
        """Check that the tree shape matches expectations."""
81
        tree.lock_read()
82
        try:
83
            entries = [(path, ie.file_id, ie.kind)
84
                       for path, ie in tree.iter_entries_by_dir()]
85
        finally:
86
            tree.unlock()
87
        self.assertEqual(expected_shape, entries)
88
89
    def build_a_rev(self):
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
90
        builder = BranchBuilder(self.get_transport().clone('foo'))
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
91
        rev_id1 = builder.build_snapshot('A-id', None,
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
92
            [('add', ('', 'a-root-id', 'directory', None)),
93
             ('add', ('a', 'a-id', 'file', 'contents'))])
94
        self.assertEqual('A-id', rev_id1)
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
95
        return builder
96
97
    def test_add_one_file(self):
98
        builder = self.build_a_rev()
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
99
        branch = builder.get_branch()
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
100
        self.assertEqual((1, 'A-id'), branch.last_revision_info())
101
        rev_tree = branch.repository.revision_tree('A-id')
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
102
        rev_tree.lock_read()
103
        self.addCleanup(rev_tree.unlock)
3567.4.2 by John Arbash Meinel
test that we can add more files into an existing build
104
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
105
                              (u'a', 'a-id', 'file')], rev_tree)
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
106
        self.assertEqual('contents', rev_tree.get_file_text('a-id'))
3567.4.2 by John Arbash Meinel
test that we can add more files into an existing build
107
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
108
    def test_add_second_file(self):
109
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
110
        rev_id2 = builder.build_snapshot('B-id', None,
3567.4.2 by John Arbash Meinel
test that we can add more files into an existing build
111
            [('add', ('b', 'b-id', 'file', 'content_b'))])
112
        self.assertEqual('B-id', rev_id2)
113
        branch = builder.get_branch()
114
        self.assertEqual((2, rev_id2), branch.last_revision_info())
115
        rev_tree = branch.repository.revision_tree(rev_id2)
116
        rev_tree.lock_read()
117
        self.addCleanup(rev_tree.unlock)
118
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
119
                              (u'a', 'a-id', 'file'),
120
                              (u'b', 'b-id', 'file')], rev_tree)
121
        self.assertEqual('content_b', rev_tree.get_file_text('b-id'))
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
122
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
123
    def test_add_empty_dir(self):
124
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
125
        rev_id2 = builder.build_snapshot('B-id', None,
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
126
            [('add', ('b', 'b-id', 'directory', None))])
127
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
128
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
129
                              (u'a', 'a-id', 'file'),
130
                              (u'b', 'b-id', 'directory'),
131
                             ], rev_tree)
132
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
133
    def test_commit_message_default(self):
134
        builder = BranchBuilder(self.get_transport().clone('foo'))
135
        rev_id = builder.build_snapshot(None, None,
136
            [('add', (u'', None, 'directory', None))])
137
        branch = builder.get_branch()
138
        rev = branch.repository.get_revision(rev_id)
139
        self.assertEqual(u'commit 1', rev.message)
140
141
    def test_commit_message_supplied(self):
142
        builder = BranchBuilder(self.get_transport().clone('foo'))
143
        rev_id = builder.build_snapshot(None, None,
144
            [('add', (u'', None, 'directory', None))],
145
            message=u'Foo')
146
        branch = builder.get_branch()
147
        rev = branch.repository.get_revision(rev_id)
148
        self.assertEqual(u'Foo', rev.message)
149
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
150
    def test_modify_file(self):
151
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
152
        rev_id2 = builder.build_snapshot('B-id', None,
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
153
            [('modify', ('a-id', 'new\ncontent\n'))])
154
        self.assertEqual('B-id', rev_id2)
155
        branch = builder.get_branch()
156
        rev_tree = branch.repository.revision_tree(rev_id2)
157
        rev_tree.lock_read()
158
        self.addCleanup(rev_tree.unlock)
159
        self.assertEqual('new\ncontent\n', rev_tree.get_file_text('a-id'))
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
160
161
    def test_delete_file(self):
162
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
163
        rev_id2 = builder.build_snapshot('B-id', None,
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
164
            [('unversion', 'a-id')])
165
        self.assertEqual('B-id', rev_id2)
166
        branch = builder.get_branch()
167
        rev_tree = branch.repository.revision_tree(rev_id2)
168
        rev_tree.lock_read()
169
        self.addCleanup(rev_tree.unlock)
170
        self.assertTreeShape([(u'', 'a-root-id', 'directory')], rev_tree)
171
3567.4.5 by John Arbash Meinel
MemoryTree.add(directory) will now create a directory node in the Transport
172
    def test_delete_directory(self):
173
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
174
        rev_id2 = builder.build_snapshot('B-id', None,
3567.4.5 by John Arbash Meinel
MemoryTree.add(directory) will now create a directory node in the Transport
175
            [('add', ('b', 'b-id', 'directory', None)),
176
             ('add', ('b/c', 'c-id', 'file', 'foo\n')),
177
             ('add', ('b/d', 'd-id', 'directory', None)),
178
             ('add', ('b/d/e', 'e-id', 'file', 'eff\n')),
179
            ])
180
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
181
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
182
                              (u'a', 'a-id', 'file'),
183
                              (u'b', 'b-id', 'directory'),
184
                              (u'b/c', 'c-id', 'file'),
185
                              (u'b/d', 'd-id', 'directory'),
186
                              (u'b/d/e', 'e-id', 'file')], rev_tree)
3567.4.6 by John Arbash Meinel
unversioning a directory is recursive.
187
        # Removing a directory removes all child dirs
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
188
        builder.build_snapshot('C-id', None, [('unversion', 'b-id')])
3567.4.6 by John Arbash Meinel
unversioning a directory is recursive.
189
        rev_tree = builder.get_branch().repository.revision_tree('C-id')
190
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
191
                              (u'a', 'a-id', 'file'),
192
                             ], rev_tree)
3567.4.5 by John Arbash Meinel
MemoryTree.add(directory) will now create a directory node in the Transport
193
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
194
    def test_unknown_action(self):
195
        builder = self.build_a_rev()
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
196
        e = self.assertRaises(ValueError,
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
197
            builder.build_snapshot, 'B-id', None, [('weirdo', ('foo',))])
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
198
        self.assertEqual('Unknown build action: "weirdo"', str(e))
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
199
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
200
    def test_rename(self):
201
        builder = self.build_a_rev()
202
        builder.build_snapshot('B-id', None,
203
            [('rename', ('a', 'b'))])
204
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
205
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
206
                              (u'b', 'a-id', 'file')], rev_tree)
207
208
    def test_rename_into_subdir(self):
209
        builder = self.build_a_rev()
210
        builder.build_snapshot('B-id', None,
211
            [('add', ('dir', 'dir-id', 'directory', None)),
212
             ('rename', ('a', 'dir/a'))])
213
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
214
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
215
                              (u'dir', 'dir-id', 'directory'),
216
                              (u'dir/a', 'a-id', 'file')], rev_tree)
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
217
218
    def test_set_parent(self):
219
        builder = self.build_a_rev()
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
220
        builder.start_series()
221
        self.addCleanup(builder.finish_series)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
222
        builder.build_snapshot('B-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
223
            [('modify', ('a-id', 'new\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
224
        builder.build_snapshot('C-id', ['A-id'], 
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
225
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
226
        # We should now have a graph:
227
        #   A
228
        #   |\
229
        #   C B
230
        # And not A => B => C
231
        repo = builder.get_branch().repository
232
        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',)},
233
                         repo.get_parent_map(['B-id', 'C-id']))
234
        b_tree = repo.revision_tree('B-id')
235
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
236
                              (u'a', 'a-id', 'file'),
237
                             ], b_tree)
238
        self.assertEqual('new\ncontent\n', b_tree.get_file_text('a-id'))
239
240
        # We should still be using the content from A in C, not from B
241
        c_tree = repo.revision_tree('C-id')
242
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
243
                              (u'a', 'a-id', 'file'),
244
                              (u'c', 'c-id', 'file'),
245
                             ], c_tree)
246
        self.assertEqual('contents', c_tree.get_file_text('a-id'))
247
        self.assertEqual('alt\ncontent\n', c_tree.get_file_text('c-id'))
248
249
    def test_set_merge_parent(self):
250
        builder = self.build_a_rev()
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
251
        builder.start_series()
252
        self.addCleanup(builder.finish_series)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
253
        builder.build_snapshot('B-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
254
            [('add', ('b', 'b-id', 'file', 'b\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
255
        builder.build_snapshot('C-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
256
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
257
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
258
        repo = builder.get_branch().repository
259
        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',),
260
                          'D-id': ('B-id', 'C-id')},
261
                         repo.get_parent_map(['B-id', 'C-id', 'D-id']))
262
        d_tree = repo.revision_tree('D-id')
263
        # Note: by default a merge node does *not* pull in the changes from the
264
        #       merged tree, you have to supply it yourself.
265
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
266
                              (u'a', 'a-id', 'file'),
267
                              (u'b', 'b-id', 'file'),
268
                             ], d_tree)
269
270
    def test_set_merge_parent_and_contents(self):
271
        builder = self.build_a_rev()
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
272
        builder.start_series()
273
        self.addCleanup(builder.finish_series)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
274
        builder.build_snapshot('B-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
275
            [('add', ('b', 'b-id', 'file', 'b\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
276
        builder.build_snapshot('C-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
277
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
278
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
279
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
280
        repo = builder.get_branch().repository
281
        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',),
282
                          'D-id': ('B-id', 'C-id')},
283
                         repo.get_parent_map(['B-id', 'C-id', 'D-id']))
284
        d_tree = repo.revision_tree('D-id')
285
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
286
                              (u'a', 'a-id', 'file'),
287
                              (u'b', 'b-id', 'file'),
288
                              (u'c', 'c-id', 'file'),
289
                             ], d_tree)
290
        # Because we copied the exact text into *this* tree, the 'c' file
291
        # should look like it was not modified in the merge
292
        self.assertEqual('C-id', d_tree.inventory['c-id'].revision)
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
293
294
    def test_start_finish_series(self):
295
        builder = BranchBuilder(self.get_transport().clone('foo'))
296
        builder.start_series()
297
        try:
298
            self.assertIsNot(None, builder._tree)
299
            self.assertEqual('w', builder._tree._lock_mode)
300
            self.assertTrue(builder._branch.is_locked())
301
        finally:
302
            builder.finish_series()
303
        self.assertIs(None, builder._tree)
304
        self.assertFalse(builder._branch.is_locked())