1
# Copyright (C) 2005-2010 Canonical Ltd
1
# (C) 2005 Canonical Ltd
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for branch implementations - tests a branch format."""
20
branch as _mod_branch,
34
from bzrlib.symbol_versioning import deprecated_in
35
from bzrlib.tests import (
39
from bzrlib.tests.http_server import HttpServer
40
from bzrlib.transport import memory
43
class TestTestCaseWithBranch(per_branch.TestCaseWithBranch):
45
def test_branch_format_matches_bzrdir_branch_format(self):
46
bzrdir_branch_format = self.bzrdir_format.get_branch_format()
47
self.assertIs(self.branch_format.__class__,
48
bzrdir_branch_format.__class__)
50
def test_make_branch_gets_expected_format(self):
51
branch = self.make_branch('.')
52
self.assertIs(self.branch_format.__class__,
53
branch._format.__class__)
56
class TestBranch(per_branch.TestCaseWithBranch):
58
def test_create_tree_with_merge(self):
59
tree = self.create_tree_with_merge()
61
self.addCleanup(tree.unlock)
62
graph = tree.branch.repository.get_graph()
63
ancestry_graph = graph.get_parent_map(
64
tree.branch.repository.all_revision_ids())
65
self.assertEqual({'rev-1':('null:',),
67
'rev-1.1.1':('rev-1', ),
68
'rev-3':('rev-2', 'rev-1.1.1', ),
71
def test_revision_ids_are_utf8(self):
72
wt = self.make_branch_and_tree('tree')
73
wt.commit('f', rev_id='rev1')
74
wt.commit('f', rev_id='rev2')
75
wt.commit('f', rev_id='rev3')
77
br = self.get_branch()
79
br.set_revision_history(['rev1', 'rev2', 'rev3'])
80
rh = br.revision_history()
81
self.assertEqual(['rev1', 'rev2', 'rev3'], rh)
82
for revision_id in rh:
83
self.assertIsInstance(revision_id, str)
84
last = br.last_revision()
85
self.assertEqual('rev3', last)
86
self.assertIsInstance(last, str)
87
revno, last = br.last_revision_info()
88
self.assertEqual(3, revno)
89
self.assertEqual('rev3', last)
90
self.assertIsInstance(last, str)
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock
20
from bzrlib.clone import copy_branch
21
from bzrlib.commit import commit
22
import bzrlib.errors as errors
23
from bzrlib.errors import NoSuchRevision, UnlistableBranch, NotBranchError
25
from bzrlib.tests import TestCase, TestCaseInTempDir
26
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
27
from bzrlib.trace import mutter
28
import bzrlib.transactions as transactions
29
from bzrlib.revision import NULL_REVISION
31
# TODO: Make a branch using basis branch, and check that it
32
# doesn't request any files that could have been avoided, by
33
# hooking into the Transport.
35
class TestBranch(TestCaseInTempDir):
37
def test_append_revisions(self):
38
"""Test appending more than one revision"""
39
br = Branch.initialize(u".")
40
br.append_revision("rev1")
41
self.assertEquals(br.revision_history(), ["rev1",])
42
br.append_revision("rev2", "rev3")
43
self.assertEquals(br.revision_history(), ["rev1", "rev2", "rev3"])
92
45
def test_fetch_revisions(self):
93
46
"""Test fetch-revision operation."""
94
wt = self.make_branch_and_tree('b1')
96
self.build_tree_contents([('b1/foo', 'hello')])
97
wt.add(['foo'], ['foo-id'])
98
wt.commit('lala!', rev_id='revision-1', allow_pointless=False)
100
b2 = self.make_branch('b2')
103
rev = b2.repository.get_revision('revision-1')
104
tree = b2.repository.revision_tree('revision-1')
106
self.addCleanup(tree.unlock)
107
self.assertEqual(tree.get_file_text('foo-id'), 'hello')
109
def test_get_revision_delta(self):
110
tree_a = self.make_branch_and_tree('a')
111
self.build_tree(['a/foo'])
112
tree_a.add('foo', 'file1')
113
tree_a.commit('rev1', rev_id='rev1')
114
self.build_tree(['a/vla'])
115
tree_a.add('vla', 'file2')
116
tree_a.commit('rev2', rev_id='rev2')
118
delta = tree_a.branch.get_revision_delta(1)
119
self.assertIsInstance(delta, _mod_delta.TreeDelta)
120
self.assertEqual([('foo', 'file1', 'file')], delta.added)
121
delta = tree_a.branch.get_revision_delta(2)
122
self.assertIsInstance(delta, _mod_delta.TreeDelta)
123
self.assertEqual([('vla', 'file2', 'file')], delta.added)
125
def get_unbalanced_tree_pair(self):
47
from bzrlib.fetch import Fetcher
50
b1 = Branch.initialize('b1')
51
b2 = Branch.initialize('b2')
52
file(os.sep.join(['b1', 'foo']), 'w').write('hello')
53
b1.working_tree().add(['foo'], ['foo-id'])
54
b1.working_tree().commit('lala!', rev_id='revision-1', allow_pointless=False)
57
f = Fetcher(from_branch=b1, to_branch=b2)
58
eq = self.assertEquals
60
eq(f.last_revision, 'revision-1')
62
rev = b2.get_revision('revision-1')
63
tree = b2.revision_tree('revision-1')
64
eq(tree.get_file_text('foo-id'), 'hello')
66
def test_revision_tree(self):
67
b1 = Branch.initialize(u'.')
68
b1.working_tree().commit('lala!', rev_id='revision-1', allow_pointless=True)
69
tree = b1.revision_tree('revision-1')
70
tree = b1.revision_tree(None)
71
self.assertEqual(len(tree.list_files()), 0)
72
tree = b1.revision_tree(NULL_REVISION)
73
self.assertEqual(len(tree.list_files()), 0)
75
def get_unbalanced_branch_pair(self):
126
76
"""Return two branches, a and b, with one file in a."""
127
tree_a = self.make_branch_and_tree('a')
128
self.build_tree_contents([('a/b', 'b')])
130
tree_a.commit("silly commit", rev_id='A')
132
tree_b = self.make_branch_and_tree('b')
133
return tree_a, tree_b
78
br_a = Branch.initialize("a")
79
file('a/b', 'wb').write('b')
80
br_a.working_tree().add('b')
81
commit(br_a, "silly commit", rev_id='A')
83
br_b = Branch.initialize("b")
135
86
def get_balanced_branch_pair(self):
136
87
"""Returns br_a, br_b as with one commit in a, and b has a's stores."""
137
tree_a, tree_b = self.get_unbalanced_tree_pair()
138
tree_b.branch.repository.fetch(tree_a.branch.repository)
139
return tree_a, tree_b
141
def test_clone_partial(self):
88
br_a, br_b = self.get_unbalanced_branch_pair()
89
br_a.push_stores(br_b)
92
def test_push_stores(self):
93
"""Copy the stores from one branch to another"""
94
br_a, br_b = self.get_unbalanced_branch_pair()
95
# ensure the revision is missing.
96
self.assertRaises(NoSuchRevision, br_b.get_revision,
97
br_a.revision_history()[0])
98
br_a.push_stores(br_b)
99
# check that b now has all the data from a's first commit.
100
rev = br_b.get_revision(br_a.revision_history()[0])
101
tree = br_b.revision_tree(br_a.revision_history()[0])
103
if tree.inventory[file_id].kind == "file":
104
tree.get_file(file_id).read()
107
def test_copy_branch(self):
108
"""Copy the stores from one branch to another"""
109
br_a, br_b = self.get_balanced_branch_pair()
110
commit(br_b, "silly commit")
112
br_c = copy_branch(br_a, 'c', basis_branch=br_b)
113
self.assertEqual(br_a.revision_history(), br_c.revision_history())
115
def test_copy_partial(self):
142
116
"""Copy only part of the history of a branch."""
143
# TODO: RBC 20060208 test with a revision not on revision-history.
144
# what should that behaviour be ? Emailed the list.
145
# First, make a branch with two commits.
146
wt_a = self.make_branch_and_tree('a')
147
self.build_tree(['a/one'])
149
wt_a.commit('commit one', rev_id='1')
117
self.build_tree(['a/', 'a/one'])
118
br_a = Branch.initialize('a')
119
br_a.working_tree().add(['one'])
120
br_a.working_tree().commit('commit one', rev_id='u@d-1')
150
121
self.build_tree(['a/two'])
152
wt_a.commit('commit two', rev_id='2')
153
# Now make a copy of the repository.
154
repo_b = self.make_repository('b')
155
wt_a.branch.repository.copy_content_into(repo_b)
156
# wt_a might be a lightweight checkout, so get a hold of the actual
157
# branch (because you can't do a partial clone of a lightweight
159
branch = wt_a.branch.bzrdir.open_branch()
160
# Then make a branch where the new repository is, but specify a revision
161
# ID. The new branch's history will stop at the specified revision.
162
br_b = branch.clone(repo_b.bzrdir, revision_id='1')
163
self.assertEqual('1', br_b.last_revision())
165
def get_parented_branch(self):
166
wt_a = self.make_branch_and_tree('a')
167
self.build_tree(['a/one'])
169
wt_a.commit('commit one', rev_id='1')
171
branch_b = wt_a.branch.bzrdir.sprout('b', revision_id='1').open_branch()
172
self.assertEqual(wt_a.branch.base, branch_b.get_parent())
175
def test_clone_branch_nickname(self):
176
# test the nick name is preserved always
177
raise tests.TestSkipped('XXX branch cloning is not yet tested.')
179
def test_clone_branch_parent(self):
180
# test the parent is preserved always
181
branch_b = self.get_parented_branch()
182
repo_c = self.make_repository('c')
183
branch_b.repository.copy_content_into(repo_c)
184
branch_c = branch_b.clone(repo_c.bzrdir)
185
self.assertNotEqual(None, branch_c.get_parent())
186
self.assertEqual(branch_b.get_parent(), branch_c.get_parent())
188
# We can also set a specific parent, and it should be honored
189
random_parent = 'http://bazaar-vcs.org/path/to/branch'
190
branch_b.set_parent(random_parent)
191
repo_d = self.make_repository('d')
192
branch_b.repository.copy_content_into(repo_d)
193
branch_d = branch_b.clone(repo_d.bzrdir)
194
self.assertEqual(random_parent, branch_d.get_parent())
196
def test_submit_branch(self):
197
"""Submit location can be queried and set"""
198
branch = self.make_branch('branch')
199
self.assertEqual(branch.get_submit_branch(), None)
200
branch.set_submit_branch('sftp://example.com')
201
self.assertEqual(branch.get_submit_branch(), 'sftp://example.com')
202
branch.set_submit_branch('sftp://example.net')
203
self.assertEqual(branch.get_submit_branch(), 'sftp://example.net')
205
def test_public_branch(self):
206
"""public location can be queried and set"""
207
branch = self.make_branch('branch')
208
self.assertEqual(branch.get_public_branch(), None)
209
branch.set_public_branch('sftp://example.com')
210
self.assertEqual(branch.get_public_branch(), 'sftp://example.com')
211
branch.set_public_branch('sftp://example.net')
212
self.assertEqual(branch.get_public_branch(), 'sftp://example.net')
213
branch.set_public_branch(None)
214
self.assertEqual(branch.get_public_branch(), None)
216
def test_record_initial_ghost(self):
217
"""Branches should support having ghosts."""
218
wt = self.make_branch_and_tree('.')
219
wt.set_parent_ids(['non:existent@rev--ision--0--2'],
220
allow_leftmost_as_ghost=True)
221
self.assertEqual(['non:existent@rev--ision--0--2'],
223
rev_id = wt.commit('commit against a ghost first parent.')
224
rev = wt.branch.repository.get_revision(rev_id)
225
self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
122
br_a.working_tree().add(['two'])
123
br_a.working_tree().commit('commit two', rev_id='u@d-2')
124
br_b = copy_branch(br_a, 'b', revision='u@d-1')
125
self.assertEqual(br_b.last_revision(), 'u@d-1')
126
self.assertTrue(os.path.exists('b/one'))
127
self.assertFalse(os.path.exists('b/two'))
129
def test_record_initial_ghost_merge(self):
130
"""A pending merge with no revision present is still a merge."""
131
branch = Branch.initialize(u'.')
132
branch.working_tree().add_pending_merge('non:existent@rev--ision--0--2')
133
branch.working_tree().commit('pretend to merge nonexistent-revision', rev_id='first')
134
rev = branch.get_revision(branch.last_revision())
135
self.assertEqual(len(rev.parent_ids), 1)
226
136
# parent_sha1s is not populated now, WTF. rbc 20051003
227
137
self.assertEqual(len(rev.parent_sha1s), 0)
229
def test_record_two_ghosts(self):
230
"""Recording with all ghosts works."""
231
wt = self.make_branch_and_tree('.')
233
'foo@azkhazan-123123-abcabc',
234
'wibble@fofof--20050401--1928390812',
236
allow_leftmost_as_ghost=True)
237
rev_id = wt.commit("commit from ghost base with one merge")
238
# the revision should have been committed with two parents
239
rev = wt.branch.repository.get_revision(rev_id)
240
self.assertEqual(['foo@azkhazan-123123-abcabc',
241
'wibble@fofof--20050401--1928390812'],
138
self.assertEqual(rev.parent_ids[0], 'non:existent@rev--ision--0--2')
244
140
def test_bad_revision(self):
245
self.assertRaises(errors.InvalidRevisionId,
246
self.get_branch().repository.get_revision,
141
branch = Branch.initialize(u'.')
142
self.assertRaises(errors.InvalidRevisionId, branch.get_revision, None)
249
144
# TODO 20051003 RBC:
250
# compare the gpg-to-sign info for a commit with a ghost and
145
# compare the gpg-to-sign info for a commit with a ghost and
251
146
# an identical tree without a ghost
252
147
# fetch missing should rewrite the TOC of weaves to list newly available parents.
149
def test_pending_merges(self):
150
"""Tracking pending-merged revisions."""
151
b = Branch.initialize(u'.')
152
wt = b.working_tree()
153
self.assertEquals(wt.pending_merges(), [])
154
wt.add_pending_merge('foo@azkhazan-123123-abcabc')
155
self.assertEquals(wt.pending_merges(), ['foo@azkhazan-123123-abcabc'])
156
wt.add_pending_merge('foo@azkhazan-123123-abcabc')
157
self.assertEquals(wt.pending_merges(), ['foo@azkhazan-123123-abcabc'])
158
wt.add_pending_merge('wibble@fofof--20050401--1928390812')
159
self.assertEquals(wt.pending_merges(),
160
['foo@azkhazan-123123-abcabc',
161
'wibble@fofof--20050401--1928390812'])
162
b.working_tree().commit("commit from base with two merges")
163
rev = b.get_revision(b.revision_history()[0])
164
self.assertEquals(len(rev.parent_ids), 2)
165
self.assertEquals(rev.parent_ids[0],
166
'foo@azkhazan-123123-abcabc')
167
self.assertEquals(rev.parent_ids[1],
168
'wibble@fofof--20050401--1928390812')
169
# list should be cleared when we do a commit
170
self.assertEquals(wt.pending_merges(), [])
254
172
def test_sign_existing_revision(self):
255
wt = self.make_branch_and_tree('.')
257
wt.commit("base", allow_pointless=True, rev_id='A')
173
branch = Branch.initialize(u'.')
174
branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
258
175
from bzrlib.testament import Testament
259
strategy = gpg.LoopbackGPGStrategy(None)
260
branch.repository.lock_write()
261
branch.repository.start_write_group()
262
branch.repository.sign_revision('A', strategy)
263
branch.repository.commit_write_group()
264
branch.repository.unlock()
265
self.assertEqual('-----BEGIN PSEUDO-SIGNED CONTENT-----\n' +
266
Testament.from_revision(branch.repository,
267
'A').as_short_text() +
268
'-----END PSEUDO-SIGNED CONTENT-----\n',
269
branch.repository.get_signature_text('A'))
176
branch.sign_revision('A', bzrlib.gpg.LoopbackGPGStrategy(None))
177
self.assertEqual(Testament.from_revision(branch, 'A').as_short_text(),
178
branch.revision_store.get('A', 'sig').read())
271
180
def test_store_signature(self):
272
wt = self.make_branch_and_tree('.')
276
branch.repository.start_write_group()
278
branch.repository.store_revision_signature(
279
gpg.LoopbackGPGStrategy(None), 'FOO', 'A')
281
branch.repository.abort_write_group()
284
branch.repository.commit_write_group()
287
# A signature without a revision should not be accessible.
288
self.assertRaises(errors.NoSuchRevision,
289
branch.repository.has_signature_for_revision_id,
291
wt.commit("base", allow_pointless=True, rev_id='A')
292
self.assertEqual('-----BEGIN PSEUDO-SIGNED CONTENT-----\n'
293
'FOO-----END PSEUDO-SIGNED CONTENT-----\n',
294
branch.repository.get_signature_text('A'))
296
def test_branch_keeps_signatures(self):
297
wt = self.make_branch_and_tree('source')
298
wt.commit('A', allow_pointless=True, rev_id='A')
299
repo = wt.branch.repository
301
repo.start_write_group()
302
repo.sign_revision('A', gpg.LoopbackGPGStrategy(None))
303
repo.commit_write_group()
305
#FIXME: clone should work to urls,
306
# wt.clone should work to disks.
307
self.build_tree(['target/'])
308
d2 = repo.bzrdir.clone(urlutils.local_path_to_url('target'))
309
self.assertEqual(repo.get_signature_text('A'),
310
d2.open_repository().get_signature_text('A'))
312
def test_missing_revisions(self):
313
t1 = self.make_branch_and_tree('b1')
314
rev1 = t1.commit('one')
315
t2 = t1.bzrdir.sprout('b2').open_workingtree()
316
rev2 = t1.commit('two')
317
rev3 = t1.commit('three')
319
self.assertEqual([rev2, rev3],
320
self.applyDeprecated(deprecated_in((1, 6, 0)),
321
t2.branch.missing_revisions, t1.branch))
324
self.applyDeprecated(deprecated_in((1, 6, 0)),
325
t2.branch.missing_revisions, t1.branch, stop_revision=1))
326
self.assertEqual([rev2],
327
self.applyDeprecated(deprecated_in((1, 6, 0)),
328
t2.branch.missing_revisions, t1.branch, stop_revision=2))
329
self.assertEqual([rev2, rev3],
330
self.applyDeprecated(deprecated_in((1, 6, 0)),
331
t2.branch.missing_revisions, t1.branch, stop_revision=3))
333
self.assertRaises(errors.NoSuchRevision,
334
self.applyDeprecated, deprecated_in((1, 6, 0)),
335
t2.branch.missing_revisions, t1.branch, stop_revision=4)
337
rev4 = t2.commit('four')
338
self.assertRaises(errors.DivergedBranches,
339
self.applyDeprecated, deprecated_in((1, 6, 0)),
340
t2.branch.missing_revisions, t1.branch)
181
branch = Branch.initialize(u'.')
182
branch.store_revision_signature(bzrlib.gpg.LoopbackGPGStrategy(None),
184
self.assertEqual('FOO', branch.revision_store.get('A', 'sig').read())
186
def test__relcontrolfilename(self):
187
branch = Branch.initialize(u'.')
188
self.assertEqual('.bzr/%25', branch._rel_controlfilename('%'))
190
def test__relcontrolfilename_empty(self):
191
branch = Branch.initialize(u'.')
192
self.assertEqual('.bzr', branch._rel_controlfilename(''))
342
194
def test_nicks(self):
343
"""Test explicit and implicit branch nicknames.
345
Nicknames are implicitly the name of the branch's directory, unless an
346
explicit nickname is set. That is, an explicit nickname always
347
overrides the implicit one.
349
t = transport.get_transport(self.get_url())
350
branch = self.make_branch('bzr.dev')
351
# The nick will be 'bzr.dev', because there is no explicit nick set.
195
"""Branch nicknames"""
197
branch = Branch.initialize('bzr.dev')
352
198
self.assertEqual(branch.nick, 'bzr.dev')
353
# Move the branch to a different directory, 'bzr.ab'. Now that branch
354
# will report its nick as 'bzr.ab'.
355
t.move('bzr.dev', 'bzr.ab')
356
branch = _mod_branch.Branch.open(self.get_url('bzr.ab'))
199
os.rename('bzr.dev', 'bzr.ab')
200
branch = Branch.open('bzr.ab')
357
201
self.assertEqual(branch.nick, 'bzr.ab')
358
# Set the branch nick explicitly. This will ensure there's a branch
359
# config file in the branch.
360
branch.nick = "Aaron's branch"
361
if not isinstance(branch, remote.RemoteBranch):
362
self.failUnless(branch._transport.has("branch.conf"))
363
# Because the nick has been set explicitly, the nick is now always
364
# "Aaron's branch", regardless of directory name.
202
branch.nick = "Aaron's branch"
203
branch.nick = "Aaron's branch"
204
self.failUnless(os.path.exists(branch.controlfilename("branch.conf")))
365
205
self.assertEqual(branch.nick, "Aaron's branch")
366
t.move('bzr.ab', 'integration')
367
branch = _mod_branch.Branch.open(self.get_url('integration'))
206
os.rename('bzr.ab', 'integration')
207
branch = Branch.open('integration')
368
208
self.assertEqual(branch.nick, "Aaron's branch")
369
209
branch.nick = u"\u1234"
370
210
self.assertEqual(branch.nick, u"\u1234")
372
212
def test_commit_nicks(self):
373
213
"""Nicknames are committed to the revision"""
374
wt = self.make_branch_and_tree('bzr.dev')
215
branch = Branch.initialize('bzr.dev')
376
216
branch.nick = "My happy branch"
377
wt.commit('My commit respect da nick.')
378
committed = branch.repository.get_revision(branch.last_revision())
379
self.assertEqual(committed.properties["branch-nick"],
217
branch.working_tree().commit('My commit respect da nick.')
218
committed = branch.get_revision(branch.last_revision())
219
self.assertEqual(committed.properties["branch-nick"],
380
220
"My happy branch")
382
def test_create_colocated(self):
384
repo = self.make_repository('.', shared=True)
385
except errors.IncompatibleFormat:
387
self.assertEquals(0, len(repo.bzrdir.list_branches()))
389
child_branch1 = self.branch_format.initialize(repo.bzrdir,
391
except (errors.UninitializableFormat, errors.NoColocatedBranchSupport):
392
# branch references are not default init'able and
393
# not all bzrdirs support colocated branches.
395
self.assertEquals(1, len(repo.bzrdir.list_branches()))
396
self.branch_format.initialize(repo.bzrdir, name='branch2')
397
self.assertEquals(2, len(repo.bzrdir.list_branches()))
399
def test_create_open_branch_uses_repository(self):
401
repo = self.make_repository('.', shared=True)
402
except errors.IncompatibleFormat:
404
child_transport = repo.bzrdir.root_transport.clone('child')
405
child_transport.mkdir('.')
406
child_dir = self.bzrdir_format.initialize_on_transport(child_transport)
408
child_branch = self.branch_format.initialize(child_dir)
409
except errors.UninitializableFormat:
410
# branch references are not default init'able.
412
self.assertEqual(repo.bzrdir.root_transport.base,
413
child_branch.repository.bzrdir.root_transport.base)
414
child_branch = _mod_branch.Branch.open(self.get_url('child'))
415
self.assertEqual(repo.bzrdir.root_transport.base,
416
child_branch.repository.bzrdir.root_transport.base)
418
def test_format_description(self):
419
tree = self.make_branch_and_tree('tree')
420
text = tree.branch._format.get_format_description()
421
self.failUnless(len(text))
423
def test_get_commit_builder(self):
424
branch = self.make_branch(".")
426
builder = branch.get_commit_builder([])
427
self.assertIsInstance(builder, repository.CommitBuilder)
428
branch.repository.commit_write_group()
431
def test_generate_revision_history(self):
432
"""Create a fake revision history easily."""
433
tree = self.make_branch_and_tree('.')
434
rev1 = tree.commit('foo')
435
orig_history = tree.branch.revision_history()
436
rev2 = tree.commit('bar', allow_pointless=True)
437
tree.branch.generate_revision_history(rev1)
438
self.assertEqual(orig_history, tree.branch.revision_history())
440
def test_generate_revision_history_NULL_REVISION(self):
441
tree = self.make_branch_and_tree('.')
442
rev1 = tree.commit('foo')
443
tree.branch.generate_revision_history(revision.NULL_REVISION)
444
self.assertEqual([], tree.branch.revision_history())
446
def test_create_checkout(self):
447
tree_a = self.make_branch_and_tree('a')
448
branch_a = tree_a.branch
449
checkout_b = branch_a.create_checkout('b')
450
self.assertEqual('null:', checkout_b.last_revision())
451
checkout_b.commit('rev1', rev_id='rev1')
452
self.assertEqual('rev1', branch_a.last_revision())
453
self.assertNotEqual(checkout_b.branch.base, branch_a.base)
455
checkout_c = branch_a.create_checkout('c', lightweight=True)
456
self.assertEqual('rev1', checkout_c.last_revision())
457
checkout_c.commit('rev2', rev_id='rev2')
458
self.assertEqual('rev2', branch_a.last_revision())
459
self.assertEqual(checkout_c.branch.base, branch_a.base)
461
checkout_d = branch_a.create_checkout('d', lightweight=True)
462
self.assertEqual('rev2', checkout_d.last_revision())
463
checkout_e = branch_a.create_checkout('e')
464
self.assertEqual('rev2', checkout_e.last_revision())
466
def test_create_anonymous_lightweight_checkout(self):
467
"""A lightweight checkout from a readonly branch should succeed."""
468
tree_a = self.make_branch_and_tree('a')
469
rev_id = tree_a.commit('put some content in the branch')
470
# open the branch via a readonly transport
471
source_branch = _mod_branch.Branch.open(self.get_readonly_url('a'))
472
# sanity check that the test will be valid
473
self.assertRaises((errors.LockError, errors.TransportNotPossible),
474
source_branch.lock_write)
475
checkout = source_branch.create_checkout('c', lightweight=True)
476
self.assertEqual(rev_id, checkout.last_revision())
478
def test_create_anonymous_heavyweight_checkout(self):
479
"""A regular checkout from a readonly branch should succeed."""
480
tree_a = self.make_branch_and_tree('a')
481
rev_id = tree_a.commit('put some content in the branch')
482
# open the branch via a readonly transport
483
source_branch = _mod_branch.Branch.open(self.get_readonly_url('a'))
484
# sanity check that the test will be valid
485
self.assertRaises((errors.LockError, errors.TransportNotPossible),
486
source_branch.lock_write)
487
checkout = source_branch.create_checkout('c')
488
self.assertEqual(rev_id, checkout.last_revision())
490
def test_set_revision_history(self):
491
tree = self.make_branch_and_tree('a')
492
tree.commit('a commit', rev_id='rev1')
494
br.set_revision_history(["rev1"])
495
self.assertEquals(br.revision_history(), ["rev1"])
496
br.set_revision_history([])
497
self.assertEquals(br.revision_history(), [])
500
class TestBranchFormat(per_branch.TestCaseWithBranch):
502
def test_branch_format_network_name(self):
503
br = self.make_branch('.')
505
network_name = format.network_name()
506
self.assertIsInstance(network_name, str)
507
# We want to test that the network_name matches the actual format on
508
# disk. For local branches that means that using network_name as a key
509
# in the registry gives back the same format. For remote branches we
510
# check that the network_name of the RemoteBranchFormat we have locally
511
# matches the actual format present on disk.
512
if isinstance(format, remote.RemoteBranchFormat):
514
real_branch = br._real_branch
515
self.assertEqual(real_branch._format.network_name(), network_name)
517
registry = _mod_branch.network_format_registry
518
looked_up_format = registry.get(network_name)
519
self.assertEqual(format.__class__, looked_up_format.__class__)
522
class ChrootedTests(per_branch.TestCaseWithBranch):
523
"""A support class that provides readonly urls outside the local namespace.
525
This is done by checking if self.transport_server is a MemoryServer. if it
526
is then we are chrooted already, if it is not then an HttpServer is used
531
super(ChrootedTests, self).setUp()
532
if not self.vfs_transport_factory == memory.MemoryServer:
533
self.transport_readonly_server = HttpServer
223
class TestRemote(TestCaseWithWebserver):
535
225
def test_open_containing(self):
536
self.assertRaises(errors.NotBranchError,
537
_mod_branch.Branch.open_containing,
538
self.get_readonly_url(''))
539
self.assertRaises(errors.NotBranchError,
540
_mod_branch.Branch.open_containing,
541
self.get_readonly_url('g/p/q'))
542
branch = self.make_branch('.')
543
branch, relpath = _mod_branch.Branch.open_containing(
544
self.get_readonly_url(''))
226
self.assertRaises(NotBranchError, Branch.open_containing,
227
self.get_remote_url(''))
228
self.assertRaises(NotBranchError, Branch.open_containing,
229
self.get_remote_url('g/p/q'))
230
b = Branch.initialize(u'.')
231
branch, relpath = Branch.open_containing(self.get_remote_url(''))
545
232
self.assertEqual('', relpath)
546
branch, relpath = _mod_branch.Branch.open_containing(
547
self.get_readonly_url('g/p/q'))
233
branch, relpath = Branch.open_containing(self.get_remote_url('g/p/q'))
548
234
self.assertEqual('g/p/q', relpath)
236
# TODO: rewrite this as a regular unittest, without relying on the displayed output
237
# >>> from bzrlib.commit import commit
238
# >>> bzrlib.trace.silent = True
239
# >>> br1 = ScratchBranch(files=['foo', 'bar'])
240
# >>> br1.working_tree().add('foo')
241
# >>> br1.working_tree().add('bar')
242
# >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
243
# >>> br2 = ScratchBranch()
244
# >>> br2.update_revisions(br1)
246
# Added 1 inventories.
248
# >>> br2.revision_history()
250
# >>> br2.update_revisions(br1)
252
# >>> br1.text_store.total_size() == br2.text_store.total_size()
551
255
class InstrumentedTransaction(object):
611
315
self.assertEqual(['lw', 'ul'], branch._calls)
614
class TestBranchPushLocations(per_branch.TestCaseWithBranch):
318
class TestBranchTransaction(TestCaseInTempDir):
321
super(TestBranchTransaction, self).setUp()
322
self.branch = Branch.initialize(u'.')
324
def test_default_get_transaction(self):
325
"""branch.get_transaction on a new branch should give a PassThrough."""
326
self.failUnless(isinstance(self.branch.get_transaction(),
327
transactions.PassThroughTransaction))
329
def test__set_new_transaction(self):
330
self.branch._set_transaction(transactions.ReadOnlyTransaction())
332
def test__set_over_existing_transaction_raises(self):
333
self.branch._set_transaction(transactions.ReadOnlyTransaction())
334
self.assertRaises(errors.LockError,
335
self.branch._set_transaction,
336
transactions.ReadOnlyTransaction())
338
def test_finish_no_transaction_raises(self):
339
self.assertRaises(errors.LockError, self.branch._finish_transaction)
341
def test_finish_readonly_transaction_works(self):
342
self.branch._set_transaction(transactions.ReadOnlyTransaction())
343
self.branch._finish_transaction()
344
self.assertEqual(None, self.branch._transaction)
346
def test_unlock_calls_finish(self):
347
self.branch.lock_read()
348
transaction = InstrumentedTransaction()
349
self.branch._transaction = transaction
351
self.assertEqual(['finish'], transaction.calls)
353
def test_lock_read_acquires_ro_transaction(self):
354
self.branch.lock_read()
355
self.failUnless(isinstance(self.branch.get_transaction(),
356
transactions.ReadOnlyTransaction))
359
def test_lock_write_acquires_passthrough_transaction(self):
360
self.branch.lock_write()
361
# cannot use get_transaction as its magic
362
self.failUnless(isinstance(self.branch._transaction,
363
transactions.PassThroughTransaction))
367
class TestBranchPushLocations(TestCaseInTempDir):
370
super(TestBranchPushLocations, self).setUp()
371
self.branch = Branch.initialize(u'.')
616
373
def test_get_push_location_unset(self):
617
self.assertEqual(None, self.get_branch().get_push_location())
374
self.assertEqual(None, self.branch.get_push_location())
619
376
def test_get_push_location_exact(self):
620
from bzrlib.config import (locations_config_filename,
621
ensure_config_dir_exists)
622
ensure_config_dir_exists()
623
fn = locations_config_filename()
624
open(fn, 'wt').write(("[%s]\n"
625
"push_location=foo\n" %
626
self.get_branch().base[:-1]))
627
self.assertEqual("foo", self.get_branch().get_push_location())
377
self.build_tree(['.bazaar/'])
378
print >> open('.bazaar/branches.conf', 'wt'), ("[%s]\n"
379
"push_location=foo" %
381
self.assertEqual("foo", self.branch.get_push_location())
629
383
def test_set_push_location(self):
630
branch = self.get_branch()
631
branch.set_push_location('foo')
632
self.assertEqual('foo', branch.get_push_location())
635
class TestChildSubmitFormats(per_branch.TestCaseWithBranch):
637
def test_get_child_submit_format_default(self):
638
self.assertEqual(None, self.get_branch().get_child_submit_format())
640
def test_get_child_submit_format(self):
641
branch = self.get_branch()
642
branch.get_config().set_user_option('child_submit_format', '10')
643
branch = self.get_branch()
644
self.assertEqual('10', branch.get_child_submit_format())
647
class TestFormat(per_branch.TestCaseWithBranch):
648
"""Tests for the format itself."""
650
def test_get_reference(self):
651
"""get_reference on all regular branches should return None."""
652
if not self.branch_format.is_supported():
653
# unsupported formats are not loopback testable
654
# because the default open will not open them and
655
# they may not be initializable.
657
made_branch = self.make_branch('.')
658
self.assertEqual(None,
659
made_branch._format.get_reference(made_branch.bzrdir))
661
def test_set_reference(self):
662
"""set_reference on all regular branches should be callable."""
663
if not self.branch_format.is_supported():
664
# unsupported formats are not loopback testable
665
# because the default open will not open them and
666
# they may not be initializable.
668
this_branch = self.make_branch('this')
669
other_branch = self.make_branch('other')
671
this_branch._format.set_reference(this_branch.bzrdir, other_branch)
672
except NotImplementedError:
676
ref = this_branch._format.get_reference(this_branch.bzrdir)
677
self.assertEqual(ref, other_branch.base)
679
def test_format_initialize_find_open(self):
680
# loopback test to check the current format initializes to itself.
681
if not self.branch_format.is_supported():
682
# unsupported formats are not loopback testable
683
# because the default open will not open them and
684
# they may not be initializable.
686
# supported formats must be able to init and open
687
t = transport.get_transport(self.get_url())
688
readonly_t = transport.get_transport(self.get_readonly_url())
689
made_branch = self.make_branch('.')
690
self.failUnless(isinstance(made_branch, _mod_branch.Branch))
692
# find it via bzrdir opening:
693
opened_control = bzrdir.BzrDir.open(readonly_t.base)
694
direct_opened_branch = opened_control.open_branch()
695
self.assertEqual(direct_opened_branch.__class__, made_branch.__class__)
696
self.assertEqual(opened_control, direct_opened_branch.bzrdir)
697
self.failUnless(isinstance(direct_opened_branch._format,
698
self.branch_format.__class__))
700
# find it via Branch.open
701
opened_branch = _mod_branch.Branch.open(readonly_t.base)
702
self.failUnless(isinstance(opened_branch, made_branch.__class__))
703
self.assertEqual(made_branch._format.__class__,
704
opened_branch._format.__class__)
705
# if it has a unique id string, can we probe for it ?
707
self.branch_format.get_format_string()
708
except NotImplementedError:
710
self.assertEqual(self.branch_format,
711
opened_control.find_branch_format())
714
class TestBound(per_branch.TestCaseWithBranch):
716
def test_bind_unbind(self):
717
branch = self.make_branch('1')
718
branch2 = self.make_branch('2')
721
except errors.UpgradeRequired:
722
raise tests.TestNotApplicable('Format does not support binding')
723
self.assertTrue(branch.unbind())
724
self.assertFalse(branch.unbind())
725
self.assertIs(None, branch.get_bound_location())
727
def test_old_bound_location(self):
728
branch = self.make_branch('branch1')
730
self.assertIs(None, branch.get_old_bound_location())
731
except errors.UpgradeRequired:
732
raise tests.TestNotApplicable(
733
'Format does not store old bound locations')
734
branch2 = self.make_branch('branch2')
736
self.assertIs(None, branch.get_old_bound_location())
738
self.assertContainsRe(branch.get_old_bound_location(), '\/branch2\/$')
740
def test_bind_diverged(self):
741
tree_a = self.make_branch_and_tree('tree_a')
742
tree_a.commit('rev1a')
743
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
744
tree_a.commit('rev2a')
745
tree_b.commit('rev2b')
747
tree_b.branch.bind(tree_a.branch)
748
except errors.UpgradeRequired:
749
raise tests.TestNotApplicable('Format does not support binding')
752
class TestStrict(per_branch.TestCaseWithBranch):
754
def test_strict_history(self):
755
tree1 = self.make_branch_and_tree('tree1')
757
tree1.branch.set_append_revisions_only(True)
758
except errors.UpgradeRequired:
759
raise tests.TestSkipped('Format does not support strict history')
760
tree1.commit('empty commit')
761
tree2 = tree1.bzrdir.sprout('tree2').open_workingtree()
762
tree2.commit('empty commit 2')
763
tree1.pull(tree2.branch)
764
tree1.commit('empty commit 3')
765
tree2.commit('empty commit 4')
766
self.assertRaises(errors.DivergedBranches, tree1.pull, tree2.branch)
767
tree2.merge_from_branch(tree1.branch)
768
tree2.commit('empty commit 5')
769
self.assertRaises(errors.AppendRevisionsOnlyViolation, tree1.pull,
771
tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
772
tree3.merge_from_branch(tree2.branch)
773
tree3.commit('empty commit 6')
774
tree2.pull(tree3.branch)
777
class TestIgnoreFallbacksParameter(per_branch.TestCaseWithBranch):
779
def make_branch_with_fallback(self):
780
fallback = self.make_branch('fallback')
781
if not fallback._format.supports_stacking():
782
raise tests.TestNotApplicable("format does not support stacking")
783
stacked = self.make_branch('stacked')
784
stacked.set_stacked_on_url(fallback.base)
787
def test_fallbacks_not_opened(self):
788
stacked = self.make_branch_with_fallback()
789
self.get_transport('').rename('fallback', 'moved')
790
reopened = stacked.bzrdir.open_branch(ignore_fallbacks=True)
791
self.assertEqual([], reopened.repository._fallback_repositories)
793
def test_fallbacks_are_opened(self):
794
stacked = self.make_branch_with_fallback()
795
reopened = stacked.bzrdir.open_branch(ignore_fallbacks=False)
796
self.assertLength(1, reopened.repository._fallback_repositories)
799
class TestReferenceLocation(per_branch.TestCaseWithBranch):
801
def test_reference_parent(self):
802
tree = self.make_branch_and_tree('tree')
803
subtree = self.make_branch_and_tree('tree/subtree')
804
subtree.set_root_id('subtree-id')
806
tree.add_reference(subtree)
807
except errors.UnsupportedOperation:
808
raise tests.TestNotApplicable('Tree cannot hold references.')
809
reference_parent = tree.branch.reference_parent('subtree-id',
811
self.assertEqual(subtree.branch.base, reference_parent.base)
813
def test_reference_parent_accepts_possible_transports(self):
814
tree = self.make_branch_and_tree('tree')
815
subtree = self.make_branch_and_tree('tree/subtree')
816
subtree.set_root_id('subtree-id')
818
tree.add_reference(subtree)
819
except errors.UnsupportedOperation:
820
raise tests.TestNotApplicable('Tree cannot hold references.')
821
reference_parent = tree.branch.reference_parent('subtree-id',
822
'subtree', possible_transports=[subtree.bzrdir.root_transport])
824
def test_get_reference_info(self):
825
branch = self.make_branch('branch')
827
path, loc = branch.get_reference_info('file-id')
828
except errors.UnsupportedOperation:
829
raise tests.TestNotApplicable('Branch cannot hold references.')
830
self.assertIs(None, path)
831
self.assertIs(None, loc)
833
def test_set_reference_info(self):
834
branch = self.make_branch('branch')
836
branch.set_reference_info('file-id', 'path/to/location',
838
except errors.UnsupportedOperation:
839
raise tests.TestNotApplicable('Branch cannot hold references.')
841
def test_set_get_reference_info(self):
842
branch = self.make_branch('branch')
844
branch.set_reference_info('file-id', 'path/to/file',
846
except errors.UnsupportedOperation:
847
raise tests.TestNotApplicable('Branch cannot hold references.')
848
# Create a new instance to ensure storage is permanent
849
branch = _mod_branch.Branch.open('branch')
850
tree_path, branch_location = branch.get_reference_info('file-id')
851
self.assertEqual('path/to/location', branch_location)
853
def test_set_null_reference_info(self):
854
branch = self.make_branch('branch')
856
branch.set_reference_info('file-id', 'path/to/file',
858
except errors.UnsupportedOperation:
859
raise tests.TestNotApplicable('Branch cannot hold references.')
860
branch.set_reference_info('file-id', None, None)
861
tree_path, branch_location = branch.get_reference_info('file-id')
862
self.assertIs(None, tree_path)
863
self.assertIs(None, branch_location)
865
def test_set_null_reference_info_when_null(self):
866
branch = self.make_branch('branch')
868
tree_path, branch_location = branch.get_reference_info('file-id')
869
except errors.UnsupportedOperation:
870
raise tests.TestNotApplicable('Branch cannot hold references.')
871
self.assertIs(None, tree_path)
872
self.assertIs(None, branch_location)
873
branch.set_reference_info('file-id', None, None)
875
def test_set_null_requires_two_nones(self):
876
branch = self.make_branch('branch')
878
e = self.assertRaises(ValueError, branch.set_reference_info,
879
'file-id', 'path', None)
880
except errors.UnsupportedOperation:
881
raise tests.TestNotApplicable('Branch cannot hold references.')
882
self.assertEqual('tree_path must be None when branch_location is'
884
e = self.assertRaises(ValueError, branch.set_reference_info,
885
'file-id', None, 'location')
886
self.assertEqual('branch_location must be None when tree_path is'
889
def make_branch_with_reference(self, location, reference_location,
891
branch = self.make_branch(location)
893
branch.set_reference_info(file_id, 'path/to/file',
895
except errors.UnsupportedOperation:
896
raise tests.TestNotApplicable('Branch cannot hold references.')
899
def test_reference_parent_from_reference_info_(self):
900
referenced_branch = self.make_branch('reference_branch')
901
branch = self.make_branch_with_reference('branch',
902
referenced_branch.base)
903
parent = branch.reference_parent('file-id', 'path/to/file')
904
self.assertEqual(parent.base, referenced_branch.base)
906
def test_branch_relative_reference_location(self):
907
branch = self.make_branch('branch')
909
branch.set_reference_info('file-id', 'path/to/file',
910
'../reference_branch')
911
except errors.UnsupportedOperation:
912
raise tests.TestNotApplicable('Branch cannot hold references.')
913
referenced_branch = self.make_branch('reference_branch')
914
parent = branch.reference_parent('file-id', 'path/to/file')
915
self.assertEqual(parent.base, referenced_branch.base)
917
def test_sprout_copies_reference_location(self):
918
branch = self.make_branch_with_reference('branch', '../reference')
919
new_branch = branch.bzrdir.sprout('new-branch').open_branch()
920
self.assertEqual('../reference',
921
new_branch.get_reference_info('file-id')[1])
923
def test_clone_copies_reference_location(self):
924
branch = self.make_branch_with_reference('branch', '../reference')
925
new_branch = branch.bzrdir.clone('new-branch').open_branch()
926
self.assertEqual('../reference',
927
new_branch.get_reference_info('file-id')[1])
929
def test_copied_locations_are_rebased(self):
930
branch = self.make_branch_with_reference('branch', 'reference')
931
new_branch = branch.bzrdir.sprout('branch/new-branch').open_branch()
932
self.assertEqual('../reference',
933
new_branch.get_reference_info('file-id')[1])
935
def test_update_references_retains_old_references(self):
936
branch = self.make_branch_with_reference('branch', 'reference')
937
new_branch = self.make_branch_with_reference(
938
'new_branch', 'reference', 'file-id2')
939
new_branch.update_references(branch)
940
self.assertEqual('reference',
941
branch.get_reference_info('file-id')[1])
943
def test_update_references_retains_known_references(self):
944
branch = self.make_branch_with_reference('branch', 'reference')
945
new_branch = self.make_branch_with_reference(
946
'new_branch', 'reference2')
947
new_branch.update_references(branch)
948
self.assertEqual('reference',
949
branch.get_reference_info('file-id')[1])
951
def test_update_references_skips_known_references(self):
952
branch = self.make_branch_with_reference('branch', 'reference')
953
new_branch = branch.bzrdir.sprout('branch/new-branch').open_branch()
954
new_branch.set_reference_info('file-id', '../foo', '../foo')
955
new_branch.update_references(branch)
956
self.assertEqual('reference',
957
branch.get_reference_info('file-id')[1])
959
def test_pull_updates_references(self):
960
branch = self.make_branch_with_reference('branch', 'reference')
961
new_branch = branch.bzrdir.sprout('branch/new-branch').open_branch()
962
new_branch.set_reference_info('file-id2', '../foo', '../foo')
963
branch.pull(new_branch)
964
self.assertEqual('foo',
965
branch.get_reference_info('file-id2')[1])
967
def test_push_updates_references(self):
968
branch = self.make_branch_with_reference('branch', 'reference')
969
new_branch = branch.bzrdir.sprout('branch/new-branch').open_branch()
970
new_branch.set_reference_info('file-id2', '../foo', '../foo')
971
new_branch.push(branch)
972
self.assertEqual('foo',
973
branch.get_reference_info('file-id2')[1])
975
def test_merge_updates_references(self):
976
branch = self.make_branch_with_reference('branch', 'reference')
977
tree = self.make_branch_and_tree('tree')
979
branch.pull(tree.branch)
980
checkout = branch.create_checkout('checkout', lightweight=True)
981
checkout.commit('bar')
983
self.addCleanup(tree.unlock)
984
merger = merge.Merger.from_revision_ids(None, tree,
985
branch.last_revision(),
987
merger.merge_type = merge.Merge3Merger
989
self.assertEqual('../branch/reference',
990
tree.branch.get_reference_info('file-id')[1])
993
class TestBranchControlComponent(per_branch.TestCaseWithBranch):
994
"""Branch implementations adequately implement ControlComponent."""
997
br = self.make_branch('branch')
998
self.assertIsInstance(br.user_url, str)
999
self.assertEqual(br.user_url, br.user_transport.base)
1000
# for all current bzrdir implementations the user dir must be
1001
# above the control dir but we might need to relax that?
1002
self.assertEqual(br.control_url.find(br.user_url), 0)
1003
self.assertEqual(br.control_url, br.control_transport.base)
384
self.branch.set_push_location('foo')
385
self.assertFileEqual("[%s]\n"
386
"push_location = foo" % os.getcwdu(),
387
'.bazaar/branches.conf')
389
# TODO RBC 20051029 test getting a push location from a branch in a
390
# recursive section - that is, it appends the branch name.