~bzr-pqm/bzr/bzr.dev

4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
1
# Copyright (C) 2007, 2009 Canonical Ltd
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
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):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
29
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
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
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
62
    def test_build_commit_timestamp(self):
63
        """You can set a date when committing."""
64
        builder = self.make_branch_builder('foo')
65
        rev_id = builder.build_commit(timestamp=1236043340)
66
        branch = builder.get_branch()
67
        self.assertEqual((1, rev_id), branch.last_revision_info())
68
        rev = branch.repository.get_revision(branch.last_revision())
69
        self.assertEqual(
70
            'commit 1',
71
            rev.message)
72
        self.assertEqual(
73
            1236043340,
74
            int(rev.timestamp))
75
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
76
    def test_build_two_commits(self):
77
        """The second commit has the right parents and message."""
78
        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.
79
        rev_id1 = builder.build_commit()
80
        rev_id2 = builder.build_commit()
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
81
        branch = builder.get_branch()
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
82
        self.assertEqual((2, rev_id2), branch.last_revision_info())
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
83
        self.assertEqual(
84
            'commit 2',
85
            branch.repository.get_revision(branch.last_revision()).message)
86
        self.assertEqual(
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
87
            [rev_id1],
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
88
            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.
89
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
90
91
class TestBranchBuilderBuildSnapshot(tests.TestCaseWithMemoryTransport):
92
93
    def assertTreeShape(self, expected_shape, tree):
94
        """Check that the tree shape matches expectations."""
95
        tree.lock_read()
96
        try:
97
            entries = [(path, ie.file_id, ie.kind)
98
                       for path, ie in tree.iter_entries_by_dir()]
99
        finally:
100
            tree.unlock()
101
        self.assertEqual(expected_shape, entries)
102
103
    def build_a_rev(self):
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
104
        builder = BranchBuilder(self.get_transport().clone('foo'))
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
105
        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.
106
            [('add', ('', 'a-root-id', 'directory', None)),
107
             ('add', ('a', 'a-id', 'file', 'contents'))])
108
        self.assertEqual('A-id', rev_id1)
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
109
        return builder
110
111
    def test_add_one_file(self):
112
        builder = self.build_a_rev()
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
113
        branch = builder.get_branch()
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
114
        self.assertEqual((1, 'A-id'), branch.last_revision_info())
115
        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.
116
        rev_tree.lock_read()
117
        self.addCleanup(rev_tree.unlock)
3567.4.2 by John Arbash Meinel
test that we can add more files into an existing build
118
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
119
                              (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.
120
        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
121
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
122
    def test_add_second_file(self):
123
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
124
        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
125
            [('add', ('b', 'b-id', 'file', 'content_b'))])
126
        self.assertEqual('B-id', rev_id2)
127
        branch = builder.get_branch()
128
        self.assertEqual((2, rev_id2), branch.last_revision_info())
129
        rev_tree = branch.repository.revision_tree(rev_id2)
130
        rev_tree.lock_read()
131
        self.addCleanup(rev_tree.unlock)
132
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
133
                              (u'a', 'a-id', 'file'),
134
                              (u'b', 'b-id', 'file')], rev_tree)
135
        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
136
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
137
    def test_add_empty_dir(self):
138
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
139
        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().
140
            [('add', ('b', 'b-id', 'directory', None))])
141
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
142
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
143
                              (u'a', 'a-id', 'file'),
144
                              (u'b', 'b-id', 'directory'),
145
                             ], rev_tree)
146
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
147
    def test_commit_timestamp(self):
148
        builder = self.make_branch_builder('foo')
149
        rev_id = builder.build_snapshot(None, None,
150
            [('add', (u'', None, 'directory', None))],
151
            timestamp=1234567890)
152
        rev = builder.get_branch().repository.get_revision(rev_id)
153
        self.assertEqual(
154
            1234567890,
155
            int(rev.timestamp))
156
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
157
    def test_commit_message_default(self):
158
        builder = BranchBuilder(self.get_transport().clone('foo'))
159
        rev_id = builder.build_snapshot(None, None,
160
            [('add', (u'', None, 'directory', None))])
161
        branch = builder.get_branch()
162
        rev = branch.repository.get_revision(rev_id)
163
        self.assertEqual(u'commit 1', rev.message)
164
165
    def test_commit_message_supplied(self):
166
        builder = BranchBuilder(self.get_transport().clone('foo'))
167
        rev_id = builder.build_snapshot(None, None,
168
            [('add', (u'', None, 'directory', None))],
169
            message=u'Foo')
170
        branch = builder.get_branch()
171
        rev = branch.repository.get_revision(rev_id)
172
        self.assertEqual(u'Foo', rev.message)
173
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
174
    def test_modify_file(self):
175
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
176
        rev_id2 = builder.build_snapshot('B-id', None,
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
177
            [('modify', ('a-id', 'new\ncontent\n'))])
178
        self.assertEqual('B-id', rev_id2)
179
        branch = builder.get_branch()
180
        rev_tree = branch.repository.revision_tree(rev_id2)
181
        rev_tree.lock_read()
182
        self.addCleanup(rev_tree.unlock)
183
        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.
184
185
    def test_delete_file(self):
186
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
187
        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.
188
            [('unversion', 'a-id')])
189
        self.assertEqual('B-id', rev_id2)
190
        branch = builder.get_branch()
191
        rev_tree = branch.repository.revision_tree(rev_id2)
192
        rev_tree.lock_read()
193
        self.addCleanup(rev_tree.unlock)
194
        self.assertTreeShape([(u'', 'a-root-id', 'directory')], rev_tree)
195
3567.4.5 by John Arbash Meinel
MemoryTree.add(directory) will now create a directory node in the Transport
196
    def test_delete_directory(self):
197
        builder = self.build_a_rev()
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
198
        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
199
            [('add', ('b', 'b-id', 'directory', None)),
200
             ('add', ('b/c', 'c-id', 'file', 'foo\n')),
201
             ('add', ('b/d', 'd-id', 'directory', None)),
202
             ('add', ('b/d/e', 'e-id', 'file', 'eff\n')),
203
            ])
204
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
205
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
206
                              (u'a', 'a-id', 'file'),
207
                              (u'b', 'b-id', 'directory'),
208
                              (u'b/c', 'c-id', 'file'),
209
                              (u'b/d', 'd-id', 'directory'),
210
                              (u'b/d/e', 'e-id', 'file')], rev_tree)
3567.4.6 by John Arbash Meinel
unversioning a directory is recursive.
211
        # Removing a directory removes all child dirs
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
212
        builder.build_snapshot('C-id', None, [('unversion', 'b-id')])
3567.4.6 by John Arbash Meinel
unversioning a directory is recursive.
213
        rev_tree = builder.get_branch().repository.revision_tree('C-id')
214
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
215
                              (u'a', 'a-id', 'file'),
216
                             ], rev_tree)
3567.4.5 by John Arbash Meinel
MemoryTree.add(directory) will now create a directory node in the Transport
217
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
218
    def test_unknown_action(self):
219
        builder = self.build_a_rev()
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
220
        e = self.assertRaises(ValueError,
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
221
            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.
222
        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.
223
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
224
    def test_rename(self):
225
        builder = self.build_a_rev()
226
        builder.build_snapshot('B-id', None,
227
            [('rename', ('a', 'b'))])
228
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
229
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
230
                              (u'b', 'a-id', 'file')], rev_tree)
231
232
    def test_rename_into_subdir(self):
233
        builder = self.build_a_rev()
234
        builder.build_snapshot('B-id', None,
235
            [('add', ('dir', 'dir-id', 'directory', None)),
236
             ('rename', ('a', 'dir/a'))])
237
        rev_tree = builder.get_branch().repository.revision_tree('B-id')
238
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
239
                              (u'dir', 'dir-id', 'directory'),
240
                              (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.
241
242
    def test_set_parent(self):
243
        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.
244
        builder.start_series()
245
        self.addCleanup(builder.finish_series)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
246
        builder.build_snapshot('B-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
247
            [('modify', ('a-id', 'new\ncontent\n'))])
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
248
        builder.build_snapshot('C-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
249
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
250
        # We should now have a graph:
251
        #   A
252
        #   |\
253
        #   C B
254
        # And not A => B => C
255
        repo = builder.get_branch().repository
256
        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',)},
257
                         repo.get_parent_map(['B-id', 'C-id']))
258
        b_tree = repo.revision_tree('B-id')
259
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
260
                              (u'a', 'a-id', 'file'),
261
                             ], b_tree)
262
        self.assertEqual('new\ncontent\n', b_tree.get_file_text('a-id'))
263
264
        # We should still be using the content from A in C, not from B
265
        c_tree = repo.revision_tree('C-id')
266
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
267
                              (u'a', 'a-id', 'file'),
268
                              (u'c', 'c-id', 'file'),
269
                             ], c_tree)
270
        self.assertEqual('contents', c_tree.get_file_text('a-id'))
271
        self.assertEqual('alt\ncontent\n', c_tree.get_file_text('c-id'))
272
273
    def test_set_merge_parent(self):
274
        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.
275
        builder.start_series()
276
        self.addCleanup(builder.finish_series)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
277
        builder.build_snapshot('B-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
278
            [('add', ('b', 'b-id', 'file', 'b\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
279
        builder.build_snapshot('C-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
280
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
281
        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.
282
        repo = builder.get_branch().repository
283
        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',),
284
                          'D-id': ('B-id', 'C-id')},
285
                         repo.get_parent_map(['B-id', 'C-id', 'D-id']))
286
        d_tree = repo.revision_tree('D-id')
287
        # Note: by default a merge node does *not* pull in the changes from the
288
        #       merged tree, you have to supply it yourself.
289
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
290
                              (u'a', 'a-id', 'file'),
291
                              (u'b', 'b-id', 'file'),
292
                             ], d_tree)
293
294
    def test_set_merge_parent_and_contents(self):
295
        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.
296
        builder.start_series()
297
        self.addCleanup(builder.finish_series)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
298
        builder.build_snapshot('B-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
299
            [('add', ('b', 'b-id', 'file', 'b\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
300
        builder.build_snapshot('C-id', ['A-id'],
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
301
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
302
        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.
303
            [('add', ('c', 'c-id', 'file', 'alt\ncontent\n'))])
304
        repo = builder.get_branch().repository
305
        self.assertEqual({'B-id': ('A-id',), 'C-id': ('A-id',),
306
                          'D-id': ('B-id', 'C-id')},
307
                         repo.get_parent_map(['B-id', 'C-id', 'D-id']))
308
        d_tree = repo.revision_tree('D-id')
309
        self.assertTreeShape([(u'', 'a-root-id', 'directory'),
310
                              (u'a', 'a-id', 'file'),
311
                              (u'b', 'b-id', 'file'),
312
                              (u'c', 'c-id', 'file'),
313
                             ], d_tree)
314
        # Because we copied the exact text into *this* tree, the 'c' file
315
        # should look like it was not modified in the merge
316
        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.
317
318
    def test_start_finish_series(self):
319
        builder = BranchBuilder(self.get_transport().clone('foo'))
320
        builder.start_series()
321
        try:
322
            self.assertIsNot(None, builder._tree)
323
            self.assertEqual('w', builder._tree._lock_mode)
324
            self.assertTrue(builder._branch.is_locked())
325
        finally:
326
            builder.finish_series()
327
        self.assertIs(None, builder._tree)
328
        self.assertFalse(builder._branch.is_locked())
4324.3.1 by Robert Collins
When adding rich root data follow the standard revision graph rules, so it does not create 'inconstent parents'.
329
330
    def test_ghost_mainline_history(self):
331
        builder = BranchBuilder(self.get_transport().clone('foo'))
332
        builder.start_series()
333
        try:
334
            builder.build_snapshot('tip', ['ghost'],
335
                [('add', ('', 'ROOT_ID', 'directory', ''))],
336
                allow_leftmost_as_ghost=True)
337
        finally:
338
            builder.finish_series()
339
        b = builder.get_branch()
340
        b.lock_read()
341
        self.addCleanup(b.unlock)
342
        self.assertEqual(('ghost',),
343
            b.repository.get_graph().get_parent_map(['tip'])['tip'])