~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_repository.py

  • Committer: Patch Queue Manager
  • Date: 2011-10-14 16:54:26 UTC
  • mfrom: (6216.1.1 remove-this-file)
  • Revision ID: pqm@pqm.ubuntu.com-20111014165426-tjix4e6idryf1r2z
(jelmer) Remove an accidentally committed .THIS file. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
23
23
"""
24
24
 
25
25
from stat import S_ISDIR
26
 
from StringIO import StringIO
27
26
 
28
27
import bzrlib
29
 
from bzrlib.errors import (NotBranchError,
30
 
                           NoSuchFile,
31
 
                           UnknownFormatError,
32
 
                           UnsupportedFormatError,
33
 
                           )
 
28
from bzrlib.errors import (
 
29
    UnknownFormatError,
 
30
    UnsupportedFormatError,
 
31
    )
34
32
from bzrlib import (
 
33
    btree_index,
35
34
    graph,
 
35
    symbol_versioning,
36
36
    tests,
 
37
    transport,
37
38
    )
38
 
from bzrlib.branchbuilder import BranchBuilder
39
39
from bzrlib.btree_index import BTreeBuilder, BTreeGraphIndex
40
 
from bzrlib.index import GraphIndex, InMemoryGraphIndex
 
40
from bzrlib.index import GraphIndex
41
41
from bzrlib.repository import RepositoryFormat
42
 
from bzrlib.smart import server
43
42
from bzrlib.tests import (
44
43
    TestCase,
45
44
    TestCaseWithTransport,
46
 
    TestSkipped,
47
 
    test_knit,
48
 
    )
49
 
from bzrlib.transport import (
50
 
    fakenfs,
51
 
    get_transport,
52
 
    )
53
 
from bzrlib.transport.memory import MemoryServer
 
45
    )
54
46
from bzrlib import (
55
 
    bencode,
56
47
    bzrdir,
57
48
    errors,
58
49
    inventory,
59
50
    osutils,
60
 
    progress,
61
51
    repository,
62
52
    revision as _mod_revision,
63
 
    symbol_versioning,
64
53
    upgrade,
 
54
    versionedfile,
 
55
    vf_repository,
65
56
    workingtree,
66
57
    )
67
58
from bzrlib.repofmt import (
68
59
    groupcompress_repo,
69
60
    knitrepo,
 
61
    knitpack_repo,
70
62
    pack_repo,
71
 
    weaverepo,
72
63
    )
73
64
 
74
65
 
77
68
    def test_get_set_default_format(self):
78
69
        old_default = bzrdir.format_registry.get('default')
79
70
        private_default = old_default().repository_format.__class__
80
 
        old_format = repository.RepositoryFormat.get_default_format()
 
71
        old_format = repository.format_registry.get_default()
81
72
        self.assertTrue(isinstance(old_format, private_default))
82
73
        def make_sample_bzrdir():
83
74
            my_bzrdir = bzrdir.BzrDirMetaFormat1()
97
88
            bzrdir.format_registry.remove('default')
98
89
            bzrdir.format_registry.remove('sample')
99
90
            bzrdir.format_registry.register('default', old_default, '')
100
 
        self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
 
91
        self.assertIsInstance(repository.format_registry.get_default(),
101
92
                              old_format.__class__)
102
93
 
103
94
 
125
116
        return "opened repository."
126
117
 
127
118
 
 
119
class SampleExtraRepositoryFormat(repository.RepositoryFormat):
 
120
    """A sample format that can not be used in a metadir
 
121
 
 
122
    """
 
123
 
 
124
    def get_format_string(self):
 
125
        raise NotImplementedError
 
126
 
 
127
 
128
128
class TestRepositoryFormat(TestCaseWithTransport):
129
129
    """Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
130
130
 
136
136
        def check_format(format, url):
137
137
            dir = format._matchingbzrdir.initialize(url)
138
138
            format.initialize(dir)
139
 
            t = get_transport(url)
 
139
            t = transport.get_transport_from_path(url)
140
140
            found_format = repository.RepositoryFormat.find_format(dir)
141
 
            self.failUnless(isinstance(found_format, format.__class__))
142
 
        check_format(weaverepo.RepositoryFormat7(), "bar")
 
141
            self.assertIsInstance(found_format, format.__class__)
 
142
        check_format(repository.format_registry.get_default(), "bar")
143
143
 
144
144
    def test_find_format_no_repository(self):
145
145
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
155
155
                          dir)
156
156
 
157
157
    def test_register_unregister_format(self):
 
158
        # Test deprecated format registration functions
158
159
        format = SampleRepositoryFormat()
159
160
        # make a control dir
160
161
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
161
162
        # make a repo
162
163
        format.initialize(dir)
163
164
        # register a format for it.
164
 
        repository.RepositoryFormat.register_format(format)
 
165
        self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
 
166
            repository.RepositoryFormat.register_format, format)
165
167
        # which repository.Open will refuse (not supported)
166
 
        self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
 
168
        self.assertRaises(UnsupportedFormatError, repository.Repository.open,
 
169
            self.get_url())
167
170
        # but open(unsupported) will work
168
171
        self.assertEqual(format.open(dir), "opened repository.")
169
172
        # unregister the format
170
 
        repository.RepositoryFormat.unregister_format(format)
171
 
 
172
 
 
173
 
class TestFormat6(TestCaseWithTransport):
174
 
 
175
 
    def test_attribute__fetch_order(self):
176
 
        """Weaves need topological data insertion."""
177
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
178
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
179
 
        self.assertEqual('topological', repo._format._fetch_order)
180
 
 
181
 
    def test_attribute__fetch_uses_deltas(self):
182
 
        """Weaves do not reuse deltas."""
183
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
184
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
185
 
        self.assertEqual(False, repo._format._fetch_uses_deltas)
186
 
 
187
 
    def test_attribute__fetch_reconcile(self):
188
 
        """Weave repositories need a reconcile after fetch."""
189
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
190
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
191
 
        self.assertEqual(True, repo._format._fetch_reconcile)
192
 
 
193
 
    def test_no_ancestry_weave(self):
194
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
195
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
196
 
        # We no longer need to create the ancestry.weave file
197
 
        # since it is *never* used.
198
 
        self.assertRaises(NoSuchFile,
199
 
                          control.transport.get,
200
 
                          'ancestry.weave')
201
 
 
202
 
    def test_supports_external_lookups(self):
203
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
204
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
205
 
        self.assertFalse(repo._format.supports_external_lookups)
206
 
 
207
 
 
208
 
class TestFormat7(TestCaseWithTransport):
209
 
 
210
 
    def test_attribute__fetch_order(self):
211
 
        """Weaves need topological data insertion."""
212
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
213
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
214
 
        self.assertEqual('topological', repo._format._fetch_order)
215
 
 
216
 
    def test_attribute__fetch_uses_deltas(self):
217
 
        """Weaves do not reuse deltas."""
218
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
219
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
220
 
        self.assertEqual(False, repo._format._fetch_uses_deltas)
221
 
 
222
 
    def test_attribute__fetch_reconcile(self):
223
 
        """Weave repositories need a reconcile after fetch."""
224
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
225
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
226
 
        self.assertEqual(True, repo._format._fetch_reconcile)
227
 
 
228
 
    def test_disk_layout(self):
229
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
230
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
231
 
        # in case of side effects of locking.
232
 
        repo.lock_write()
233
 
        repo.unlock()
234
 
        # we want:
235
 
        # format 'Bazaar-NG Repository format 7'
236
 
        # lock ''
237
 
        # inventory.weave == empty_weave
238
 
        # empty revision-store directory
239
 
        # empty weaves directory
240
 
        t = control.get_repository_transport(None)
241
 
        self.assertEqualDiff('Bazaar-NG Repository format 7',
242
 
                             t.get('format').read())
243
 
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
244
 
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
245
 
        self.assertEqualDiff('# bzr weave file v5\n'
246
 
                             'w\n'
247
 
                             'W\n',
248
 
                             t.get('inventory.weave').read())
249
 
        # Creating a file with id Foo:Bar results in a non-escaped file name on
250
 
        # disk.
251
 
        control.create_branch()
252
 
        tree = control.create_workingtree()
253
 
        tree.add(['foo'], ['Foo:Bar'], ['file'])
254
 
        tree.put_file_bytes_non_atomic('Foo:Bar', 'content\n')
255
 
        tree.commit('first post', rev_id='first')
256
 
        self.assertEqualDiff(
257
 
            '# bzr weave file v5\n'
258
 
            'i\n'
259
 
            '1 7fe70820e08a1aac0ef224d9c66ab66831cc4ab1\n'
260
 
            'n first\n'
261
 
            '\n'
262
 
            'w\n'
263
 
            '{ 0\n'
264
 
            '. content\n'
265
 
            '}\n'
266
 
            'W\n',
267
 
            t.get('weaves/74/Foo%3ABar.weave').read())
268
 
 
269
 
    def test_shared_disk_layout(self):
270
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
271
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
272
 
        # we want:
273
 
        # format 'Bazaar-NG Repository format 7'
274
 
        # inventory.weave == empty_weave
275
 
        # empty revision-store directory
276
 
        # empty weaves directory
277
 
        # a 'shared-storage' marker file.
278
 
        # lock is not present when unlocked
279
 
        t = control.get_repository_transport(None)
280
 
        self.assertEqualDiff('Bazaar-NG Repository format 7',
281
 
                             t.get('format').read())
282
 
        self.assertEqualDiff('', t.get('shared-storage').read())
283
 
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
284
 
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
285
 
        self.assertEqualDiff('# bzr weave file v5\n'
286
 
                             'w\n'
287
 
                             'W\n',
288
 
                             t.get('inventory.weave').read())
289
 
        self.assertFalse(t.has('branch-lock'))
290
 
 
291
 
    def test_creates_lockdir(self):
292
 
        """Make sure it appears to be controlled by a LockDir existence"""
293
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
294
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
295
 
        t = control.get_repository_transport(None)
296
 
        # TODO: Should check there is a 'lock' toplevel directory,
297
 
        # regardless of contents
298
 
        self.assertFalse(t.has('lock/held/info'))
299
 
        repo.lock_write()
300
 
        try:
301
 
            self.assertTrue(t.has('lock/held/info'))
302
 
        finally:
303
 
            # unlock so we don't get a warning about failing to do so
304
 
            repo.unlock()
305
 
 
306
 
    def test_uses_lockdir(self):
307
 
        """repo format 7 actually locks on lockdir"""
308
 
        base_url = self.get_url()
309
 
        control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
310
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
311
 
        t = control.get_repository_transport(None)
312
 
        repo.lock_write()
313
 
        repo.unlock()
314
 
        del repo
315
 
        # make sure the same lock is created by opening it
316
 
        repo = repository.Repository.open(base_url)
317
 
        repo.lock_write()
318
 
        self.assertTrue(t.has('lock/held/info'))
319
 
        repo.unlock()
320
 
        self.assertFalse(t.has('lock/held/info'))
321
 
 
322
 
    def test_shared_no_tree_disk_layout(self):
323
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
324
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
325
 
        repo.set_make_working_trees(False)
326
 
        # we want:
327
 
        # format 'Bazaar-NG Repository format 7'
328
 
        # lock ''
329
 
        # inventory.weave == empty_weave
330
 
        # empty revision-store directory
331
 
        # empty weaves directory
332
 
        # a 'shared-storage' marker file.
333
 
        t = control.get_repository_transport(None)
334
 
        self.assertEqualDiff('Bazaar-NG Repository format 7',
335
 
                             t.get('format').read())
336
 
        ## self.assertEqualDiff('', t.get('lock').read())
337
 
        self.assertEqualDiff('', t.get('shared-storage').read())
338
 
        self.assertEqualDiff('', t.get('no-working-trees').read())
339
 
        repo.set_make_working_trees(True)
340
 
        self.assertFalse(t.has('no-working-trees'))
341
 
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
342
 
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
343
 
        self.assertEqualDiff('# bzr weave file v5\n'
344
 
                             'w\n'
345
 
                             'W\n',
346
 
                             t.get('inventory.weave').read())
347
 
 
348
 
    def test_supports_external_lookups(self):
349
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
350
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
351
 
        self.assertFalse(repo._format.supports_external_lookups)
 
173
        self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
 
174
            repository.RepositoryFormat.unregister_format, format)
 
175
 
 
176
 
 
177
class TestRepositoryFormatRegistry(TestCase):
 
178
 
 
179
    def setUp(self):
 
180
        super(TestRepositoryFormatRegistry, self).setUp()
 
181
        self.registry = repository.RepositoryFormatRegistry()
 
182
 
 
183
    def test_register_unregister_format(self):
 
184
        format = SampleRepositoryFormat()
 
185
        self.registry.register(format)
 
186
        self.assertEquals(format, self.registry.get("Sample .bzr repository format."))
 
187
        self.registry.remove(format)
 
188
        self.assertRaises(KeyError, self.registry.get, "Sample .bzr repository format.")
 
189
 
 
190
    def test_get_all(self):
 
191
        format = SampleRepositoryFormat()
 
192
        self.assertEquals([], self.registry._get_all())
 
193
        self.registry.register(format)
 
194
        self.assertEquals([format], self.registry._get_all())
 
195
 
 
196
    def test_register_extra(self):
 
197
        format = SampleExtraRepositoryFormat()
 
198
        self.assertEquals([], self.registry._get_all())
 
199
        self.registry.register_extra(format)
 
200
        self.assertEquals([format], self.registry._get_all())
 
201
 
 
202
    def test_register_extra_lazy(self):
 
203
        self.assertEquals([], self.registry._get_all())
 
204
        self.registry.register_extra_lazy("bzrlib.tests.test_repository",
 
205
            "SampleExtraRepositoryFormat")
 
206
        formats = self.registry._get_all()
 
207
        self.assertEquals(1, len(formats))
 
208
        self.assertIsInstance(formats[0], SampleExtraRepositoryFormat)
352
209
 
353
210
 
354
211
class TestFormatKnit1(TestCaseWithTransport):
456
313
        repo = self.make_repository('.',
457
314
                format=bzrdir.format_registry.get('knit')())
458
315
        inv_xml = '<inventory format="5">\n</inventory>\n'
459
 
        inv = repo.deserialise_inventory('test-rev-id', inv_xml)
 
316
        inv = repo._deserialise_inventory('test-rev-id', inv_xml)
460
317
        self.assertEqual('test-rev-id', inv.root.revision)
461
318
 
462
319
    def test_deserialise_uses_global_revision_id(self):
468
325
        # Arguably, the deserialise_inventory should detect a mismatch, and
469
326
        # raise an error, rather than silently using one revision_id over the
470
327
        # other.
471
 
        self.assertRaises(AssertionError, repo.deserialise_inventory,
 
328
        self.assertRaises(AssertionError, repo._deserialise_inventory,
472
329
            'test-rev-id', inv_xml)
473
 
        inv = repo.deserialise_inventory('other-rev-id', inv_xml)
 
330
        inv = repo._deserialise_inventory('other-rev-id', inv_xml)
474
331
        self.assertEqual('other-rev-id', inv.root.revision)
475
332
 
476
333
    def test_supports_external_lookups(self):
486
343
    _serializer = None
487
344
 
488
345
    def supports_rich_root(self):
 
346
        if self._format is not None:
 
347
            return self._format.rich_root_data
489
348
        return False
490
349
 
491
350
    def get_graph(self):
520
379
        # classes do not barf inappropriately when a surprising repository type
521
380
        # is handed to them.
522
381
        dummy_a = DummyRepository()
 
382
        dummy_a._format = RepositoryFormat()
 
383
        dummy_a._format.supports_full_versioned_files = True
523
384
        dummy_b = DummyRepository()
 
385
        dummy_b._format = RepositoryFormat()
 
386
        dummy_b._format.supports_full_versioned_files = True
524
387
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
525
388
 
526
389
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
530
393
        no actual sane default in the presence of incompatible data models.
531
394
        """
532
395
        inter_repo = repository.InterRepository.get(repo_a, repo_b)
533
 
        self.assertEqual(repository.InterSameDataRepository,
 
396
        self.assertEqual(vf_repository.InterSameDataRepository,
534
397
                         inter_repo.__class__)
535
398
        self.assertEqual(repo_a, inter_repo.source)
536
399
        self.assertEqual(repo_b, inter_repo.target)
542
405
        # pair that it returns true on for the is_compatible static method
543
406
        # check
544
407
        dummy_a = DummyRepository()
 
408
        dummy_a._format = RepositoryFormat()
545
409
        dummy_b = DummyRepository()
 
410
        dummy_b._format = RepositoryFormat()
546
411
        repo = self.make_repository('.')
547
412
        # hack dummies to look like repo somewhat.
548
413
        dummy_a._serializer = repo._serializer
 
414
        dummy_a._format.supports_tree_reference = repo._format.supports_tree_reference
 
415
        dummy_a._format.rich_root_data = repo._format.rich_root_data
 
416
        dummy_a._format.supports_full_versioned_files = repo._format.supports_full_versioned_files
549
417
        dummy_b._serializer = repo._serializer
 
418
        dummy_b._format.supports_tree_reference = repo._format.supports_tree_reference
 
419
        dummy_b._format.rich_root_data = repo._format.rich_root_data
 
420
        dummy_b._format.supports_full_versioned_files = repo._format.supports_full_versioned_files
550
421
        repository.InterRepository.register_optimiser(InterDummy)
551
422
        try:
552
423
            # we should get the default for something InterDummy returns False
565
436
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
566
437
 
567
438
 
568
 
class TestInterWeaveRepo(TestCaseWithTransport):
569
 
 
570
 
    def test_is_compatible_and_registered(self):
571
 
        # InterWeaveRepo is compatible when either side
572
 
        # is a format 5/6/7 branch
573
 
        from bzrlib.repofmt import knitrepo, weaverepo
574
 
        formats = [weaverepo.RepositoryFormat5(),
575
 
                   weaverepo.RepositoryFormat6(),
576
 
                   weaverepo.RepositoryFormat7()]
577
 
        incompatible_formats = [weaverepo.RepositoryFormat4(),
578
 
                                knitrepo.RepositoryFormatKnit1(),
579
 
                                ]
580
 
        repo_a = self.make_repository('a')
581
 
        repo_b = self.make_repository('b')
582
 
        is_compatible = repository.InterWeaveRepo.is_compatible
583
 
        for source in incompatible_formats:
584
 
            # force incompatible left then right
585
 
            repo_a._format = source
586
 
            repo_b._format = formats[0]
587
 
            self.assertFalse(is_compatible(repo_a, repo_b))
588
 
            self.assertFalse(is_compatible(repo_b, repo_a))
589
 
        for source in formats:
590
 
            repo_a._format = source
591
 
            for target in formats:
592
 
                repo_b._format = target
593
 
                self.assertTrue(is_compatible(repo_a, repo_b))
594
 
        self.assertEqual(repository.InterWeaveRepo,
595
 
                         repository.InterRepository.get(repo_a,
596
 
                                                        repo_b).__class__)
 
439
class TestRepositoryFormat1(knitrepo.RepositoryFormatKnit1):
 
440
 
 
441
    def get_format_string(self):
 
442
        return "Test Format 1"
 
443
 
 
444
 
 
445
class TestRepositoryFormat2(knitrepo.RepositoryFormatKnit1):
 
446
 
 
447
    def get_format_string(self):
 
448
        return "Test Format 2"
597
449
 
598
450
 
599
451
class TestRepositoryConverter(TestCaseWithTransport):
600
452
 
601
453
    def test_convert_empty(self):
602
 
        t = get_transport(self.get_url('.'))
 
454
        source_format = TestRepositoryFormat1()
 
455
        target_format = TestRepositoryFormat2()
 
456
        repository.format_registry.register(source_format)
 
457
        self.addCleanup(repository.format_registry.remove,
 
458
            source_format)
 
459
        repository.format_registry.register(target_format)
 
460
        self.addCleanup(repository.format_registry.remove,
 
461
            target_format)
 
462
        t = self.get_transport()
603
463
        t.mkdir('repository')
604
464
        repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
605
 
        repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
606
 
        target_format = knitrepo.RepositoryFormatKnit1()
 
465
        repo = TestRepositoryFormat1().initialize(repo_dir)
607
466
        converter = repository.CopyConverter(target_format)
608
467
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
609
468
        try:
614
473
        self.assertTrue(isinstance(target_format, repo._format.__class__))
615
474
 
616
475
 
617
 
class TestMisc(TestCase):
618
 
 
619
 
    def test_unescape_xml(self):
620
 
        """We get some kind of error when malformed entities are passed"""
621
 
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;')
622
 
 
623
 
 
624
476
class TestRepositoryFormatKnit3(TestCaseWithTransport):
625
477
 
626
478
    def test_attribute__fetch_order(self):
673
525
        self.assertFalse(repo._format.supports_external_lookups)
674
526
 
675
527
 
676
 
class Test2a(TestCaseWithTransport):
 
528
class Test2a(tests.TestCaseWithMemoryTransport):
 
529
 
 
530
    def test_chk_bytes_uses_custom_btree_parser(self):
 
531
        mt = self.make_branch_and_memory_tree('test', format='2a')
 
532
        mt.lock_write()
 
533
        self.addCleanup(mt.unlock)
 
534
        mt.add([''], ['root-id'])
 
535
        mt.commit('first')
 
536
        index = mt.branch.repository.chk_bytes._index._graph_index._indices[0]
 
537
        self.assertEqual(btree_index._gcchk_factory, index._leaf_factory)
 
538
        # It should also work if we re-open the repo
 
539
        repo = mt.branch.repository.bzrdir.open_repository()
 
540
        repo.lock_read()
 
541
        self.addCleanup(repo.unlock)
 
542
        index = repo.chk_bytes._index._graph_index._indices[0]
 
543
        self.assertEqual(btree_index._gcchk_factory, index._leaf_factory)
 
544
 
 
545
    def test_fetch_combines_groups(self):
 
546
        builder = self.make_branch_builder('source', format='2a')
 
547
        builder.start_series()
 
548
        builder.build_snapshot('1', None, [
 
549
            ('add', ('', 'root-id', 'directory', '')),
 
550
            ('add', ('file', 'file-id', 'file', 'content\n'))])
 
551
        builder.build_snapshot('2', ['1'], [
 
552
            ('modify', ('file-id', 'content-2\n'))])
 
553
        builder.finish_series()
 
554
        source = builder.get_branch()
 
555
        target = self.make_repository('target', format='2a')
 
556
        target.fetch(source.repository)
 
557
        target.lock_read()
 
558
        self.addCleanup(target.unlock)
 
559
        details = target.texts._index.get_build_details(
 
560
            [('file-id', '1',), ('file-id', '2',)])
 
561
        file_1_details = details[('file-id', '1')]
 
562
        file_2_details = details[('file-id', '2')]
 
563
        # The index, and what to read off disk, should be the same for both
 
564
        # versions of the file.
 
565
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
 
566
 
 
567
    def test_fetch_combines_groups(self):
 
568
        builder = self.make_branch_builder('source', format='2a')
 
569
        builder.start_series()
 
570
        builder.build_snapshot('1', None, [
 
571
            ('add', ('', 'root-id', 'directory', '')),
 
572
            ('add', ('file', 'file-id', 'file', 'content\n'))])
 
573
        builder.build_snapshot('2', ['1'], [
 
574
            ('modify', ('file-id', 'content-2\n'))])
 
575
        builder.finish_series()
 
576
        source = builder.get_branch()
 
577
        target = self.make_repository('target', format='2a')
 
578
        target.fetch(source.repository)
 
579
        target.lock_read()
 
580
        self.addCleanup(target.unlock)
 
581
        details = target.texts._index.get_build_details(
 
582
            [('file-id', '1',), ('file-id', '2',)])
 
583
        file_1_details = details[('file-id', '1')]
 
584
        file_2_details = details[('file-id', '2')]
 
585
        # The index, and what to read off disk, should be the same for both
 
586
        # versions of the file.
 
587
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
 
588
 
 
589
    def test_fetch_combines_groups(self):
 
590
        builder = self.make_branch_builder('source', format='2a')
 
591
        builder.start_series()
 
592
        builder.build_snapshot('1', None, [
 
593
            ('add', ('', 'root-id', 'directory', '')),
 
594
            ('add', ('file', 'file-id', 'file', 'content\n'))])
 
595
        builder.build_snapshot('2', ['1'], [
 
596
            ('modify', ('file-id', 'content-2\n'))])
 
597
        builder.finish_series()
 
598
        source = builder.get_branch()
 
599
        target = self.make_repository('target', format='2a')
 
600
        target.fetch(source.repository)
 
601
        target.lock_read()
 
602
        self.addCleanup(target.unlock)
 
603
        details = target.texts._index.get_build_details(
 
604
            [('file-id', '1',), ('file-id', '2',)])
 
605
        file_1_details = details[('file-id', '1')]
 
606
        file_2_details = details[('file-id', '2')]
 
607
        # The index, and what to read off disk, should be the same for both
 
608
        # versions of the file.
 
609
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
677
610
 
678
611
    def test_format_pack_compresses_True(self):
679
612
        repo = self.make_repository('repo', format='2a')
680
613
        self.assertTrue(repo._format.pack_compresses)
681
614
 
682
615
    def test_inventories_use_chk_map_with_parent_base_dict(self):
683
 
        tree = self.make_branch_and_tree('repo', format="2a")
 
616
        tree = self.make_branch_and_memory_tree('repo', format="2a")
 
617
        tree.lock_write()
 
618
        tree.add([''], ['TREE_ROOT'])
684
619
        revid = tree.commit("foo")
 
620
        tree.unlock()
685
621
        tree.lock_read()
686
622
        self.addCleanup(tree.unlock)
687
623
        inv = tree.branch.repository.get_inventory(revid)
696
632
        # at 20 unchanged commits, chk pages are packed that are split into
697
633
        # two groups such that the new pack being made doesn't have all its
698
634
        # pages in the source packs (though they are in the repository).
699
 
        tree = self.make_branch_and_tree('tree', format='2a')
 
635
        # Use a memory backed repository, we don't need to hit disk for this
 
636
        tree = self.make_branch_and_memory_tree('tree', format='2a')
 
637
        tree.lock_write()
 
638
        self.addCleanup(tree.unlock)
 
639
        tree.add([''], ['TREE_ROOT'])
700
640
        for pos in range(20):
701
641
            tree.commit(str(pos))
702
642
 
703
643
    def test_pack_with_hint(self):
704
 
        tree = self.make_branch_and_tree('tree', format='2a')
 
644
        tree = self.make_branch_and_memory_tree('tree', format='2a')
 
645
        tree.lock_write()
 
646
        self.addCleanup(tree.unlock)
 
647
        tree.add([''], ['TREE_ROOT'])
705
648
        # 1 commit to leave untouched
706
649
        tree.commit('1')
707
650
        to_keep = tree.branch.repository._pack_collection.names()
730
673
        target = self.make_repository('target', format='rich-root-pack')
731
674
        stream = source._get_source(target._format)
732
675
        # We don't want the child GroupCHKStreamSource
733
 
        self.assertIs(type(stream), repository.StreamSource)
 
676
        self.assertIs(type(stream), vf_repository.StreamSource)
734
677
 
735
678
    def test_get_stream_for_missing_keys_includes_all_chk_refs(self):
736
679
        source_builder = self.make_branch_builder('source',
812
755
        source = self.make_repository('source', format='pack-0.92')
813
756
        target = self.make_repository('target', format='pack-0.92')
814
757
        stream_source = source._get_source(target._format)
815
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
758
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
816
759
 
817
760
    def test_source_to_exact_pack_rich_root_pack(self):
818
761
        source = self.make_repository('source', format='rich-root-pack')
819
762
        target = self.make_repository('target', format='rich-root-pack')
820
763
        stream_source = source._get_source(target._format)
821
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
764
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
822
765
 
823
766
    def test_source_to_exact_pack_19(self):
824
767
        source = self.make_repository('source', format='1.9')
825
768
        target = self.make_repository('target', format='1.9')
826
769
        stream_source = source._get_source(target._format)
827
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
770
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
828
771
 
829
772
    def test_source_to_exact_pack_19_rich_root(self):
830
773
        source = self.make_repository('source', format='1.9-rich-root')
831
774
        target = self.make_repository('target', format='1.9-rich-root')
832
775
        stream_source = source._get_source(target._format)
833
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
776
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
834
777
 
835
778
    def test_source_to_remote_exact_pack_19(self):
836
779
        trans = self.make_smart_server('target')
839
782
        target = self.make_repository('target', format='1.9')
840
783
        target = repository.Repository.open(trans.base)
841
784
        stream_source = source._get_source(target._format)
842
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
785
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
843
786
 
844
787
    def test_stream_source_to_non_exact(self):
845
788
        source = self.make_repository('source', format='pack-0.92')
846
789
        target = self.make_repository('target', format='1.9')
847
790
        stream = source._get_source(target._format)
848
 
        self.assertIs(type(stream), repository.StreamSource)
 
791
        self.assertIs(type(stream), vf_repository.StreamSource)
849
792
 
850
793
    def test_stream_source_to_non_exact_rich_root(self):
851
794
        source = self.make_repository('source', format='1.9')
852
795
        target = self.make_repository('target', format='1.9-rich-root')
853
796
        stream = source._get_source(target._format)
854
 
        self.assertIs(type(stream), repository.StreamSource)
 
797
        self.assertIs(type(stream), vf_repository.StreamSource)
855
798
 
856
799
    def test_source_to_remote_non_exact_pack_19(self):
857
800
        trans = self.make_smart_server('target')
860
803
        target = self.make_repository('target', format='1.6')
861
804
        target = repository.Repository.open(trans.base)
862
805
        stream_source = source._get_source(target._format)
863
 
        self.assertIs(type(stream_source), repository.StreamSource)
 
806
        self.assertIs(type(stream_source), vf_repository.StreamSource)
864
807
 
865
808
    def test_stream_source_to_knit(self):
866
809
        source = self.make_repository('source', format='pack-0.92')
867
810
        target = self.make_repository('target', format='dirstate')
868
811
        stream = source._get_source(target._format)
869
 
        self.assertIs(type(stream), repository.StreamSource)
 
812
        self.assertIs(type(stream), vf_repository.StreamSource)
870
813
 
871
814
 
872
815
class TestDevelopment6FindParentIdsOfRevisions(TestCaseWithTransport):
874
817
 
875
818
    def setUp(self):
876
819
        super(TestDevelopment6FindParentIdsOfRevisions, self).setUp()
877
 
        self.builder = self.make_branch_builder('source',
878
 
            format='development6-rich-root')
 
820
        self.builder = self.make_branch_builder('source')
879
821
        self.builder.start_series()
880
822
        self.builder.build_snapshot('initial', None,
881
823
            [('add', ('', 'tree-root', 'directory', None))])
946
888
            inv = inventory.Inventory(revision_id='rev1a')
947
889
            inv.root.revision = 'rev1a'
948
890
            self.add_file(repo, inv, 'file1', 'rev1a', [])
 
891
            repo.texts.add_lines((inv.root.file_id, 'rev1a'), [], [])
949
892
            repo.add_inventory('rev1a', inv, [])
950
893
            revision = _mod_revision.Revision('rev1a',
951
894
                committer='jrandom@example.com', timestamp=0,
986
929
    def add_revision(self, repo, revision_id, inv, parent_ids):
987
930
        inv.revision_id = revision_id
988
931
        inv.root.revision = revision_id
 
932
        repo.texts.add_lines((inv.root.file_id, revision_id), [], [])
989
933
        repo.add_inventory(revision_id, inv, parent_ids)
990
934
        revision = _mod_revision.Revision(revision_id,
991
935
            committer='jrandom@example.com', timestamp=0, inventory_sha1='',
1008
952
        """
1009
953
        broken_repo = self.make_broken_repository()
1010
954
        empty_repo = self.make_repository('empty-repo')
1011
 
        # See bug https://bugs.launchpad.net/bzr/+bug/389141 for information
1012
 
        # about why this was turned into expectFailure
1013
 
        self.expectFailure('new Stream fetch fills in missing compression'
1014
 
           ' parents (bug #389141)',
1015
 
           self.assertRaises, (errors.RevisionNotPresent, errors.BzrCheckError),
1016
 
                              empty_repo.fetch, broken_repo)
1017
 
        self.assertRaises((errors.RevisionNotPresent, errors.BzrCheckError),
1018
 
                          empty_repo.fetch, broken_repo)
 
955
        try:
 
956
            empty_repo.fetch(broken_repo)
 
957
        except (errors.RevisionNotPresent, errors.BzrCheckError):
 
958
            # Test successful: compression parent not being copied leads to
 
959
            # error.
 
960
            return
 
961
        empty_repo.lock_read()
 
962
        self.addCleanup(empty_repo.unlock)
 
963
        text = empty_repo.texts.get_record_stream(
 
964
            [('file2-id', 'rev3')], 'topological', True).next()
 
965
        self.assertEqual('line\n', text.get_bytes_as('fulltext'))
1019
966
 
1020
967
 
1021
968
class TestRepositoryPackCollection(TestCaseWithTransport):
1030
977
 
1031
978
    def make_packs_and_alt_repo(self, write_lock=False):
1032
979
        """Create a pack repo with 3 packs, and access it via a second repo."""
1033
 
        tree = self.make_branch_and_tree('.')
 
980
        tree = self.make_branch_and_tree('.', format=self.get_format())
1034
981
        tree.lock_write()
1035
982
        self.addCleanup(tree.unlock)
1036
983
        rev1 = tree.commit('one')
1046
993
        packs.ensure_loaded()
1047
994
        return tree, r, packs, [rev1, rev2, rev3]
1048
995
 
 
996
    def test__clear_obsolete_packs(self):
 
997
        packs = self.get_packs()
 
998
        obsolete_pack_trans = packs.transport.clone('obsolete_packs')
 
999
        obsolete_pack_trans.put_bytes('a-pack.pack', 'content\n')
 
1000
        obsolete_pack_trans.put_bytes('a-pack.rix', 'content\n')
 
1001
        obsolete_pack_trans.put_bytes('a-pack.iix', 'content\n')
 
1002
        obsolete_pack_trans.put_bytes('another-pack.pack', 'foo\n')
 
1003
        obsolete_pack_trans.put_bytes('not-a-pack.rix', 'foo\n')
 
1004
        res = packs._clear_obsolete_packs()
 
1005
        self.assertEqual(['a-pack', 'another-pack'], sorted(res))
 
1006
        self.assertEqual([], obsolete_pack_trans.list_dir('.'))
 
1007
 
 
1008
    def test__clear_obsolete_packs_preserve(self):
 
1009
        packs = self.get_packs()
 
1010
        obsolete_pack_trans = packs.transport.clone('obsolete_packs')
 
1011
        obsolete_pack_trans.put_bytes('a-pack.pack', 'content\n')
 
1012
        obsolete_pack_trans.put_bytes('a-pack.rix', 'content\n')
 
1013
        obsolete_pack_trans.put_bytes('a-pack.iix', 'content\n')
 
1014
        obsolete_pack_trans.put_bytes('another-pack.pack', 'foo\n')
 
1015
        obsolete_pack_trans.put_bytes('not-a-pack.rix', 'foo\n')
 
1016
        res = packs._clear_obsolete_packs(preserve=set(['a-pack']))
 
1017
        self.assertEqual(['a-pack', 'another-pack'], sorted(res))
 
1018
        self.assertEqual(['a-pack.iix', 'a-pack.pack', 'a-pack.rix'],
 
1019
                         sorted(obsolete_pack_trans.list_dir('.')))
 
1020
 
1049
1021
    def test__max_pack_count(self):
1050
1022
        """The maximum pack count is a function of the number of revisions."""
1051
1023
        # no revisions - one pack, so that we can have a revision free repo
1071
1043
        # check some arbitrary big numbers
1072
1044
        self.assertEqual(25, packs._max_pack_count(112894))
1073
1045
 
 
1046
    def test_repr(self):
 
1047
        packs = self.get_packs()
 
1048
        self.assertContainsRe(repr(packs),
 
1049
            'RepositoryPackCollection(.*Repository(.*))')
 
1050
 
 
1051
    def test__obsolete_packs(self):
 
1052
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
 
1053
        names = packs.names()
 
1054
        pack = packs.get_pack_by_name(names[0])
 
1055
        # Schedule this one for removal
 
1056
        packs._remove_pack_from_memory(pack)
 
1057
        # Simulate a concurrent update by renaming the .pack file and one of
 
1058
        # the indices
 
1059
        packs.transport.rename('packs/%s.pack' % (names[0],),
 
1060
                               'obsolete_packs/%s.pack' % (names[0],))
 
1061
        packs.transport.rename('indices/%s.iix' % (names[0],),
 
1062
                               'obsolete_packs/%s.iix' % (names[0],))
 
1063
        # Now trigger the obsoletion, and ensure that all the remaining files
 
1064
        # are still renamed
 
1065
        packs._obsolete_packs([pack])
 
1066
        self.assertEqual([n + '.pack' for n in names[1:]],
 
1067
                         sorted(packs._pack_transport.list_dir('.')))
 
1068
        # names[0] should not be present in the index anymore
 
1069
        self.assertEqual(names[1:],
 
1070
            sorted(set([osutils.splitext(n)[0] for n in
 
1071
                        packs._index_transport.list_dir('.')])))
 
1072
 
1074
1073
    def test_pack_distribution_zero(self):
1075
1074
        packs = self.get_packs()
1076
1075
        self.assertEqual([0], packs.pack_distribution(0))
1244
1243
        self.assertEqual({revs[-1]:(revs[-2],)}, r.get_parent_map([revs[-1]]))
1245
1244
        self.assertFalse(packs.reload_pack_names())
1246
1245
 
 
1246
    def test_reload_pack_names_preserves_pending(self):
 
1247
        # TODO: Update this to also test for pending-deleted names
 
1248
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
 
1249
        # We will add one pack (via start_write_group + insert_record_stream),
 
1250
        # and remove another pack (via _remove_pack_from_memory)
 
1251
        orig_names = packs.names()
 
1252
        orig_at_load = packs._packs_at_load
 
1253
        to_remove_name = iter(orig_names).next()
 
1254
        r.start_write_group()
 
1255
        self.addCleanup(r.abort_write_group)
 
1256
        r.texts.insert_record_stream([versionedfile.FulltextContentFactory(
 
1257
            ('text', 'rev'), (), None, 'content\n')])
 
1258
        new_pack = packs._new_pack
 
1259
        self.assertTrue(new_pack.data_inserted())
 
1260
        new_pack.finish()
 
1261
        packs.allocate(new_pack)
 
1262
        packs._new_pack = None
 
1263
        removed_pack = packs.get_pack_by_name(to_remove_name)
 
1264
        packs._remove_pack_from_memory(removed_pack)
 
1265
        names = packs.names()
 
1266
        all_nodes, deleted_nodes, new_nodes, _ = packs._diff_pack_names()
 
1267
        new_names = set([x[0][0] for x in new_nodes])
 
1268
        self.assertEqual(names, sorted([x[0][0] for x in all_nodes]))
 
1269
        self.assertEqual(set(names) - set(orig_names), new_names)
 
1270
        self.assertEqual(set([new_pack.name]), new_names)
 
1271
        self.assertEqual([to_remove_name],
 
1272
                         sorted([x[0][0] for x in deleted_nodes]))
 
1273
        packs.reload_pack_names()
 
1274
        reloaded_names = packs.names()
 
1275
        self.assertEqual(orig_at_load, packs._packs_at_load)
 
1276
        self.assertEqual(names, reloaded_names)
 
1277
        all_nodes, deleted_nodes, new_nodes, _ = packs._diff_pack_names()
 
1278
        new_names = set([x[0][0] for x in new_nodes])
 
1279
        self.assertEqual(names, sorted([x[0][0] for x in all_nodes]))
 
1280
        self.assertEqual(set(names) - set(orig_names), new_names)
 
1281
        self.assertEqual(set([new_pack.name]), new_names)
 
1282
        self.assertEqual([to_remove_name],
 
1283
                         sorted([x[0][0] for x in deleted_nodes]))
 
1284
 
 
1285
    def test_autopack_obsoletes_new_pack(self):
 
1286
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
 
1287
        packs._max_pack_count = lambda x: 1
 
1288
        packs.pack_distribution = lambda x: [10]
 
1289
        r.start_write_group()
 
1290
        r.revisions.insert_record_stream([versionedfile.FulltextContentFactory(
 
1291
            ('bogus-rev',), (), None, 'bogus-content\n')])
 
1292
        # This should trigger an autopack, which will combine everything into a
 
1293
        # single pack file.
 
1294
        new_names = r.commit_write_group()
 
1295
        names = packs.names()
 
1296
        self.assertEqual(1, len(names))
 
1297
        self.assertEqual([names[0] + '.pack'],
 
1298
                         packs._pack_transport.list_dir('.'))
 
1299
 
1247
1300
    def test_autopack_reloads_and_stops(self):
1248
1301
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1249
1302
        # After we have determined what needs to be autopacked, trigger a
1261
1314
        self.assertEqual(tree.branch.repository._pack_collection.names(),
1262
1315
                         packs.names())
1263
1316
 
 
1317
    def test__save_pack_names(self):
 
1318
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
 
1319
        names = packs.names()
 
1320
        pack = packs.get_pack_by_name(names[0])
 
1321
        packs._remove_pack_from_memory(pack)
 
1322
        packs._save_pack_names(obsolete_packs=[pack])
 
1323
        cur_packs = packs._pack_transport.list_dir('.')
 
1324
        self.assertEqual([n + '.pack' for n in names[1:]], sorted(cur_packs))
 
1325
        # obsolete_packs will also have stuff like .rix and .iix present.
 
1326
        obsolete_packs = packs.transport.list_dir('obsolete_packs')
 
1327
        obsolete_names = set([osutils.splitext(n)[0] for n in obsolete_packs])
 
1328
        self.assertEqual([pack.name], sorted(obsolete_names))
 
1329
 
 
1330
    def test__save_pack_names_already_obsoleted(self):
 
1331
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
 
1332
        names = packs.names()
 
1333
        pack = packs.get_pack_by_name(names[0])
 
1334
        packs._remove_pack_from_memory(pack)
 
1335
        # We are going to simulate a concurrent autopack by manually obsoleting
 
1336
        # the pack directly.
 
1337
        packs._obsolete_packs([pack])
 
1338
        packs._save_pack_names(clear_obsolete_packs=True,
 
1339
                               obsolete_packs=[pack])
 
1340
        cur_packs = packs._pack_transport.list_dir('.')
 
1341
        self.assertEqual([n + '.pack' for n in names[1:]], sorted(cur_packs))
 
1342
        # Note that while we set clear_obsolete_packs=True, it should not
 
1343
        # delete a pack file that we have also scheduled for obsoletion.
 
1344
        obsolete_packs = packs.transport.list_dir('obsolete_packs')
 
1345
        obsolete_names = set([osutils.splitext(n)[0] for n in obsolete_packs])
 
1346
        self.assertEqual([pack.name], sorted(obsolete_names))
 
1347
 
 
1348
 
1264
1349
 
1265
1350
class TestPack(TestCaseWithTransport):
1266
1351
    """Tests for the Pack object."""
1330
1415
            index_class=BTreeGraphIndex,
1331
1416
            use_chk_index=False)
1332
1417
        pack = pack_repo.NewPack(collection)
 
1418
        self.addCleanup(pack.abort) # Make sure the write stream gets closed
1333
1419
        self.assertIsInstance(pack.revision_index, BTreeBuilder)
1334
1420
        self.assertIsInstance(pack.inventory_index, BTreeBuilder)
1335
1421
        self.assertIsInstance(pack._hash, type(osutils.md5()))
1346
1432
    """Tests for the packs repository Packer class."""
1347
1433
 
1348
1434
    def test_pack_optimizes_pack_order(self):
1349
 
        builder = self.make_branch_builder('.')
 
1435
        builder = self.make_branch_builder('.', format="1.9")
1350
1436
        builder.start_series()
1351
1437
        builder.build_snapshot('A', None, [
1352
1438
            ('add', ('', 'root-id', 'directory', None)),
1365
1451
        # Because of how they were built, they correspond to
1366
1452
        # ['D', 'C', 'B', 'A']
1367
1453
        packs = b.repository._pack_collection.packs
1368
 
        packer = pack_repo.Packer(b.repository._pack_collection,
 
1454
        packer = knitpack_repo.KnitPacker(b.repository._pack_collection,
1369
1455
                                  packs, 'testing',
1370
1456
                                  revision_ids=['B', 'C'])
1371
1457
        # Now, when we are copying the B & C revisions, their pack files should
1385
1471
        return repo._pack_collection
1386
1472
 
1387
1473
    def test_open_pack_will_optimise(self):
1388
 
        packer = pack_repo.OptimisingPacker(self.get_pack_collection(),
 
1474
        packer = knitpack_repo.OptimisingKnitPacker(self.get_pack_collection(),
1389
1475
                                            [], '.test')
1390
1476
        new_pack = packer.open_pack()
 
1477
        self.addCleanup(new_pack.abort) # ensure cleanup
1391
1478
        self.assertIsInstance(new_pack, pack_repo.NewPack)
1392
1479
        self.assertTrue(new_pack.revision_index._optimize_for_size)
1393
1480
        self.assertTrue(new_pack.inventory_index._optimize_for_size)
1395
1482
        self.assertTrue(new_pack.signature_index._optimize_for_size)
1396
1483
 
1397
1484
 
 
1485
class TestGCCHKPacker(TestCaseWithTransport):
 
1486
 
 
1487
    def make_abc_branch(self):
 
1488
        builder = self.make_branch_builder('source')
 
1489
        builder.start_series()
 
1490
        builder.build_snapshot('A', None, [
 
1491
            ('add', ('', 'root-id', 'directory', None)),
 
1492
            ('add', ('file', 'file-id', 'file', 'content\n')),
 
1493
            ])
 
1494
        builder.build_snapshot('B', ['A'], [
 
1495
            ('add', ('dir', 'dir-id', 'directory', None))])
 
1496
        builder.build_snapshot('C', ['B'], [
 
1497
            ('modify', ('file-id', 'new content\n'))])
 
1498
        builder.finish_series()
 
1499
        return builder.get_branch()
 
1500
 
 
1501
    def make_branch_with_disjoint_inventory_and_revision(self):
 
1502
        """a repo with separate packs for a revisions Revision and Inventory.
 
1503
 
 
1504
        There will be one pack file that holds the Revision content, and one
 
1505
        for the Inventory content.
 
1506
 
 
1507
        :return: (repository,
 
1508
                  pack_name_with_rev_A_Revision,
 
1509
                  pack_name_with_rev_A_Inventory,
 
1510
                  pack_name_with_rev_C_content)
 
1511
        """
 
1512
        b_source = self.make_abc_branch()
 
1513
        b_base = b_source.bzrdir.sprout('base', revision_id='A').open_branch()
 
1514
        b_stacked = b_base.bzrdir.sprout('stacked', stacked=True).open_branch()
 
1515
        b_stacked.lock_write()
 
1516
        self.addCleanup(b_stacked.unlock)
 
1517
        b_stacked.fetch(b_source, 'B')
 
1518
        # Now re-open the stacked repo directly (no fallbacks) so that we can
 
1519
        # fill in the A rev.
 
1520
        repo_not_stacked = b_stacked.bzrdir.open_repository()
 
1521
        repo_not_stacked.lock_write()
 
1522
        self.addCleanup(repo_not_stacked.unlock)
 
1523
        # Now we should have a pack file with A's inventory, but not its
 
1524
        # Revision
 
1525
        self.assertEqual([('A',), ('B',)],
 
1526
                         sorted(repo_not_stacked.inventories.keys()))
 
1527
        self.assertEqual([('B',)],
 
1528
                         sorted(repo_not_stacked.revisions.keys()))
 
1529
        stacked_pack_names = repo_not_stacked._pack_collection.names()
 
1530
        # We have a couple names here, figure out which has A's inventory
 
1531
        for name in stacked_pack_names:
 
1532
            pack = repo_not_stacked._pack_collection.get_pack_by_name(name)
 
1533
            keys = [n[1] for n in pack.inventory_index.iter_all_entries()]
 
1534
            if ('A',) in keys:
 
1535
                inv_a_pack_name = name
 
1536
                break
 
1537
        else:
 
1538
            self.fail('Could not find pack containing A\'s inventory')
 
1539
        repo_not_stacked.fetch(b_source.repository, 'A')
 
1540
        self.assertEqual([('A',), ('B',)],
 
1541
                         sorted(repo_not_stacked.revisions.keys()))
 
1542
        new_pack_names = set(repo_not_stacked._pack_collection.names())
 
1543
        rev_a_pack_names = new_pack_names.difference(stacked_pack_names)
 
1544
        self.assertEqual(1, len(rev_a_pack_names))
 
1545
        rev_a_pack_name = list(rev_a_pack_names)[0]
 
1546
        # Now fetch 'C', so we have a couple pack files to join
 
1547
        repo_not_stacked.fetch(b_source.repository, 'C')
 
1548
        rev_c_pack_names = set(repo_not_stacked._pack_collection.names())
 
1549
        rev_c_pack_names = rev_c_pack_names.difference(new_pack_names)
 
1550
        self.assertEqual(1, len(rev_c_pack_names))
 
1551
        rev_c_pack_name = list(rev_c_pack_names)[0]
 
1552
        return (repo_not_stacked, rev_a_pack_name, inv_a_pack_name,
 
1553
                rev_c_pack_name)
 
1554
 
 
1555
    def test_pack_with_distant_inventories(self):
 
1556
        # See https://bugs.launchpad.net/bzr/+bug/437003
 
1557
        # When repacking, it is possible to have an inventory in a different
 
1558
        # pack file than the associated revision. An autopack can then come
 
1559
        # along, and miss that inventory, and complain.
 
1560
        (repo, rev_a_pack_name, inv_a_pack_name, rev_c_pack_name
 
1561
         ) = self.make_branch_with_disjoint_inventory_and_revision()
 
1562
        a_pack = repo._pack_collection.get_pack_by_name(rev_a_pack_name)
 
1563
        c_pack = repo._pack_collection.get_pack_by_name(rev_c_pack_name)
 
1564
        packer = groupcompress_repo.GCCHKPacker(repo._pack_collection,
 
1565
                    [a_pack, c_pack], '.test-pack')
 
1566
        # This would raise ValueError in bug #437003, but should not raise an
 
1567
        # error once fixed.
 
1568
        packer.pack()
 
1569
 
 
1570
    def test_pack_with_missing_inventory(self):
 
1571
        # Similar to test_pack_with_missing_inventory, but this time, we force
 
1572
        # the A inventory to actually be gone from the repository.
 
1573
        (repo, rev_a_pack_name, inv_a_pack_name, rev_c_pack_name
 
1574
         ) = self.make_branch_with_disjoint_inventory_and_revision()
 
1575
        inv_a_pack = repo._pack_collection.get_pack_by_name(inv_a_pack_name)
 
1576
        repo._pack_collection._remove_pack_from_memory(inv_a_pack)
 
1577
        packer = groupcompress_repo.GCCHKPacker(repo._pack_collection,
 
1578
            repo._pack_collection.all_packs(), '.test-pack')
 
1579
        e = self.assertRaises(ValueError, packer.pack)
 
1580
        packer.new_pack.abort()
 
1581
        self.assertContainsRe(str(e),
 
1582
            r"We are missing inventories for revisions: .*'A'")
 
1583
 
 
1584
 
1398
1585
class TestCrossFormatPacks(TestCaseWithTransport):
1399
1586
 
1400
1587
    def log_pack(self, hint=None):
1415
1602
        self.addCleanup(target.unlock)
1416
1603
        source = source_tree.branch.repository._get_source(target._format)
1417
1604
        self.orig_pack = target.pack
1418
 
        target.pack = self.log_pack
 
1605
        self.overrideAttr(target, "pack", self.log_pack)
1419
1606
        search = target.search_missing_revision_ids(
1420
 
            source_tree.branch.repository, tip)
 
1607
            source_tree.branch.repository, revision_ids=[tip])
1421
1608
        stream = source.get_stream(search)
1422
1609
        from_format = source_tree.branch.repository._format
1423
1610
        sink = target._get_sink()
1439
1626
        self.addCleanup(target.unlock)
1440
1627
        source = source_tree.branch.repository
1441
1628
        self.orig_pack = target.pack
1442
 
        target.pack = self.log_pack
 
1629
        self.overrideAttr(target, "pack", self.log_pack)
1443
1630
        target.fetch(source)
1444
1631
        if expect_pack_called:
1445
1632
            self.assertLength(1, self.calls)
1473
1660
    def test_IDS_format_same_no(self):
1474
1661
        # When the formats are the same, pack is not called.
1475
1662
        self.run_fetch('2a', '2a', False)
 
1663
 
 
1664
 
 
1665
class Test_LazyListJoin(tests.TestCase):
 
1666
 
 
1667
    def test__repr__(self):
 
1668
        lazy = repository._LazyListJoin(['a'], ['b'])
 
1669
        self.assertEqual("bzrlib.repository._LazyListJoin((['a'], ['b']))",
 
1670
                         repr(lazy))