/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_repository.py

  • Committer: Martin Pool
  • Date: 2009-12-14 06:06:59 UTC
  • mfrom: (4889 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4891.
  • Revision ID: mbp@sourcefrog.net-20091214060659-1ucv8hpnky0cbnaj
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for the Repository facility that are not interface tests.
18
18
 
19
 
For interface tests see tests/repository_implementations/*.py.
 
19
For interface tests see tests/per_repository/*.py.
20
20
 
21
21
For concrete class tests see this file, and for storage formats tests
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
 
27
import sys
28
28
 
29
29
import bzrlib
30
30
from bzrlib.errors import (NotBranchError,
32
32
                           UnknownFormatError,
33
33
                           UnsupportedFormatError,
34
34
                           )
35
 
from bzrlib import graph
 
35
from bzrlib import (
 
36
    graph,
 
37
    tests,
 
38
    )
 
39
from bzrlib.branchbuilder import BranchBuilder
 
40
from bzrlib.btree_index import BTreeBuilder, BTreeGraphIndex
36
41
from bzrlib.index import GraphIndex, InMemoryGraphIndex
37
42
from bzrlib.repository import RepositoryFormat
38
43
from bzrlib.smart import server
39
44
from bzrlib.tests import (
40
45
    TestCase,
41
46
    TestCaseWithTransport,
 
47
    TestSkipped,
42
48
    test_knit,
43
49
    )
44
 
from bzrlib.transport import get_transport
 
50
from bzrlib.transport import (
 
51
    fakenfs,
 
52
    get_transport,
 
53
    )
45
54
from bzrlib.transport.memory import MemoryServer
46
 
from bzrlib.util import bencode
47
55
from bzrlib import (
 
56
    bencode,
48
57
    bzrdir,
49
58
    errors,
50
59
    inventory,
 
60
    osutils,
51
61
    progress,
52
62
    repository,
53
63
    revision as _mod_revision,
55
65
    upgrade,
56
66
    workingtree,
57
67
    )
58
 
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
 
68
from bzrlib.repofmt import (
 
69
    groupcompress_repo,
 
70
    knitrepo,
 
71
    pack_repo,
 
72
    weaverepo,
 
73
    )
59
74
 
60
75
 
61
76
class TestDefaultFormat(TestCase):
90
105
class SampleRepositoryFormat(repository.RepositoryFormat):
91
106
    """A sample format
92
107
 
93
 
    this format is initializable, unsupported to aid in testing the 
 
108
    this format is initializable, unsupported to aid in testing the
94
109
    open and open(unsupported=True) routines.
95
110
    """
96
111
 
117
132
    def test_find_format(self):
118
133
        # is the right format object found for a repository?
119
134
        # create a branch with a few known format objects.
120
 
        # this is not quite the same as 
 
135
        # this is not quite the same as
121
136
        self.build_tree(["foo/", "bar/"])
122
137
        def check_format(format, url):
123
138
            dir = format._matchingbzrdir.initialize(url)
126
141
            found_format = repository.RepositoryFormat.find_format(dir)
127
142
            self.failUnless(isinstance(found_format, format.__class__))
128
143
        check_format(weaverepo.RepositoryFormat7(), "bar")
129
 
        
 
144
 
130
145
    def test_find_format_no_repository(self):
131
146
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
132
147
        self.assertRaises(errors.NoRepositoryPresent,
158
173
 
159
174
class TestFormat6(TestCaseWithTransport):
160
175
 
 
176
    def test_attribute__fetch_order(self):
 
177
        """Weaves need topological data insertion."""
 
178
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
 
179
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
180
        self.assertEqual('topological', repo._format._fetch_order)
 
181
 
 
182
    def test_attribute__fetch_uses_deltas(self):
 
183
        """Weaves do not reuse deltas."""
 
184
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
 
185
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
186
        self.assertEqual(False, repo._format._fetch_uses_deltas)
 
187
 
 
188
    def test_attribute__fetch_reconcile(self):
 
189
        """Weave repositories need a reconcile after fetch."""
 
190
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
 
191
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
192
        self.assertEqual(True, repo._format._fetch_reconcile)
 
193
 
161
194
    def test_no_ancestry_weave(self):
162
195
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
163
196
        repo = weaverepo.RepositoryFormat6().initialize(control)
167
200
                          control.transport.get,
168
201
                          'ancestry.weave')
169
202
 
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
203
    def test_supports_external_lookups(self):
180
204
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
181
205
        repo = weaverepo.RepositoryFormat6().initialize(control)
183
207
 
184
208
 
185
209
class TestFormat7(TestCaseWithTransport):
186
 
    
 
210
 
 
211
    def test_attribute__fetch_order(self):
 
212
        """Weaves need topological data insertion."""
 
213
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
214
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
215
        self.assertEqual('topological', repo._format._fetch_order)
 
216
 
 
217
    def test_attribute__fetch_uses_deltas(self):
 
218
        """Weaves do not reuse deltas."""
 
219
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
220
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
221
        self.assertEqual(False, repo._format._fetch_uses_deltas)
 
222
 
 
223
    def test_attribute__fetch_reconcile(self):
 
224
        """Weave repositories need a reconcile after fetch."""
 
225
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
226
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
227
        self.assertEqual(True, repo._format._fetch_reconcile)
 
228
 
187
229
    def test_disk_layout(self):
188
230
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
189
231
        repo = weaverepo.RepositoryFormat7().initialize(control)
205
247
                             'w\n'
206
248
                             'W\n',
207
249
                             t.get('inventory.weave').read())
 
250
        # Creating a file with id Foo:Bar results in a non-escaped file name on
 
251
        # disk.
 
252
        control.create_branch()
 
253
        tree = control.create_workingtree()
 
254
        tree.add(['foo'], ['Foo:Bar'], ['file'])
 
255
        tree.put_file_bytes_non_atomic('Foo:Bar', 'content\n')
 
256
        try:
 
257
            tree.commit('first post', rev_id='first')
 
258
        except errors.IllegalPath:
 
259
            if sys.platform != 'win32':
 
260
                raise
 
261
            self.knownFailure('Foo:Bar cannot be used as a file-id on windows'
 
262
                              ' in repo format 7')
 
263
            return
 
264
        self.assertEqualDiff(
 
265
            '# bzr weave file v5\n'
 
266
            'i\n'
 
267
            '1 7fe70820e08a1aac0ef224d9c66ab66831cc4ab1\n'
 
268
            'n first\n'
 
269
            '\n'
 
270
            'w\n'
 
271
            '{ 0\n'
 
272
            '. content\n'
 
273
            '}\n'
 
274
            'W\n',
 
275
            t.get('weaves/74/Foo%3ABar.weave').read())
208
276
 
209
277
    def test_shared_disk_layout(self):
210
278
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
233
301
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
234
302
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
235
303
        t = control.get_repository_transport(None)
236
 
        # TODO: Should check there is a 'lock' toplevel directory, 
 
304
        # TODO: Should check there is a 'lock' toplevel directory,
237
305
        # regardless of contents
238
306
        self.assertFalse(t.has('lock/held/info'))
239
307
        repo.lock_write()
285
353
                             'W\n',
286
354
                             t.get('inventory.weave').read())
287
355
 
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
356
    def test_supports_external_lookups(self):
298
357
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
299
358
        repo = weaverepo.RepositoryFormat7().initialize(control)
301
360
 
302
361
 
303
362
class TestFormatKnit1(TestCaseWithTransport):
304
 
    
 
363
 
 
364
    def test_attribute__fetch_order(self):
 
365
        """Knits need topological data insertion."""
 
366
        repo = self.make_repository('.',
 
367
                format=bzrdir.format_registry.get('knit')())
 
368
        self.assertEqual('topological', repo._format._fetch_order)
 
369
 
 
370
    def test_attribute__fetch_uses_deltas(self):
 
371
        """Knits reuse deltas."""
 
372
        repo = self.make_repository('.',
 
373
                format=bzrdir.format_registry.get('knit')())
 
374
        self.assertEqual(True, repo._format._fetch_uses_deltas)
 
375
 
305
376
    def test_disk_layout(self):
306
377
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
307
378
        repo = knitrepo.RepositoryFormatKnit1().initialize(control)
321
392
        # self.assertEqualDiff('', t.get('lock').read())
322
393
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
323
394
        self.check_knits(t)
 
395
        # Check per-file knits.
 
396
        branch = control.create_branch()
 
397
        tree = control.create_workingtree()
 
398
        tree.add(['foo'], ['Nasty-IdC:'], ['file'])
 
399
        tree.put_file_bytes_non_atomic('Nasty-IdC:', '')
 
400
        tree.commit('1st post', rev_id='foo')
 
401
        self.assertHasKnit(t, 'knits/e8/%254easty-%2549d%2543%253a',
 
402
            '\nfoo fulltext 0 81  :')
324
403
 
325
 
    def assertHasKnit(self, t, knit_name):
 
404
    def assertHasKnit(self, t, knit_name, extra_content=''):
326
405
        """Assert that knit_name exists on t."""
327
 
        self.assertEqualDiff('# bzr knit index 8\n',
 
406
        self.assertEqualDiff('# bzr knit index 8\n' + extra_content,
328
407
                             t.get(knit_name + '.kndx').read())
329
 
        # no default content
330
 
        self.assertTrue(t.has(knit_name + '.knit'))
331
408
 
332
409
    def check_knits(self, t):
333
410
        """check knit content for a repository."""
377
454
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
378
455
        self.check_knits(t)
379
456
 
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
457
    def test_deserialise_sets_root_revision(self):
391
458
        """We must have a inventory.root.revision
392
459
 
420
487
        self.assertFalse(repo._format.supports_external_lookups)
421
488
 
422
489
 
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
490
class DummyRepository(object):
487
491
    """A dummy repository for testing."""
488
492
 
 
493
    _format = None
489
494
    _serializer = None
490
495
 
491
496
    def supports_rich_root(self):
 
497
        if self._format is not None:
 
498
            return self._format.rich_root_data
492
499
        return False
493
500
 
 
501
    def get_graph(self):
 
502
        raise NotImplementedError
 
503
 
 
504
    def get_parent_map(self, revision_ids):
 
505
        raise NotImplementedError
 
506
 
494
507
 
495
508
class InterDummy(repository.InterRepository):
496
509
    """An inter-repository optimised code path for DummyRepository.
503
516
    @staticmethod
504
517
    def is_compatible(repo_source, repo_target):
505
518
        """InterDummy is compatible with DummyRepository."""
506
 
        return (isinstance(repo_source, DummyRepository) and 
 
519
        return (isinstance(repo_source, DummyRepository) and
507
520
            isinstance(repo_target, DummyRepository))
508
521
 
509
522
 
522
535
 
523
536
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
524
537
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default.
525
 
        
 
538
 
526
539
        The effective default is now InterSameDataRepository because there is
527
540
        no actual sane default in the presence of incompatible data models.
528
541
        """
539
552
        # pair that it returns true on for the is_compatible static method
540
553
        # check
541
554
        dummy_a = DummyRepository()
 
555
        dummy_a._format = RepositoryFormat()
542
556
        dummy_b = DummyRepository()
 
557
        dummy_b._format = RepositoryFormat()
543
558
        repo = self.make_repository('.')
544
559
        # hack dummies to look like repo somewhat.
545
560
        dummy_a._serializer = repo._serializer
 
561
        dummy_a._format.supports_tree_reference = repo._format.supports_tree_reference
 
562
        dummy_a._format.rich_root_data = repo._format.rich_root_data
546
563
        dummy_b._serializer = repo._serializer
 
564
        dummy_b._format.supports_tree_reference = repo._format.supports_tree_reference
 
565
        dummy_b._format.rich_root_data = repo._format.rich_root_data
547
566
        repository.InterRepository.register_optimiser(InterDummy)
548
567
        try:
549
568
            # we should get the default for something InterDummy returns False
593
612
                                                        repo_b).__class__)
594
613
 
595
614
 
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
615
class TestRepositoryConverter(TestCaseWithTransport):
640
616
 
641
617
    def test_convert_empty(self):
655
631
 
656
632
 
657
633
class TestMisc(TestCase):
658
 
    
 
634
 
659
635
    def test_unescape_xml(self):
660
636
        """We get some kind of error when malformed entities are passed"""
661
 
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;') 
 
637
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;')
662
638
 
663
639
 
664
640
class TestRepositoryFormatKnit3(TestCaseWithTransport):
665
641
 
 
642
    def test_attribute__fetch_order(self):
 
643
        """Knits need topological data insertion."""
 
644
        format = bzrdir.BzrDirMetaFormat1()
 
645
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
646
        repo = self.make_repository('.', format=format)
 
647
        self.assertEqual('topological', repo._format._fetch_order)
 
648
 
 
649
    def test_attribute__fetch_uses_deltas(self):
 
650
        """Knits reuse deltas."""
 
651
        format = bzrdir.BzrDirMetaFormat1()
 
652
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
653
        repo = self.make_repository('.', format=format)
 
654
        self.assertEqual(True, repo._format._fetch_uses_deltas)
 
655
 
666
656
    def test_convert(self):
667
657
        """Ensure the upgrade adds weaves for roots"""
668
658
        format = bzrdir.BzrDirMetaFormat1()
670
660
        tree = self.make_branch_and_tree('.', format)
671
661
        tree.commit("Dull commit", rev_id="dull")
672
662
        revision_tree = tree.branch.repository.revision_tree('dull')
673
 
        self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
674
 
            revision_tree.inventory.root.file_id)
 
663
        revision_tree.lock_read()
 
664
        try:
 
665
            self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
 
666
                revision_tree.inventory.root.file_id)
 
667
        finally:
 
668
            revision_tree.unlock()
675
669
        format = bzrdir.BzrDirMetaFormat1()
676
670
        format.repository_format = knitrepo.RepositoryFormatKnit3()
677
671
        upgrade.Convert('.', format)
678
672
        tree = workingtree.WorkingTree.open('.')
679
673
        revision_tree = tree.branch.repository.revision_tree('dull')
680
 
        revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
 
674
        revision_tree.lock_read()
 
675
        try:
 
676
            revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
 
677
        finally:
 
678
            revision_tree.unlock()
681
679
        tree.commit("Another dull commit", rev_id='dull2')
682
680
        revision_tree = tree.branch.repository.revision_tree('dull2')
 
681
        revision_tree.lock_read()
 
682
        self.addCleanup(revision_tree.unlock)
683
683
        self.assertEqual('dull', revision_tree.inventory.root.revision)
684
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
685
    def test_supports_external_lookups(self):
696
686
        format = bzrdir.BzrDirMetaFormat1()
697
687
        format.repository_format = knitrepo.RepositoryFormatKnit3()
699
689
        self.assertFalse(repo._format.supports_external_lookups)
700
690
 
701
691
 
 
692
class Test2a(tests.TestCaseWithMemoryTransport):
 
693
 
 
694
    def test_fetch_combines_groups(self):
 
695
        builder = self.make_branch_builder('source', format='2a')
 
696
        builder.start_series()
 
697
        builder.build_snapshot('1', None, [
 
698
            ('add', ('', 'root-id', 'directory', '')),
 
699
            ('add', ('file', 'file-id', 'file', 'content\n'))])
 
700
        builder.build_snapshot('2', ['1'], [
 
701
            ('modify', ('file-id', 'content-2\n'))])
 
702
        builder.finish_series()
 
703
        source = builder.get_branch()
 
704
        target = self.make_repository('target', format='2a')
 
705
        target.fetch(source.repository)
 
706
        target.lock_read()
 
707
        self.addCleanup(target.unlock)
 
708
        details = target.texts._index.get_build_details(
 
709
            [('file-id', '1',), ('file-id', '2',)])
 
710
        file_1_details = details[('file-id', '1')]
 
711
        file_2_details = details[('file-id', '2')]
 
712
        # The index, and what to read off disk, should be the same for both
 
713
        # versions of the file.
 
714
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
 
715
 
 
716
    def test_fetch_combines_groups(self):
 
717
        builder = self.make_branch_builder('source', format='2a')
 
718
        builder.start_series()
 
719
        builder.build_snapshot('1', None, [
 
720
            ('add', ('', 'root-id', 'directory', '')),
 
721
            ('add', ('file', 'file-id', 'file', 'content\n'))])
 
722
        builder.build_snapshot('2', ['1'], [
 
723
            ('modify', ('file-id', 'content-2\n'))])
 
724
        builder.finish_series()
 
725
        source = builder.get_branch()
 
726
        target = self.make_repository('target', format='2a')
 
727
        target.fetch(source.repository)
 
728
        target.lock_read()
 
729
        self.addCleanup(target.unlock)
 
730
        details = target.texts._index.get_build_details(
 
731
            [('file-id', '1',), ('file-id', '2',)])
 
732
        file_1_details = details[('file-id', '1')]
 
733
        file_2_details = details[('file-id', '2')]
 
734
        # The index, and what to read off disk, should be the same for both
 
735
        # versions of the file.
 
736
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
 
737
 
 
738
    def test_fetch_combines_groups(self):
 
739
        builder = self.make_branch_builder('source', format='2a')
 
740
        builder.start_series()
 
741
        builder.build_snapshot('1', None, [
 
742
            ('add', ('', 'root-id', 'directory', '')),
 
743
            ('add', ('file', 'file-id', 'file', 'content\n'))])
 
744
        builder.build_snapshot('2', ['1'], [
 
745
            ('modify', ('file-id', 'content-2\n'))])
 
746
        builder.finish_series()
 
747
        source = builder.get_branch()
 
748
        target = self.make_repository('target', format='2a')
 
749
        target.fetch(source.repository)
 
750
        target.lock_read()
 
751
        self.addCleanup(target.unlock)
 
752
        details = target.texts._index.get_build_details(
 
753
            [('file-id', '1',), ('file-id', '2',)])
 
754
        file_1_details = details[('file-id', '1')]
 
755
        file_2_details = details[('file-id', '2')]
 
756
        # The index, and what to read off disk, should be the same for both
 
757
        # versions of the file.
 
758
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
 
759
 
 
760
    def test_format_pack_compresses_True(self):
 
761
        repo = self.make_repository('repo', format='2a')
 
762
        self.assertTrue(repo._format.pack_compresses)
 
763
 
 
764
    def test_inventories_use_chk_map_with_parent_base_dict(self):
 
765
        tree = self.make_branch_and_memory_tree('repo', format="2a")
 
766
        tree.lock_write()
 
767
        tree.add([''], ['TREE_ROOT'])
 
768
        revid = tree.commit("foo")
 
769
        tree.unlock()
 
770
        tree.lock_read()
 
771
        self.addCleanup(tree.unlock)
 
772
        inv = tree.branch.repository.get_inventory(revid)
 
773
        self.assertNotEqual(None, inv.parent_id_basename_to_file_id)
 
774
        inv.parent_id_basename_to_file_id._ensure_root()
 
775
        inv.id_to_entry._ensure_root()
 
776
        self.assertEqual(65536, inv.id_to_entry._root_node.maximum_size)
 
777
        self.assertEqual(65536,
 
778
            inv.parent_id_basename_to_file_id._root_node.maximum_size)
 
779
 
 
780
    def test_autopack_unchanged_chk_nodes(self):
 
781
        # at 20 unchanged commits, chk pages are packed that are split into
 
782
        # two groups such that the new pack being made doesn't have all its
 
783
        # pages in the source packs (though they are in the repository).
 
784
        # Use a memory backed repository, we don't need to hit disk for this
 
785
        tree = self.make_branch_and_memory_tree('tree', format='2a')
 
786
        tree.lock_write()
 
787
        self.addCleanup(tree.unlock)
 
788
        tree.add([''], ['TREE_ROOT'])
 
789
        for pos in range(20):
 
790
            tree.commit(str(pos))
 
791
 
 
792
    def test_pack_with_hint(self):
 
793
        tree = self.make_branch_and_memory_tree('tree', format='2a')
 
794
        tree.lock_write()
 
795
        self.addCleanup(tree.unlock)
 
796
        tree.add([''], ['TREE_ROOT'])
 
797
        # 1 commit to leave untouched
 
798
        tree.commit('1')
 
799
        to_keep = tree.branch.repository._pack_collection.names()
 
800
        # 2 to combine
 
801
        tree.commit('2')
 
802
        tree.commit('3')
 
803
        all = tree.branch.repository._pack_collection.names()
 
804
        combine = list(set(all) - set(to_keep))
 
805
        self.assertLength(3, all)
 
806
        self.assertLength(2, combine)
 
807
        tree.branch.repository.pack(hint=combine)
 
808
        final = tree.branch.repository._pack_collection.names()
 
809
        self.assertLength(2, final)
 
810
        self.assertFalse(combine[0] in final)
 
811
        self.assertFalse(combine[1] in final)
 
812
        self.assertSubset(to_keep, final)
 
813
 
 
814
    def test_stream_source_to_gc(self):
 
815
        source = self.make_repository('source', format='2a')
 
816
        target = self.make_repository('target', format='2a')
 
817
        stream = source._get_source(target._format)
 
818
        self.assertIsInstance(stream, groupcompress_repo.GroupCHKStreamSource)
 
819
 
 
820
    def test_stream_source_to_non_gc(self):
 
821
        source = self.make_repository('source', format='2a')
 
822
        target = self.make_repository('target', format='rich-root-pack')
 
823
        stream = source._get_source(target._format)
 
824
        # We don't want the child GroupCHKStreamSource
 
825
        self.assertIs(type(stream), repository.StreamSource)
 
826
 
 
827
    def test_get_stream_for_missing_keys_includes_all_chk_refs(self):
 
828
        source_builder = self.make_branch_builder('source',
 
829
                            format='2a')
 
830
        # We have to build a fairly large tree, so that we are sure the chk
 
831
        # pages will have split into multiple pages.
 
832
        entries = [('add', ('', 'a-root-id', 'directory', None))]
 
833
        for i in 'abcdefghijklmnopqrstuvwxyz123456789':
 
834
            for j in 'abcdefghijklmnopqrstuvwxyz123456789':
 
835
                fname = i + j
 
836
                fid = fname + '-id'
 
837
                content = 'content for %s\n' % (fname,)
 
838
                entries.append(('add', (fname, fid, 'file', content)))
 
839
        source_builder.start_series()
 
840
        source_builder.build_snapshot('rev-1', None, entries)
 
841
        # Now change a few of them, so we get a few new pages for the second
 
842
        # revision
 
843
        source_builder.build_snapshot('rev-2', ['rev-1'], [
 
844
            ('modify', ('aa-id', 'new content for aa-id\n')),
 
845
            ('modify', ('cc-id', 'new content for cc-id\n')),
 
846
            ('modify', ('zz-id', 'new content for zz-id\n')),
 
847
            ])
 
848
        source_builder.finish_series()
 
849
        source_branch = source_builder.get_branch()
 
850
        source_branch.lock_read()
 
851
        self.addCleanup(source_branch.unlock)
 
852
        target = self.make_repository('target', format='2a')
 
853
        source = source_branch.repository._get_source(target._format)
 
854
        self.assertIsInstance(source, groupcompress_repo.GroupCHKStreamSource)
 
855
 
 
856
        # On a regular pass, getting the inventories and chk pages for rev-2
 
857
        # would only get the newly created chk pages
 
858
        search = graph.SearchResult(set(['rev-2']), set(['rev-1']), 1,
 
859
                                    set(['rev-2']))
 
860
        simple_chk_records = []
 
861
        for vf_name, substream in source.get_stream(search):
 
862
            if vf_name == 'chk_bytes':
 
863
                for record in substream:
 
864
                    simple_chk_records.append(record.key)
 
865
            else:
 
866
                for _ in substream:
 
867
                    continue
 
868
        # 3 pages, the root (InternalNode), + 2 pages which actually changed
 
869
        self.assertEqual([('sha1:91481f539e802c76542ea5e4c83ad416bf219f73',),
 
870
                          ('sha1:4ff91971043668583985aec83f4f0ab10a907d3f',),
 
871
                          ('sha1:81e7324507c5ca132eedaf2d8414ee4bb2226187',),
 
872
                          ('sha1:b101b7da280596c71a4540e9a1eeba8045985ee0',)],
 
873
                         simple_chk_records)
 
874
        # Now, when we do a similar call using 'get_stream_for_missing_keys'
 
875
        # we should get a much larger set of pages.
 
876
        missing = [('inventories', 'rev-2')]
 
877
        full_chk_records = []
 
878
        for vf_name, substream in source.get_stream_for_missing_keys(missing):
 
879
            if vf_name == 'inventories':
 
880
                for record in substream:
 
881
                    self.assertEqual(('rev-2',), record.key)
 
882
            elif vf_name == 'chk_bytes':
 
883
                for record in substream:
 
884
                    full_chk_records.append(record.key)
 
885
            else:
 
886
                self.fail('Should not be getting a stream of %s' % (vf_name,))
 
887
        # We have 257 records now. This is because we have 1 root page, and 256
 
888
        # leaf pages in a complete listing.
 
889
        self.assertEqual(257, len(full_chk_records))
 
890
        self.assertSubset(simple_chk_records, full_chk_records)
 
891
 
 
892
    def test_inconsistency_fatal(self):
 
893
        repo = self.make_repository('repo', format='2a')
 
894
        self.assertTrue(repo.revisions._index._inconsistency_fatal)
 
895
        self.assertFalse(repo.texts._index._inconsistency_fatal)
 
896
        self.assertFalse(repo.inventories._index._inconsistency_fatal)
 
897
        self.assertFalse(repo.signatures._index._inconsistency_fatal)
 
898
        self.assertFalse(repo.chk_bytes._index._inconsistency_fatal)
 
899
 
 
900
 
 
901
class TestKnitPackStreamSource(tests.TestCaseWithMemoryTransport):
 
902
 
 
903
    def test_source_to_exact_pack_092(self):
 
904
        source = self.make_repository('source', format='pack-0.92')
 
905
        target = self.make_repository('target', format='pack-0.92')
 
906
        stream_source = source._get_source(target._format)
 
907
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
908
 
 
909
    def test_source_to_exact_pack_rich_root_pack(self):
 
910
        source = self.make_repository('source', format='rich-root-pack')
 
911
        target = self.make_repository('target', format='rich-root-pack')
 
912
        stream_source = source._get_source(target._format)
 
913
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
914
 
 
915
    def test_source_to_exact_pack_19(self):
 
916
        source = self.make_repository('source', format='1.9')
 
917
        target = self.make_repository('target', format='1.9')
 
918
        stream_source = source._get_source(target._format)
 
919
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
920
 
 
921
    def test_source_to_exact_pack_19_rich_root(self):
 
922
        source = self.make_repository('source', format='1.9-rich-root')
 
923
        target = self.make_repository('target', format='1.9-rich-root')
 
924
        stream_source = source._get_source(target._format)
 
925
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
926
 
 
927
    def test_source_to_remote_exact_pack_19(self):
 
928
        trans = self.make_smart_server('target')
 
929
        trans.ensure_base()
 
930
        source = self.make_repository('source', format='1.9')
 
931
        target = self.make_repository('target', format='1.9')
 
932
        target = repository.Repository.open(trans.base)
 
933
        stream_source = source._get_source(target._format)
 
934
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
 
935
 
 
936
    def test_stream_source_to_non_exact(self):
 
937
        source = self.make_repository('source', format='pack-0.92')
 
938
        target = self.make_repository('target', format='1.9')
 
939
        stream = source._get_source(target._format)
 
940
        self.assertIs(type(stream), repository.StreamSource)
 
941
 
 
942
    def test_stream_source_to_non_exact_rich_root(self):
 
943
        source = self.make_repository('source', format='1.9')
 
944
        target = self.make_repository('target', format='1.9-rich-root')
 
945
        stream = source._get_source(target._format)
 
946
        self.assertIs(type(stream), repository.StreamSource)
 
947
 
 
948
    def test_source_to_remote_non_exact_pack_19(self):
 
949
        trans = self.make_smart_server('target')
 
950
        trans.ensure_base()
 
951
        source = self.make_repository('source', format='1.9')
 
952
        target = self.make_repository('target', format='1.6')
 
953
        target = repository.Repository.open(trans.base)
 
954
        stream_source = source._get_source(target._format)
 
955
        self.assertIs(type(stream_source), repository.StreamSource)
 
956
 
 
957
    def test_stream_source_to_knit(self):
 
958
        source = self.make_repository('source', format='pack-0.92')
 
959
        target = self.make_repository('target', format='dirstate')
 
960
        stream = source._get_source(target._format)
 
961
        self.assertIs(type(stream), repository.StreamSource)
 
962
 
 
963
 
 
964
class TestDevelopment6FindParentIdsOfRevisions(TestCaseWithTransport):
 
965
    """Tests for _find_parent_ids_of_revisions."""
 
966
 
 
967
    def setUp(self):
 
968
        super(TestDevelopment6FindParentIdsOfRevisions, self).setUp()
 
969
        self.builder = self.make_branch_builder('source',
 
970
            format='development6-rich-root')
 
971
        self.builder.start_series()
 
972
        self.builder.build_snapshot('initial', None,
 
973
            [('add', ('', 'tree-root', 'directory', None))])
 
974
        self.repo = self.builder.get_branch().repository
 
975
        self.addCleanup(self.builder.finish_series)
 
976
 
 
977
    def assertParentIds(self, expected_result, rev_set):
 
978
        self.assertEqual(sorted(expected_result),
 
979
            sorted(self.repo._find_parent_ids_of_revisions(rev_set)))
 
980
 
 
981
    def test_simple(self):
 
982
        self.builder.build_snapshot('revid1', None, [])
 
983
        self.builder.build_snapshot('revid2', ['revid1'], [])
 
984
        rev_set = ['revid2']
 
985
        self.assertParentIds(['revid1'], rev_set)
 
986
 
 
987
    def test_not_first_parent(self):
 
988
        self.builder.build_snapshot('revid1', None, [])
 
989
        self.builder.build_snapshot('revid2', ['revid1'], [])
 
990
        self.builder.build_snapshot('revid3', ['revid2'], [])
 
991
        rev_set = ['revid3', 'revid2']
 
992
        self.assertParentIds(['revid1'], rev_set)
 
993
 
 
994
    def test_not_null(self):
 
995
        rev_set = ['initial']
 
996
        self.assertParentIds([], rev_set)
 
997
 
 
998
    def test_not_null_set(self):
 
999
        self.builder.build_snapshot('revid1', None, [])
 
1000
        rev_set = [_mod_revision.NULL_REVISION]
 
1001
        self.assertParentIds([], rev_set)
 
1002
 
 
1003
    def test_ghost(self):
 
1004
        self.builder.build_snapshot('revid1', None, [])
 
1005
        rev_set = ['ghost', 'revid1']
 
1006
        self.assertParentIds(['initial'], rev_set)
 
1007
 
 
1008
    def test_ghost_parent(self):
 
1009
        self.builder.build_snapshot('revid1', None, [])
 
1010
        self.builder.build_snapshot('revid2', ['revid1', 'ghost'], [])
 
1011
        rev_set = ['revid2', 'revid1']
 
1012
        self.assertParentIds(['ghost', 'initial'], rev_set)
 
1013
 
 
1014
    def test_righthand_parent(self):
 
1015
        self.builder.build_snapshot('revid1', None, [])
 
1016
        self.builder.build_snapshot('revid2a', ['revid1'], [])
 
1017
        self.builder.build_snapshot('revid2b', ['revid1'], [])
 
1018
        self.builder.build_snapshot('revid3', ['revid2a', 'revid2b'], [])
 
1019
        rev_set = ['revid3', 'revid2a']
 
1020
        self.assertParentIds(['revid1', 'revid2b'], rev_set)
 
1021
 
 
1022
 
702
1023
class TestWithBrokenRepo(TestCaseWithTransport):
703
1024
    """These tests seem to be more appropriate as interface tests?"""
704
1025
 
717
1038
            inv = inventory.Inventory(revision_id='rev1a')
718
1039
            inv.root.revision = 'rev1a'
719
1040
            self.add_file(repo, inv, 'file1', 'rev1a', [])
 
1041
            repo.texts.add_lines((inv.root.file_id, 'rev1a'), [], [])
720
1042
            repo.add_inventory('rev1a', inv, [])
721
1043
            revision = _mod_revision.Revision('rev1a',
722
1044
                committer='jrandom@example.com', timestamp=0,
757
1079
    def add_revision(self, repo, revision_id, inv, parent_ids):
758
1080
        inv.revision_id = revision_id
759
1081
        inv.root.revision = revision_id
 
1082
        repo.texts.add_lines((inv.root.file_id, revision_id), [], [])
760
1083
        repo.add_inventory(revision_id, inv, parent_ids)
761
1084
        revision = _mod_revision.Revision(revision_id,
762
1085
            committer='jrandom@example.com', timestamp=0, inventory_sha1='',
769
1092
        entry.revision = revision
770
1093
        entry.text_size = 0
771
1094
        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'])
 
1095
        text_key = (file_id, revision)
 
1096
        parent_keys = [(file_id, parent) for parent in parents]
 
1097
        repo.texts.add_lines(text_key, parent_keys, ['line\n'])
775
1098
 
776
1099
    def test_insert_from_broken_repo(self):
777
1100
        """Inserting a data stream from a broken repository won't silently
779
1102
        """
780
1103
        broken_repo = self.make_broken_repository()
781
1104
        empty_repo = self.make_repository('empty-repo')
782
 
        search = graph.SearchResult(set(['rev1a', 'rev2', 'rev3']),
783
 
            set(), 3, ['rev1a', 'rev2', 'rev3'])
784
 
        broken_repo.lock_read()
785
 
        self.addCleanup(broken_repo.unlock)
786
 
        stream = broken_repo.get_data_stream_for_search(search)
787
 
        empty_repo.lock_write()
 
1105
        try:
 
1106
            empty_repo.fetch(broken_repo)
 
1107
        except (errors.RevisionNotPresent, errors.BzrCheckError):
 
1108
            # Test successful: compression parent not being copied leads to
 
1109
            # error.
 
1110
            return
 
1111
        empty_repo.lock_read()
788
1112
        self.addCleanup(empty_repo.unlock)
789
 
        empty_repo.start_write_group()
790
 
        try:
791
 
            self.assertRaises(
792
 
                errors.KnitCorrupt, empty_repo.insert_data_stream, stream)
793
 
        finally:
794
 
            empty_repo.abort_write_group()
795
 
 
796
 
 
797
 
class TestKnitPackNoSubtrees(TestCaseWithTransport):
798
 
 
799
 
    def get_format(self):
800
 
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
801
 
 
802
 
    def test_disk_layout(self):
803
 
        format = self.get_format()
804
 
        repo = self.make_repository('.', format=format)
805
 
        # in case of side effects of locking.
806
 
        repo.lock_write()
807
 
        repo.unlock()
808
 
        t = repo.bzrdir.get_repository_transport(None)
809
 
        self.check_format(t)
810
 
        # XXX: no locks left when unlocked at the moment
811
 
        # self.assertEqualDiff('', t.get('lock').read())
812
 
        self.check_databases(t)
813
 
 
814
 
    def check_format(self, t):
815
 
        self.assertEqualDiff(
816
 
            "Bazaar pack repository format 1 (needs bzr 0.92)\n",
817
 
                             t.get('format').read())
818
 
 
819
 
    def assertHasKndx(self, t, knit_name):
820
 
        """Assert that knit_name exists on t."""
821
 
        self.assertEqualDiff('# bzr knit index 8\n',
822
 
                             t.get(knit_name + '.kndx').read())
823
 
 
824
 
    def assertHasNoKndx(self, t, knit_name):
825
 
        """Assert that knit_name has no index on t."""
826
 
        self.assertFalse(t.has(knit_name + '.kndx'))
827
 
 
828
 
    def assertHasNoKnit(self, t, knit_name):
829
 
        """Assert that knit_name exists on t."""
830
 
        # no default content
831
 
        self.assertFalse(t.has(knit_name + '.knit'))
832
 
 
833
 
    def check_databases(self, t):
834
 
        """check knit content for a repository."""
835
 
        # check conversion worked
836
 
        self.assertHasNoKndx(t, 'inventory')
837
 
        self.assertHasNoKnit(t, 'inventory')
838
 
        self.assertHasNoKndx(t, 'revisions')
839
 
        self.assertHasNoKnit(t, 'revisions')
840
 
        self.assertHasNoKndx(t, 'signatures')
841
 
        self.assertHasNoKnit(t, 'signatures')
842
 
        self.assertFalse(t.has('knits'))
843
 
        # revision-indexes file-container directory
844
 
        self.assertEqual([],
845
 
            list(GraphIndex(t, 'pack-names', None).iter_all_entries()))
846
 
        self.assertTrue(S_ISDIR(t.stat('packs').st_mode))
847
 
        self.assertTrue(S_ISDIR(t.stat('upload').st_mode))
848
 
        self.assertTrue(S_ISDIR(t.stat('indices').st_mode))
849
 
        self.assertTrue(S_ISDIR(t.stat('obsolete_packs').st_mode))
850
 
 
851
 
    def test_shared_disk_layout(self):
852
 
        format = self.get_format()
853
 
        repo = self.make_repository('.', shared=True, format=format)
854
 
        # we want:
855
 
        t = repo.bzrdir.get_repository_transport(None)
856
 
        self.check_format(t)
857
 
        # XXX: no locks left when unlocked at the moment
858
 
        # self.assertEqualDiff('', t.get('lock').read())
859
 
        # We should have a 'shared-storage' marker file.
860
 
        self.assertEqualDiff('', t.get('shared-storage').read())
861
 
        self.check_databases(t)
862
 
 
863
 
    def test_shared_no_tree_disk_layout(self):
864
 
        format = self.get_format()
865
 
        repo = self.make_repository('.', shared=True, format=format)
866
 
        repo.set_make_working_trees(False)
867
 
        # we want:
868
 
        t = repo.bzrdir.get_repository_transport(None)
869
 
        self.check_format(t)
870
 
        # XXX: no locks left when unlocked at the moment
871
 
        # self.assertEqualDiff('', t.get('lock').read())
872
 
        # We should have a 'shared-storage' marker file.
873
 
        self.assertEqualDiff('', t.get('shared-storage').read())
874
 
        # We should have a marker for the no-working-trees flag.
875
 
        self.assertEqualDiff('', t.get('no-working-trees').read())
876
 
        # The marker should go when we toggle the setting.
877
 
        repo.set_make_working_trees(True)
878
 
        self.assertFalse(t.has('no-working-trees'))
879
 
        self.check_databases(t)
880
 
 
881
 
    def test_adding_revision_creates_pack_indices(self):
882
 
        format = self.get_format()
883
 
        tree = self.make_branch_and_tree('.', format=format)
884
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
885
 
        self.assertEqual([],
886
 
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
887
 
        tree.commit('foobarbaz')
888
 
        index = GraphIndex(trans, 'pack-names', None)
889
 
        index_nodes = list(index.iter_all_entries())
890
 
        self.assertEqual(1, len(index_nodes))
891
 
        node = index_nodes[0]
892
 
        name = node[1][0]
893
 
        # the pack sizes should be listed in the index
894
 
        pack_value = node[2]
895
 
        sizes = [int(digits) for digits in pack_value.split(' ')]
896
 
        for size, suffix in zip(sizes, ['.rix', '.iix', '.tix', '.six']):
897
 
            stat = trans.stat('indices/%s%s' % (name, suffix))
898
 
            self.assertEqual(size, stat.st_size)
899
 
 
900
 
    def test_pulling_nothing_leads_to_no_new_names(self):
901
 
        format = self.get_format()
902
 
        tree1 = self.make_branch_and_tree('1', format=format)
903
 
        tree2 = self.make_branch_and_tree('2', format=format)
904
 
        tree1.branch.repository.fetch(tree2.branch.repository)
905
 
        trans = tree1.branch.repository.bzrdir.get_repository_transport(None)
906
 
        self.assertEqual([],
907
 
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
908
 
 
909
 
    def test_commit_across_pack_shape_boundary_autopacks(self):
910
 
        format = self.get_format()
911
 
        tree = self.make_branch_and_tree('.', format=format)
912
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
913
 
        # This test could be a little cheaper by replacing the packs
914
 
        # attribute on the repository to allow a different pack distribution
915
 
        # and max packs policy - so we are checking the policy is honoured
916
 
        # in the test. But for now 11 commits is not a big deal in a single
917
 
        # test.
918
 
        for x in range(9):
919
 
            tree.commit('commit %s' % x)
920
 
        # there should be 9 packs:
921
 
        index = GraphIndex(trans, 'pack-names', None)
922
 
        self.assertEqual(9, len(list(index.iter_all_entries())))
923
 
        # insert some files in obsolete_packs which should be removed by pack.
924
 
        trans.put_bytes('obsolete_packs/foo', '123')
925
 
        trans.put_bytes('obsolete_packs/bar', '321')
926
 
        # committing one more should coalesce to 1 of 10.
927
 
        tree.commit('commit triggering pack')
928
 
        index = GraphIndex(trans, 'pack-names', None)
929
 
        self.assertEqual(1, len(list(index.iter_all_entries())))
930
 
        # packing should not damage data
931
 
        tree = tree.bzrdir.open_workingtree()
932
 
        check_result = tree.branch.repository.check(
933
 
            [tree.branch.last_revision()])
934
 
        # We should have 50 (10x5) files in the obsolete_packs directory.
935
 
        obsolete_files = list(trans.list_dir('obsolete_packs'))
936
 
        self.assertFalse('foo' in obsolete_files)
937
 
        self.assertFalse('bar' in obsolete_files)
938
 
        self.assertEqual(50, len(obsolete_files))
939
 
        # XXX: Todo check packs obsoleted correctly - old packs and indices
940
 
        # in the obsolete_packs directory.
941
 
        large_pack_name = list(index.iter_all_entries())[0][1][0]
942
 
        # finally, committing again should not touch the large pack.
943
 
        tree.commit('commit not triggering pack')
944
 
        index = GraphIndex(trans, 'pack-names', None)
945
 
        self.assertEqual(2, len(list(index.iter_all_entries())))
946
 
        pack_names = [node[1][0] for node in index.iter_all_entries()]
947
 
        self.assertTrue(large_pack_name in pack_names)
948
 
 
949
 
    def test_pack_after_two_commits_packs_everything(self):
950
 
        format = self.get_format()
951
 
        tree = self.make_branch_and_tree('.', format=format)
952
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
953
 
        tree.commit('start')
954
 
        tree.commit('more work')
955
 
        tree.branch.repository.pack()
956
 
        # there should be 1 pack:
957
 
        index = GraphIndex(trans, 'pack-names', None)
958
 
        self.assertEqual(1, len(list(index.iter_all_entries())))
959
 
        self.assertEqual(2, len(tree.branch.repository.all_revision_ids()))
960
 
 
961
 
    def test_pack_layout(self):
962
 
        format = self.get_format()
963
 
        tree = self.make_branch_and_tree('.', format=format)
964
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
965
 
        tree.commit('start', rev_id='1')
966
 
        tree.commit('more work', rev_id='2')
967
 
        tree.branch.repository.pack()
968
 
        tree.lock_read()
969
 
        self.addCleanup(tree.unlock)
970
 
        pack = tree.branch.repository._pack_collection.get_pack_by_name(
971
 
            tree.branch.repository._pack_collection.names()[0])
972
 
        # revision access tends to be tip->ancestor, so ordering that way on 
973
 
        # disk is a good idea.
974
 
        for _1, key, val, refs in pack.revision_index.iter_all_entries():
975
 
            if key == ('1',):
976
 
                pos_1 = int(val[1:].split()[0])
977
 
            else:
978
 
                pos_2 = int(val[1:].split()[0])
979
 
        self.assertTrue(pos_2 < pos_1)
980
 
 
981
 
    def test_pack_repositories_support_multiple_write_locks(self):
982
 
        format = self.get_format()
983
 
        self.make_repository('.', shared=True, format=format)
984
 
        r1 = repository.Repository.open('.')
985
 
        r2 = repository.Repository.open('.')
986
 
        r1.lock_write()
987
 
        self.addCleanup(r1.unlock)
988
 
        r2.lock_write()
989
 
        r2.unlock()
990
 
 
991
 
    def _add_text(self, repo, fileid):
992
 
        """Add a text to the repository within a write group."""
993
 
        vf =repo.weave_store.get_weave(fileid, repo.get_transaction())
994
 
        vf.add_lines('samplerev+' + fileid, [], [])
995
 
 
996
 
    def test_concurrent_writers_merge_new_packs(self):
997
 
        format = self.get_format()
998
 
        self.make_repository('.', shared=True, format=format)
999
 
        r1 = repository.Repository.open('.')
1000
 
        r2 = repository.Repository.open('.')
1001
 
        r1.lock_write()
1002
 
        try:
1003
 
            # access enough data to load the names list
1004
 
            list(r1.all_revision_ids())
1005
 
            r2.lock_write()
1006
 
            try:
1007
 
                # access enough data to load the names list
1008
 
                list(r2.all_revision_ids())
1009
 
                r1.start_write_group()
1010
 
                try:
1011
 
                    r2.start_write_group()
1012
 
                    try:
1013
 
                        self._add_text(r1, 'fileidr1')
1014
 
                        self._add_text(r2, 'fileidr2')
1015
 
                    except:
1016
 
                        r2.abort_write_group()
1017
 
                        raise
1018
 
                except:
1019
 
                    r1.abort_write_group()
1020
 
                    raise
1021
 
                # both r1 and r2 have open write groups with data in them
1022
 
                # created while the other's write group was open.
1023
 
                # Commit both which requires a merge to the pack-names.
1024
 
                try:
1025
 
                    r1.commit_write_group()
1026
 
                except:
1027
 
                    r1.abort_write_group()
1028
 
                    r2.abort_write_group()
1029
 
                    raise
1030
 
                r2.commit_write_group()
1031
 
                # tell r1 to reload from disk
1032
 
                r1._pack_collection.reset()
1033
 
                # Now both repositories should know about both names
1034
 
                r1._pack_collection.ensure_loaded()
1035
 
                r2._pack_collection.ensure_loaded()
1036
 
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1037
 
                self.assertEqual(2, len(r1._pack_collection.names()))
1038
 
            finally:
1039
 
                r2.unlock()
1040
 
        finally:
1041
 
            r1.unlock()
1042
 
 
1043
 
    def test_concurrent_writer_second_preserves_dropping_a_pack(self):
1044
 
        format = self.get_format()
1045
 
        self.make_repository('.', shared=True, format=format)
1046
 
        r1 = repository.Repository.open('.')
1047
 
        r2 = repository.Repository.open('.')
1048
 
        # add a pack to drop
1049
 
        r1.lock_write()
1050
 
        try:
1051
 
            r1.start_write_group()
1052
 
            try:
1053
 
                self._add_text(r1, 'fileidr1')
1054
 
            except:
1055
 
                r1.abort_write_group()
1056
 
                raise
1057
 
            else:
1058
 
                r1.commit_write_group()
1059
 
            r1._pack_collection.ensure_loaded()
1060
 
            name_to_drop = r1._pack_collection.all_packs()[0].name
1061
 
        finally:
1062
 
            r1.unlock()
1063
 
        r1.lock_write()
1064
 
        try:
1065
 
            # access enough data to load the names list
1066
 
            list(r1.all_revision_ids())
1067
 
            r2.lock_write()
1068
 
            try:
1069
 
                # access enough data to load the names list
1070
 
                list(r2.all_revision_ids())
1071
 
                r1._pack_collection.ensure_loaded()
1072
 
                try:
1073
 
                    r2.start_write_group()
1074
 
                    try:
1075
 
                        # in r1, drop the pack
1076
 
                        r1._pack_collection._remove_pack_from_memory(
1077
 
                            r1._pack_collection.get_pack_by_name(name_to_drop))
1078
 
                        # in r2, add a pack
1079
 
                        self._add_text(r2, 'fileidr2')
1080
 
                    except:
1081
 
                        r2.abort_write_group()
1082
 
                        raise
1083
 
                except:
1084
 
                    r1._pack_collection.reset()
1085
 
                    raise
1086
 
                # r1 has a changed names list, and r2 an open write groups with
1087
 
                # changes.
1088
 
                # save r1, and then commit the r2 write group, which requires a
1089
 
                # merge to the pack-names, which should not reinstate
1090
 
                # name_to_drop
1091
 
                try:
1092
 
                    r1._pack_collection._save_pack_names()
1093
 
                    r1._pack_collection.reset()
1094
 
                except:
1095
 
                    r2.abort_write_group()
1096
 
                    raise
1097
 
                try:
1098
 
                    r2.commit_write_group()
1099
 
                except:
1100
 
                    r2.abort_write_group()
1101
 
                    raise
1102
 
                # Now both repositories should now about just one name.
1103
 
                r1._pack_collection.ensure_loaded()
1104
 
                r2._pack_collection.ensure_loaded()
1105
 
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1106
 
                self.assertEqual(1, len(r1._pack_collection.names()))
1107
 
                self.assertFalse(name_to_drop in r1._pack_collection.names())
1108
 
            finally:
1109
 
                r2.unlock()
1110
 
        finally:
1111
 
            r1.unlock()
1112
 
 
1113
 
    def test_lock_write_does_not_physically_lock(self):
1114
 
        repo = self.make_repository('.', format=self.get_format())
1115
 
        repo.lock_write()
1116
 
        self.addCleanup(repo.unlock)
1117
 
        self.assertFalse(repo.get_physical_lock_status())
1118
 
 
1119
 
    def prepare_for_break_lock(self):
1120
 
        # Setup the global ui factory state so that a break-lock method call
1121
 
        # will find usable input in the input stream.
1122
 
        old_factory = bzrlib.ui.ui_factory
1123
 
        def restoreFactory():
1124
 
            bzrlib.ui.ui_factory = old_factory
1125
 
        self.addCleanup(restoreFactory)
1126
 
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1127
 
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
1128
 
 
1129
 
    def test_break_lock_breaks_physical_lock(self):
1130
 
        repo = self.make_repository('.', format=self.get_format())
1131
 
        repo._pack_collection.lock_names()
1132
 
        repo2 = repository.Repository.open('.')
1133
 
        self.assertTrue(repo.get_physical_lock_status())
1134
 
        self.prepare_for_break_lock()
1135
 
        repo2.break_lock()
1136
 
        self.assertFalse(repo.get_physical_lock_status())
1137
 
 
1138
 
    def test_broken_physical_locks_error_on__unlock_names_lock(self):
1139
 
        repo = self.make_repository('.', format=self.get_format())
1140
 
        repo._pack_collection.lock_names()
1141
 
        self.assertTrue(repo.get_physical_lock_status())
1142
 
        repo2 = repository.Repository.open('.')
1143
 
        self.prepare_for_break_lock()
1144
 
        repo2.break_lock()
1145
 
        self.assertRaises(errors.LockBroken, repo._pack_collection._unlock_names)
1146
 
 
1147
 
    def test_fetch_without_find_ghosts_ignores_ghosts(self):
1148
 
        # we want two repositories at this point:
1149
 
        # one with a revision that is a ghost in the other
1150
 
        # repository.
1151
 
        # 'ghost' is present in has_ghost, 'ghost' is absent in 'missing_ghost'.
1152
 
        # 'references' is present in both repositories, and 'tip' is present
1153
 
        # just in has_ghost.
1154
 
        # has_ghost       missing_ghost
1155
 
        #------------------------------
1156
 
        # 'ghost'             -
1157
 
        # 'references'    'references'
1158
 
        # 'tip'               -
1159
 
        # In this test we fetch 'tip' which should not fetch 'ghost'
1160
 
        has_ghost = self.make_repository('has_ghost', format=self.get_format())
1161
 
        missing_ghost = self.make_repository('missing_ghost',
1162
 
            format=self.get_format())
1163
 
 
1164
 
        def add_commit(repo, revision_id, parent_ids):
1165
 
            repo.lock_write()
1166
 
            repo.start_write_group()
1167
 
            inv = inventory.Inventory(revision_id=revision_id)
1168
 
            inv.root.revision = revision_id
1169
 
            root_id = inv.root.file_id
1170
 
            sha1 = repo.add_inventory(revision_id, inv, [])
1171
 
            vf = repo.weave_store.get_weave_or_empty(root_id,
1172
 
                repo.get_transaction())
1173
 
            vf.add_lines(revision_id, [], [])
1174
 
            rev = bzrlib.revision.Revision(timestamp=0,
1175
 
                                           timezone=None,
1176
 
                                           committer="Foo Bar <foo@example.com>",
1177
 
                                           message="Message",
1178
 
                                           inventory_sha1=sha1,
1179
 
                                           revision_id=revision_id)
1180
 
            rev.parent_ids = parent_ids
1181
 
            repo.add_revision(revision_id, rev)
1182
 
            repo.commit_write_group()
1183
 
            repo.unlock()
1184
 
        add_commit(has_ghost, 'ghost', [])
1185
 
        add_commit(has_ghost, 'references', ['ghost'])
1186
 
        add_commit(missing_ghost, 'references', ['ghost'])
1187
 
        add_commit(has_ghost, 'tip', ['references'])
1188
 
        missing_ghost.fetch(has_ghost, 'tip')
1189
 
        # missing ghost now has tip and not ghost.
1190
 
        rev = missing_ghost.get_revision('tip')
1191
 
        inv = missing_ghost.get_inventory('tip')
1192
 
        self.assertRaises(errors.NoSuchRevision,
1193
 
            missing_ghost.get_revision, 'ghost')
1194
 
        self.assertRaises(errors.RevisionNotPresent,
1195
 
            missing_ghost.get_inventory, 'ghost')
1196
 
 
1197
 
    def test_supports_external_lookups(self):
1198
 
        repo = self.make_repository('.', format=self.get_format())
1199
 
        self.assertFalse(repo._format.supports_external_lookups)
1200
 
 
1201
 
 
1202
 
class TestKnitPackSubtrees(TestKnitPackNoSubtrees):
1203
 
 
1204
 
    def get_format(self):
1205
 
        return bzrdir.format_registry.make_bzrdir(
1206
 
            'pack-0.92-subtree')
1207
 
 
1208
 
    def check_format(self, t):
1209
 
        self.assertEqualDiff(
1210
 
            "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n",
1211
 
            t.get('format').read())
1212
 
 
1213
 
 
1214
 
class TestDevelopment0(TestKnitPackNoSubtrees):
1215
 
 
1216
 
    def get_format(self):
1217
 
        return bzrdir.format_registry.make_bzrdir(
1218
 
            'development')
1219
 
 
1220
 
    def check_format(self, t):
1221
 
        self.assertEqualDiff(
1222
 
            "Bazaar development format 0 (needs bzr.dev from before 1.3)\n",
1223
 
            t.get('format').read())
1224
 
 
1225
 
 
1226
 
class TestDevelopment0Subtree(TestKnitPackNoSubtrees):
1227
 
 
1228
 
    def get_format(self):
1229
 
        return bzrdir.format_registry.make_bzrdir(
1230
 
            'development-subtree')
1231
 
 
1232
 
    def check_format(self, t):
1233
 
        self.assertEqualDiff(
1234
 
            "Bazaar development format 0 with subtree support "
1235
 
            "(needs bzr.dev from before 1.3)\n",
1236
 
            t.get('format').read())
 
1113
        text = empty_repo.texts.get_record_stream(
 
1114
            [('file2-id', 'rev3')], 'topological', True).next()
 
1115
        self.assertEqual('line\n', text.get_bytes_as('fulltext'))
1237
1116
 
1238
1117
 
1239
1118
class TestRepositoryPackCollection(TestCaseWithTransport):
1241
1120
    def get_format(self):
1242
1121
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
1243
1122
 
 
1123
    def get_packs(self):
 
1124
        format = self.get_format()
 
1125
        repo = self.make_repository('.', format=format)
 
1126
        return repo._pack_collection
 
1127
 
 
1128
    def make_packs_and_alt_repo(self, write_lock=False):
 
1129
        """Create a pack repo with 3 packs, and access it via a second repo."""
 
1130
        tree = self.make_branch_and_tree('.', format=self.get_format())
 
1131
        tree.lock_write()
 
1132
        self.addCleanup(tree.unlock)
 
1133
        rev1 = tree.commit('one')
 
1134
        rev2 = tree.commit('two')
 
1135
        rev3 = tree.commit('three')
 
1136
        r = repository.Repository.open('.')
 
1137
        if write_lock:
 
1138
            r.lock_write()
 
1139
        else:
 
1140
            r.lock_read()
 
1141
        self.addCleanup(r.unlock)
 
1142
        packs = r._pack_collection
 
1143
        packs.ensure_loaded()
 
1144
        return tree, r, packs, [rev1, rev2, rev3]
 
1145
 
1244
1146
    def test__max_pack_count(self):
1245
1147
        """The maximum pack count is a function of the number of revisions."""
1246
 
        format = self.get_format()
1247
 
        repo = self.make_repository('.', format=format)
1248
 
        packs = repo._pack_collection
1249
1148
        # no revisions - one pack, so that we can have a revision free repo
1250
1149
        # without it blowing up
 
1150
        packs = self.get_packs()
1251
1151
        self.assertEqual(1, packs._max_pack_count(0))
1252
1152
        # after that the sum of the digits, - check the first 1-9
1253
1153
        self.assertEqual(1, packs._max_pack_count(1))
1269
1169
        self.assertEqual(25, packs._max_pack_count(112894))
1270
1170
 
1271
1171
    def test_pack_distribution_zero(self):
1272
 
        format = self.get_format()
1273
 
        repo = self.make_repository('.', format=format)
1274
 
        packs = repo._pack_collection
 
1172
        packs = self.get_packs()
1275
1173
        self.assertEqual([0], packs.pack_distribution(0))
1276
1174
 
1277
1175
    def test_ensure_loaded_unlocked(self):
1278
 
        format = self.get_format()
1279
 
        repo = self.make_repository('.', format=format)
 
1176
        packs = self.get_packs()
1280
1177
        self.assertRaises(errors.ObjectNotLocked,
1281
 
                          repo._pack_collection.ensure_loaded)
 
1178
                          packs.ensure_loaded)
1282
1179
 
1283
1180
    def test_pack_distribution_one_to_nine(self):
1284
 
        format = self.get_format()
1285
 
        repo = self.make_repository('.', format=format)
1286
 
        packs = repo._pack_collection
 
1181
        packs = self.get_packs()
1287
1182
        self.assertEqual([1],
1288
1183
            packs.pack_distribution(1))
1289
1184
        self.assertEqual([1, 1],
1305
1200
 
1306
1201
    def test_pack_distribution_stable_at_boundaries(self):
1307
1202
        """When there are multi-rev packs the counts are stable."""
1308
 
        format = self.get_format()
1309
 
        repo = self.make_repository('.', format=format)
1310
 
        packs = repo._pack_collection
 
1203
        packs = self.get_packs()
1311
1204
        # in 10s:
1312
1205
        self.assertEqual([10], packs.pack_distribution(10))
1313
1206
        self.assertEqual([10, 1], packs.pack_distribution(11))
1322
1215
        self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1323
1216
 
1324
1217
    def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1325
 
        format = self.get_format()
1326
 
        repo = self.make_repository('.', format=format)
1327
 
        packs = repo._pack_collection
 
1218
        packs = self.get_packs()
1328
1219
        existing_packs = [(2000, "big"), (9, "medium")]
1329
1220
        # rev count - 2009 -> 2x1000 + 9x1
1330
1221
        pack_operations = packs.plan_autopack_combinations(
1332
1223
        self.assertEqual([], pack_operations)
1333
1224
 
1334
1225
    def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1335
 
        format = self.get_format()
1336
 
        repo = self.make_repository('.', format=format)
1337
 
        packs = repo._pack_collection
 
1226
        packs = self.get_packs()
1338
1227
        existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1339
1228
        # rev count - 2010 -> 2x1000 + 1x10
1340
1229
        pack_operations = packs.plan_autopack_combinations(
1342
1231
        self.assertEqual([], pack_operations)
1343
1232
 
1344
1233
    def test_plan_pack_operations_2010_combines_smallest_two(self):
1345
 
        format = self.get_format()
1346
 
        repo = self.make_repository('.', format=format)
1347
 
        packs = repo._pack_collection
 
1234
        packs = self.get_packs()
1348
1235
        existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1349
1236
            (1, "single1")]
1350
1237
        # rev count - 2010 -> 2x1000 + 1x10 (3)
1351
1238
        pack_operations = packs.plan_autopack_combinations(
1352
1239
            existing_packs, [1000, 1000, 10])
1353
 
        self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
 
1240
        self.assertEqual([[2, ["single2", "single1"]]], pack_operations)
 
1241
 
 
1242
    def test_plan_pack_operations_creates_a_single_op(self):
 
1243
        packs = self.get_packs()
 
1244
        existing_packs = [(50, 'a'), (40, 'b'), (30, 'c'), (10, 'd'),
 
1245
                          (10, 'e'), (6, 'f'), (4, 'g')]
 
1246
        # rev count 150 -> 1x100 and 5x10
 
1247
        # The two size 10 packs do not need to be touched. The 50, 40, 30 would
 
1248
        # be combined into a single 120 size pack, and the 6 & 4 would
 
1249
        # becombined into a size 10 pack. However, if we have to rewrite them,
 
1250
        # we save a pack file with no increased I/O by putting them into the
 
1251
        # same file.
 
1252
        distribution = packs.pack_distribution(150)
 
1253
        pack_operations = packs.plan_autopack_combinations(existing_packs,
 
1254
                                                           distribution)
 
1255
        self.assertEqual([[130, ['a', 'b', 'c', 'f', 'g']]], pack_operations)
1354
1256
 
1355
1257
    def test_all_packs_none(self):
1356
1258
        format = self.get_format()
1394
1296
        tree.lock_read()
1395
1297
        self.addCleanup(tree.unlock)
1396
1298
        packs = tree.branch.repository._pack_collection
 
1299
        packs.reset()
1397
1300
        packs.ensure_loaded()
1398
1301
        name = packs.names()[0]
1399
1302
        pack_1 = packs.get_pack_by_name(name)
1400
1303
        # the pack should be correctly initialised
1401
 
        rev_index = GraphIndex(packs._index_transport, name + '.rix',
1402
 
            packs._names[name][0])
1403
 
        inv_index = GraphIndex(packs._index_transport, name + '.iix',
1404
 
            packs._names[name][1])
1405
 
        txt_index = GraphIndex(packs._index_transport, name + '.tix',
1406
 
            packs._names[name][2])
1407
 
        sig_index = GraphIndex(packs._index_transport, name + '.six',
1408
 
            packs._names[name][3])
 
1304
        sizes = packs._names[name]
 
1305
        rev_index = GraphIndex(packs._index_transport, name + '.rix', sizes[0])
 
1306
        inv_index = GraphIndex(packs._index_transport, name + '.iix', sizes[1])
 
1307
        txt_index = GraphIndex(packs._index_transport, name + '.tix', sizes[2])
 
1308
        sig_index = GraphIndex(packs._index_transport, name + '.six', sizes[3])
1409
1309
        self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
1410
1310
            name, rev_index, inv_index, txt_index, sig_index), pack_1)
1411
1311
        # and the same instance should be returned on successive calls.
1412
1312
        self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1413
1313
 
 
1314
    def test_reload_pack_names_new_entry(self):
 
1315
        tree, r, packs, revs = self.make_packs_and_alt_repo()
 
1316
        names = packs.names()
 
1317
        # Add a new pack file into the repository
 
1318
        rev4 = tree.commit('four')
 
1319
        new_names = tree.branch.repository._pack_collection.names()
 
1320
        new_name = set(new_names).difference(names)
 
1321
        self.assertEqual(1, len(new_name))
 
1322
        new_name = new_name.pop()
 
1323
        # The old collection hasn't noticed yet
 
1324
        self.assertEqual(names, packs.names())
 
1325
        self.assertTrue(packs.reload_pack_names())
 
1326
        self.assertEqual(new_names, packs.names())
 
1327
        # And the repository can access the new revision
 
1328
        self.assertEqual({rev4:(revs[-1],)}, r.get_parent_map([rev4]))
 
1329
        self.assertFalse(packs.reload_pack_names())
 
1330
 
 
1331
    def test_reload_pack_names_added_and_removed(self):
 
1332
        tree, r, packs, revs = self.make_packs_and_alt_repo()
 
1333
        names = packs.names()
 
1334
        # Now repack the whole thing
 
1335
        tree.branch.repository.pack()
 
1336
        new_names = tree.branch.repository._pack_collection.names()
 
1337
        # The other collection hasn't noticed yet
 
1338
        self.assertEqual(names, packs.names())
 
1339
        self.assertTrue(packs.reload_pack_names())
 
1340
        self.assertEqual(new_names, packs.names())
 
1341
        self.assertEqual({revs[-1]:(revs[-2],)}, r.get_parent_map([revs[-1]]))
 
1342
        self.assertFalse(packs.reload_pack_names())
 
1343
 
 
1344
    def test_autopack_reloads_and_stops(self):
 
1345
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
 
1346
        # After we have determined what needs to be autopacked, trigger a
 
1347
        # full-pack via the other repo which will cause us to re-evaluate and
 
1348
        # decide we don't need to do anything
 
1349
        orig_execute = packs._execute_pack_operations
 
1350
        def _munged_execute_pack_ops(*args, **kwargs):
 
1351
            tree.branch.repository.pack()
 
1352
            return orig_execute(*args, **kwargs)
 
1353
        packs._execute_pack_operations = _munged_execute_pack_ops
 
1354
        packs._max_pack_count = lambda x: 1
 
1355
        packs.pack_distribution = lambda x: [10]
 
1356
        self.assertFalse(packs.autopack())
 
1357
        self.assertEqual(1, len(packs.names()))
 
1358
        self.assertEqual(tree.branch.repository._pack_collection.names(),
 
1359
                         packs.names())
 
1360
 
1414
1361
 
1415
1362
class TestPack(TestCaseWithTransport):
1416
1363
    """Tests for the Pack object."""
1470
1417
        pack_transport = self.get_transport('pack')
1471
1418
        index_transport = self.get_transport('index')
1472
1419
        upload_transport.mkdir('.')
1473
 
        pack = pack_repo.NewPack(upload_transport, index_transport,
1474
 
            pack_transport)
1475
 
        self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
1476
 
        self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
1477
 
        self.assertIsInstance(pack._hash, type(md5.new()))
 
1420
        collection = pack_repo.RepositoryPackCollection(
 
1421
            repo=None,
 
1422
            transport=self.get_transport('.'),
 
1423
            index_transport=index_transport,
 
1424
            upload_transport=upload_transport,
 
1425
            pack_transport=pack_transport,
 
1426
            index_builder_class=BTreeBuilder,
 
1427
            index_class=BTreeGraphIndex,
 
1428
            use_chk_index=False)
 
1429
        pack = pack_repo.NewPack(collection)
 
1430
        self.addCleanup(pack.abort) # Make sure the write stream gets closed
 
1431
        self.assertIsInstance(pack.revision_index, BTreeBuilder)
 
1432
        self.assertIsInstance(pack.inventory_index, BTreeBuilder)
 
1433
        self.assertIsInstance(pack._hash, type(osutils.md5()))
1478
1434
        self.assertTrue(pack.upload_transport is upload_transport)
1479
1435
        self.assertTrue(pack.index_transport is index_transport)
1480
1436
        self.assertTrue(pack.pack_transport is pack_transport)
1487
1443
class TestPacker(TestCaseWithTransport):
1488
1444
    """Tests for the packs repository Packer class."""
1489
1445
 
1490
 
    # To date, this class has been factored out and nothing new added to it;
1491
 
    # thus there are not yet any tests.
1492
 
 
1493
 
 
1494
 
class TestInterDifferingSerializer(TestCaseWithTransport):
1495
 
 
1496
 
    def test_progress_bar(self):
1497
 
        tree = self.make_branch_and_tree('tree')
1498
 
        tree.commit('rev1', rev_id='rev-1')
1499
 
        tree.commit('rev2', rev_id='rev-2')
1500
 
        tree.commit('rev3', rev_id='rev-3')
1501
 
        repo = self.make_repository('repo')
1502
 
        inter_repo = repository.InterDifferingSerializer(
1503
 
            tree.branch.repository, repo)
1504
 
        pb = progress.InstrumentedProgress(to_file=StringIO())
1505
 
        pb.never_throttle = True
1506
 
        inter_repo.fetch('rev-1', pb)
1507
 
        self.assertEqual('Transferring revisions', pb.last_msg)
1508
 
        self.assertEqual(1, pb.last_cnt)
1509
 
        self.assertEqual(1, pb.last_total)
1510
 
        inter_repo.fetch('rev-3', pb)
1511
 
        self.assertEqual(2, pb.last_cnt)
1512
 
        self.assertEqual(2, pb.last_total)
 
1446
    def test_pack_optimizes_pack_order(self):
 
1447
        builder = self.make_branch_builder('.', format="1.9")
 
1448
        builder.start_series()
 
1449
        builder.build_snapshot('A', None, [
 
1450
            ('add', ('', 'root-id', 'directory', None)),
 
1451
            ('add', ('f', 'f-id', 'file', 'content\n'))])
 
1452
        builder.build_snapshot('B', ['A'],
 
1453
            [('modify', ('f-id', 'new-content\n'))])
 
1454
        builder.build_snapshot('C', ['B'],
 
1455
            [('modify', ('f-id', 'third-content\n'))])
 
1456
        builder.build_snapshot('D', ['C'],
 
1457
            [('modify', ('f-id', 'fourth-content\n'))])
 
1458
        b = builder.get_branch()
 
1459
        b.lock_read()
 
1460
        builder.finish_series()
 
1461
        self.addCleanup(b.unlock)
 
1462
        # At this point, we should have 4 pack files available
 
1463
        # Because of how they were built, they correspond to
 
1464
        # ['D', 'C', 'B', 'A']
 
1465
        packs = b.repository._pack_collection.packs
 
1466
        packer = pack_repo.Packer(b.repository._pack_collection,
 
1467
                                  packs, 'testing',
 
1468
                                  revision_ids=['B', 'C'])
 
1469
        # Now, when we are copying the B & C revisions, their pack files should
 
1470
        # be moved to the front of the stack
 
1471
        # The new ordering moves B & C to the front of the .packs attribute,
 
1472
        # and leaves the others in the original order.
 
1473
        new_packs = [packs[1], packs[2], packs[0], packs[3]]
 
1474
        new_pack = packer.pack()
 
1475
        self.assertEqual(new_packs, packer.packs)
 
1476
 
 
1477
 
 
1478
class TestOptimisingPacker(TestCaseWithTransport):
 
1479
    """Tests for the OptimisingPacker class."""
 
1480
 
 
1481
    def get_pack_collection(self):
 
1482
        repo = self.make_repository('.')
 
1483
        return repo._pack_collection
 
1484
 
 
1485
    def test_open_pack_will_optimise(self):
 
1486
        packer = pack_repo.OptimisingPacker(self.get_pack_collection(),
 
1487
                                            [], '.test')
 
1488
        new_pack = packer.open_pack()
 
1489
        self.addCleanup(new_pack.abort) # ensure cleanup
 
1490
        self.assertIsInstance(new_pack, pack_repo.NewPack)
 
1491
        self.assertTrue(new_pack.revision_index._optimize_for_size)
 
1492
        self.assertTrue(new_pack.inventory_index._optimize_for_size)
 
1493
        self.assertTrue(new_pack.text_index._optimize_for_size)
 
1494
        self.assertTrue(new_pack.signature_index._optimize_for_size)
 
1495
 
 
1496
 
 
1497
class TestCrossFormatPacks(TestCaseWithTransport):
 
1498
 
 
1499
    def log_pack(self, hint=None):
 
1500
        self.calls.append(('pack', hint))
 
1501
        self.orig_pack(hint=hint)
 
1502
        if self.expect_hint:
 
1503
            self.assertTrue(hint)
 
1504
 
 
1505
    def run_stream(self, src_fmt, target_fmt, expect_pack_called):
 
1506
        self.expect_hint = expect_pack_called
 
1507
        self.calls = []
 
1508
        source_tree = self.make_branch_and_tree('src', format=src_fmt)
 
1509
        source_tree.lock_write()
 
1510
        self.addCleanup(source_tree.unlock)
 
1511
        tip = source_tree.commit('foo')
 
1512
        target = self.make_repository('target', format=target_fmt)
 
1513
        target.lock_write()
 
1514
        self.addCleanup(target.unlock)
 
1515
        source = source_tree.branch.repository._get_source(target._format)
 
1516
        self.orig_pack = target.pack
 
1517
        target.pack = self.log_pack
 
1518
        search = target.search_missing_revision_ids(
 
1519
            source_tree.branch.repository, tip)
 
1520
        stream = source.get_stream(search)
 
1521
        from_format = source_tree.branch.repository._format
 
1522
        sink = target._get_sink()
 
1523
        sink.insert_stream(stream, from_format, [])
 
1524
        if expect_pack_called:
 
1525
            self.assertLength(1, self.calls)
 
1526
        else:
 
1527
            self.assertLength(0, self.calls)
 
1528
 
 
1529
    def run_fetch(self, src_fmt, target_fmt, expect_pack_called):
 
1530
        self.expect_hint = expect_pack_called
 
1531
        self.calls = []
 
1532
        source_tree = self.make_branch_and_tree('src', format=src_fmt)
 
1533
        source_tree.lock_write()
 
1534
        self.addCleanup(source_tree.unlock)
 
1535
        tip = source_tree.commit('foo')
 
1536
        target = self.make_repository('target', format=target_fmt)
 
1537
        target.lock_write()
 
1538
        self.addCleanup(target.unlock)
 
1539
        source = source_tree.branch.repository
 
1540
        self.orig_pack = target.pack
 
1541
        target.pack = self.log_pack
 
1542
        target.fetch(source)
 
1543
        if expect_pack_called:
 
1544
            self.assertLength(1, self.calls)
 
1545
        else:
 
1546
            self.assertLength(0, self.calls)
 
1547
 
 
1548
    def test_sink_format_hint_no(self):
 
1549
        # When the target format says packing makes no difference, pack is not
 
1550
        # called.
 
1551
        self.run_stream('1.9', 'rich-root-pack', False)
 
1552
 
 
1553
    def test_sink_format_hint_yes(self):
 
1554
        # When the target format says packing makes a difference, pack is
 
1555
        # called.
 
1556
        self.run_stream('1.9', '2a', True)
 
1557
 
 
1558
    def test_sink_format_same_no(self):
 
1559
        # When the formats are the same, pack is not called.
 
1560
        self.run_stream('2a', '2a', False)
 
1561
 
 
1562
    def test_IDS_format_hint_no(self):
 
1563
        # When the target format says packing makes no difference, pack is not
 
1564
        # called.
 
1565
        self.run_fetch('1.9', 'rich-root-pack', False)
 
1566
 
 
1567
    def test_IDS_format_hint_yes(self):
 
1568
        # When the target format says packing makes a difference, pack is
 
1569
        # called.
 
1570
        self.run_fetch('1.9', '2a', True)
 
1571
 
 
1572
    def test_IDS_format_same_no(self):
 
1573
        # When the formats are the same, pack is not called.
 
1574
        self.run_fetch('2a', '2a', False)