1
# Copyright (C) 2005, 2006, 2007 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for branch implementations - tests a branch format."""
32
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock
33
from bzrlib.delta import TreeDelta
34
from bzrlib.errors import (FileExists,
37
UninitializableFormat,
40
from bzrlib.osutils import getcwd
41
import bzrlib.revision
42
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
43
from bzrlib.tests.branch_implementations import TestCaseWithBranch
44
from bzrlib.tests.HttpServer import HttpServer
45
from bzrlib.trace import mutter
46
from bzrlib.transport import get_transport
47
from bzrlib.transport.memory import MemoryServer
48
from bzrlib.upgrade import upgrade
49
from bzrlib.workingtree import WorkingTree
50
from bzrlib.symbol_versioning import (
55
class TestBranch(TestCaseWithBranch):
57
def test_create_tree_with_merge(self):
58
tree = self.create_tree_with_merge()
59
ancestry_graph = tree.branch.repository.get_revision_graph('rev-3')
60
self.assertEqual({'rev-1':(),
62
'rev-1.1.1':('rev-1', ),
63
'rev-3':('rev-2', 'rev-1.1.1', ),
66
def test_revision_ids_are_utf8(self):
67
wt = self.make_branch_and_tree('tree')
68
wt.commit('f', rev_id='rev1')
69
wt.commit('f', rev_id='rev2')
70
wt.commit('f', rev_id='rev3')
72
br = self.get_branch()
74
br.set_revision_history(['rev1', 'rev2', 'rev3'])
75
rh = br.revision_history()
76
self.assertEqual(['rev1', 'rev2', 'rev3'], rh)
77
for revision_id in rh:
78
self.assertIsInstance(revision_id, str)
79
last = br.last_revision()
80
self.assertEqual('rev3', last)
81
self.assertIsInstance(last, str)
82
revno, last = br.last_revision_info()
83
self.assertEqual(3, revno)
84
self.assertEqual('rev3', last)
85
self.assertIsInstance(last, str)
87
def test_fetch_revisions(self):
88
"""Test fetch-revision operation."""
89
wt = self.make_branch_and_tree('b1')
91
self.build_tree_contents([('b1/foo', 'hello')])
92
wt.add(['foo'], ['foo-id'])
93
wt.commit('lala!', rev_id='revision-1', allow_pointless=False)
95
b2 = self.make_branch('b2')
96
self.assertEqual((1, []), b2.fetch(b1))
98
rev = b2.repository.get_revision('revision-1')
99
tree = b2.repository.revision_tree('revision-1')
101
self.addCleanup(tree.unlock)
102
self.assertEqual(tree.get_file_text('foo-id'), 'hello')
104
def test_get_revision_delta(self):
105
tree_a = self.make_branch_and_tree('a')
106
self.build_tree(['a/foo'])
107
tree_a.add('foo', 'file1')
108
tree_a.commit('rev1', rev_id='rev1')
109
self.build_tree(['a/vla'])
110
tree_a.add('vla', 'file2')
111
tree_a.commit('rev2', rev_id='rev2')
113
delta = tree_a.branch.get_revision_delta(1)
114
self.assertIsInstance(delta, TreeDelta)
115
self.assertEqual([('foo', 'file1', 'file')], delta.added)
116
delta = tree_a.branch.get_revision_delta(2)
117
self.assertIsInstance(delta, TreeDelta)
118
self.assertEqual([('vla', 'file2', 'file')], delta.added)
120
def get_unbalanced_tree_pair(self):
121
"""Return two branches, a and b, with one file in a."""
122
tree_a = self.make_branch_and_tree('a')
123
self.build_tree_contents([('a/b', 'b')])
125
tree_a.commit("silly commit", rev_id='A')
127
tree_b = self.make_branch_and_tree('b')
128
return tree_a, tree_b
130
def get_balanced_branch_pair(self):
131
"""Returns br_a, br_b as with one commit in a, and b has a's stores."""
132
tree_a, tree_b = self.get_unbalanced_tree_pair()
133
tree_b.branch.repository.fetch(tree_a.branch.repository)
134
return tree_a, tree_b
136
def test_clone_partial(self):
137
"""Copy only part of the history of a branch."""
138
# TODO: RBC 20060208 test with a revision not on revision-history.
139
# what should that behaviour be ? Emailed the list.
140
# First, make a branch with two commits.
141
wt_a = self.make_branch_and_tree('a')
142
self.build_tree(['a/one'])
144
wt_a.commit('commit one', rev_id='1')
145
self.build_tree(['a/two'])
147
wt_a.commit('commit two', rev_id='2')
148
# Now make a copy of the repository.
149
repo_b = self.make_repository('b')
150
wt_a.branch.repository.copy_content_into(repo_b)
151
# wt_a might be a lightweight checkout, so get a hold of the actual
152
# branch (because you can't do a partial clone of a lightweight
154
branch = wt_a.branch.bzrdir.open_branch()
155
# Then make a branch where the new repository is, but specify a revision
156
# ID. The new branch's history will stop at the specified revision.
157
br_b = branch.clone(repo_b.bzrdir, revision_id='1')
158
self.assertEqual('1', br_b.last_revision())
160
def get_parented_branch(self):
161
wt_a = self.make_branch_and_tree('a')
162
self.build_tree(['a/one'])
164
wt_a.commit('commit one', rev_id='1')
166
branch_b = wt_a.bzrdir.sprout('b', revision_id='1').open_branch()
167
self.assertEqual(wt_a.branch.base, branch_b.get_parent())
170
def test_clone_branch_nickname(self):
171
# test the nick name is preserved always
172
raise TestSkipped('XXX branch cloning is not yet tested.')
174
def test_clone_branch_parent(self):
175
# test the parent is preserved always
176
branch_b = self.get_parented_branch()
177
repo_c = self.make_repository('c')
178
branch_b.repository.copy_content_into(repo_c)
179
branch_c = branch_b.clone(repo_c.bzrdir)
180
self.assertNotEqual(None, branch_c.get_parent())
181
self.assertEqual(branch_b.get_parent(), branch_c.get_parent())
183
# We can also set a specific parent, and it should be honored
184
random_parent = 'http://bazaar-vcs.org/path/to/branch'
185
branch_b.set_parent(random_parent)
186
repo_d = self.make_repository('d')
187
branch_b.repository.copy_content_into(repo_d)
188
branch_d = branch_b.clone(repo_d.bzrdir)
189
self.assertEqual(random_parent, branch_d.get_parent())
191
def test_submit_branch(self):
192
"""Submit location can be queried and set"""
193
branch = self.make_branch('branch')
194
self.assertEqual(branch.get_submit_branch(), None)
195
branch.set_submit_branch('sftp://example.com')
196
self.assertEqual(branch.get_submit_branch(), 'sftp://example.com')
197
branch.set_submit_branch('sftp://example.net')
198
self.assertEqual(branch.get_submit_branch(), 'sftp://example.net')
200
def test_public_branch(self):
201
"""public location can be queried and set"""
202
branch = self.make_branch('branch')
203
self.assertEqual(branch.get_public_branch(), None)
204
branch.set_public_branch('sftp://example.com')
205
self.assertEqual(branch.get_public_branch(), 'sftp://example.com')
206
branch.set_public_branch('sftp://example.net')
207
self.assertEqual(branch.get_public_branch(), 'sftp://example.net')
208
branch.set_public_branch(None)
209
self.assertEqual(branch.get_public_branch(), None)
211
def test_record_initial_ghost(self):
212
"""Branches should support having ghosts."""
213
wt = self.make_branch_and_tree('.')
214
wt.set_parent_ids(['non:existent@rev--ision--0--2'],
215
allow_leftmost_as_ghost=True)
216
rev_id = wt.commit('commit against a ghost first parent.')
217
rev = wt.branch.repository.get_revision(rev_id)
218
self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
219
# parent_sha1s is not populated now, WTF. rbc 20051003
220
self.assertEqual(len(rev.parent_sha1s), 0)
222
def test_record_two_ghosts(self):
223
"""Recording with all ghosts works."""
224
wt = self.make_branch_and_tree('.')
226
'foo@azkhazan-123123-abcabc',
227
'wibble@fofof--20050401--1928390812',
229
allow_leftmost_as_ghost=True)
230
rev_id = wt.commit("commit from ghost base with one merge")
231
# the revision should have been committed with two parents
232
rev = wt.branch.repository.get_revision(rev_id)
233
self.assertEqual(['foo@azkhazan-123123-abcabc',
234
'wibble@fofof--20050401--1928390812'],
237
def test_bad_revision(self):
238
self.assertRaises(errors.InvalidRevisionId,
239
self.get_branch().repository.get_revision,
243
# compare the gpg-to-sign info for a commit with a ghost and
244
# an identical tree without a ghost
245
# fetch missing should rewrite the TOC of weaves to list newly available parents.
247
def test_sign_existing_revision(self):
248
wt = self.make_branch_and_tree('.')
250
wt.commit("base", allow_pointless=True, rev_id='A')
251
from bzrlib.testament import Testament
252
strategy = gpg.LoopbackGPGStrategy(None)
253
branch.repository.lock_write()
254
branch.repository.start_write_group()
255
branch.repository.sign_revision('A', strategy)
256
branch.repository.commit_write_group()
257
branch.repository.unlock()
258
self.assertEqual('-----BEGIN PSEUDO-SIGNED CONTENT-----\n' +
259
Testament.from_revision(branch.repository,
260
'A').as_short_text() +
261
'-----END PSEUDO-SIGNED CONTENT-----\n',
262
branch.repository.get_signature_text('A'))
264
def test_store_signature(self):
265
wt = self.make_branch_and_tree('.')
269
branch.repository.start_write_group()
271
branch.repository.store_revision_signature(
272
gpg.LoopbackGPGStrategy(None), 'FOO', 'A')
274
branch.repository.abort_write_group()
277
branch.repository.commit_write_group()
280
self.assertRaises(errors.NoSuchRevision,
281
branch.repository.has_signature_for_revision_id,
283
wt.commit("base", allow_pointless=True, rev_id='A')
284
self.assertEqual('-----BEGIN PSEUDO-SIGNED CONTENT-----\n'
285
'FOO-----END PSEUDO-SIGNED CONTENT-----\n',
286
branch.repository.get_signature_text('A'))
288
def test_branch_keeps_signatures(self):
289
wt = self.make_branch_and_tree('source')
290
wt.commit('A', allow_pointless=True, rev_id='A')
291
repo = wt.branch.repository
293
repo.start_write_group()
294
repo.sign_revision('A', gpg.LoopbackGPGStrategy(None))
295
repo.commit_write_group()
297
#FIXME: clone should work to urls,
298
# wt.clone should work to disks.
299
self.build_tree(['target/'])
300
d2 = repo.bzrdir.clone(urlutils.local_path_to_url('target'))
301
self.assertEqual(repo.get_signature_text('A'),
302
d2.open_repository().get_signature_text('A'))
304
def test_nicks(self):
305
"""Test explicit and implicit branch nicknames.
307
Nicknames are implicitly the name of the branch's directory, unless an
308
explicit nickname is set. That is, an explicit nickname always
309
overrides the implicit one.
311
t = get_transport(self.get_url())
312
branch = self.make_branch('bzr.dev')
313
# The nick will be 'bzr.dev', because there is no explicit nick set.
314
self.assertEqual(branch.nick, 'bzr.dev')
315
# Move the branch to a different directory, 'bzr.ab'. Now that branch
316
# will report its nick as 'bzr.ab'.
317
t.move('bzr.dev', 'bzr.ab')
318
branch = Branch.open(self.get_url('bzr.ab'))
319
self.assertEqual(branch.nick, 'bzr.ab')
320
# Set the branch nick explicitly. This will ensure there's a branch
321
# config file in the branch.
322
branch.nick = "Aaron's branch"
323
branch.nick = "Aaron's branch"
324
if not isinstance(branch, remote.RemoteBranch):
325
controlfilename = branch.control_files.controlfilename
326
self.failUnless(t.has(t.relpath(controlfilename("branch.conf"))))
327
# Because the nick has been set explicitly, the nick is now always
328
# "Aaron's branch", regardless of directory name.
329
self.assertEqual(branch.nick, "Aaron's branch")
330
t.move('bzr.ab', 'integration')
331
branch = Branch.open(self.get_url('integration'))
332
self.assertEqual(branch.nick, "Aaron's branch")
333
branch.nick = u"\u1234"
334
self.assertEqual(branch.nick, u"\u1234")
336
def test_commit_nicks(self):
337
"""Nicknames are committed to the revision"""
338
wt = self.make_branch_and_tree('bzr.dev')
340
branch.nick = "My happy branch"
341
wt.commit('My commit respect da nick.')
342
committed = branch.repository.get_revision(branch.last_revision())
343
self.assertEqual(committed.properties["branch-nick"],
346
def test_create_open_branch_uses_repository(self):
348
repo = self.make_repository('.', shared=True)
349
except errors.IncompatibleFormat:
351
child_transport = repo.bzrdir.root_transport.clone('child')
352
child_transport.mkdir('.')
353
child_dir = self.bzrdir_format.initialize_on_transport(child_transport)
355
child_branch = self.branch_format.initialize(child_dir)
356
except errors.UninitializableFormat:
357
# branch references are not default init'able.
359
self.assertEqual(repo.bzrdir.root_transport.base,
360
child_branch.repository.bzrdir.root_transport.base)
361
child_branch = branch.Branch.open(self.get_url('child'))
362
self.assertEqual(repo.bzrdir.root_transport.base,
363
child_branch.repository.bzrdir.root_transport.base)
365
def test_format_description(self):
366
tree = self.make_branch_and_tree('tree')
367
text = tree.branch._format.get_format_description()
368
self.failUnless(len(text))
370
def test_check_branch_report_results(self):
371
"""Checking a branch produces results which can be printed"""
372
branch = self.make_branch('.')
373
result = branch.check()
374
# reports results through logging
375
result.report_results(verbose=True)
376
result.report_results(verbose=False)
378
def test_get_commit_builder(self):
379
branch = self.make_branch(".")
381
builder = branch.get_commit_builder([])
382
self.assertIsInstance(builder, repository.CommitBuilder)
383
branch.repository.commit_write_group()
386
def test_generate_revision_history(self):
387
"""Create a fake revision history easily."""
388
tree = self.make_branch_and_tree('.')
389
rev1 = tree.commit('foo')
390
orig_history = tree.branch.revision_history()
391
rev2 = tree.commit('bar', allow_pointless=True)
392
tree.branch.generate_revision_history(rev1)
393
self.assertEqual(orig_history, tree.branch.revision_history())
395
def test_generate_revision_history_NULL_REVISION(self):
396
tree = self.make_branch_and_tree('.')
397
rev1 = tree.commit('foo')
398
tree.branch.generate_revision_history(bzrlib.revision.NULL_REVISION)
399
self.assertEqual([], tree.branch.revision_history())
401
def test_create_checkout(self):
402
tree_a = self.make_branch_and_tree('a')
403
branch_a = tree_a.branch
404
checkout_b = branch_a.create_checkout('b')
405
self.assertEqual('null:', checkout_b.last_revision())
406
checkout_b.commit('rev1', rev_id='rev1')
407
self.assertEqual('rev1', branch_a.last_revision())
408
self.assertNotEqual(checkout_b.branch.base, branch_a.base)
410
checkout_c = branch_a.create_checkout('c', lightweight=True)
411
self.assertEqual('rev1', checkout_c.last_revision())
412
checkout_c.commit('rev2', rev_id='rev2')
413
self.assertEqual('rev2', branch_a.last_revision())
414
self.assertEqual(checkout_c.branch.base, branch_a.base)
417
checkout_d = branch_a.create_checkout('d', lightweight=True)
418
self.assertEqual('rev2', checkout_d.last_revision())
420
checkout_e = branch_a.create_checkout('e')
421
self.assertEqual('rev2', checkout_e.last_revision())
423
def test_create_anonymous_lightweight_checkout(self):
424
"""A lightweight checkout from a readonly branch should succeed."""
425
tree_a = self.make_branch_and_tree('a')
426
rev_id = tree_a.commit('put some content in the branch')
427
# open the branch via a readonly transport
428
source_branch = bzrlib.branch.Branch.open(self.get_readonly_url('a'))
429
# sanity check that the test will be valid
430
self.assertRaises((errors.LockError, errors.TransportNotPossible),
431
source_branch.lock_write)
432
checkout = source_branch.create_checkout('c', lightweight=True)
433
self.assertEqual(rev_id, checkout.last_revision())
435
def test_create_anonymous_heavyweight_checkout(self):
436
"""A regular checkout from a readonly branch should succeed."""
437
tree_a = self.make_branch_and_tree('a')
438
rev_id = tree_a.commit('put some content in the branch')
439
# open the branch via a readonly transport
440
source_branch = bzrlib.branch.Branch.open(self.get_readonly_url('a'))
441
# sanity check that the test will be valid
442
self.assertRaises((errors.LockError, errors.TransportNotPossible),
443
source_branch.lock_write)
444
checkout = source_branch.create_checkout('c')
445
self.assertEqual(rev_id, checkout.last_revision())
447
def test_set_revision_history(self):
448
tree = self.make_branch_and_tree('a')
449
tree.commit('a commit', rev_id='rev1')
451
br.set_revision_history(["rev1"])
452
self.assertEquals(br.revision_history(), ["rev1"])
453
br.set_revision_history([])
454
self.assertEquals(br.revision_history(), [])
457
class ChrootedTests(TestCaseWithBranch):
458
"""A support class that provides readonly urls outside the local namespace.
460
This is done by checking if self.transport_server is a MemoryServer. if it
461
is then we are chrooted already, if it is not then an HttpServer is used
466
super(ChrootedTests, self).setUp()
467
if not self.vfs_transport_factory == MemoryServer:
468
self.transport_readonly_server = HttpServer
470
def test_open_containing(self):
471
self.assertRaises(NotBranchError, Branch.open_containing,
472
self.get_readonly_url(''))
473
self.assertRaises(NotBranchError, Branch.open_containing,
474
self.get_readonly_url('g/p/q'))
475
branch = self.make_branch('.')
476
branch, relpath = Branch.open_containing(self.get_readonly_url(''))
477
self.assertEqual('', relpath)
478
branch, relpath = Branch.open_containing(self.get_readonly_url('g/p/q'))
479
self.assertEqual('g/p/q', relpath)
482
class InstrumentedTransaction(object):
485
self.calls.append('finish')
491
class TestDecorator(object):
497
self._calls.append('lr')
499
def lock_write(self):
500
self._calls.append('lw')
503
self._calls.append('ul')
506
def do_with_read(self):
510
def except_with_read(self):
514
def do_with_write(self):
518
def except_with_write(self):
522
class TestDecorators(TestCase):
524
def test_needs_read_lock(self):
525
branch = TestDecorator()
526
self.assertEqual(1, branch.do_with_read())
527
self.assertEqual(['lr', 'ul'], branch._calls)
529
def test_excepts_in_read_lock(self):
530
branch = TestDecorator()
531
self.assertRaises(RuntimeError, branch.except_with_read)
532
self.assertEqual(['lr', 'ul'], branch._calls)
534
def test_needs_write_lock(self):
535
branch = TestDecorator()
536
self.assertEqual(2, branch.do_with_write())
537
self.assertEqual(['lw', 'ul'], branch._calls)
539
def test_excepts_in_write_lock(self):
540
branch = TestDecorator()
541
self.assertRaises(RuntimeError, branch.except_with_write)
542
self.assertEqual(['lw', 'ul'], branch._calls)
545
class TestBranchPushLocations(TestCaseWithBranch):
547
def test_get_push_location_unset(self):
548
self.assertEqual(None, self.get_branch().get_push_location())
550
def test_get_push_location_exact(self):
551
from bzrlib.config import (locations_config_filename,
552
ensure_config_dir_exists)
553
ensure_config_dir_exists()
554
fn = locations_config_filename()
555
open(fn, 'wt').write(("[%s]\n"
556
"push_location=foo\n" %
557
self.get_branch().base[:-1]))
558
self.assertEqual("foo", self.get_branch().get_push_location())
560
def test_set_push_location(self):
561
branch = self.get_branch()
562
branch.set_push_location('foo')
563
self.assertEqual('foo', branch.get_push_location())
566
class TestFormat(TestCaseWithBranch):
567
"""Tests for the format itself."""
569
def test_get_reference(self):
570
"""get_reference on all regular branches should return None."""
571
if not self.branch_format.is_supported():
572
# unsupported formats are not loopback testable
573
# because the default open will not open them and
574
# they may not be initializable.
576
made_branch = self.make_branch('.')
577
self.assertEqual(None,
578
made_branch._format.get_reference(made_branch.bzrdir))
580
def test_format_initialize_find_open(self):
581
# loopback test to check the current format initializes to itself.
582
if not self.branch_format.is_supported():
583
# unsupported formats are not loopback testable
584
# because the default open will not open them and
585
# they may not be initializable.
587
# supported formats must be able to init and open
588
t = get_transport(self.get_url())
589
readonly_t = get_transport(self.get_readonly_url())
590
made_branch = self.make_branch('.')
591
self.failUnless(isinstance(made_branch, branch.Branch))
593
# find it via bzrdir opening:
594
opened_control = bzrdir.BzrDir.open(readonly_t.base)
595
direct_opened_branch = opened_control.open_branch()
596
self.assertEqual(direct_opened_branch.__class__, made_branch.__class__)
597
self.assertEqual(opened_control, direct_opened_branch.bzrdir)
598
self.failUnless(isinstance(direct_opened_branch._format,
599
self.branch_format.__class__))
601
# find it via Branch.open
602
opened_branch = branch.Branch.open(readonly_t.base)
603
self.failUnless(isinstance(opened_branch, made_branch.__class__))
604
self.assertEqual(made_branch._format.__class__,
605
opened_branch._format.__class__)
606
# if it has a unique id string, can we probe for it ?
608
self.branch_format.get_format_string()
609
except NotImplementedError:
611
self.assertEqual(self.branch_format,
612
opened_control.find_branch_format())
615
class TestBound(TestCaseWithBranch):
617
def test_bind_unbind(self):
618
branch = self.make_branch('1')
619
branch2 = self.make_branch('2')
622
except errors.UpgradeRequired:
623
raise TestSkipped('Format does not support binding')
624
self.assertTrue(branch.unbind())
625
self.assertFalse(branch.unbind())
626
self.assertIs(None, branch.get_bound_location())
628
def test_old_bound_location(self):
629
branch = self.make_branch('branch1')
631
self.assertIs(None, branch.get_old_bound_location())
632
except errors.UpgradeRequired:
633
raise TestSkipped('Format does not store old bound locations')
634
branch2 = self.make_branch('branch2')
636
self.assertIs(None, branch.get_old_bound_location())
638
self.assertContainsRe(branch.get_old_bound_location(), '\/branch2\/$')
641
class TestStrict(TestCaseWithBranch):
643
def test_strict_history(self):
644
tree1 = self.make_branch_and_tree('tree1')
646
tree1.branch.set_append_revisions_only(True)
647
except errors.UpgradeRequired:
648
raise TestSkipped('Format does not support strict history')
649
tree1.commit('empty commit')
650
tree2 = tree1.bzrdir.sprout('tree2').open_workingtree()
651
tree2.commit('empty commit 2')
652
tree1.pull(tree2.branch)
653
tree1.commit('empty commit 3')
654
tree2.commit('empty commit 4')
655
self.assertRaises(errors.DivergedBranches, tree1.pull, tree2.branch)
656
tree2.merge_from_branch(tree1.branch)
657
tree2.commit('empty commit 5')
658
self.assertRaises(errors.AppendRevisionsOnlyViolation, tree1.pull,
660
tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
661
tree3.merge_from_branch(tree2.branch)
662
tree3.commit('empty commit 6')
663
tree2.pull(tree3.branch)
17
from bzrlib.selftest import InTempDir
21
class TestAppendRevisions(InTempDir):
22
"""Test appending more than one revision"""
24
from bzrlib.branch import Branch
25
br = Branch(".", init=True)
26
br.append_revision("rev1")
27
self.assertEquals(br.revision_history(), ["rev1",])
28
br.append_revision("rev2", "rev3")
29
self.assertEquals(br.revision_history(), ["rev1", "rev2", "rev3"])