1
# Copyright (C) 2006, 2007 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/repository_implementations/*.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,
44
from bzrlib.transport import get_transport
45
from bzrlib.transport.memory import MemoryServer
46
from bzrlib.util import bencode
53
revision as _mod_revision,
58
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
61
class TestDefaultFormat(TestCase):
63
def test_get_set_default_format(self):
64
old_default = bzrdir.format_registry.get('default')
65
private_default = old_default().repository_format.__class__
66
old_format = repository.RepositoryFormat.get_default_format()
67
self.assertTrue(isinstance(old_format, private_default))
68
def make_sample_bzrdir():
69
my_bzrdir = bzrdir.BzrDirMetaFormat1()
70
my_bzrdir.repository_format = SampleRepositoryFormat()
72
bzrdir.format_registry.remove('default')
73
bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
74
bzrdir.format_registry.set_default('sample')
75
# creating a repository should now create an instrumented dir.
77
# the default branch format is used by the meta dir format
78
# which is not the default bzrdir format at this point
79
dir = bzrdir.BzrDirMetaFormat1().initialize('memory:///')
80
result = dir.create_repository()
81
self.assertEqual(result, 'A bzr repository dir')
83
bzrdir.format_registry.remove('default')
84
bzrdir.format_registry.remove('sample')
85
bzrdir.format_registry.register('default', old_default, '')
86
self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
90
class SampleRepositoryFormat(repository.RepositoryFormat):
93
this format is initializable, unsupported to aid in testing the
94
open and open(unsupported=True) routines.
97
def get_format_string(self):
98
"""See RepositoryFormat.get_format_string()."""
99
return "Sample .bzr repository format."
101
def initialize(self, a_bzrdir, shared=False):
102
"""Initialize a repository in a BzrDir"""
103
t = a_bzrdir.get_repository_transport(self)
104
t.put_bytes('format', self.get_format_string())
105
return 'A bzr repository dir'
107
def is_supported(self):
110
def open(self, a_bzrdir, _found=False):
111
return "opened repository."
114
class TestRepositoryFormat(TestCaseWithTransport):
115
"""Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
117
def test_find_format(self):
118
# is the right format object found for a repository?
119
# create a branch with a few known format objects.
120
# this is not quite the same as
121
self.build_tree(["foo/", "bar/"])
122
def check_format(format, url):
123
dir = format._matchingbzrdir.initialize(url)
124
format.initialize(dir)
125
t = get_transport(url)
126
found_format = repository.RepositoryFormat.find_format(dir)
127
self.failUnless(isinstance(found_format, format.__class__))
128
check_format(weaverepo.RepositoryFormat7(), "bar")
130
def test_find_format_no_repository(self):
131
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
132
self.assertRaises(errors.NoRepositoryPresent,
133
repository.RepositoryFormat.find_format,
136
def test_find_format_unknown_format(self):
137
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
138
SampleRepositoryFormat().initialize(dir)
139
self.assertRaises(UnknownFormatError,
140
repository.RepositoryFormat.find_format,
143
def test_register_unregister_format(self):
144
format = SampleRepositoryFormat()
146
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
148
format.initialize(dir)
149
# register a format for it.
150
repository.RepositoryFormat.register_format(format)
151
# which repository.Open will refuse (not supported)
152
self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
153
# but open(unsupported) will work
154
self.assertEqual(format.open(dir), "opened repository.")
155
# unregister the format
156
repository.RepositoryFormat.unregister_format(format)
159
class TestFormat6(TestCaseWithTransport):
161
def test_no_ancestry_weave(self):
162
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
163
repo = weaverepo.RepositoryFormat6().initialize(control)
164
# We no longer need to create the ancestry.weave file
165
# since it is *never* used.
166
self.assertRaises(NoSuchFile,
167
control.transport.get,
170
def test_exposed_versioned_files_are_marked_dirty(self):
171
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
172
repo = weaverepo.RepositoryFormat6().initialize(control)
174
inv = repo.get_inventory_weave()
176
self.assertRaises(errors.OutSideTransaction,
177
inv.add_lines, 'foo', [], [])
180
class TestFormat7(TestCaseWithTransport):
182
def test_disk_layout(self):
183
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
184
repo = weaverepo.RepositoryFormat7().initialize(control)
185
# in case of side effects of locking.
189
# format 'Bazaar-NG Repository format 7'
191
# inventory.weave == empty_weave
192
# empty revision-store directory
193
# empty weaves directory
194
t = control.get_repository_transport(None)
195
self.assertEqualDiff('Bazaar-NG Repository format 7',
196
t.get('format').read())
197
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
198
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
199
self.assertEqualDiff('# bzr weave file v5\n'
202
t.get('inventory.weave').read())
204
def test_shared_disk_layout(self):
205
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
206
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
208
# format 'Bazaar-NG Repository format 7'
209
# inventory.weave == empty_weave
210
# empty revision-store directory
211
# empty weaves directory
212
# a 'shared-storage' marker file.
213
# lock is not present when unlocked
214
t = control.get_repository_transport(None)
215
self.assertEqualDiff('Bazaar-NG Repository format 7',
216
t.get('format').read())
217
self.assertEqualDiff('', t.get('shared-storage').read())
218
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
219
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
220
self.assertEqualDiff('# bzr weave file v5\n'
223
t.get('inventory.weave').read())
224
self.assertFalse(t.has('branch-lock'))
226
def test_creates_lockdir(self):
227
"""Make sure it appears to be controlled by a LockDir existence"""
228
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
229
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
230
t = control.get_repository_transport(None)
231
# TODO: Should check there is a 'lock' toplevel directory,
232
# regardless of contents
233
self.assertFalse(t.has('lock/held/info'))
236
self.assertTrue(t.has('lock/held/info'))
238
# unlock so we don't get a warning about failing to do so
241
def test_uses_lockdir(self):
242
"""repo format 7 actually locks on lockdir"""
243
base_url = self.get_url()
244
control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
245
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
246
t = control.get_repository_transport(None)
250
# make sure the same lock is created by opening it
251
repo = repository.Repository.open(base_url)
253
self.assertTrue(t.has('lock/held/info'))
255
self.assertFalse(t.has('lock/held/info'))
257
def test_shared_no_tree_disk_layout(self):
258
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
259
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
260
repo.set_make_working_trees(False)
262
# 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
t = control.get_repository_transport(None)
269
self.assertEqualDiff('Bazaar-NG Repository format 7',
270
t.get('format').read())
271
## self.assertEqualDiff('', t.get('lock').read())
272
self.assertEqualDiff('', t.get('shared-storage').read())
273
self.assertEqualDiff('', t.get('no-working-trees').read())
274
repo.set_make_working_trees(True)
275
self.assertFalse(t.has('no-working-trees'))
276
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
277
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
278
self.assertEqualDiff('# bzr weave file v5\n'
281
t.get('inventory.weave').read())
283
def test_exposed_versioned_files_are_marked_dirty(self):
284
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
285
repo = weaverepo.RepositoryFormat7().initialize(control)
287
inv = repo.get_inventory_weave()
289
self.assertRaises(errors.OutSideTransaction,
290
inv.add_lines, 'foo', [], [])
293
class TestFormatKnit1(TestCaseWithTransport):
295
def test_disk_layout(self):
296
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
297
repo = knitrepo.RepositoryFormatKnit1().initialize(control)
298
# in case of side effects of locking.
302
# format 'Bazaar-NG Knit Repository Format 1'
303
# lock: is a directory
304
# inventory.weave == empty_weave
305
# empty revision-store directory
306
# empty weaves directory
307
t = control.get_repository_transport(None)
308
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
309
t.get('format').read())
310
# XXX: no locks left when unlocked at the moment
311
# self.assertEqualDiff('', t.get('lock').read())
312
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
315
def assertHasKnit(self, t, knit_name):
316
"""Assert that knit_name exists on t."""
317
self.assertEqualDiff('# bzr knit index 8\n',
318
t.get(knit_name + '.kndx').read())
320
self.assertTrue(t.has(knit_name + '.knit'))
322
def check_knits(self, t):
323
"""check knit content for a repository."""
324
self.assertHasKnit(t, 'inventory')
325
self.assertHasKnit(t, 'revisions')
326
self.assertHasKnit(t, 'signatures')
328
def test_shared_disk_layout(self):
329
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
330
repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
332
# format 'Bazaar-NG Knit Repository Format 1'
333
# lock: is a directory
334
# inventory.weave == empty_weave
335
# empty revision-store directory
336
# empty weaves directory
337
# a 'shared-storage' marker file.
338
t = control.get_repository_transport(None)
339
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
340
t.get('format').read())
341
# XXX: no locks left when unlocked at the moment
342
# self.assertEqualDiff('', t.get('lock').read())
343
self.assertEqualDiff('', t.get('shared-storage').read())
344
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
347
def test_shared_no_tree_disk_layout(self):
348
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
349
repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
350
repo.set_make_working_trees(False)
352
# format 'Bazaar-NG Knit Repository Format 1'
354
# inventory.weave == empty_weave
355
# empty revision-store directory
356
# empty weaves directory
357
# a 'shared-storage' marker file.
358
t = control.get_repository_transport(None)
359
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
360
t.get('format').read())
361
# XXX: no locks left when unlocked at the moment
362
# self.assertEqualDiff('', t.get('lock').read())
363
self.assertEqualDiff('', t.get('shared-storage').read())
364
self.assertEqualDiff('', t.get('no-working-trees').read())
365
repo.set_make_working_trees(True)
366
self.assertFalse(t.has('no-working-trees'))
367
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
370
def test_exposed_versioned_files_are_marked_dirty(self):
371
format = bzrdir.BzrDirMetaFormat1()
372
format.repository_format = knitrepo.RepositoryFormatKnit1()
373
repo = self.make_repository('.', format=format)
375
inv = repo.get_inventory_weave()
377
self.assertRaises(errors.OutSideTransaction,
378
inv.add_lines, 'foo', [], [])
380
def test_deserialise_sets_root_revision(self):
381
"""We must have a inventory.root.revision
383
Old versions of the XML5 serializer did not set the revision_id for
384
the whole inventory. So we grab the one from the expected text. Which
385
is valid when the api is not being abused.
387
repo = self.make_repository('.',
388
format=bzrdir.format_registry.get('knit')())
389
inv_xml = '<inventory format="5">\n</inventory>\n'
390
inv = repo.deserialise_inventory('test-rev-id', inv_xml)
391
self.assertEqual('test-rev-id', inv.root.revision)
393
def test_deserialise_uses_global_revision_id(self):
394
"""If it is set, then we re-use the global revision id"""
395
repo = self.make_repository('.',
396
format=bzrdir.format_registry.get('knit')())
397
inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
399
# Arguably, the deserialise_inventory should detect a mismatch, and
400
# raise an error, rather than silently using one revision_id over the
402
self.assertRaises(AssertionError, repo.deserialise_inventory,
403
'test-rev-id', inv_xml)
404
inv = repo.deserialise_inventory('other-rev-id', inv_xml)
405
self.assertEqual('other-rev-id', inv.root.revision)
408
class KnitRepositoryStreamTests(test_knit.KnitTests):
409
"""Tests for knitrepo._get_stream_as_bytes."""
411
def test_get_stream_as_bytes(self):
413
k1 = self.make_test_knit()
414
k1.add_lines('text-a', [], test_knit.split_lines(test_knit.TEXT_1))
416
# Serialise it, check the output.
417
bytes = knitrepo._get_stream_as_bytes(k1, ['text-a'])
418
data = bencode.bdecode(bytes)
419
format, record = data
420
self.assertEqual('knit-plain', format)
421
self.assertEqual(['text-a', ['fulltext'], []], record[:3])
422
self.assertRecordContentEqual(k1, 'text-a', record[3])
424
def test_get_stream_as_bytes_all(self):
425
"""Get a serialised data stream for all the records in a knit.
427
Much like test_get_stream_all, except for get_stream_as_bytes.
429
k1 = self.make_test_knit()
430
# Insert the same data as BasicKnitTests.test_knit_join, as they seem
431
# to cover a range of cases (no parents, one parent, multiple parents).
433
('text-a', [], test_knit.TEXT_1),
434
('text-b', ['text-a'], test_knit.TEXT_1),
435
('text-c', [], test_knit.TEXT_1),
436
('text-d', ['text-c'], test_knit.TEXT_1),
437
('text-m', ['text-b', 'text-d'], test_knit.TEXT_1),
439
# This test is actually a bit strict as the order in which they're
440
# returned is not defined. This matches the current (deterministic)
442
expected_data_list = [
443
# version, options, parents
444
('text-a', ['fulltext'], []),
445
('text-b', ['line-delta'], ['text-a']),
446
('text-m', ['line-delta'], ['text-b', 'text-d']),
447
('text-c', ['fulltext'], []),
448
('text-d', ['line-delta'], ['text-c']),
450
for version_id, parents, lines in test_data:
451
k1.add_lines(version_id, parents, test_knit.split_lines(lines))
453
bytes = knitrepo._get_stream_as_bytes(
454
k1, ['text-a', 'text-b', 'text-m', 'text-c', 'text-d', ])
456
data = bencode.bdecode(bytes)
458
self.assertEqual('knit-plain', format)
460
for expected, actual in zip(expected_data_list, data):
461
expected_version = expected[0]
462
expected_options = expected[1]
463
expected_parents = expected[2]
464
version, options, parents, bytes = actual
465
self.assertEqual(expected_version, version)
466
self.assertEqual(expected_options, options)
467
self.assertEqual(expected_parents, parents)
468
self.assertRecordContentEqual(k1, version, bytes)
471
class DummyRepository(object):
472
"""A dummy repository for testing."""
476
def supports_rich_root(self):
480
class InterDummy(repository.InterRepository):
481
"""An inter-repository optimised code path for DummyRepository.
483
This is for use during testing where we use DummyRepository as repositories
484
so that none of the default regsitered inter-repository classes will
489
def is_compatible(repo_source, repo_target):
490
"""InterDummy is compatible with DummyRepository."""
491
return (isinstance(repo_source, DummyRepository) and
492
isinstance(repo_target, DummyRepository))
495
class TestInterRepository(TestCaseWithTransport):
497
def test_get_default_inter_repository(self):
498
# test that the InterRepository.get(repo_a, repo_b) probes
499
# for a inter_repo class where is_compatible(repo_a, repo_b) returns
500
# true and returns a default inter_repo otherwise.
501
# This also tests that the default registered optimised interrepository
502
# classes do not barf inappropriately when a surprising repository type
504
dummy_a = DummyRepository()
505
dummy_b = DummyRepository()
506
self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
508
def assertGetsDefaultInterRepository(self, repo_a, repo_b):
509
"""Asserts that InterRepository.get(repo_a, repo_b) -> the default.
511
The effective default is now InterSameDataRepository because there is
512
no actual sane default in the presence of incompatible data models.
514
inter_repo = repository.InterRepository.get(repo_a, repo_b)
515
self.assertEqual(repository.InterSameDataRepository,
516
inter_repo.__class__)
517
self.assertEqual(repo_a, inter_repo.source)
518
self.assertEqual(repo_b, inter_repo.target)
520
def test_register_inter_repository_class(self):
521
# test that a optimised code path provider - a
522
# InterRepository subclass can be registered and unregistered
523
# and that it is correctly selected when given a repository
524
# pair that it returns true on for the is_compatible static method
526
dummy_a = DummyRepository()
527
dummy_b = DummyRepository()
528
repo = self.make_repository('.')
529
# hack dummies to look like repo somewhat.
530
dummy_a._serializer = repo._serializer
531
dummy_b._serializer = repo._serializer
532
repository.InterRepository.register_optimiser(InterDummy)
534
# we should get the default for something InterDummy returns False
536
self.assertFalse(InterDummy.is_compatible(dummy_a, repo))
537
self.assertGetsDefaultInterRepository(dummy_a, repo)
538
# and we should get an InterDummy for a pair it 'likes'
539
self.assertTrue(InterDummy.is_compatible(dummy_a, dummy_b))
540
inter_repo = repository.InterRepository.get(dummy_a, dummy_b)
541
self.assertEqual(InterDummy, inter_repo.__class__)
542
self.assertEqual(dummy_a, inter_repo.source)
543
self.assertEqual(dummy_b, inter_repo.target)
545
repository.InterRepository.unregister_optimiser(InterDummy)
546
# now we should get the default InterRepository object again.
547
self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
550
class TestInterWeaveRepo(TestCaseWithTransport):
552
def test_is_compatible_and_registered(self):
553
# InterWeaveRepo is compatible when either side
554
# is a format 5/6/7 branch
555
from bzrlib.repofmt import knitrepo, weaverepo
556
formats = [weaverepo.RepositoryFormat5(),
557
weaverepo.RepositoryFormat6(),
558
weaverepo.RepositoryFormat7()]
559
incompatible_formats = [weaverepo.RepositoryFormat4(),
560
knitrepo.RepositoryFormatKnit1(),
562
repo_a = self.make_repository('a')
563
repo_b = self.make_repository('b')
564
is_compatible = repository.InterWeaveRepo.is_compatible
565
for source in incompatible_formats:
566
# force incompatible left then right
567
repo_a._format = source
568
repo_b._format = formats[0]
569
self.assertFalse(is_compatible(repo_a, repo_b))
570
self.assertFalse(is_compatible(repo_b, repo_a))
571
for source in formats:
572
repo_a._format = source
573
for target in formats:
574
repo_b._format = target
575
self.assertTrue(is_compatible(repo_a, repo_b))
576
self.assertEqual(repository.InterWeaveRepo,
577
repository.InterRepository.get(repo_a,
581
class TestInterRemoteToOther(TestCaseWithTransport):
583
def make_remote_repository(self, path, backing_format=None):
584
"""Make a RemoteRepository object backed by a real repository that will
585
be created at the given path."""
586
self.make_repository(path, format=backing_format)
587
smart_server = server.SmartTCPServer_for_testing()
589
remote_transport = get_transport(smart_server.get_url()).clone(path)
590
self.addCleanup(smart_server.tearDown)
591
remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
592
remote_repo = remote_bzrdir.open_repository()
595
def test_is_compatible_same_format(self):
596
"""InterRemoteToOther is compatible with a remote repository and a
597
second repository that have the same format."""
598
local_repo = self.make_repository('local')
599
remote_repo = self.make_remote_repository('remote')
600
is_compatible = repository.InterRemoteToOther.is_compatible
602
is_compatible(remote_repo, local_repo),
603
"InterRemoteToOther(%r, %r) is false" % (remote_repo, local_repo))
605
def test_is_incompatible_different_format(self):
606
local_repo = self.make_repository('local', 'dirstate')
607
remote_repo = self.make_remote_repository('a', 'dirstate-with-subtree')
608
is_compatible = repository.InterRemoteToOther.is_compatible
610
is_compatible(remote_repo, local_repo),
611
"InterRemoteToOther(%r, %r) is true" % (local_repo, remote_repo))
613
def test_is_incompatible_different_format_both_remote(self):
614
remote_repo_a = self.make_remote_repository(
615
'a', 'dirstate-with-subtree')
616
remote_repo_b = self.make_remote_repository('b', 'dirstate')
617
is_compatible = repository.InterRemoteToOther.is_compatible
619
is_compatible(remote_repo_a, remote_repo_b),
620
"InterRemoteToOther(%r, %r) is true"
621
% (remote_repo_a, remote_repo_b))
624
class TestRepositoryConverter(TestCaseWithTransport):
626
def test_convert_empty(self):
627
t = get_transport(self.get_url('.'))
628
t.mkdir('repository')
629
repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
630
repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
631
target_format = knitrepo.RepositoryFormatKnit1()
632
converter = repository.CopyConverter(target_format)
633
pb = bzrlib.ui.ui_factory.nested_progress_bar()
635
converter.convert(repo, pb)
638
repo = repo_dir.open_repository()
639
self.assertTrue(isinstance(target_format, repo._format.__class__))
642
class TestMisc(TestCase):
644
def test_unescape_xml(self):
645
"""We get some kind of error when malformed entities are passed"""
646
self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;')
649
class TestRepositoryFormatKnit3(TestCaseWithTransport):
651
def test_convert(self):
652
"""Ensure the upgrade adds weaves for roots"""
653
format = bzrdir.BzrDirMetaFormat1()
654
format.repository_format = knitrepo.RepositoryFormatKnit1()
655
tree = self.make_branch_and_tree('.', format)
656
tree.commit("Dull commit", rev_id="dull")
657
revision_tree = tree.branch.repository.revision_tree('dull')
658
self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
659
revision_tree.inventory.root.file_id)
660
format = bzrdir.BzrDirMetaFormat1()
661
format.repository_format = knitrepo.RepositoryFormatKnit3()
662
upgrade.Convert('.', format)
663
tree = workingtree.WorkingTree.open('.')
664
revision_tree = tree.branch.repository.revision_tree('dull')
665
revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
666
tree.commit("Another dull commit", rev_id='dull2')
667
revision_tree = tree.branch.repository.revision_tree('dull2')
668
self.assertEqual('dull', revision_tree.inventory.root.revision)
670
def test_exposed_versioned_files_are_marked_dirty(self):
671
format = bzrdir.BzrDirMetaFormat1()
672
format.repository_format = knitrepo.RepositoryFormatKnit3()
673
repo = self.make_repository('.', format=format)
675
inv = repo.get_inventory_weave()
677
self.assertRaises(errors.OutSideTransaction,
678
inv.add_lines, 'foo', [], [])
681
class TestWithBrokenRepo(TestCaseWithTransport):
682
"""These tests seem to be more appropriate as interface tests?"""
684
def make_broken_repository(self):
685
# XXX: This function is borrowed from Aaron's "Reconcile can fix bad
686
# parent references" branch which is due to land in bzr.dev soon. Once
687
# it does, this duplication should be removed.
688
repo = self.make_repository('broken-repo')
692
cleanups.append(repo.unlock)
693
repo.start_write_group()
694
cleanups.append(repo.commit_write_group)
695
# make rev1a: A well-formed revision, containing 'file1'
696
inv = inventory.Inventory(revision_id='rev1a')
697
inv.root.revision = 'rev1a'
698
self.add_file(repo, inv, 'file1', 'rev1a', [])
699
repo.add_inventory('rev1a', inv, [])
700
revision = _mod_revision.Revision('rev1a',
701
committer='jrandom@example.com', timestamp=0,
702
inventory_sha1='', timezone=0, message='foo', parent_ids=[])
703
repo.add_revision('rev1a',revision, inv)
705
# make rev1b, which has no Revision, but has an Inventory, and
707
inv = inventory.Inventory(revision_id='rev1b')
708
inv.root.revision = 'rev1b'
709
self.add_file(repo, inv, 'file1', 'rev1b', [])
710
repo.add_inventory('rev1b', inv, [])
712
# make rev2, with file1 and file2
714
# file1 has 'rev1b' as an ancestor, even though this is not
715
# mentioned by 'rev1a', making it an unreferenced ancestor
716
inv = inventory.Inventory()
717
self.add_file(repo, inv, 'file1', 'rev2', ['rev1a', 'rev1b'])
718
self.add_file(repo, inv, 'file2', 'rev2', [])
719
self.add_revision(repo, 'rev2', inv, ['rev1a'])
721
# make ghost revision rev1c
722
inv = inventory.Inventory()
723
self.add_file(repo, inv, 'file2', 'rev1c', [])
725
# make rev3 with file2
726
# file2 refers to 'rev1c', which is a ghost in this repository, so
727
# file2 cannot have rev1c as its ancestor.
728
inv = inventory.Inventory()
729
self.add_file(repo, inv, 'file2', 'rev3', ['rev1c'])
730
self.add_revision(repo, 'rev3', inv, ['rev1c'])
733
for cleanup in reversed(cleanups):
736
def add_revision(self, repo, revision_id, inv, parent_ids):
737
inv.revision_id = revision_id
738
inv.root.revision = revision_id
739
repo.add_inventory(revision_id, inv, parent_ids)
740
revision = _mod_revision.Revision(revision_id,
741
committer='jrandom@example.com', timestamp=0, inventory_sha1='',
742
timezone=0, message='foo', parent_ids=parent_ids)
743
repo.add_revision(revision_id,revision, inv)
745
def add_file(self, repo, inv, filename, revision, parents):
746
file_id = filename + '-id'
747
entry = inventory.InventoryFile(file_id, filename, 'TREE_ROOT')
748
entry.revision = revision
751
vf = repo.weave_store.get_weave_or_empty(file_id,
752
repo.get_transaction())
753
vf.add_lines(revision, parents, ['line\n'])
755
def test_insert_from_broken_repo(self):
756
"""Inserting a data stream from a broken repository won't silently
757
corrupt the target repository.
759
broken_repo = self.make_broken_repository()
760
empty_repo = self.make_repository('empty-repo')
761
search = graph.SearchResult(set(['rev1a', 'rev2', 'rev3']),
762
set(), 3, ['rev1a', 'rev2', 'rev3'])
763
stream = broken_repo.get_data_stream_for_search(search)
764
empty_repo.lock_write()
765
self.addCleanup(empty_repo.unlock)
766
empty_repo.start_write_group()
769
errors.KnitCorrupt, empty_repo.insert_data_stream, stream)
771
empty_repo.abort_write_group()
774
class TestKnitPackNoSubtrees(TestCaseWithTransport):
776
def get_format(self):
777
return bzrdir.format_registry.make_bzrdir('pack-0.92')
779
def test_disk_layout(self):
780
format = self.get_format()
781
repo = self.make_repository('.', format=format)
782
# in case of side effects of locking.
785
t = repo.bzrdir.get_repository_transport(None)
787
# XXX: no locks left when unlocked at the moment
788
# self.assertEqualDiff('', t.get('lock').read())
789
self.check_databases(t)
791
def check_format(self, t):
792
self.assertEqualDiff(
793
"Bazaar pack repository format 1 (needs bzr 0.92)\n",
794
t.get('format').read())
796
def assertHasKndx(self, t, knit_name):
797
"""Assert that knit_name exists on t."""
798
self.assertEqualDiff('# bzr knit index 8\n',
799
t.get(knit_name + '.kndx').read())
801
def assertHasNoKndx(self, t, knit_name):
802
"""Assert that knit_name has no index on t."""
803
self.assertFalse(t.has(knit_name + '.kndx'))
805
def assertHasNoKnit(self, t, knit_name):
806
"""Assert that knit_name exists on t."""
808
self.assertFalse(t.has(knit_name + '.knit'))
810
def check_databases(self, t):
811
"""check knit content for a repository."""
812
# check conversion worked
813
self.assertHasNoKndx(t, 'inventory')
814
self.assertHasNoKnit(t, 'inventory')
815
self.assertHasNoKndx(t, 'revisions')
816
self.assertHasNoKnit(t, 'revisions')
817
self.assertHasNoKndx(t, 'signatures')
818
self.assertHasNoKnit(t, 'signatures')
819
self.assertFalse(t.has('knits'))
820
# revision-indexes file-container directory
822
list(GraphIndex(t, 'pack-names', None).iter_all_entries()))
823
self.assertTrue(S_ISDIR(t.stat('packs').st_mode))
824
self.assertTrue(S_ISDIR(t.stat('upload').st_mode))
825
self.assertTrue(S_ISDIR(t.stat('indices').st_mode))
826
self.assertTrue(S_ISDIR(t.stat('obsolete_packs').st_mode))
828
def test_shared_disk_layout(self):
829
format = self.get_format()
830
repo = self.make_repository('.', shared=True, format=format)
832
t = repo.bzrdir.get_repository_transport(None)
834
# XXX: no locks left when unlocked at the moment
835
# self.assertEqualDiff('', t.get('lock').read())
836
# We should have a 'shared-storage' marker file.
837
self.assertEqualDiff('', t.get('shared-storage').read())
838
self.check_databases(t)
840
def test_shared_no_tree_disk_layout(self):
841
format = self.get_format()
842
repo = self.make_repository('.', shared=True, format=format)
843
repo.set_make_working_trees(False)
845
t = repo.bzrdir.get_repository_transport(None)
847
# XXX: no locks left when unlocked at the moment
848
# self.assertEqualDiff('', t.get('lock').read())
849
# We should have a 'shared-storage' marker file.
850
self.assertEqualDiff('', t.get('shared-storage').read())
851
# We should have a marker for the no-working-trees flag.
852
self.assertEqualDiff('', t.get('no-working-trees').read())
853
# The marker should go when we toggle the setting.
854
repo.set_make_working_trees(True)
855
self.assertFalse(t.has('no-working-trees'))
856
self.check_databases(t)
858
def test_adding_revision_creates_pack_indices(self):
859
format = self.get_format()
860
tree = self.make_branch_and_tree('.', format=format)
861
trans = tree.branch.repository.bzrdir.get_repository_transport(None)
863
list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
864
tree.commit('foobarbaz')
865
index = GraphIndex(trans, 'pack-names', None)
866
index_nodes = list(index.iter_all_entries())
867
self.assertEqual(1, len(index_nodes))
868
node = index_nodes[0]
870
# the pack sizes should be listed in the index
872
sizes = [int(digits) for digits in pack_value.split(' ')]
873
for size, suffix in zip(sizes, ['.rix', '.iix', '.tix', '.six']):
874
stat = trans.stat('indices/%s%s' % (name, suffix))
875
self.assertEqual(size, stat.st_size)
877
def test_pulling_nothing_leads_to_no_new_names(self):
878
format = self.get_format()
879
tree1 = self.make_branch_and_tree('1', format=format)
880
tree2 = self.make_branch_and_tree('2', format=format)
881
tree1.branch.repository.fetch(tree2.branch.repository)
882
trans = tree1.branch.repository.bzrdir.get_repository_transport(None)
884
list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
886
def test_commit_across_pack_shape_boundary_autopacks(self):
887
format = self.get_format()
888
tree = self.make_branch_and_tree('.', format=format)
889
trans = tree.branch.repository.bzrdir.get_repository_transport(None)
890
# This test could be a little cheaper by replacing the packs
891
# attribute on the repository to allow a different pack distribution
892
# and max packs policy - so we are checking the policy is honoured
893
# in the test. But for now 11 commits is not a big deal in a single
896
tree.commit('commit %s' % x)
897
# there should be 9 packs:
898
index = GraphIndex(trans, 'pack-names', None)
899
self.assertEqual(9, len(list(index.iter_all_entries())))
900
# insert some files in obsolete_packs which should be removed by pack.
901
trans.put_bytes('obsolete_packs/foo', '123')
902
trans.put_bytes('obsolete_packs/bar', '321')
903
# committing one more should coalesce to 1 of 10.
904
tree.commit('commit triggering pack')
905
index = GraphIndex(trans, 'pack-names', None)
906
self.assertEqual(1, len(list(index.iter_all_entries())))
907
# packing should not damage data
908
tree = tree.bzrdir.open_workingtree()
909
check_result = tree.branch.repository.check(
910
[tree.branch.last_revision()])
911
# We should have 50 (10x5) files in the obsolete_packs directory.
912
obsolete_files = list(trans.list_dir('obsolete_packs'))
913
self.assertFalse('foo' in obsolete_files)
914
self.assertFalse('bar' in obsolete_files)
915
self.assertEqual(50, len(obsolete_files))
916
# XXX: Todo check packs obsoleted correctly - old packs and indices
917
# in the obsolete_packs directory.
918
large_pack_name = list(index.iter_all_entries())[0][1][0]
919
# finally, committing again should not touch the large pack.
920
tree.commit('commit not triggering pack')
921
index = GraphIndex(trans, 'pack-names', None)
922
self.assertEqual(2, len(list(index.iter_all_entries())))
923
pack_names = [node[1][0] for node in index.iter_all_entries()]
924
self.assertTrue(large_pack_name in pack_names)
926
def test_pack_after_two_commits_packs_everything(self):
927
format = self.get_format()
928
tree = self.make_branch_and_tree('.', format=format)
929
trans = tree.branch.repository.bzrdir.get_repository_transport(None)
931
tree.commit('more work')
932
tree.branch.repository.pack()
933
# there should be 1 pack:
934
index = GraphIndex(trans, 'pack-names', None)
935
self.assertEqual(1, len(list(index.iter_all_entries())))
936
self.assertEqual(2, len(tree.branch.repository.all_revision_ids()))
938
def test_pack_layout(self):
939
format = self.get_format()
940
tree = self.make_branch_and_tree('.', format=format)
941
trans = tree.branch.repository.bzrdir.get_repository_transport(None)
942
tree.commit('start', rev_id='1')
943
tree.commit('more work', rev_id='2')
944
tree.branch.repository.pack()
946
self.addCleanup(tree.unlock)
947
pack = tree.branch.repository._pack_collection.get_pack_by_name(
948
tree.branch.repository._pack_collection.names()[0])
949
# revision access tends to be tip->ancestor, so ordering that way on
950
# disk is a good idea.
951
for _1, key, val, refs in pack.revision_index.iter_all_entries():
953
pos_1 = int(val[1:].split()[0])
955
pos_2 = int(val[1:].split()[0])
956
self.assertTrue(pos_2 < pos_1)
958
def test_pack_repositories_support_multiple_write_locks(self):
959
format = self.get_format()
960
self.make_repository('.', shared=True, format=format)
961
r1 = repository.Repository.open('.')
962
r2 = repository.Repository.open('.')
964
self.addCleanup(r1.unlock)
968
def _add_text(self, repo, fileid):
969
"""Add a text to the repository within a write group."""
970
vf =repo.weave_store.get_weave(fileid, repo.get_transaction())
971
vf.add_lines('samplerev+' + fileid, [], [])
973
def test_concurrent_writers_merge_new_packs(self):
974
format = self.get_format()
975
self.make_repository('.', shared=True, format=format)
976
r1 = repository.Repository.open('.')
977
r2 = repository.Repository.open('.')
980
# access enough data to load the names list
981
list(r1.all_revision_ids())
984
# access enough data to load the names list
985
list(r2.all_revision_ids())
986
r1.start_write_group()
988
r2.start_write_group()
990
self._add_text(r1, 'fileidr1')
991
self._add_text(r2, 'fileidr2')
993
r2.abort_write_group()
996
r1.abort_write_group()
998
# both r1 and r2 have open write groups with data in them
999
# created while the other's write group was open.
1000
# Commit both which requires a merge to the pack-names.
1002
r1.commit_write_group()
1004
r1.abort_write_group()
1005
r2.abort_write_group()
1007
r2.commit_write_group()
1008
# tell r1 to reload from disk
1009
r1._pack_collection.reset()
1010
# Now both repositories should know about both names
1011
r1._pack_collection.ensure_loaded()
1012
r2._pack_collection.ensure_loaded()
1013
self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1014
self.assertEqual(2, len(r1._pack_collection.names()))
1020
def test_concurrent_writer_second_preserves_dropping_a_pack(self):
1021
format = self.get_format()
1022
self.make_repository('.', shared=True, format=format)
1023
r1 = repository.Repository.open('.')
1024
r2 = repository.Repository.open('.')
1025
# add a pack to drop
1028
r1.start_write_group()
1030
self._add_text(r1, 'fileidr1')
1032
r1.abort_write_group()
1035
r1.commit_write_group()
1036
r1._pack_collection.ensure_loaded()
1037
name_to_drop = r1._pack_collection.all_packs()[0].name
1042
# access enough data to load the names list
1043
list(r1.all_revision_ids())
1046
# access enough data to load the names list
1047
list(r2.all_revision_ids())
1048
r1._pack_collection.ensure_loaded()
1050
r2.start_write_group()
1052
# in r1, drop the pack
1053
r1._pack_collection._remove_pack_from_memory(
1054
r1._pack_collection.get_pack_by_name(name_to_drop))
1056
self._add_text(r2, 'fileidr2')
1058
r2.abort_write_group()
1061
r1._pack_collection.reset()
1063
# r1 has a changed names list, and r2 an open write groups with
1065
# save r1, and then commit the r2 write group, which requires a
1066
# merge to the pack-names, which should not reinstate
1069
r1._pack_collection._save_pack_names()
1070
r1._pack_collection.reset()
1072
r2.abort_write_group()
1075
r2.commit_write_group()
1077
r2.abort_write_group()
1079
# Now both repositories should now about just one name.
1080
r1._pack_collection.ensure_loaded()
1081
r2._pack_collection.ensure_loaded()
1082
self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1083
self.assertEqual(1, len(r1._pack_collection.names()))
1084
self.assertFalse(name_to_drop in r1._pack_collection.names())
1090
def test_lock_write_does_not_physically_lock(self):
1091
repo = self.make_repository('.', format=self.get_format())
1093
self.addCleanup(repo.unlock)
1094
self.assertFalse(repo.get_physical_lock_status())
1096
def prepare_for_break_lock(self):
1097
# Setup the global ui factory state so that a break-lock method call
1098
# will find usable input in the input stream.
1099
old_factory = bzrlib.ui.ui_factory
1100
def restoreFactory():
1101
bzrlib.ui.ui_factory = old_factory
1102
self.addCleanup(restoreFactory)
1103
bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1104
bzrlib.ui.ui_factory.stdin = StringIO("y\n")
1106
def test_break_lock_breaks_physical_lock(self):
1107
repo = self.make_repository('.', format=self.get_format())
1108
repo._pack_collection.lock_names()
1109
repo2 = repository.Repository.open('.')
1110
self.assertTrue(repo.get_physical_lock_status())
1111
self.prepare_for_break_lock()
1113
self.assertFalse(repo.get_physical_lock_status())
1115
def test_broken_physical_locks_error_on__unlock_names_lock(self):
1116
repo = self.make_repository('.', format=self.get_format())
1117
repo._pack_collection.lock_names()
1118
self.assertTrue(repo.get_physical_lock_status())
1119
repo2 = repository.Repository.open('.')
1120
self.prepare_for_break_lock()
1122
self.assertRaises(errors.LockBroken, repo._pack_collection._unlock_names)
1124
def test_fetch_without_find_ghosts_ignores_ghosts(self):
1125
# we want two repositories at this point:
1126
# one with a revision that is a ghost in the other
1128
# 'ghost' is present in has_ghost, 'ghost' is absent in 'missing_ghost'.
1129
# 'references' is present in both repositories, and 'tip' is present
1130
# just in has_ghost.
1131
# has_ghost missing_ghost
1132
#------------------------------
1134
# 'references' 'references'
1136
# In this test we fetch 'tip' which should not fetch 'ghost'
1137
has_ghost = self.make_repository('has_ghost', format=self.get_format())
1138
missing_ghost = self.make_repository('missing_ghost',
1139
format=self.get_format())
1141
def add_commit(repo, revision_id, parent_ids):
1143
repo.start_write_group()
1144
inv = inventory.Inventory(revision_id=revision_id)
1145
inv.root.revision = revision_id
1146
root_id = inv.root.file_id
1147
sha1 = repo.add_inventory(revision_id, inv, [])
1148
vf = repo.weave_store.get_weave_or_empty(root_id,
1149
repo.get_transaction())
1150
vf.add_lines(revision_id, [], [])
1151
rev = bzrlib.revision.Revision(timestamp=0,
1153
committer="Foo Bar <foo@example.com>",
1155
inventory_sha1=sha1,
1156
revision_id=revision_id)
1157
rev.parent_ids = parent_ids
1158
repo.add_revision(revision_id, rev)
1159
repo.commit_write_group()
1161
add_commit(has_ghost, 'ghost', [])
1162
add_commit(has_ghost, 'references', ['ghost'])
1163
add_commit(missing_ghost, 'references', ['ghost'])
1164
add_commit(has_ghost, 'tip', ['references'])
1165
missing_ghost.fetch(has_ghost, 'tip')
1166
# missing ghost now has tip and not ghost.
1167
rev = missing_ghost.get_revision('tip')
1168
inv = missing_ghost.get_inventory('tip')
1169
self.assertRaises(errors.NoSuchRevision,
1170
missing_ghost.get_revision, 'ghost')
1171
self.assertRaises(errors.RevisionNotPresent,
1172
missing_ghost.get_inventory, 'ghost')
1175
class TestKnitPackSubtrees(TestKnitPackNoSubtrees):
1177
def get_format(self):
1178
return bzrdir.format_registry.make_bzrdir(
1179
'pack-0.92-subtree')
1181
def check_format(self, t):
1182
self.assertEqualDiff(
1183
"Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n",
1184
t.get('format').read())
1187
class TestRepositoryPackCollection(TestCaseWithTransport):
1189
def get_format(self):
1190
return bzrdir.format_registry.make_bzrdir('pack-0.92')
1192
def test__max_pack_count(self):
1193
"""The maximum pack count is a function of the number of revisions."""
1194
format = self.get_format()
1195
repo = self.make_repository('.', format=format)
1196
packs = repo._pack_collection
1197
# no revisions - one pack, so that we can have a revision free repo
1198
# without it blowing up
1199
self.assertEqual(1, packs._max_pack_count(0))
1200
# after that the sum of the digits, - check the first 1-9
1201
self.assertEqual(1, packs._max_pack_count(1))
1202
self.assertEqual(2, packs._max_pack_count(2))
1203
self.assertEqual(3, packs._max_pack_count(3))
1204
self.assertEqual(4, packs._max_pack_count(4))
1205
self.assertEqual(5, packs._max_pack_count(5))
1206
self.assertEqual(6, packs._max_pack_count(6))
1207
self.assertEqual(7, packs._max_pack_count(7))
1208
self.assertEqual(8, packs._max_pack_count(8))
1209
self.assertEqual(9, packs._max_pack_count(9))
1210
# check the boundary cases with two digits for the next decade
1211
self.assertEqual(1, packs._max_pack_count(10))
1212
self.assertEqual(2, packs._max_pack_count(11))
1213
self.assertEqual(10, packs._max_pack_count(19))
1214
self.assertEqual(2, packs._max_pack_count(20))
1215
self.assertEqual(3, packs._max_pack_count(21))
1216
# check some arbitrary big numbers
1217
self.assertEqual(25, packs._max_pack_count(112894))
1219
def test_pack_distribution_zero(self):
1220
format = self.get_format()
1221
repo = self.make_repository('.', format=format)
1222
packs = repo._pack_collection
1223
self.assertEqual([0], packs.pack_distribution(0))
1225
def test_ensure_loaded_unlocked(self):
1226
format = self.get_format()
1227
repo = self.make_repository('.', format=format)
1228
self.assertRaises(errors.ObjectNotLocked,
1229
repo._pack_collection.ensure_loaded)
1231
def test_pack_distribution_one_to_nine(self):
1232
format = self.get_format()
1233
repo = self.make_repository('.', format=format)
1234
packs = repo._pack_collection
1235
self.assertEqual([1],
1236
packs.pack_distribution(1))
1237
self.assertEqual([1, 1],
1238
packs.pack_distribution(2))
1239
self.assertEqual([1, 1, 1],
1240
packs.pack_distribution(3))
1241
self.assertEqual([1, 1, 1, 1],
1242
packs.pack_distribution(4))
1243
self.assertEqual([1, 1, 1, 1, 1],
1244
packs.pack_distribution(5))
1245
self.assertEqual([1, 1, 1, 1, 1, 1],
1246
packs.pack_distribution(6))
1247
self.assertEqual([1, 1, 1, 1, 1, 1, 1],
1248
packs.pack_distribution(7))
1249
self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1],
1250
packs.pack_distribution(8))
1251
self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1, 1],
1252
packs.pack_distribution(9))
1254
def test_pack_distribution_stable_at_boundaries(self):
1255
"""When there are multi-rev packs the counts are stable."""
1256
format = self.get_format()
1257
repo = self.make_repository('.', format=format)
1258
packs = repo._pack_collection
1260
self.assertEqual([10], packs.pack_distribution(10))
1261
self.assertEqual([10, 1], packs.pack_distribution(11))
1262
self.assertEqual([10, 10], packs.pack_distribution(20))
1263
self.assertEqual([10, 10, 1], packs.pack_distribution(21))
1265
self.assertEqual([100], packs.pack_distribution(100))
1266
self.assertEqual([100, 1], packs.pack_distribution(101))
1267
self.assertEqual([100, 10, 1], packs.pack_distribution(111))
1268
self.assertEqual([100, 100], packs.pack_distribution(200))
1269
self.assertEqual([100, 100, 1], packs.pack_distribution(201))
1270
self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1272
def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1273
format = self.get_format()
1274
repo = self.make_repository('.', format=format)
1275
packs = repo._pack_collection
1276
existing_packs = [(2000, "big"), (9, "medium")]
1277
# rev count - 2009 -> 2x1000 + 9x1
1278
pack_operations = packs.plan_autopack_combinations(
1279
existing_packs, [1000, 1000, 1, 1, 1, 1, 1, 1, 1, 1, 1])
1280
self.assertEqual([], pack_operations)
1282
def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1283
format = self.get_format()
1284
repo = self.make_repository('.', format=format)
1285
packs = repo._pack_collection
1286
existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1287
# rev count - 2010 -> 2x1000 + 1x10
1288
pack_operations = packs.plan_autopack_combinations(
1289
existing_packs, [1000, 1000, 10])
1290
self.assertEqual([], pack_operations)
1292
def test_plan_pack_operations_2010_combines_smallest_two(self):
1293
format = self.get_format()
1294
repo = self.make_repository('.', format=format)
1295
packs = repo._pack_collection
1296
existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1298
# rev count - 2010 -> 2x1000 + 1x10 (3)
1299
pack_operations = packs.plan_autopack_combinations(
1300
existing_packs, [1000, 1000, 10])
1301
self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
1303
def test_all_packs_none(self):
1304
format = self.get_format()
1305
tree = self.make_branch_and_tree('.', format=format)
1307
self.addCleanup(tree.unlock)
1308
packs = tree.branch.repository._pack_collection
1309
packs.ensure_loaded()
1310
self.assertEqual([], packs.all_packs())
1312
def test_all_packs_one(self):
1313
format = self.get_format()
1314
tree = self.make_branch_and_tree('.', format=format)
1315
tree.commit('start')
1317
self.addCleanup(tree.unlock)
1318
packs = tree.branch.repository._pack_collection
1319
packs.ensure_loaded()
1321
packs.get_pack_by_name(packs.names()[0])],
1324
def test_all_packs_two(self):
1325
format = self.get_format()
1326
tree = self.make_branch_and_tree('.', format=format)
1327
tree.commit('start')
1328
tree.commit('continue')
1330
self.addCleanup(tree.unlock)
1331
packs = tree.branch.repository._pack_collection
1332
packs.ensure_loaded()
1334
packs.get_pack_by_name(packs.names()[0]),
1335
packs.get_pack_by_name(packs.names()[1]),
1336
], packs.all_packs())
1338
def test_get_pack_by_name(self):
1339
format = self.get_format()
1340
tree = self.make_branch_and_tree('.', format=format)
1341
tree.commit('start')
1343
self.addCleanup(tree.unlock)
1344
packs = tree.branch.repository._pack_collection
1345
packs.ensure_loaded()
1346
name = packs.names()[0]
1347
pack_1 = packs.get_pack_by_name(name)
1348
# the pack should be correctly initialised
1349
rev_index = GraphIndex(packs._index_transport, name + '.rix',
1350
packs._names[name][0])
1351
inv_index = GraphIndex(packs._index_transport, name + '.iix',
1352
packs._names[name][1])
1353
txt_index = GraphIndex(packs._index_transport, name + '.tix',
1354
packs._names[name][2])
1355
sig_index = GraphIndex(packs._index_transport, name + '.six',
1356
packs._names[name][3])
1357
self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
1358
name, rev_index, inv_index, txt_index, sig_index), pack_1)
1359
# and the same instance should be returned on successive calls.
1360
self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1363
class TestPack(TestCaseWithTransport):
1364
"""Tests for the Pack object."""
1366
def assertCurrentlyEqual(self, left, right):
1367
self.assertTrue(left == right)
1368
self.assertTrue(right == left)
1369
self.assertFalse(left != right)
1370
self.assertFalse(right != left)
1372
def assertCurrentlyNotEqual(self, left, right):
1373
self.assertFalse(left == right)
1374
self.assertFalse(right == left)
1375
self.assertTrue(left != right)
1376
self.assertTrue(right != left)
1378
def test___eq____ne__(self):
1379
left = pack_repo.ExistingPack('', '', '', '', '', '')
1380
right = pack_repo.ExistingPack('', '', '', '', '', '')
1381
self.assertCurrentlyEqual(left, right)
1382
# change all attributes and ensure equality changes as we do.
1383
left.revision_index = 'a'
1384
self.assertCurrentlyNotEqual(left, right)
1385
right.revision_index = 'a'
1386
self.assertCurrentlyEqual(left, right)
1387
left.inventory_index = 'a'
1388
self.assertCurrentlyNotEqual(left, right)
1389
right.inventory_index = 'a'
1390
self.assertCurrentlyEqual(left, right)
1391
left.text_index = 'a'
1392
self.assertCurrentlyNotEqual(left, right)
1393
right.text_index = 'a'
1394
self.assertCurrentlyEqual(left, right)
1395
left.signature_index = 'a'
1396
self.assertCurrentlyNotEqual(left, right)
1397
right.signature_index = 'a'
1398
self.assertCurrentlyEqual(left, right)
1400
self.assertCurrentlyNotEqual(left, right)
1402
self.assertCurrentlyEqual(left, right)
1403
left.transport = 'a'
1404
self.assertCurrentlyNotEqual(left, right)
1405
right.transport = 'a'
1406
self.assertCurrentlyEqual(left, right)
1408
def test_file_name(self):
1409
pack = pack_repo.ExistingPack('', 'a_name', '', '', '', '')
1410
self.assertEqual('a_name.pack', pack.file_name())
1413
class TestNewPack(TestCaseWithTransport):
1414
"""Tests for pack_repo.NewPack."""
1416
def test_new_instance_attributes(self):
1417
upload_transport = self.get_transport('upload')
1418
pack_transport = self.get_transport('pack')
1419
index_transport = self.get_transport('index')
1420
upload_transport.mkdir('.')
1421
pack = pack_repo.NewPack(upload_transport, index_transport,
1423
self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
1424
self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
1425
self.assertIsInstance(pack._hash, type(md5.new()))
1426
self.assertTrue(pack.upload_transport is upload_transport)
1427
self.assertTrue(pack.index_transport is index_transport)
1428
self.assertTrue(pack.pack_transport is pack_transport)
1429
self.assertEqual(None, pack.index_sizes)
1430
self.assertEqual(20, len(pack.random_name))
1431
self.assertIsInstance(pack.random_name, str)
1432
self.assertIsInstance(pack.start_time, float)
1435
class TestPacker(TestCaseWithTransport):
1436
"""Tests for the packs repository Packer class."""
1438
# To date, this class has been factored out and nothing new added to it;
1439
# thus there are not yet any tests.
1442
class TestInterDifferingSerializer(TestCaseWithTransport):
1444
def test_progress_bar(self):
1445
tree = self.make_branch_and_tree('tree')
1446
tree.commit('rev1', rev_id='rev-1')
1447
tree.commit('rev2', rev_id='rev-2')
1448
tree.commit('rev3', rev_id='rev-3')
1449
repo = self.make_repository('repo')
1450
inter_repo = repository.InterDifferingSerializer(
1451
tree.branch.repository, repo)
1452
pb = progress.InstrumentedProgress(to_file=StringIO())
1453
pb.never_throttle = True
1454
inter_repo.fetch('rev-1', pb)
1455
self.assertEqual('Transferring revisions', pb.last_msg)
1456
self.assertEqual(1, pb.last_cnt)
1457
self.assertEqual(1, pb.last_total)
1458
inter_repo.fetch('rev-3', pb)
1459
self.assertEqual(2, pb.last_cnt)
1460
self.assertEqual(2, pb.last_total)