~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_repository.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 00:23:23 UTC
  • mfrom: (2070 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2071.
  • Revision ID: john@arbash-meinel.com-20061011002323-82ba88c293d7caff
[merge] bzr.dev 2070

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
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
22
22
also see this file.
23
23
"""
24
24
 
25
 
import md5
26
25
from stat import S_ISDIR
27
26
from StringIO import StringIO
28
27
 
29
28
import bzrlib
 
29
import bzrlib.bzrdir as bzrdir
 
30
import bzrlib.errors as errors
30
31
from bzrlib.errors import (NotBranchError,
31
32
                           NoSuchFile,
32
33
                           UnknownFormatError,
33
34
                           UnsupportedFormatError,
34
35
                           )
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 (
40
 
    TestCase,
41
 
    TestCaseWithTransport,
42
 
    test_knit,
43
 
    )
 
36
import bzrlib.repository as repository
 
37
from bzrlib.tests import TestCase, TestCaseWithTransport
44
38
from bzrlib.transport import get_transport
 
39
from bzrlib.transport.http import HttpServer
45
40
from bzrlib.transport.memory import MemoryServer
46
 
from bzrlib.util import bencode
47
 
from bzrlib import (
48
 
    bzrdir,
49
 
    errors,
50
 
    inventory,
51
 
    progress,
52
 
    repository,
53
 
    revision as _mod_revision,
54
 
    symbol_versioning,
55
 
    upgrade,
56
 
    workingtree,
57
 
    )
58
 
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
 
41
from bzrlib import upgrade, workingtree
59
42
 
60
43
 
61
44
class TestDefaultFormat(TestCase):
62
45
 
63
46
    def test_get_set_default_format(self):
64
 
        old_default = bzrdir.format_registry.get('default')
65
 
        private_default = old_default().repository_format.__class__
 
47
        private_default = repository._default_format.__class__
66
48
        old_format = repository.RepositoryFormat.get_default_format()
67
49
        self.assertTrue(isinstance(old_format, private_default))
68
 
        def make_sample_bzrdir():
69
 
            my_bzrdir = bzrdir.BzrDirMetaFormat1()
70
 
            my_bzrdir.repository_format = SampleRepositoryFormat()
71
 
            return my_bzrdir
72
 
        bzrdir.format_registry.remove('default')
73
 
        bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
74
 
        bzrdir.format_registry.set_default('sample')
 
50
        repository.RepositoryFormat.set_default_format(SampleRepositoryFormat())
75
51
        # creating a repository should now create an instrumented dir.
76
52
        try:
77
53
            # the default branch format is used by the meta dir format
80
56
            result = dir.create_repository()
81
57
            self.assertEqual(result, 'A bzr repository dir')
82
58
        finally:
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(),
87
 
                              old_format.__class__)
 
59
            repository.RepositoryFormat.set_default_format(old_format)
 
60
        self.assertEqual(old_format, repository.RepositoryFormat.get_default_format())
88
61
 
89
62
 
90
63
class SampleRepositoryFormat(repository.RepositoryFormat):
125
98
            t = get_transport(url)
126
99
            found_format = repository.RepositoryFormat.find_format(dir)
127
100
            self.failUnless(isinstance(found_format, format.__class__))
128
 
        check_format(weaverepo.RepositoryFormat7(), "bar")
 
101
        check_format(repository.RepositoryFormat7(), "bar")
129
102
        
130
103
    def test_find_format_no_repository(self):
131
104
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
160
133
 
161
134
    def test_no_ancestry_weave(self):
162
135
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
163
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
136
        repo = repository.RepositoryFormat6().initialize(control)
164
137
        # We no longer need to create the ancestry.weave file
165
138
        # since it is *never* used.
166
139
        self.assertRaises(NoSuchFile,
167
140
                          control.transport.get,
168
141
                          'ancestry.weave')
169
142
 
170
 
    def test_exposed_versioned_files_are_marked_dirty(self):
171
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
172
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
173
 
        repo.lock_write()
174
 
        inv = repo.get_inventory_weave()
175
 
        repo.unlock()
176
 
        self.assertRaises(errors.OutSideTransaction,
177
 
            inv.add_lines, 'foo', [], [])
178
 
 
179
 
    def test_supports_external_lookups(self):
180
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
181
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
182
 
        self.assertFalse(repo._format.supports_external_lookups)
183
 
 
184
143
 
185
144
class TestFormat7(TestCaseWithTransport):
186
145
    
187
146
    def test_disk_layout(self):
188
147
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
189
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
148
        repo = repository.RepositoryFormat7().initialize(control)
190
149
        # in case of side effects of locking.
191
150
        repo.lock_write()
192
151
        repo.unlock()
208
167
 
209
168
    def test_shared_disk_layout(self):
210
169
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
211
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
170
        repo = repository.RepositoryFormat7().initialize(control, shared=True)
212
171
        # we want:
213
172
        # format 'Bazaar-NG Repository format 7'
214
173
        # inventory.weave == empty_weave
231
190
    def test_creates_lockdir(self):
232
191
        """Make sure it appears to be controlled by a LockDir existence"""
233
192
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
234
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
193
        repo = repository.RepositoryFormat7().initialize(control, shared=True)
235
194
        t = control.get_repository_transport(None)
236
195
        # TODO: Should check there is a 'lock' toplevel directory, 
237
196
        # regardless of contents
247
206
        """repo format 7 actually locks on lockdir"""
248
207
        base_url = self.get_url()
249
208
        control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
250
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
209
        repo = repository.RepositoryFormat7().initialize(control, shared=True)
251
210
        t = control.get_repository_transport(None)
252
211
        repo.lock_write()
253
212
        repo.unlock()
261
220
 
262
221
    def test_shared_no_tree_disk_layout(self):
263
222
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
264
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
223
        repo = repository.RepositoryFormat7().initialize(control, shared=True)
265
224
        repo.set_make_working_trees(False)
266
225
        # we want:
267
226
        # format 'Bazaar-NG Repository format 7'
285
244
                             'W\n',
286
245
                             t.get('inventory.weave').read())
287
246
 
288
 
    def test_exposed_versioned_files_are_marked_dirty(self):
289
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
290
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
291
 
        repo.lock_write()
292
 
        inv = repo.get_inventory_weave()
293
 
        repo.unlock()
294
 
        self.assertRaises(errors.OutSideTransaction,
295
 
            inv.add_lines, 'foo', [], [])
296
 
 
297
 
    def test_supports_external_lookups(self):
298
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
299
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
300
 
        self.assertFalse(repo._format.supports_external_lookups)
301
 
 
302
247
 
303
248
class TestFormatKnit1(TestCaseWithTransport):
304
249
    
305
250
    def test_disk_layout(self):
306
251
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
307
 
        repo = knitrepo.RepositoryFormatKnit1().initialize(control)
 
252
        repo = repository.RepositoryFormatKnit1().initialize(control)
308
253
        # in case of side effects of locking.
309
254
        repo.lock_write()
310
255
        repo.unlock()
337
282
 
338
283
    def test_shared_disk_layout(self):
339
284
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
340
 
        repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
 
285
        repo = repository.RepositoryFormatKnit1().initialize(control, shared=True)
341
286
        # we want:
342
287
        # format 'Bazaar-NG Knit Repository Format 1'
343
288
        # lock: is a directory
356
301
 
357
302
    def test_shared_no_tree_disk_layout(self):
358
303
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
359
 
        repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
 
304
        repo = repository.RepositoryFormatKnit1().initialize(control, shared=True)
360
305
        repo.set_make_working_trees(False)
361
306
        # we want:
362
307
        # format 'Bazaar-NG Knit Repository Format 1'
377
322
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
378
323
        self.check_knits(t)
379
324
 
380
 
    def test_exposed_versioned_files_are_marked_dirty(self):
381
 
        format = bzrdir.BzrDirMetaFormat1()
382
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
383
 
        repo = self.make_repository('.', format=format)
384
 
        repo.lock_write()
385
 
        inv = repo.get_inventory_weave()
386
 
        repo.unlock()
387
 
        self.assertRaises(errors.OutSideTransaction,
388
 
            inv.add_lines, 'foo', [], [])
389
 
 
390
 
    def test_deserialise_sets_root_revision(self):
391
 
        """We must have a inventory.root.revision
392
 
 
393
 
        Old versions of the XML5 serializer did not set the revision_id for
394
 
        the whole inventory. So we grab the one from the expected text. Which
395
 
        is valid when the api is not being abused.
396
 
        """
397
 
        repo = self.make_repository('.',
398
 
                format=bzrdir.format_registry.get('knit')())
399
 
        inv_xml = '<inventory format="5">\n</inventory>\n'
400
 
        inv = repo.deserialise_inventory('test-rev-id', inv_xml)
401
 
        self.assertEqual('test-rev-id', inv.root.revision)
402
 
 
403
 
    def test_deserialise_uses_global_revision_id(self):
404
 
        """If it is set, then we re-use the global revision id"""
405
 
        repo = self.make_repository('.',
406
 
                format=bzrdir.format_registry.get('knit')())
407
 
        inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
408
 
                   '</inventory>\n')
409
 
        # Arguably, the deserialise_inventory should detect a mismatch, and
410
 
        # raise an error, rather than silently using one revision_id over the
411
 
        # other.
412
 
        self.assertRaises(AssertionError, repo.deserialise_inventory,
413
 
            'test-rev-id', inv_xml)
414
 
        inv = repo.deserialise_inventory('other-rev-id', inv_xml)
415
 
        self.assertEqual('other-rev-id', inv.root.revision)
416
 
 
417
 
    def test_supports_external_lookups(self):
418
 
        repo = self.make_repository('.',
419
 
                format=bzrdir.format_registry.get('knit')())
420
 
        self.assertFalse(repo._format.supports_external_lookups)
421
 
 
422
 
 
423
 
class KnitRepositoryStreamTests(test_knit.KnitTests):
424
 
    """Tests for knitrepo._get_stream_as_bytes."""
425
 
 
426
 
    def test_get_stream_as_bytes(self):
427
 
        # Make a simple knit
428
 
        k1 = self.make_test_knit()
429
 
        k1.add_lines('text-a', [], test_knit.split_lines(test_knit.TEXT_1))
430
 
        
431
 
        # Serialise it, check the output.
432
 
        bytes = knitrepo._get_stream_as_bytes(k1, ['text-a'])
433
 
        data = bencode.bdecode(bytes)
434
 
        format, record = data
435
 
        self.assertEqual('knit-plain', format)
436
 
        self.assertEqual(['text-a', ['fulltext'], []], record[:3])
437
 
        self.assertRecordContentEqual(k1, 'text-a', record[3])
438
 
 
439
 
    def test_get_stream_as_bytes_all(self):
440
 
        """Get a serialised data stream for all the records in a knit.
441
 
 
442
 
        Much like test_get_stream_all, except for get_stream_as_bytes.
443
 
        """
444
 
        k1 = self.make_test_knit()
445
 
        # Insert the same data as BasicKnitTests.test_knit_join, as they seem
446
 
        # to cover a range of cases (no parents, one parent, multiple parents).
447
 
        test_data = [
448
 
            ('text-a', [], test_knit.TEXT_1),
449
 
            ('text-b', ['text-a'], test_knit.TEXT_1),
450
 
            ('text-c', [], test_knit.TEXT_1),
451
 
            ('text-d', ['text-c'], test_knit.TEXT_1),
452
 
            ('text-m', ['text-b', 'text-d'], test_knit.TEXT_1),
453
 
           ]
454
 
        # This test is actually a bit strict as the order in which they're
455
 
        # returned is not defined.  This matches the current (deterministic)
456
 
        # behaviour.
457
 
        expected_data_list = [
458
 
            # version, options, parents
459
 
            ('text-a', ['fulltext'], []),
460
 
            ('text-b', ['line-delta'], ['text-a']),
461
 
            ('text-m', ['line-delta'], ['text-b', 'text-d']),
462
 
            ('text-c', ['fulltext'], []),
463
 
            ('text-d', ['line-delta'], ['text-c']),
464
 
            ]
465
 
        for version_id, parents, lines in test_data:
466
 
            k1.add_lines(version_id, parents, test_knit.split_lines(lines))
467
 
 
468
 
        bytes = knitrepo._get_stream_as_bytes(
469
 
            k1, ['text-a', 'text-b', 'text-m', 'text-c', 'text-d', ])
470
 
 
471
 
        data = bencode.bdecode(bytes)
472
 
        format = data.pop(0)
473
 
        self.assertEqual('knit-plain', format)
474
 
 
475
 
        for expected, actual in zip(expected_data_list, data):
476
 
            expected_version = expected[0]
477
 
            expected_options = expected[1]
478
 
            expected_parents = expected[2]
479
 
            version, options, parents, bytes = actual
480
 
            self.assertEqual(expected_version, version)
481
 
            self.assertEqual(expected_options, options)
482
 
            self.assertEqual(expected_parents, parents)
483
 
            self.assertRecordContentEqual(k1, version, bytes)
484
 
 
485
 
 
486
 
class DummyRepository(object):
487
 
    """A dummy repository for testing."""
488
 
 
489
 
    _serializer = None
490
 
 
491
 
    def supports_rich_root(self):
492
 
        return False
493
 
 
494
 
 
495
 
class InterDummy(repository.InterRepository):
496
 
    """An inter-repository optimised code path for DummyRepository.
497
 
 
498
 
    This is for use during testing where we use DummyRepository as repositories
 
325
 
 
326
class InterString(repository.InterRepository):
 
327
    """An inter-repository optimised code path for strings.
 
328
 
 
329
    This is for use during testing where we use strings as repositories
499
330
    so that none of the default regsitered inter-repository classes will
500
 
    MATCH.
 
331
    match.
501
332
    """
502
333
 
503
334
    @staticmethod
504
335
    def is_compatible(repo_source, repo_target):
505
 
        """InterDummy is compatible with DummyRepository."""
506
 
        return (isinstance(repo_source, DummyRepository) and 
507
 
            isinstance(repo_target, DummyRepository))
 
336
        """InterString is compatible with strings-as-repos."""
 
337
        return isinstance(repo_source, str) and isinstance(repo_target, str)
508
338
 
509
339
 
510
340
class TestInterRepository(TestCaseWithTransport):
516
346
        # This also tests that the default registered optimised interrepository
517
347
        # classes do not barf inappropriately when a surprising repository type
518
348
        # is handed to them.
519
 
        dummy_a = DummyRepository()
520
 
        dummy_b = DummyRepository()
 
349
        dummy_a = "Repository 1."
 
350
        dummy_b = "Repository 2."
521
351
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
522
352
 
523
353
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
524
 
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default.
525
 
        
526
 
        The effective default is now InterSameDataRepository because there is
527
 
        no actual sane default in the presence of incompatible data models.
528
 
        """
 
354
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default."""
529
355
        inter_repo = repository.InterRepository.get(repo_a, repo_b)
530
 
        self.assertEqual(repository.InterSameDataRepository,
 
356
        self.assertEqual(repository.InterRepository,
531
357
                         inter_repo.__class__)
532
358
        self.assertEqual(repo_a, inter_repo.source)
533
359
        self.assertEqual(repo_b, inter_repo.target)
538
364
        # and that it is correctly selected when given a repository
539
365
        # pair that it returns true on for the is_compatible static method
540
366
        # check
541
 
        dummy_a = DummyRepository()
542
 
        dummy_b = DummyRepository()
543
 
        repo = self.make_repository('.')
544
 
        # hack dummies to look like repo somewhat.
545
 
        dummy_a._serializer = repo._serializer
546
 
        dummy_b._serializer = repo._serializer
547
 
        repository.InterRepository.register_optimiser(InterDummy)
 
367
        dummy_a = "Repository 1."
 
368
        dummy_b = "Repository 2."
 
369
        repository.InterRepository.register_optimiser(InterString)
548
370
        try:
549
 
            # we should get the default for something InterDummy returns False
 
371
            # we should get the default for something InterString returns False
550
372
            # to
551
 
            self.assertFalse(InterDummy.is_compatible(dummy_a, repo))
552
 
            self.assertGetsDefaultInterRepository(dummy_a, repo)
553
 
            # and we should get an InterDummy for a pair it 'likes'
554
 
            self.assertTrue(InterDummy.is_compatible(dummy_a, dummy_b))
 
373
            self.assertFalse(InterString.is_compatible(dummy_a, None))
 
374
            self.assertGetsDefaultInterRepository(dummy_a, None)
 
375
            # and we should get an InterString for a pair it 'likes'
 
376
            self.assertTrue(InterString.is_compatible(dummy_a, dummy_b))
555
377
            inter_repo = repository.InterRepository.get(dummy_a, dummy_b)
556
 
            self.assertEqual(InterDummy, inter_repo.__class__)
 
378
            self.assertEqual(InterString, inter_repo.__class__)
557
379
            self.assertEqual(dummy_a, inter_repo.source)
558
380
            self.assertEqual(dummy_b, inter_repo.target)
559
381
        finally:
560
 
            repository.InterRepository.unregister_optimiser(InterDummy)
 
382
            repository.InterRepository.unregister_optimiser(InterString)
561
383
        # now we should get the default InterRepository object again.
562
384
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
563
385
 
567
389
    def test_is_compatible_and_registered(self):
568
390
        # InterWeaveRepo is compatible when either side
569
391
        # is a format 5/6/7 branch
570
 
        from bzrlib.repofmt import knitrepo, weaverepo
571
 
        formats = [weaverepo.RepositoryFormat5(),
572
 
                   weaverepo.RepositoryFormat6(),
573
 
                   weaverepo.RepositoryFormat7()]
574
 
        incompatible_formats = [weaverepo.RepositoryFormat4(),
575
 
                                knitrepo.RepositoryFormatKnit1(),
 
392
        formats = [repository.RepositoryFormat5(),
 
393
                   repository.RepositoryFormat6(),
 
394
                   repository.RepositoryFormat7()]
 
395
        incompatible_formats = [repository.RepositoryFormat4(),
 
396
                                repository.RepositoryFormatKnit1(),
576
397
                                ]
577
398
        repo_a = self.make_repository('a')
578
399
        repo_b = self.make_repository('b')
593
414
                                                        repo_b).__class__)
594
415
 
595
416
 
596
 
class TestInterRemoteToOther(TestCaseWithTransport):
597
 
 
598
 
    def make_remote_repository(self, path, backing_format=None):
599
 
        """Make a RemoteRepository object backed by a real repository that will
600
 
        be created at the given path."""
601
 
        self.make_repository(path, format=backing_format)
602
 
        smart_server = server.SmartTCPServer_for_testing()
603
 
        smart_server.setUp()
604
 
        remote_transport = get_transport(smart_server.get_url()).clone(path)
605
 
        self.addCleanup(smart_server.tearDown)
606
 
        remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
607
 
        remote_repo = remote_bzrdir.open_repository()
608
 
        return remote_repo
609
 
 
610
 
    def test_is_compatible_same_format(self):
611
 
        """InterRemoteToOther is compatible with a remote repository and a
612
 
        second repository that have the same format."""
613
 
        local_repo = self.make_repository('local')
614
 
        remote_repo = self.make_remote_repository('remote')
615
 
        is_compatible = repository.InterRemoteToOther.is_compatible
616
 
        self.assertTrue(
617
 
            is_compatible(remote_repo, local_repo),
618
 
            "InterRemoteToOther(%r, %r) is false" % (remote_repo, local_repo))
619
 
          
620
 
    def test_is_incompatible_different_format(self):
621
 
        local_repo = self.make_repository('local', 'dirstate')
622
 
        remote_repo = self.make_remote_repository('a', 'dirstate-with-subtree')
623
 
        is_compatible = repository.InterRemoteToOther.is_compatible
624
 
        self.assertFalse(
625
 
            is_compatible(remote_repo, local_repo),
626
 
            "InterRemoteToOther(%r, %r) is true" % (local_repo, remote_repo))
627
 
 
628
 
    def test_is_incompatible_different_format_both_remote(self):
629
 
        remote_repo_a = self.make_remote_repository(
630
 
            'a', 'dirstate-with-subtree')
631
 
        remote_repo_b = self.make_remote_repository('b', 'dirstate')
632
 
        is_compatible = repository.InterRemoteToOther.is_compatible
633
 
        self.assertFalse(
634
 
            is_compatible(remote_repo_a, remote_repo_b),
635
 
            "InterRemoteToOther(%r, %r) is true"
636
 
            % (remote_repo_a, remote_repo_b))
637
 
 
638
 
 
639
417
class TestRepositoryConverter(TestCaseWithTransport):
640
418
 
641
419
    def test_convert_empty(self):
642
420
        t = get_transport(self.get_url('.'))
643
421
        t.mkdir('repository')
644
422
        repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
645
 
        repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
646
 
        target_format = knitrepo.RepositoryFormatKnit1()
 
423
        repo = repository.RepositoryFormat7().initialize(repo_dir)
 
424
        target_format = repository.RepositoryFormatKnit1()
647
425
        converter = repository.CopyConverter(target_format)
648
426
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
649
427
        try:
661
439
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;') 
662
440
 
663
441
 
664
 
class TestRepositoryFormatKnit3(TestCaseWithTransport):
 
442
class TestRepositoryFormatKnit2(TestCaseWithTransport):
665
443
 
666
444
    def test_convert(self):
667
445
        """Ensure the upgrade adds weaves for roots"""
668
446
        format = bzrdir.BzrDirMetaFormat1()
669
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
447
        format.repository_format = repository.RepositoryFormatKnit1()
670
448
        tree = self.make_branch_and_tree('.', format)
671
449
        tree.commit("Dull commit", rev_id="dull")
672
450
        revision_tree = tree.branch.repository.revision_tree('dull')
673
451
        self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
674
452
            revision_tree.inventory.root.file_id)
675
453
        format = bzrdir.BzrDirMetaFormat1()
676
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
454
        format.repository_format = repository.RepositoryFormatKnit2()
677
455
        upgrade.Convert('.', format)
678
456
        tree = workingtree.WorkingTree.open('.')
679
457
        revision_tree = tree.branch.repository.revision_tree('dull')
681
459
        tree.commit("Another dull commit", rev_id='dull2')
682
460
        revision_tree = tree.branch.repository.revision_tree('dull2')
683
461
        self.assertEqual('dull', revision_tree.inventory.root.revision)
684
 
 
685
 
    def test_exposed_versioned_files_are_marked_dirty(self):
686
 
        format = bzrdir.BzrDirMetaFormat1()
687
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
688
 
        repo = self.make_repository('.', format=format)
689
 
        repo.lock_write()
690
 
        inv = repo.get_inventory_weave()
691
 
        repo.unlock()
692
 
        self.assertRaises(errors.OutSideTransaction,
693
 
            inv.add_lines, 'foo', [], [])
694
 
 
695
 
    def test_supports_external_lookups(self):
696
 
        format = bzrdir.BzrDirMetaFormat1()
697
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
698
 
        repo = self.make_repository('.', format=format)
699
 
        self.assertFalse(repo._format.supports_external_lookups)
700
 
 
701
 
 
702
 
class TestWithBrokenRepo(TestCaseWithTransport):
703
 
    """These tests seem to be more appropriate as interface tests?"""
704
 
 
705
 
    def make_broken_repository(self):
706
 
        # XXX: This function is borrowed from Aaron's "Reconcile can fix bad
707
 
        # parent references" branch which is due to land in bzr.dev soon.  Once
708
 
        # it does, this duplication should be removed.
709
 
        repo = self.make_repository('broken-repo')
710
 
        cleanups = []
711
 
        try:
712
 
            repo.lock_write()
713
 
            cleanups.append(repo.unlock)
714
 
            repo.start_write_group()
715
 
            cleanups.append(repo.commit_write_group)
716
 
            # make rev1a: A well-formed revision, containing 'file1'
717
 
            inv = inventory.Inventory(revision_id='rev1a')
718
 
            inv.root.revision = 'rev1a'
719
 
            self.add_file(repo, inv, 'file1', 'rev1a', [])
720
 
            repo.add_inventory('rev1a', inv, [])
721
 
            revision = _mod_revision.Revision('rev1a',
722
 
                committer='jrandom@example.com', timestamp=0,
723
 
                inventory_sha1='', timezone=0, message='foo', parent_ids=[])
724
 
            repo.add_revision('rev1a',revision, inv)
725
 
 
726
 
            # make rev1b, which has no Revision, but has an Inventory, and
727
 
            # file1
728
 
            inv = inventory.Inventory(revision_id='rev1b')
729
 
            inv.root.revision = 'rev1b'
730
 
            self.add_file(repo, inv, 'file1', 'rev1b', [])
731
 
            repo.add_inventory('rev1b', inv, [])
732
 
 
733
 
            # make rev2, with file1 and file2
734
 
            # file2 is sane
735
 
            # file1 has 'rev1b' as an ancestor, even though this is not
736
 
            # mentioned by 'rev1a', making it an unreferenced ancestor
737
 
            inv = inventory.Inventory()
738
 
            self.add_file(repo, inv, 'file1', 'rev2', ['rev1a', 'rev1b'])
739
 
            self.add_file(repo, inv, 'file2', 'rev2', [])
740
 
            self.add_revision(repo, 'rev2', inv, ['rev1a'])
741
 
 
742
 
            # make ghost revision rev1c
743
 
            inv = inventory.Inventory()
744
 
            self.add_file(repo, inv, 'file2', 'rev1c', [])
745
 
 
746
 
            # make rev3 with file2
747
 
            # file2 refers to 'rev1c', which is a ghost in this repository, so
748
 
            # file2 cannot have rev1c as its ancestor.
749
 
            inv = inventory.Inventory()
750
 
            self.add_file(repo, inv, 'file2', 'rev3', ['rev1c'])
751
 
            self.add_revision(repo, 'rev3', inv, ['rev1c'])
752
 
            return repo
753
 
        finally:
754
 
            for cleanup in reversed(cleanups):
755
 
                cleanup()
756
 
 
757
 
    def add_revision(self, repo, revision_id, inv, parent_ids):
758
 
        inv.revision_id = revision_id
759
 
        inv.root.revision = revision_id
760
 
        repo.add_inventory(revision_id, inv, parent_ids)
761
 
        revision = _mod_revision.Revision(revision_id,
762
 
            committer='jrandom@example.com', timestamp=0, inventory_sha1='',
763
 
            timezone=0, message='foo', parent_ids=parent_ids)
764
 
        repo.add_revision(revision_id,revision, inv)
765
 
 
766
 
    def add_file(self, repo, inv, filename, revision, parents):
767
 
        file_id = filename + '-id'
768
 
        entry = inventory.InventoryFile(file_id, filename, 'TREE_ROOT')
769
 
        entry.revision = revision
770
 
        entry.text_size = 0
771
 
        inv.add(entry)
772
 
        vf = repo.weave_store.get_weave_or_empty(file_id,
773
 
                                                 repo.get_transaction())
774
 
        vf.add_lines(revision, parents, ['line\n'])
775
 
 
776
 
    def test_insert_from_broken_repo(self):
777
 
        """Inserting a data stream from a broken repository won't silently
778
 
        corrupt the target repository.
779
 
        """
780
 
        broken_repo = self.make_broken_repository()
781
 
        empty_repo = self.make_repository('empty-repo')
782
 
        search = graph.SearchResult(set(['rev1a', 'rev2', 'rev3']),
783
 
            set(), 3, ['rev1a', 'rev2', 'rev3'])
784
 
        stream = broken_repo.get_data_stream_for_search(search)
785
 
        empty_repo.lock_write()
786
 
        self.addCleanup(empty_repo.unlock)
787
 
        empty_repo.start_write_group()
788
 
        try:
789
 
            self.assertRaises(
790
 
                errors.KnitCorrupt, empty_repo.insert_data_stream, stream)
791
 
        finally:
792
 
            empty_repo.abort_write_group()
793
 
 
794
 
 
795
 
class TestKnitPackNoSubtrees(TestCaseWithTransport):
796
 
 
797
 
    def get_format(self):
798
 
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
799
 
 
800
 
    def test_disk_layout(self):
801
 
        format = self.get_format()
802
 
        repo = self.make_repository('.', format=format)
803
 
        # in case of side effects of locking.
804
 
        repo.lock_write()
805
 
        repo.unlock()
806
 
        t = repo.bzrdir.get_repository_transport(None)
807
 
        self.check_format(t)
808
 
        # XXX: no locks left when unlocked at the moment
809
 
        # self.assertEqualDiff('', t.get('lock').read())
810
 
        self.check_databases(t)
811
 
 
812
 
    def check_format(self, t):
813
 
        self.assertEqualDiff(
814
 
            "Bazaar pack repository format 1 (needs bzr 0.92)\n",
815
 
                             t.get('format').read())
816
 
 
817
 
    def assertHasKndx(self, t, knit_name):
818
 
        """Assert that knit_name exists on t."""
819
 
        self.assertEqualDiff('# bzr knit index 8\n',
820
 
                             t.get(knit_name + '.kndx').read())
821
 
 
822
 
    def assertHasNoKndx(self, t, knit_name):
823
 
        """Assert that knit_name has no index on t."""
824
 
        self.assertFalse(t.has(knit_name + '.kndx'))
825
 
 
826
 
    def assertHasNoKnit(self, t, knit_name):
827
 
        """Assert that knit_name exists on t."""
828
 
        # no default content
829
 
        self.assertFalse(t.has(knit_name + '.knit'))
830
 
 
831
 
    def check_databases(self, t):
832
 
        """check knit content for a repository."""
833
 
        # check conversion worked
834
 
        self.assertHasNoKndx(t, 'inventory')
835
 
        self.assertHasNoKnit(t, 'inventory')
836
 
        self.assertHasNoKndx(t, 'revisions')
837
 
        self.assertHasNoKnit(t, 'revisions')
838
 
        self.assertHasNoKndx(t, 'signatures')
839
 
        self.assertHasNoKnit(t, 'signatures')
840
 
        self.assertFalse(t.has('knits'))
841
 
        # revision-indexes file-container directory
842
 
        self.assertEqual([],
843
 
            list(GraphIndex(t, 'pack-names', None).iter_all_entries()))
844
 
        self.assertTrue(S_ISDIR(t.stat('packs').st_mode))
845
 
        self.assertTrue(S_ISDIR(t.stat('upload').st_mode))
846
 
        self.assertTrue(S_ISDIR(t.stat('indices').st_mode))
847
 
        self.assertTrue(S_ISDIR(t.stat('obsolete_packs').st_mode))
848
 
 
849
 
    def test_shared_disk_layout(self):
850
 
        format = self.get_format()
851
 
        repo = self.make_repository('.', shared=True, format=format)
852
 
        # we want:
853
 
        t = repo.bzrdir.get_repository_transport(None)
854
 
        self.check_format(t)
855
 
        # XXX: no locks left when unlocked at the moment
856
 
        # self.assertEqualDiff('', t.get('lock').read())
857
 
        # We should have a 'shared-storage' marker file.
858
 
        self.assertEqualDiff('', t.get('shared-storage').read())
859
 
        self.check_databases(t)
860
 
 
861
 
    def test_shared_no_tree_disk_layout(self):
862
 
        format = self.get_format()
863
 
        repo = self.make_repository('.', shared=True, format=format)
864
 
        repo.set_make_working_trees(False)
865
 
        # we want:
866
 
        t = repo.bzrdir.get_repository_transport(None)
867
 
        self.check_format(t)
868
 
        # XXX: no locks left when unlocked at the moment
869
 
        # self.assertEqualDiff('', t.get('lock').read())
870
 
        # We should have a 'shared-storage' marker file.
871
 
        self.assertEqualDiff('', t.get('shared-storage').read())
872
 
        # We should have a marker for the no-working-trees flag.
873
 
        self.assertEqualDiff('', t.get('no-working-trees').read())
874
 
        # The marker should go when we toggle the setting.
875
 
        repo.set_make_working_trees(True)
876
 
        self.assertFalse(t.has('no-working-trees'))
877
 
        self.check_databases(t)
878
 
 
879
 
    def test_adding_revision_creates_pack_indices(self):
880
 
        format = self.get_format()
881
 
        tree = self.make_branch_and_tree('.', format=format)
882
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
883
 
        self.assertEqual([],
884
 
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
885
 
        tree.commit('foobarbaz')
886
 
        index = GraphIndex(trans, 'pack-names', None)
887
 
        index_nodes = list(index.iter_all_entries())
888
 
        self.assertEqual(1, len(index_nodes))
889
 
        node = index_nodes[0]
890
 
        name = node[1][0]
891
 
        # the pack sizes should be listed in the index
892
 
        pack_value = node[2]
893
 
        sizes = [int(digits) for digits in pack_value.split(' ')]
894
 
        for size, suffix in zip(sizes, ['.rix', '.iix', '.tix', '.six']):
895
 
            stat = trans.stat('indices/%s%s' % (name, suffix))
896
 
            self.assertEqual(size, stat.st_size)
897
 
 
898
 
    def test_pulling_nothing_leads_to_no_new_names(self):
899
 
        format = self.get_format()
900
 
        tree1 = self.make_branch_and_tree('1', format=format)
901
 
        tree2 = self.make_branch_and_tree('2', format=format)
902
 
        tree1.branch.repository.fetch(tree2.branch.repository)
903
 
        trans = tree1.branch.repository.bzrdir.get_repository_transport(None)
904
 
        self.assertEqual([],
905
 
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
906
 
 
907
 
    def test_commit_across_pack_shape_boundary_autopacks(self):
908
 
        format = self.get_format()
909
 
        tree = self.make_branch_and_tree('.', format=format)
910
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
911
 
        # This test could be a little cheaper by replacing the packs
912
 
        # attribute on the repository to allow a different pack distribution
913
 
        # and max packs policy - so we are checking the policy is honoured
914
 
        # in the test. But for now 11 commits is not a big deal in a single
915
 
        # test.
916
 
        for x in range(9):
917
 
            tree.commit('commit %s' % x)
918
 
        # there should be 9 packs:
919
 
        index = GraphIndex(trans, 'pack-names', None)
920
 
        self.assertEqual(9, len(list(index.iter_all_entries())))
921
 
        # insert some files in obsolete_packs which should be removed by pack.
922
 
        trans.put_bytes('obsolete_packs/foo', '123')
923
 
        trans.put_bytes('obsolete_packs/bar', '321')
924
 
        # committing one more should coalesce to 1 of 10.
925
 
        tree.commit('commit triggering pack')
926
 
        index = GraphIndex(trans, 'pack-names', None)
927
 
        self.assertEqual(1, len(list(index.iter_all_entries())))
928
 
        # packing should not damage data
929
 
        tree = tree.bzrdir.open_workingtree()
930
 
        check_result = tree.branch.repository.check(
931
 
            [tree.branch.last_revision()])
932
 
        # We should have 50 (10x5) files in the obsolete_packs directory.
933
 
        obsolete_files = list(trans.list_dir('obsolete_packs'))
934
 
        self.assertFalse('foo' in obsolete_files)
935
 
        self.assertFalse('bar' in obsolete_files)
936
 
        self.assertEqual(50, len(obsolete_files))
937
 
        # XXX: Todo check packs obsoleted correctly - old packs and indices
938
 
        # in the obsolete_packs directory.
939
 
        large_pack_name = list(index.iter_all_entries())[0][1][0]
940
 
        # finally, committing again should not touch the large pack.
941
 
        tree.commit('commit not triggering pack')
942
 
        index = GraphIndex(trans, 'pack-names', None)
943
 
        self.assertEqual(2, len(list(index.iter_all_entries())))
944
 
        pack_names = [node[1][0] for node in index.iter_all_entries()]
945
 
        self.assertTrue(large_pack_name in pack_names)
946
 
 
947
 
    def test_pack_after_two_commits_packs_everything(self):
948
 
        format = self.get_format()
949
 
        tree = self.make_branch_and_tree('.', format=format)
950
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
951
 
        tree.commit('start')
952
 
        tree.commit('more work')
953
 
        tree.branch.repository.pack()
954
 
        # there should be 1 pack:
955
 
        index = GraphIndex(trans, 'pack-names', None)
956
 
        self.assertEqual(1, len(list(index.iter_all_entries())))
957
 
        self.assertEqual(2, len(tree.branch.repository.all_revision_ids()))
958
 
 
959
 
    def test_pack_layout(self):
960
 
        format = self.get_format()
961
 
        tree = self.make_branch_and_tree('.', format=format)
962
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
963
 
        tree.commit('start', rev_id='1')
964
 
        tree.commit('more work', rev_id='2')
965
 
        tree.branch.repository.pack()
966
 
        tree.lock_read()
967
 
        self.addCleanup(tree.unlock)
968
 
        pack = tree.branch.repository._pack_collection.get_pack_by_name(
969
 
            tree.branch.repository._pack_collection.names()[0])
970
 
        # revision access tends to be tip->ancestor, so ordering that way on 
971
 
        # disk is a good idea.
972
 
        for _1, key, val, refs in pack.revision_index.iter_all_entries():
973
 
            if key == ('1',):
974
 
                pos_1 = int(val[1:].split()[0])
975
 
            else:
976
 
                pos_2 = int(val[1:].split()[0])
977
 
        self.assertTrue(pos_2 < pos_1)
978
 
 
979
 
    def test_pack_repositories_support_multiple_write_locks(self):
980
 
        format = self.get_format()
981
 
        self.make_repository('.', shared=True, format=format)
982
 
        r1 = repository.Repository.open('.')
983
 
        r2 = repository.Repository.open('.')
984
 
        r1.lock_write()
985
 
        self.addCleanup(r1.unlock)
986
 
        r2.lock_write()
987
 
        r2.unlock()
988
 
 
989
 
    def _add_text(self, repo, fileid):
990
 
        """Add a text to the repository within a write group."""
991
 
        vf =repo.weave_store.get_weave(fileid, repo.get_transaction())
992
 
        vf.add_lines('samplerev+' + fileid, [], [])
993
 
 
994
 
    def test_concurrent_writers_merge_new_packs(self):
995
 
        format = self.get_format()
996
 
        self.make_repository('.', shared=True, format=format)
997
 
        r1 = repository.Repository.open('.')
998
 
        r2 = repository.Repository.open('.')
999
 
        r1.lock_write()
1000
 
        try:
1001
 
            # access enough data to load the names list
1002
 
            list(r1.all_revision_ids())
1003
 
            r2.lock_write()
1004
 
            try:
1005
 
                # access enough data to load the names list
1006
 
                list(r2.all_revision_ids())
1007
 
                r1.start_write_group()
1008
 
                try:
1009
 
                    r2.start_write_group()
1010
 
                    try:
1011
 
                        self._add_text(r1, 'fileidr1')
1012
 
                        self._add_text(r2, 'fileidr2')
1013
 
                    except:
1014
 
                        r2.abort_write_group()
1015
 
                        raise
1016
 
                except:
1017
 
                    r1.abort_write_group()
1018
 
                    raise
1019
 
                # both r1 and r2 have open write groups with data in them
1020
 
                # created while the other's write group was open.
1021
 
                # Commit both which requires a merge to the pack-names.
1022
 
                try:
1023
 
                    r1.commit_write_group()
1024
 
                except:
1025
 
                    r1.abort_write_group()
1026
 
                    r2.abort_write_group()
1027
 
                    raise
1028
 
                r2.commit_write_group()
1029
 
                # tell r1 to reload from disk
1030
 
                r1._pack_collection.reset()
1031
 
                # Now both repositories should know about both names
1032
 
                r1._pack_collection.ensure_loaded()
1033
 
                r2._pack_collection.ensure_loaded()
1034
 
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1035
 
                self.assertEqual(2, len(r1._pack_collection.names()))
1036
 
            finally:
1037
 
                r2.unlock()
1038
 
        finally:
1039
 
            r1.unlock()
1040
 
 
1041
 
    def test_concurrent_writer_second_preserves_dropping_a_pack(self):
1042
 
        format = self.get_format()
1043
 
        self.make_repository('.', shared=True, format=format)
1044
 
        r1 = repository.Repository.open('.')
1045
 
        r2 = repository.Repository.open('.')
1046
 
        # add a pack to drop
1047
 
        r1.lock_write()
1048
 
        try:
1049
 
            r1.start_write_group()
1050
 
            try:
1051
 
                self._add_text(r1, 'fileidr1')
1052
 
            except:
1053
 
                r1.abort_write_group()
1054
 
                raise
1055
 
            else:
1056
 
                r1.commit_write_group()
1057
 
            r1._pack_collection.ensure_loaded()
1058
 
            name_to_drop = r1._pack_collection.all_packs()[0].name
1059
 
        finally:
1060
 
            r1.unlock()
1061
 
        r1.lock_write()
1062
 
        try:
1063
 
            # access enough data to load the names list
1064
 
            list(r1.all_revision_ids())
1065
 
            r2.lock_write()
1066
 
            try:
1067
 
                # access enough data to load the names list
1068
 
                list(r2.all_revision_ids())
1069
 
                r1._pack_collection.ensure_loaded()
1070
 
                try:
1071
 
                    r2.start_write_group()
1072
 
                    try:
1073
 
                        # in r1, drop the pack
1074
 
                        r1._pack_collection._remove_pack_from_memory(
1075
 
                            r1._pack_collection.get_pack_by_name(name_to_drop))
1076
 
                        # in r2, add a pack
1077
 
                        self._add_text(r2, 'fileidr2')
1078
 
                    except:
1079
 
                        r2.abort_write_group()
1080
 
                        raise
1081
 
                except:
1082
 
                    r1._pack_collection.reset()
1083
 
                    raise
1084
 
                # r1 has a changed names list, and r2 an open write groups with
1085
 
                # changes.
1086
 
                # save r1, and then commit the r2 write group, which requires a
1087
 
                # merge to the pack-names, which should not reinstate
1088
 
                # name_to_drop
1089
 
                try:
1090
 
                    r1._pack_collection._save_pack_names()
1091
 
                    r1._pack_collection.reset()
1092
 
                except:
1093
 
                    r2.abort_write_group()
1094
 
                    raise
1095
 
                try:
1096
 
                    r2.commit_write_group()
1097
 
                except:
1098
 
                    r2.abort_write_group()
1099
 
                    raise
1100
 
                # Now both repositories should now about just one name.
1101
 
                r1._pack_collection.ensure_loaded()
1102
 
                r2._pack_collection.ensure_loaded()
1103
 
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1104
 
                self.assertEqual(1, len(r1._pack_collection.names()))
1105
 
                self.assertFalse(name_to_drop in r1._pack_collection.names())
1106
 
            finally:
1107
 
                r2.unlock()
1108
 
        finally:
1109
 
            r1.unlock()
1110
 
 
1111
 
    def test_lock_write_does_not_physically_lock(self):
1112
 
        repo = self.make_repository('.', format=self.get_format())
1113
 
        repo.lock_write()
1114
 
        self.addCleanup(repo.unlock)
1115
 
        self.assertFalse(repo.get_physical_lock_status())
1116
 
 
1117
 
    def prepare_for_break_lock(self):
1118
 
        # Setup the global ui factory state so that a break-lock method call
1119
 
        # will find usable input in the input stream.
1120
 
        old_factory = bzrlib.ui.ui_factory
1121
 
        def restoreFactory():
1122
 
            bzrlib.ui.ui_factory = old_factory
1123
 
        self.addCleanup(restoreFactory)
1124
 
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1125
 
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
1126
 
 
1127
 
    def test_break_lock_breaks_physical_lock(self):
1128
 
        repo = self.make_repository('.', format=self.get_format())
1129
 
        repo._pack_collection.lock_names()
1130
 
        repo2 = repository.Repository.open('.')
1131
 
        self.assertTrue(repo.get_physical_lock_status())
1132
 
        self.prepare_for_break_lock()
1133
 
        repo2.break_lock()
1134
 
        self.assertFalse(repo.get_physical_lock_status())
1135
 
 
1136
 
    def test_broken_physical_locks_error_on__unlock_names_lock(self):
1137
 
        repo = self.make_repository('.', format=self.get_format())
1138
 
        repo._pack_collection.lock_names()
1139
 
        self.assertTrue(repo.get_physical_lock_status())
1140
 
        repo2 = repository.Repository.open('.')
1141
 
        self.prepare_for_break_lock()
1142
 
        repo2.break_lock()
1143
 
        self.assertRaises(errors.LockBroken, repo._pack_collection._unlock_names)
1144
 
 
1145
 
    def test_fetch_without_find_ghosts_ignores_ghosts(self):
1146
 
        # we want two repositories at this point:
1147
 
        # one with a revision that is a ghost in the other
1148
 
        # repository.
1149
 
        # 'ghost' is present in has_ghost, 'ghost' is absent in 'missing_ghost'.
1150
 
        # 'references' is present in both repositories, and 'tip' is present
1151
 
        # just in has_ghost.
1152
 
        # has_ghost       missing_ghost
1153
 
        #------------------------------
1154
 
        # 'ghost'             -
1155
 
        # 'references'    'references'
1156
 
        # 'tip'               -
1157
 
        # In this test we fetch 'tip' which should not fetch 'ghost'
1158
 
        has_ghost = self.make_repository('has_ghost', format=self.get_format())
1159
 
        missing_ghost = self.make_repository('missing_ghost',
1160
 
            format=self.get_format())
1161
 
 
1162
 
        def add_commit(repo, revision_id, parent_ids):
1163
 
            repo.lock_write()
1164
 
            repo.start_write_group()
1165
 
            inv = inventory.Inventory(revision_id=revision_id)
1166
 
            inv.root.revision = revision_id
1167
 
            root_id = inv.root.file_id
1168
 
            sha1 = repo.add_inventory(revision_id, inv, [])
1169
 
            vf = repo.weave_store.get_weave_or_empty(root_id,
1170
 
                repo.get_transaction())
1171
 
            vf.add_lines(revision_id, [], [])
1172
 
            rev = bzrlib.revision.Revision(timestamp=0,
1173
 
                                           timezone=None,
1174
 
                                           committer="Foo Bar <foo@example.com>",
1175
 
                                           message="Message",
1176
 
                                           inventory_sha1=sha1,
1177
 
                                           revision_id=revision_id)
1178
 
            rev.parent_ids = parent_ids
1179
 
            repo.add_revision(revision_id, rev)
1180
 
            repo.commit_write_group()
1181
 
            repo.unlock()
1182
 
        add_commit(has_ghost, 'ghost', [])
1183
 
        add_commit(has_ghost, 'references', ['ghost'])
1184
 
        add_commit(missing_ghost, 'references', ['ghost'])
1185
 
        add_commit(has_ghost, 'tip', ['references'])
1186
 
        missing_ghost.fetch(has_ghost, 'tip')
1187
 
        # missing ghost now has tip and not ghost.
1188
 
        rev = missing_ghost.get_revision('tip')
1189
 
        inv = missing_ghost.get_inventory('tip')
1190
 
        self.assertRaises(errors.NoSuchRevision,
1191
 
            missing_ghost.get_revision, 'ghost')
1192
 
        self.assertRaises(errors.RevisionNotPresent,
1193
 
            missing_ghost.get_inventory, 'ghost')
1194
 
 
1195
 
    def test_supports_external_lookups(self):
1196
 
        repo = self.make_repository('.', format=self.get_format())
1197
 
        self.assertFalse(repo._format.supports_external_lookups)
1198
 
 
1199
 
 
1200
 
class TestKnitPackSubtrees(TestKnitPackNoSubtrees):
1201
 
 
1202
 
    def get_format(self):
1203
 
        return bzrdir.format_registry.make_bzrdir(
1204
 
            'pack-0.92-subtree')
1205
 
 
1206
 
    def check_format(self, t):
1207
 
        self.assertEqualDiff(
1208
 
            "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n",
1209
 
            t.get('format').read())
1210
 
 
1211
 
 
1212
 
class TestDevelopment0(TestKnitPackNoSubtrees):
1213
 
 
1214
 
    def get_format(self):
1215
 
        return bzrdir.format_registry.make_bzrdir(
1216
 
            'development')
1217
 
 
1218
 
    def check_format(self, t):
1219
 
        self.assertEqualDiff(
1220
 
            "Bazaar development format 0 (needs bzr.dev from before 1.3)\n",
1221
 
            t.get('format').read())
1222
 
 
1223
 
 
1224
 
class TestDevelopment0Subtree(TestKnitPackNoSubtrees):
1225
 
 
1226
 
    def get_format(self):
1227
 
        return bzrdir.format_registry.make_bzrdir(
1228
 
            'development-subtree')
1229
 
 
1230
 
    def check_format(self, t):
1231
 
        self.assertEqualDiff(
1232
 
            "Bazaar development format 0 with subtree support "
1233
 
            "(needs bzr.dev from before 1.3)\n",
1234
 
            t.get('format').read())
1235
 
 
1236
 
 
1237
 
class TestRepositoryPackCollection(TestCaseWithTransport):
1238
 
 
1239
 
    def get_format(self):
1240
 
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
1241
 
 
1242
 
    def test__max_pack_count(self):
1243
 
        """The maximum pack count is a function of the number of revisions."""
1244
 
        format = self.get_format()
1245
 
        repo = self.make_repository('.', format=format)
1246
 
        packs = repo._pack_collection
1247
 
        # no revisions - one pack, so that we can have a revision free repo
1248
 
        # without it blowing up
1249
 
        self.assertEqual(1, packs._max_pack_count(0))
1250
 
        # after that the sum of the digits, - check the first 1-9
1251
 
        self.assertEqual(1, packs._max_pack_count(1))
1252
 
        self.assertEqual(2, packs._max_pack_count(2))
1253
 
        self.assertEqual(3, packs._max_pack_count(3))
1254
 
        self.assertEqual(4, packs._max_pack_count(4))
1255
 
        self.assertEqual(5, packs._max_pack_count(5))
1256
 
        self.assertEqual(6, packs._max_pack_count(6))
1257
 
        self.assertEqual(7, packs._max_pack_count(7))
1258
 
        self.assertEqual(8, packs._max_pack_count(8))
1259
 
        self.assertEqual(9, packs._max_pack_count(9))
1260
 
        # check the boundary cases with two digits for the next decade
1261
 
        self.assertEqual(1, packs._max_pack_count(10))
1262
 
        self.assertEqual(2, packs._max_pack_count(11))
1263
 
        self.assertEqual(10, packs._max_pack_count(19))
1264
 
        self.assertEqual(2, packs._max_pack_count(20))
1265
 
        self.assertEqual(3, packs._max_pack_count(21))
1266
 
        # check some arbitrary big numbers
1267
 
        self.assertEqual(25, packs._max_pack_count(112894))
1268
 
 
1269
 
    def test_pack_distribution_zero(self):
1270
 
        format = self.get_format()
1271
 
        repo = self.make_repository('.', format=format)
1272
 
        packs = repo._pack_collection
1273
 
        self.assertEqual([0], packs.pack_distribution(0))
1274
 
 
1275
 
    def test_ensure_loaded_unlocked(self):
1276
 
        format = self.get_format()
1277
 
        repo = self.make_repository('.', format=format)
1278
 
        self.assertRaises(errors.ObjectNotLocked,
1279
 
                          repo._pack_collection.ensure_loaded)
1280
 
 
1281
 
    def test_pack_distribution_one_to_nine(self):
1282
 
        format = self.get_format()
1283
 
        repo = self.make_repository('.', format=format)
1284
 
        packs = repo._pack_collection
1285
 
        self.assertEqual([1],
1286
 
            packs.pack_distribution(1))
1287
 
        self.assertEqual([1, 1],
1288
 
            packs.pack_distribution(2))
1289
 
        self.assertEqual([1, 1, 1],
1290
 
            packs.pack_distribution(3))
1291
 
        self.assertEqual([1, 1, 1, 1],
1292
 
            packs.pack_distribution(4))
1293
 
        self.assertEqual([1, 1, 1, 1, 1],
1294
 
            packs.pack_distribution(5))
1295
 
        self.assertEqual([1, 1, 1, 1, 1, 1],
1296
 
            packs.pack_distribution(6))
1297
 
        self.assertEqual([1, 1, 1, 1, 1, 1, 1],
1298
 
            packs.pack_distribution(7))
1299
 
        self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1],
1300
 
            packs.pack_distribution(8))
1301
 
        self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1, 1],
1302
 
            packs.pack_distribution(9))
1303
 
 
1304
 
    def test_pack_distribution_stable_at_boundaries(self):
1305
 
        """When there are multi-rev packs the counts are stable."""
1306
 
        format = self.get_format()
1307
 
        repo = self.make_repository('.', format=format)
1308
 
        packs = repo._pack_collection
1309
 
        # in 10s:
1310
 
        self.assertEqual([10], packs.pack_distribution(10))
1311
 
        self.assertEqual([10, 1], packs.pack_distribution(11))
1312
 
        self.assertEqual([10, 10], packs.pack_distribution(20))
1313
 
        self.assertEqual([10, 10, 1], packs.pack_distribution(21))
1314
 
        # 100s
1315
 
        self.assertEqual([100], packs.pack_distribution(100))
1316
 
        self.assertEqual([100, 1], packs.pack_distribution(101))
1317
 
        self.assertEqual([100, 10, 1], packs.pack_distribution(111))
1318
 
        self.assertEqual([100, 100], packs.pack_distribution(200))
1319
 
        self.assertEqual([100, 100, 1], packs.pack_distribution(201))
1320
 
        self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1321
 
 
1322
 
    def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1323
 
        format = self.get_format()
1324
 
        repo = self.make_repository('.', format=format)
1325
 
        packs = repo._pack_collection
1326
 
        existing_packs = [(2000, "big"), (9, "medium")]
1327
 
        # rev count - 2009 -> 2x1000 + 9x1
1328
 
        pack_operations = packs.plan_autopack_combinations(
1329
 
            existing_packs, [1000, 1000, 1, 1, 1, 1, 1, 1, 1, 1, 1])
1330
 
        self.assertEqual([], pack_operations)
1331
 
 
1332
 
    def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1333
 
        format = self.get_format()
1334
 
        repo = self.make_repository('.', format=format)
1335
 
        packs = repo._pack_collection
1336
 
        existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1337
 
        # rev count - 2010 -> 2x1000 + 1x10
1338
 
        pack_operations = packs.plan_autopack_combinations(
1339
 
            existing_packs, [1000, 1000, 10])
1340
 
        self.assertEqual([], pack_operations)
1341
 
 
1342
 
    def test_plan_pack_operations_2010_combines_smallest_two(self):
1343
 
        format = self.get_format()
1344
 
        repo = self.make_repository('.', format=format)
1345
 
        packs = repo._pack_collection
1346
 
        existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1347
 
            (1, "single1")]
1348
 
        # rev count - 2010 -> 2x1000 + 1x10 (3)
1349
 
        pack_operations = packs.plan_autopack_combinations(
1350
 
            existing_packs, [1000, 1000, 10])
1351
 
        self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
1352
 
 
1353
 
    def test_all_packs_none(self):
1354
 
        format = self.get_format()
1355
 
        tree = self.make_branch_and_tree('.', format=format)
1356
 
        tree.lock_read()
1357
 
        self.addCleanup(tree.unlock)
1358
 
        packs = tree.branch.repository._pack_collection
1359
 
        packs.ensure_loaded()
1360
 
        self.assertEqual([], packs.all_packs())
1361
 
 
1362
 
    def test_all_packs_one(self):
1363
 
        format = self.get_format()
1364
 
        tree = self.make_branch_and_tree('.', format=format)
1365
 
        tree.commit('start')
1366
 
        tree.lock_read()
1367
 
        self.addCleanup(tree.unlock)
1368
 
        packs = tree.branch.repository._pack_collection
1369
 
        packs.ensure_loaded()
1370
 
        self.assertEqual([
1371
 
            packs.get_pack_by_name(packs.names()[0])],
1372
 
            packs.all_packs())
1373
 
 
1374
 
    def test_all_packs_two(self):
1375
 
        format = self.get_format()
1376
 
        tree = self.make_branch_and_tree('.', format=format)
1377
 
        tree.commit('start')
1378
 
        tree.commit('continue')
1379
 
        tree.lock_read()
1380
 
        self.addCleanup(tree.unlock)
1381
 
        packs = tree.branch.repository._pack_collection
1382
 
        packs.ensure_loaded()
1383
 
        self.assertEqual([
1384
 
            packs.get_pack_by_name(packs.names()[0]),
1385
 
            packs.get_pack_by_name(packs.names()[1]),
1386
 
            ], packs.all_packs())
1387
 
 
1388
 
    def test_get_pack_by_name(self):
1389
 
        format = self.get_format()
1390
 
        tree = self.make_branch_and_tree('.', format=format)
1391
 
        tree.commit('start')
1392
 
        tree.lock_read()
1393
 
        self.addCleanup(tree.unlock)
1394
 
        packs = tree.branch.repository._pack_collection
1395
 
        packs.ensure_loaded()
1396
 
        name = packs.names()[0]
1397
 
        pack_1 = packs.get_pack_by_name(name)
1398
 
        # the pack should be correctly initialised
1399
 
        rev_index = GraphIndex(packs._index_transport, name + '.rix',
1400
 
            packs._names[name][0])
1401
 
        inv_index = GraphIndex(packs._index_transport, name + '.iix',
1402
 
            packs._names[name][1])
1403
 
        txt_index = GraphIndex(packs._index_transport, name + '.tix',
1404
 
            packs._names[name][2])
1405
 
        sig_index = GraphIndex(packs._index_transport, name + '.six',
1406
 
            packs._names[name][3])
1407
 
        self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
1408
 
            name, rev_index, inv_index, txt_index, sig_index), pack_1)
1409
 
        # and the same instance should be returned on successive calls.
1410
 
        self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1411
 
 
1412
 
 
1413
 
class TestPack(TestCaseWithTransport):
1414
 
    """Tests for the Pack object."""
1415
 
 
1416
 
    def assertCurrentlyEqual(self, left, right):
1417
 
        self.assertTrue(left == right)
1418
 
        self.assertTrue(right == left)
1419
 
        self.assertFalse(left != right)
1420
 
        self.assertFalse(right != left)
1421
 
 
1422
 
    def assertCurrentlyNotEqual(self, left, right):
1423
 
        self.assertFalse(left == right)
1424
 
        self.assertFalse(right == left)
1425
 
        self.assertTrue(left != right)
1426
 
        self.assertTrue(right != left)
1427
 
 
1428
 
    def test___eq____ne__(self):
1429
 
        left = pack_repo.ExistingPack('', '', '', '', '', '')
1430
 
        right = pack_repo.ExistingPack('', '', '', '', '', '')
1431
 
        self.assertCurrentlyEqual(left, right)
1432
 
        # change all attributes and ensure equality changes as we do.
1433
 
        left.revision_index = 'a'
1434
 
        self.assertCurrentlyNotEqual(left, right)
1435
 
        right.revision_index = 'a'
1436
 
        self.assertCurrentlyEqual(left, right)
1437
 
        left.inventory_index = 'a'
1438
 
        self.assertCurrentlyNotEqual(left, right)
1439
 
        right.inventory_index = 'a'
1440
 
        self.assertCurrentlyEqual(left, right)
1441
 
        left.text_index = 'a'
1442
 
        self.assertCurrentlyNotEqual(left, right)
1443
 
        right.text_index = 'a'
1444
 
        self.assertCurrentlyEqual(left, right)
1445
 
        left.signature_index = 'a'
1446
 
        self.assertCurrentlyNotEqual(left, right)
1447
 
        right.signature_index = 'a'
1448
 
        self.assertCurrentlyEqual(left, right)
1449
 
        left.name = 'a'
1450
 
        self.assertCurrentlyNotEqual(left, right)
1451
 
        right.name = 'a'
1452
 
        self.assertCurrentlyEqual(left, right)
1453
 
        left.transport = 'a'
1454
 
        self.assertCurrentlyNotEqual(left, right)
1455
 
        right.transport = 'a'
1456
 
        self.assertCurrentlyEqual(left, right)
1457
 
 
1458
 
    def test_file_name(self):
1459
 
        pack = pack_repo.ExistingPack('', 'a_name', '', '', '', '')
1460
 
        self.assertEqual('a_name.pack', pack.file_name())
1461
 
 
1462
 
 
1463
 
class TestNewPack(TestCaseWithTransport):
1464
 
    """Tests for pack_repo.NewPack."""
1465
 
 
1466
 
    def test_new_instance_attributes(self):
1467
 
        upload_transport = self.get_transport('upload')
1468
 
        pack_transport = self.get_transport('pack')
1469
 
        index_transport = self.get_transport('index')
1470
 
        upload_transport.mkdir('.')
1471
 
        pack = pack_repo.NewPack(upload_transport, index_transport,
1472
 
            pack_transport)
1473
 
        self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
1474
 
        self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
1475
 
        self.assertIsInstance(pack._hash, type(md5.new()))
1476
 
        self.assertTrue(pack.upload_transport is upload_transport)
1477
 
        self.assertTrue(pack.index_transport is index_transport)
1478
 
        self.assertTrue(pack.pack_transport is pack_transport)
1479
 
        self.assertEqual(None, pack.index_sizes)
1480
 
        self.assertEqual(20, len(pack.random_name))
1481
 
        self.assertIsInstance(pack.random_name, str)
1482
 
        self.assertIsInstance(pack.start_time, float)
1483
 
 
1484
 
 
1485
 
class TestPacker(TestCaseWithTransport):
1486
 
    """Tests for the packs repository Packer class."""
1487
 
 
1488
 
    # To date, this class has been factored out and nothing new added to it;
1489
 
    # thus there are not yet any tests.
1490
 
 
1491
 
 
1492
 
class TestInterDifferingSerializer(TestCaseWithTransport):
1493
 
 
1494
 
    def test_progress_bar(self):
1495
 
        tree = self.make_branch_and_tree('tree')
1496
 
        tree.commit('rev1', rev_id='rev-1')
1497
 
        tree.commit('rev2', rev_id='rev-2')
1498
 
        tree.commit('rev3', rev_id='rev-3')
1499
 
        repo = self.make_repository('repo')
1500
 
        inter_repo = repository.InterDifferingSerializer(
1501
 
            tree.branch.repository, repo)
1502
 
        pb = progress.InstrumentedProgress(to_file=StringIO())
1503
 
        pb.never_throttle = True
1504
 
        inter_repo.fetch('rev-1', pb)
1505
 
        self.assertEqual('Transferring revisions', pb.last_msg)
1506
 
        self.assertEqual(1, pb.last_cnt)
1507
 
        self.assertEqual(1, pb.last_total)
1508
 
        inter_repo.fetch('rev-3', pb)
1509
 
        self.assertEqual(2, pb.last_cnt)
1510
 
        self.assertEqual(2, pb.last_total)