1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
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.
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.
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
17
"""Tests for the Repository facility that are not interface tests.
19
For interface tests see tests/per_repository/*.py.
21
For concrete class tests see this file, and for storage formats tests
26
from stat import S_ISDIR
27
from StringIO import StringIO
30
from bzrlib.errors import (NotBranchError,
33
UnsupportedFormatError,
35
from bzrlib import graph
36
from bzrlib.index import GraphIndex, InMemoryGraphIndex
37
from bzrlib.repository import RepositoryFormat
38
from bzrlib.smart import server
39
from bzrlib.tests import (
41
TestCaseWithTransport,
45
from bzrlib.transport import (
49
from bzrlib.transport.memory import MemoryServer
50
from bzrlib.util import bencode
57
revision as _mod_revision,
62
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
65
class TestDefaultFormat(TestCase):
67
def test_get_set_default_format(self):
68
old_default = bzrdir.format_registry.get('default')
69
private_default = old_default().repository_format.__class__
70
old_format = repository.RepositoryFormat.get_default_format()
71
self.assertTrue(isinstance(old_format, private_default))
72
def make_sample_bzrdir():
73
my_bzrdir = bzrdir.BzrDirMetaFormat1()
74
my_bzrdir.repository_format = SampleRepositoryFormat()
76
bzrdir.format_registry.remove('default')
77
bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
78
bzrdir.format_registry.set_default('sample')
79
# creating a repository should now create an instrumented dir.
81
# the default branch format is used by the meta dir format
82
# which is not the default bzrdir format at this point
83
dir = bzrdir.BzrDirMetaFormat1().initialize('memory:///')
84
result = dir.create_repository()
85
self.assertEqual(result, 'A bzr repository dir')
87
bzrdir.format_registry.remove('default')
88
bzrdir.format_registry.remove('sample')
89
bzrdir.format_registry.register('default', old_default, '')
90
self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
94
class SampleRepositoryFormat(repository.RepositoryFormat):
97
this format is initializable, unsupported to aid in testing the
98
open and open(unsupported=True) routines.
101
def get_format_string(self):
102
"""See RepositoryFormat.get_format_string()."""
103
return "Sample .bzr repository format."
105
def initialize(self, a_bzrdir, shared=False):
106
"""Initialize a repository in a BzrDir"""
107
t = a_bzrdir.get_repository_transport(self)
108
t.put_bytes('format', self.get_format_string())
109
return 'A bzr repository dir'
111
def is_supported(self):
114
def open(self, a_bzrdir, _found=False):
115
return "opened repository."
118
class TestRepositoryFormat(TestCaseWithTransport):
119
"""Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
121
def test_find_format(self):
122
# is the right format object found for a repository?
123
# create a branch with a few known format objects.
124
# this is not quite the same as
125
self.build_tree(["foo/", "bar/"])
126
def check_format(format, url):
127
dir = format._matchingbzrdir.initialize(url)
128
format.initialize(dir)
129
t = get_transport(url)
130
found_format = repository.RepositoryFormat.find_format(dir)
131
self.failUnless(isinstance(found_format, format.__class__))
132
check_format(weaverepo.RepositoryFormat7(), "bar")
134
def test_find_format_no_repository(self):
135
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
136
self.assertRaises(errors.NoRepositoryPresent,
137
repository.RepositoryFormat.find_format,
140
def test_find_format_unknown_format(self):
141
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
142
SampleRepositoryFormat().initialize(dir)
143
self.assertRaises(UnknownFormatError,
144
repository.RepositoryFormat.find_format,
147
def test_register_unregister_format(self):
148
format = SampleRepositoryFormat()
150
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
152
format.initialize(dir)
153
# register a format for it.
154
repository.RepositoryFormat.register_format(format)
155
# which repository.Open will refuse (not supported)
156
self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
157
# but open(unsupported) will work
158
self.assertEqual(format.open(dir), "opened repository.")
159
# unregister the format
160
repository.RepositoryFormat.unregister_format(format)
163
class TestFormat6(TestCaseWithTransport):
165
def test_attribute__fetch_order(self):
166
"""Weaves need topological data insertion."""
167
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
168
repo = weaverepo.RepositoryFormat6().initialize(control)
169
self.assertEqual('topological', repo._fetch_order)
171
def test_attribute__fetch_uses_deltas(self):
172
"""Weaves do not reuse deltas."""
173
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
174
repo = weaverepo.RepositoryFormat6().initialize(control)
175
self.assertEqual(False, repo._fetch_uses_deltas)
177
def test_attribute__fetch_reconcile(self):
178
"""Weave repositories need a reconcile after fetch."""
179
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
180
repo = weaverepo.RepositoryFormat6().initialize(control)
181
self.assertEqual(True, repo._fetch_reconcile)
183
def test_no_ancestry_weave(self):
184
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
185
repo = weaverepo.RepositoryFormat6().initialize(control)
186
# We no longer need to create the ancestry.weave file
187
# since it is *never* used.
188
self.assertRaises(NoSuchFile,
189
control.transport.get,
192
def test_supports_external_lookups(self):
193
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
194
repo = weaverepo.RepositoryFormat6().initialize(control)
195
self.assertFalse(repo._format.supports_external_lookups)
198
class TestFormat7(TestCaseWithTransport):
200
def test_attribute__fetch_order(self):
201
"""Weaves need topological data insertion."""
202
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
203
repo = weaverepo.RepositoryFormat7().initialize(control)
204
self.assertEqual('topological', repo._fetch_order)
206
def test_attribute__fetch_uses_deltas(self):
207
"""Weaves do not reuse deltas."""
208
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
209
repo = weaverepo.RepositoryFormat7().initialize(control)
210
self.assertEqual(False, repo._fetch_uses_deltas)
212
def test_attribute__fetch_reconcile(self):
213
"""Weave repositories need a reconcile after fetch."""
214
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
215
repo = weaverepo.RepositoryFormat7().initialize(control)
216
self.assertEqual(True, repo._fetch_reconcile)
218
def test_disk_layout(self):
219
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
220
repo = weaverepo.RepositoryFormat7().initialize(control)
221
# in case of side effects of locking.
225
# format 'Bazaar-NG Repository format 7'
227
# inventory.weave == empty_weave
228
# empty revision-store directory
229
# empty weaves directory
230
t = control.get_repository_transport(None)
231
self.assertEqualDiff('Bazaar-NG Repository format 7',
232
t.get('format').read())
233
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
234
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
235
self.assertEqualDiff('# bzr weave file v5\n'
238
t.get('inventory.weave').read())
239
# Creating a file with id Foo:Bar results in a non-escaped file name on
241
control.create_branch()
242
tree = control.create_workingtree()
243
tree.add(['foo'], ['Foo:Bar'], ['file'])
244
tree.put_file_bytes_non_atomic('Foo:Bar', 'content\n')
245
tree.commit('first post', rev_id='first')
246
self.assertEqualDiff(
247
'# bzr weave file v5\n'
249
'1 7fe70820e08a1aac0ef224d9c66ab66831cc4ab1\n'
257
t.get('weaves/74/Foo%3ABar.weave').read())
259
def test_shared_disk_layout(self):
260
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
261
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
263
# format 'Bazaar-NG Repository format 7'
264
# inventory.weave == empty_weave
265
# empty revision-store directory
266
# empty weaves directory
267
# a 'shared-storage' marker file.
268
# lock is not present when unlocked
269
t = control.get_repository_transport(None)
270
self.assertEqualDiff('Bazaar-NG Repository format 7',
271
t.get('format').read())
272
self.assertEqualDiff('', t.get('shared-storage').read())
273
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
274
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
275
self.assertEqualDiff('# bzr weave file v5\n'
278
t.get('inventory.weave').read())
279
self.assertFalse(t.has('branch-lock'))
281
def test_creates_lockdir(self):
282
"""Make sure it appears to be controlled by a LockDir existence"""
283
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
284
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
285
t = control.get_repository_transport(None)
286
# TODO: Should check there is a 'lock' toplevel directory,
287
# regardless of contents
288
self.assertFalse(t.has('lock/held/info'))
291
self.assertTrue(t.has('lock/held/info'))
293
# unlock so we don't get a warning about failing to do so
296
def test_uses_lockdir(self):
297
"""repo format 7 actually locks on lockdir"""
298
base_url = self.get_url()
299
control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
300
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
301
t = control.get_repository_transport(None)
305
# make sure the same lock is created by opening it
306
repo = repository.Repository.open(base_url)
308
self.assertTrue(t.has('lock/held/info'))
310
self.assertFalse(t.has('lock/held/info'))
312
def test_shared_no_tree_disk_layout(self):
313
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
314
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
315
repo.set_make_working_trees(False)
317
# format 'Bazaar-NG Repository format 7'
319
# inventory.weave == empty_weave
320
# empty revision-store directory
321
# empty weaves directory
322
# a 'shared-storage' marker file.
323
t = control.get_repository_transport(None)
324
self.assertEqualDiff('Bazaar-NG Repository format 7',
325
t.get('format').read())
326
## self.assertEqualDiff('', t.get('lock').read())
327
self.assertEqualDiff('', t.get('shared-storage').read())
328
self.assertEqualDiff('', t.get('no-working-trees').read())
329
repo.set_make_working_trees(True)
330
self.assertFalse(t.has('no-working-trees'))
331
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
332
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
333
self.assertEqualDiff('# bzr weave file v5\n'
336
t.get('inventory.weave').read())
338
def test_supports_external_lookups(self):
339
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
340
repo = weaverepo.RepositoryFormat7().initialize(control)
341
self.assertFalse(repo._format.supports_external_lookups)
344
class TestFormatKnit1(TestCaseWithTransport):
346
def test_attribute__fetch_order(self):
347
"""Knits need topological data insertion."""
348
repo = self.make_repository('.',
349
format=bzrdir.format_registry.get('knit')())
350
self.assertEqual('topological', repo._fetch_order)
352
def test_attribute__fetch_uses_deltas(self):
353
"""Knits reuse deltas."""
354
repo = self.make_repository('.',
355
format=bzrdir.format_registry.get('knit')())
356
self.assertEqual(True, repo._fetch_uses_deltas)
358
def test_disk_layout(self):
359
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
360
repo = knitrepo.RepositoryFormatKnit1().initialize(control)
361
# in case of side effects of locking.
365
# format 'Bazaar-NG Knit Repository Format 1'
366
# lock: is a directory
367
# inventory.weave == empty_weave
368
# empty revision-store directory
369
# empty weaves directory
370
t = control.get_repository_transport(None)
371
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
372
t.get('format').read())
373
# XXX: no locks left when unlocked at the moment
374
# self.assertEqualDiff('', t.get('lock').read())
375
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
377
# Check per-file knits.
378
branch = control.create_branch()
379
tree = control.create_workingtree()
380
tree.add(['foo'], ['Nasty-IdC:'], ['file'])
381
tree.put_file_bytes_non_atomic('Nasty-IdC:', '')
382
tree.commit('1st post', rev_id='foo')
383
self.assertHasKnit(t, 'knits/e8/%254easty-%2549d%2543%253a',
384
'\nfoo fulltext 0 81 :')
386
def assertHasKnit(self, t, knit_name, extra_content=''):
387
"""Assert that knit_name exists on t."""
388
self.assertEqualDiff('# bzr knit index 8\n' + extra_content,
389
t.get(knit_name + '.kndx').read())
391
def check_knits(self, t):
392
"""check knit content for a repository."""
393
self.assertHasKnit(t, 'inventory')
394
self.assertHasKnit(t, 'revisions')
395
self.assertHasKnit(t, 'signatures')
397
def test_shared_disk_layout(self):
398
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
399
repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
401
# format 'Bazaar-NG Knit Repository Format 1'
402
# lock: is a directory
403
# inventory.weave == empty_weave
404
# empty revision-store directory
405
# empty weaves directory
406
# a 'shared-storage' marker file.
407
t = control.get_repository_transport(None)
408
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
409
t.get('format').read())
410
# XXX: no locks left when unlocked at the moment
411
# self.assertEqualDiff('', t.get('lock').read())
412
self.assertEqualDiff('', t.get('shared-storage').read())
413
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
416
def test_shared_no_tree_disk_layout(self):
417
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
418
repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
419
repo.set_make_working_trees(False)
421
# format 'Bazaar-NG Knit Repository Format 1'
423
# inventory.weave == empty_weave
424
# empty revision-store directory
425
# empty weaves directory
426
# a 'shared-storage' marker file.
427
t = control.get_repository_transport(None)
428
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
429
t.get('format').read())
430
# XXX: no locks left when unlocked at the moment
431
# self.assertEqualDiff('', t.get('lock').read())
432
self.assertEqualDiff('', t.get('shared-storage').read())
433
self.assertEqualDiff('', t.get('no-working-trees').read())
434
repo.set_make_working_trees(True)
435
self.assertFalse(t.has('no-working-trees'))
436
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
439
def test_deserialise_sets_root_revision(self):
440
"""We must have a inventory.root.revision
442
Old versions of the XML5 serializer did not set the revision_id for
443
the whole inventory. So we grab the one from the expected text. Which
444
is valid when the api is not being abused.
446
repo = self.make_repository('.',
447
format=bzrdir.format_registry.get('knit')())
448
inv_xml = '<inventory format="5">\n</inventory>\n'
449
inv = repo.deserialise_inventory('test-rev-id', inv_xml)
450
self.assertEqual('test-rev-id', inv.root.revision)
452
def test_deserialise_uses_global_revision_id(self):
453
"""If it is set, then we re-use the global revision id"""
454
repo = self.make_repository('.',
455
format=bzrdir.format_registry.get('knit')())
456
inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
458
# Arguably, the deserialise_inventory should detect a mismatch, and
459
# raise an error, rather than silently using one revision_id over the
461
self.assertRaises(AssertionError, repo.deserialise_inventory,
462
'test-rev-id', inv_xml)
463
inv = repo.deserialise_inventory('other-rev-id', inv_xml)
464
self.assertEqual('other-rev-id', inv.root.revision)
466
def test_supports_external_lookups(self):
467
repo = self.make_repository('.',
468
format=bzrdir.format_registry.get('knit')())
469
self.assertFalse(repo._format.supports_external_lookups)
472
class DummyRepository(object):
473
"""A dummy repository for testing."""
477
def supports_rich_root(self):
481
class InterDummy(repository.InterRepository):
482
"""An inter-repository optimised code path for DummyRepository.
484
This is for use during testing where we use DummyRepository as repositories
485
so that none of the default regsitered inter-repository classes will
490
def is_compatible(repo_source, repo_target):
491
"""InterDummy is compatible with DummyRepository."""
492
return (isinstance(repo_source, DummyRepository) and
493
isinstance(repo_target, DummyRepository))
496
class TestInterRepository(TestCaseWithTransport):
498
def test_get_default_inter_repository(self):
499
# test that the InterRepository.get(repo_a, repo_b) probes
500
# for a inter_repo class where is_compatible(repo_a, repo_b) returns
501
# true and returns a default inter_repo otherwise.
502
# This also tests that the default registered optimised interrepository
503
# classes do not barf inappropriately when a surprising repository type
505
dummy_a = DummyRepository()
506
dummy_b = DummyRepository()
507
self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
509
def assertGetsDefaultInterRepository(self, repo_a, repo_b):
510
"""Asserts that InterRepository.get(repo_a, repo_b) -> the default.
512
The effective default is now InterSameDataRepository because there is
513
no actual sane default in the presence of incompatible data models.
515
inter_repo = repository.InterRepository.get(repo_a, repo_b)
516
self.assertEqual(repository.InterSameDataRepository,
517
inter_repo.__class__)
518
self.assertEqual(repo_a, inter_repo.source)
519
self.assertEqual(repo_b, inter_repo.target)
521
def test_register_inter_repository_class(self):
522
# test that a optimised code path provider - a
523
# InterRepository subclass can be registered and unregistered
524
# and that it is correctly selected when given a repository
525
# pair that it returns true on for the is_compatible static method
527
dummy_a = DummyRepository()
528
dummy_b = DummyRepository()
529
repo = self.make_repository('.')
530
# hack dummies to look like repo somewhat.
531
dummy_a._serializer = repo._serializer
532
dummy_b._serializer = repo._serializer
533
repository.InterRepository.register_optimiser(InterDummy)
535
# we should get the default for something InterDummy returns False
537
self.assertFalse(InterDummy.is_compatible(dummy_a, repo))
538
self.assertGetsDefaultInterRepository(dummy_a, repo)
539
# and we should get an InterDummy for a pair it 'likes'
540
self.assertTrue(InterDummy.is_compatible(dummy_a, dummy_b))
541
inter_repo = repository.InterRepository.get(dummy_a, dummy_b)
542
self.assertEqual(InterDummy, inter_repo.__class__)
543
self.assertEqual(dummy_a, inter_repo.source)
544
self.assertEqual(dummy_b, inter_repo.target)
546
repository.InterRepository.unregister_optimiser(InterDummy)
547
# now we should get the default InterRepository object again.
548
self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
551
class TestInterWeaveRepo(TestCaseWithTransport):
553
def test_is_compatible_and_registered(self):
554
# InterWeaveRepo is compatible when either side
555
# is a format 5/6/7 branch
556
from bzrlib.repofmt import knitrepo, weaverepo
557
formats = [weaverepo.RepositoryFormat5(),
558
weaverepo.RepositoryFormat6(),
559
weaverepo.RepositoryFormat7()]
560
incompatible_formats = [weaverepo.RepositoryFormat4(),
561
knitrepo.RepositoryFormatKnit1(),
563
repo_a = self.make_repository('a')
564
repo_b = self.make_repository('b')
565
is_compatible = repository.InterWeaveRepo.is_compatible
566
for source in incompatible_formats:
567
# force incompatible left then right
568
repo_a._format = source
569
repo_b._format = formats[0]
570
self.assertFalse(is_compatible(repo_a, repo_b))
571
self.assertFalse(is_compatible(repo_b, repo_a))
572
for source in formats:
573
repo_a._format = source
574
for target in formats:
575
repo_b._format = target
576
self.assertTrue(is_compatible(repo_a, repo_b))
577
self.assertEqual(repository.InterWeaveRepo,
578
repository.InterRepository.get(repo_a,
582
class TestRepositoryConverter(TestCaseWithTransport):
584
def test_convert_empty(self):
585
t = get_transport(self.get_url('.'))
586
t.mkdir('repository')
587
repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
588
repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
589
target_format = knitrepo.RepositoryFormatKnit1()
590
converter = repository.CopyConverter(target_format)
591
pb = bzrlib.ui.ui_factory.nested_progress_bar()
593
converter.convert(repo, pb)
596
repo = repo_dir.open_repository()
597
self.assertTrue(isinstance(target_format, repo._format.__class__))
600
class TestMisc(TestCase):
602
def test_unescape_xml(self):
603
"""We get some kind of error when malformed entities are passed"""
604
self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;')
607
class TestRepositoryFormatKnit3(TestCaseWithTransport):
609
def test_attribute__fetch_order(self):
610
"""Knits need topological data insertion."""
611
format = bzrdir.BzrDirMetaFormat1()
612
format.repository_format = knitrepo.RepositoryFormatKnit3()
613
repo = self.make_repository('.', format=format)
614
self.assertEqual('topological', repo._fetch_order)
616
def test_attribute__fetch_uses_deltas(self):
617
"""Knits reuse deltas."""
618
format = bzrdir.BzrDirMetaFormat1()
619
format.repository_format = knitrepo.RepositoryFormatKnit3()
620
repo = self.make_repository('.', format=format)
621
self.assertEqual(True, repo._fetch_uses_deltas)
623
def test_convert(self):
624
"""Ensure the upgrade adds weaves for roots"""
625
format = bzrdir.BzrDirMetaFormat1()
626
format.repository_format = knitrepo.RepositoryFormatKnit1()
627
tree = self.make_branch_and_tree('.', format)
628
tree.commit("Dull commit", rev_id="dull")
629
revision_tree = tree.branch.repository.revision_tree('dull')
630
revision_tree.lock_read()
632
self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
633
revision_tree.inventory.root.file_id)
635
revision_tree.unlock()
636
format = bzrdir.BzrDirMetaFormat1()
637
format.repository_format = knitrepo.RepositoryFormatKnit3()
638
upgrade.Convert('.', format)
639
tree = workingtree.WorkingTree.open('.')
640
revision_tree = tree.branch.repository.revision_tree('dull')
641
revision_tree.lock_read()
643
revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
645
revision_tree.unlock()
646
tree.commit("Another dull commit", rev_id='dull2')
647
revision_tree = tree.branch.repository.revision_tree('dull2')
648
revision_tree.lock_read()
649
self.addCleanup(revision_tree.unlock)
650
self.assertEqual('dull', revision_tree.inventory.root.revision)
652
def test_supports_external_lookups(self):
653
format = bzrdir.BzrDirMetaFormat1()
654
format.repository_format = knitrepo.RepositoryFormatKnit3()
655
repo = self.make_repository('.', format=format)
656
self.assertFalse(repo._format.supports_external_lookups)
659
class TestWithBrokenRepo(TestCaseWithTransport):
660
"""These tests seem to be more appropriate as interface tests?"""
662
def make_broken_repository(self):
663
# XXX: This function is borrowed from Aaron's "Reconcile can fix bad
664
# parent references" branch which is due to land in bzr.dev soon. Once
665
# it does, this duplication should be removed.
666
repo = self.make_repository('broken-repo')
670
cleanups.append(repo.unlock)
671
repo.start_write_group()
672
cleanups.append(repo.commit_write_group)
673
# make rev1a: A well-formed revision, containing 'file1'
674
inv = inventory.Inventory(revision_id='rev1a')
675
inv.root.revision = 'rev1a'
676
self.add_file(repo, inv, 'file1', 'rev1a', [])
677
repo.add_inventory('rev1a', inv, [])
678
revision = _mod_revision.Revision('rev1a',
679
committer='jrandom@example.com', timestamp=0,
680
inventory_sha1='', timezone=0, message='foo', parent_ids=[])
681
repo.add_revision('rev1a',revision, inv)
683
# make rev1b, which has no Revision, but has an Inventory, and
685
inv = inventory.Inventory(revision_id='rev1b')
686
inv.root.revision = 'rev1b'
687
self.add_file(repo, inv, 'file1', 'rev1b', [])
688
repo.add_inventory('rev1b', inv, [])
690
# make rev2, with file1 and file2
692
# file1 has 'rev1b' as an ancestor, even though this is not
693
# mentioned by 'rev1a', making it an unreferenced ancestor
694
inv = inventory.Inventory()
695
self.add_file(repo, inv, 'file1', 'rev2', ['rev1a', 'rev1b'])
696
self.add_file(repo, inv, 'file2', 'rev2', [])
697
self.add_revision(repo, 'rev2', inv, ['rev1a'])
699
# make ghost revision rev1c
700
inv = inventory.Inventory()
701
self.add_file(repo, inv, 'file2', 'rev1c', [])
703
# make rev3 with file2
704
# file2 refers to 'rev1c', which is a ghost in this repository, so
705
# file2 cannot have rev1c as its ancestor.
706
inv = inventory.Inventory()
707
self.add_file(repo, inv, 'file2', 'rev3', ['rev1c'])
708
self.add_revision(repo, 'rev3', inv, ['rev1c'])
711
for cleanup in reversed(cleanups):
714
def add_revision(self, repo, revision_id, inv, parent_ids):
715
inv.revision_id = revision_id
716
inv.root.revision = revision_id
717
repo.add_inventory(revision_id, inv, parent_ids)
718
revision = _mod_revision.Revision(revision_id,
719
committer='jrandom@example.com', timestamp=0, inventory_sha1='',
720
timezone=0, message='foo', parent_ids=parent_ids)
721
repo.add_revision(revision_id,revision, inv)
723
def add_file(self, repo, inv, filename, revision, parents):
724
file_id = filename + '-id'
725
entry = inventory.InventoryFile(file_id, filename, 'TREE_ROOT')
726
entry.revision = revision
729
text_key = (file_id, revision)
730
parent_keys = [(file_id, parent) for parent in parents]
731
repo.texts.add_lines(text_key, parent_keys, ['line\n'])
733
def test_insert_from_broken_repo(self):
734
"""Inserting a data stream from a broken repository won't silently
735
corrupt the target repository.
737
broken_repo = self.make_broken_repository()
738
empty_repo = self.make_repository('empty-repo')
739
self.assertRaises(errors.RevisionNotPresent, empty_repo.fetch, broken_repo)
742
class TestRepositoryPackCollection(TestCaseWithTransport):
744
def get_format(self):
745
return bzrdir.format_registry.make_bzrdir('pack-0.92')
747
def test__max_pack_count(self):
748
"""The maximum pack count is a function of the number of revisions."""
749
format = self.get_format()
750
repo = self.make_repository('.', format=format)
751
packs = repo._pack_collection
752
# no revisions - one pack, so that we can have a revision free repo
753
# without it blowing up
754
self.assertEqual(1, packs._max_pack_count(0))
755
# after that the sum of the digits, - check the first 1-9
756
self.assertEqual(1, packs._max_pack_count(1))
757
self.assertEqual(2, packs._max_pack_count(2))
758
self.assertEqual(3, packs._max_pack_count(3))
759
self.assertEqual(4, packs._max_pack_count(4))
760
self.assertEqual(5, packs._max_pack_count(5))
761
self.assertEqual(6, packs._max_pack_count(6))
762
self.assertEqual(7, packs._max_pack_count(7))
763
self.assertEqual(8, packs._max_pack_count(8))
764
self.assertEqual(9, packs._max_pack_count(9))
765
# check the boundary cases with two digits for the next decade
766
self.assertEqual(1, packs._max_pack_count(10))
767
self.assertEqual(2, packs._max_pack_count(11))
768
self.assertEqual(10, packs._max_pack_count(19))
769
self.assertEqual(2, packs._max_pack_count(20))
770
self.assertEqual(3, packs._max_pack_count(21))
771
# check some arbitrary big numbers
772
self.assertEqual(25, packs._max_pack_count(112894))
774
def test_pack_distribution_zero(self):
775
format = self.get_format()
776
repo = self.make_repository('.', format=format)
777
packs = repo._pack_collection
778
self.assertEqual([0], packs.pack_distribution(0))
780
def test_ensure_loaded_unlocked(self):
781
format = self.get_format()
782
repo = self.make_repository('.', format=format)
783
self.assertRaises(errors.ObjectNotLocked,
784
repo._pack_collection.ensure_loaded)
786
def test_pack_distribution_one_to_nine(self):
787
format = self.get_format()
788
repo = self.make_repository('.', format=format)
789
packs = repo._pack_collection
790
self.assertEqual([1],
791
packs.pack_distribution(1))
792
self.assertEqual([1, 1],
793
packs.pack_distribution(2))
794
self.assertEqual([1, 1, 1],
795
packs.pack_distribution(3))
796
self.assertEqual([1, 1, 1, 1],
797
packs.pack_distribution(4))
798
self.assertEqual([1, 1, 1, 1, 1],
799
packs.pack_distribution(5))
800
self.assertEqual([1, 1, 1, 1, 1, 1],
801
packs.pack_distribution(6))
802
self.assertEqual([1, 1, 1, 1, 1, 1, 1],
803
packs.pack_distribution(7))
804
self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1],
805
packs.pack_distribution(8))
806
self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1, 1],
807
packs.pack_distribution(9))
809
def test_pack_distribution_stable_at_boundaries(self):
810
"""When there are multi-rev packs the counts are stable."""
811
format = self.get_format()
812
repo = self.make_repository('.', format=format)
813
packs = repo._pack_collection
815
self.assertEqual([10], packs.pack_distribution(10))
816
self.assertEqual([10, 1], packs.pack_distribution(11))
817
self.assertEqual([10, 10], packs.pack_distribution(20))
818
self.assertEqual([10, 10, 1], packs.pack_distribution(21))
820
self.assertEqual([100], packs.pack_distribution(100))
821
self.assertEqual([100, 1], packs.pack_distribution(101))
822
self.assertEqual([100, 10, 1], packs.pack_distribution(111))
823
self.assertEqual([100, 100], packs.pack_distribution(200))
824
self.assertEqual([100, 100, 1], packs.pack_distribution(201))
825
self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
827
def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
828
format = self.get_format()
829
repo = self.make_repository('.', format=format)
830
packs = repo._pack_collection
831
existing_packs = [(2000, "big"), (9, "medium")]
832
# rev count - 2009 -> 2x1000 + 9x1
833
pack_operations = packs.plan_autopack_combinations(
834
existing_packs, [1000, 1000, 1, 1, 1, 1, 1, 1, 1, 1, 1])
835
self.assertEqual([], pack_operations)
837
def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
838
format = self.get_format()
839
repo = self.make_repository('.', format=format)
840
packs = repo._pack_collection
841
existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
842
# rev count - 2010 -> 2x1000 + 1x10
843
pack_operations = packs.plan_autopack_combinations(
844
existing_packs, [1000, 1000, 10])
845
self.assertEqual([], pack_operations)
847
def test_plan_pack_operations_2010_combines_smallest_two(self):
848
format = self.get_format()
849
repo = self.make_repository('.', format=format)
850
packs = repo._pack_collection
851
existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
853
# rev count - 2010 -> 2x1000 + 1x10 (3)
854
pack_operations = packs.plan_autopack_combinations(
855
existing_packs, [1000, 1000, 10])
856
self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
858
def test_all_packs_none(self):
859
format = self.get_format()
860
tree = self.make_branch_and_tree('.', format=format)
862
self.addCleanup(tree.unlock)
863
packs = tree.branch.repository._pack_collection
864
packs.ensure_loaded()
865
self.assertEqual([], packs.all_packs())
867
def test_all_packs_one(self):
868
format = self.get_format()
869
tree = self.make_branch_and_tree('.', format=format)
872
self.addCleanup(tree.unlock)
873
packs = tree.branch.repository._pack_collection
874
packs.ensure_loaded()
876
packs.get_pack_by_name(packs.names()[0])],
879
def test_all_packs_two(self):
880
format = self.get_format()
881
tree = self.make_branch_and_tree('.', format=format)
883
tree.commit('continue')
885
self.addCleanup(tree.unlock)
886
packs = tree.branch.repository._pack_collection
887
packs.ensure_loaded()
889
packs.get_pack_by_name(packs.names()[0]),
890
packs.get_pack_by_name(packs.names()[1]),
891
], packs.all_packs())
893
def test_get_pack_by_name(self):
894
format = self.get_format()
895
tree = self.make_branch_and_tree('.', format=format)
898
self.addCleanup(tree.unlock)
899
packs = tree.branch.repository._pack_collection
900
packs.ensure_loaded()
901
name = packs.names()[0]
902
pack_1 = packs.get_pack_by_name(name)
903
# the pack should be correctly initialised
904
sizes = packs._names[name]
905
rev_index = GraphIndex(packs._index_transport, name + '.rix', sizes[0])
906
inv_index = GraphIndex(packs._index_transport, name + '.iix', sizes[1])
907
txt_index = GraphIndex(packs._index_transport, name + '.tix', sizes[2])
908
sig_index = GraphIndex(packs._index_transport, name + '.six', sizes[3])
909
self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
910
name, rev_index, inv_index, txt_index, sig_index), pack_1)
911
# and the same instance should be returned on successive calls.
912
self.assertTrue(pack_1 is packs.get_pack_by_name(name))
915
class TestPack(TestCaseWithTransport):
916
"""Tests for the Pack object."""
918
def assertCurrentlyEqual(self, left, right):
919
self.assertTrue(left == right)
920
self.assertTrue(right == left)
921
self.assertFalse(left != right)
922
self.assertFalse(right != left)
924
def assertCurrentlyNotEqual(self, left, right):
925
self.assertFalse(left == right)
926
self.assertFalse(right == left)
927
self.assertTrue(left != right)
928
self.assertTrue(right != left)
930
def test___eq____ne__(self):
931
left = pack_repo.ExistingPack('', '', '', '', '', '')
932
right = pack_repo.ExistingPack('', '', '', '', '', '')
933
self.assertCurrentlyEqual(left, right)
934
# change all attributes and ensure equality changes as we do.
935
left.revision_index = 'a'
936
self.assertCurrentlyNotEqual(left, right)
937
right.revision_index = 'a'
938
self.assertCurrentlyEqual(left, right)
939
left.inventory_index = 'a'
940
self.assertCurrentlyNotEqual(left, right)
941
right.inventory_index = 'a'
942
self.assertCurrentlyEqual(left, right)
943
left.text_index = 'a'
944
self.assertCurrentlyNotEqual(left, right)
945
right.text_index = 'a'
946
self.assertCurrentlyEqual(left, right)
947
left.signature_index = 'a'
948
self.assertCurrentlyNotEqual(left, right)
949
right.signature_index = 'a'
950
self.assertCurrentlyEqual(left, right)
952
self.assertCurrentlyNotEqual(left, right)
954
self.assertCurrentlyEqual(left, right)
956
self.assertCurrentlyNotEqual(left, right)
957
right.transport = 'a'
958
self.assertCurrentlyEqual(left, right)
960
def test_file_name(self):
961
pack = pack_repo.ExistingPack('', 'a_name', '', '', '', '')
962
self.assertEqual('a_name.pack', pack.file_name())
965
class TestNewPack(TestCaseWithTransport):
966
"""Tests for pack_repo.NewPack."""
968
def test_new_instance_attributes(self):
969
upload_transport = self.get_transport('upload')
970
pack_transport = self.get_transport('pack')
971
index_transport = self.get_transport('index')
972
upload_transport.mkdir('.')
973
pack = pack_repo.NewPack(upload_transport, index_transport,
975
self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
976
self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
977
self.assertIsInstance(pack._hash, type(md5.new()))
978
self.assertTrue(pack.upload_transport is upload_transport)
979
self.assertTrue(pack.index_transport is index_transport)
980
self.assertTrue(pack.pack_transport is pack_transport)
981
self.assertEqual(None, pack.index_sizes)
982
self.assertEqual(20, len(pack.random_name))
983
self.assertIsInstance(pack.random_name, str)
984
self.assertIsInstance(pack.start_time, float)
987
class TestPacker(TestCaseWithTransport):
988
"""Tests for the packs repository Packer class."""
990
# To date, this class has been factored out and nothing new added to it;
991
# thus there are not yet any tests.
994
class TestInterDifferingSerializer(TestCaseWithTransport):
996
def test_progress_bar(self):
997
tree = self.make_branch_and_tree('tree')
998
tree.commit('rev1', rev_id='rev-1')
999
tree.commit('rev2', rev_id='rev-2')
1000
tree.commit('rev3', rev_id='rev-3')
1001
repo = self.make_repository('repo')
1002
inter_repo = repository.InterDifferingSerializer(
1003
tree.branch.repository, repo)
1004
pb = progress.InstrumentedProgress(to_file=StringIO())
1005
pb.never_throttle = True
1006
inter_repo.fetch('rev-1', pb)
1007
self.assertEqual('Transferring revisions', pb.last_msg)
1008
self.assertEqual(1, pb.last_cnt)
1009
self.assertEqual(1, pb.last_total)
1010
inter_repo.fetch('rev-3', pb)
1011
self.assertEqual(2, pb.last_cnt)
1012
self.assertEqual(2, pb.last_total)