~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_repository.py

Fixed as per John's review.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2012, 2016 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Tests for the Repository facility that are not interface tests.
18
18
 
19
 
For interface tests see tests/per_repository/*.py.
 
19
For interface tests see tests/repository_implementations/*.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
25
26
from stat import S_ISDIR
 
27
from StringIO import StringIO
26
28
 
27
29
import bzrlib
28
 
from bzrlib.errors import (
29
 
    UnknownFormatError,
30
 
    UnsupportedFormatError,
31
 
    )
32
 
from bzrlib import (
33
 
    btree_index,
34
 
    symbol_versioning,
35
 
    tests,
36
 
    transport,
37
 
    vf_search,
38
 
    )
39
 
from bzrlib.btree_index import BTreeBuilder, BTreeGraphIndex
40
 
from bzrlib.index import GraphIndex
 
30
from bzrlib.errors import (NotBranchError,
 
31
                           NoSuchFile,
 
32
                           UnknownFormatError,
 
33
                           UnsupportedFormatError,
 
34
                           )
 
35
from bzrlib import graph
 
36
from bzrlib.index import GraphIndex, InMemoryGraphIndex
41
37
from bzrlib.repository import RepositoryFormat
 
38
from bzrlib.smart import server
42
39
from bzrlib.tests import (
43
40
    TestCase,
44
41
    TestCaseWithTransport,
45
 
    )
 
42
    TestSkipped,
 
43
    test_knit,
 
44
    )
 
45
from bzrlib.transport import (
 
46
    fakenfs,
 
47
    get_transport,
 
48
    )
 
49
from bzrlib.transport.memory import MemoryServer
 
50
from bzrlib.util import bencode
46
51
from bzrlib import (
47
52
    bzrdir,
48
 
    controldir,
49
53
    errors,
50
54
    inventory,
51
 
    osutils,
 
55
    progress,
52
56
    repository,
53
57
    revision as _mod_revision,
 
58
    symbol_versioning,
54
59
    upgrade,
55
 
    versionedfile,
56
 
    vf_repository,
57
60
    workingtree,
58
61
    )
59
 
from bzrlib.repofmt import (
60
 
    groupcompress_repo,
61
 
    knitrepo,
62
 
    knitpack_repo,
63
 
    pack_repo,
64
 
    )
 
62
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
65
63
 
66
64
 
67
65
class TestDefaultFormat(TestCase):
68
66
 
69
67
    def test_get_set_default_format(self):
70
 
        old_default = controldir.format_registry.get('default')
 
68
        old_default = bzrdir.format_registry.get('default')
71
69
        private_default = old_default().repository_format.__class__
72
 
        old_format = repository.format_registry.get_default()
 
70
        old_format = repository.RepositoryFormat.get_default_format()
73
71
        self.assertTrue(isinstance(old_format, private_default))
74
72
        def make_sample_bzrdir():
75
73
            my_bzrdir = bzrdir.BzrDirMetaFormat1()
76
74
            my_bzrdir.repository_format = SampleRepositoryFormat()
77
75
            return my_bzrdir
78
 
        controldir.format_registry.remove('default')
79
 
        controldir.format_registry.register('sample', make_sample_bzrdir, '')
80
 
        controldir.format_registry.set_default('sample')
 
76
        bzrdir.format_registry.remove('default')
 
77
        bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
 
78
        bzrdir.format_registry.set_default('sample')
81
79
        # creating a repository should now create an instrumented dir.
82
80
        try:
83
81
            # the default branch format is used by the meta dir format
86
84
            result = dir.create_repository()
87
85
            self.assertEqual(result, 'A bzr repository dir')
88
86
        finally:
89
 
            controldir.format_registry.remove('default')
90
 
            controldir.format_registry.remove('sample')
91
 
            controldir.format_registry.register('default', old_default, '')
92
 
        self.assertIsInstance(repository.format_registry.get_default(),
 
87
            bzrdir.format_registry.remove('default')
 
88
            bzrdir.format_registry.remove('sample')
 
89
            bzrdir.format_registry.register('default', old_default, '')
 
90
        self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
93
91
                              old_format.__class__)
94
92
 
95
93
 
96
 
class SampleRepositoryFormat(repository.RepositoryFormatMetaDir):
 
94
class SampleRepositoryFormat(repository.RepositoryFormat):
97
95
    """A sample format
98
96
 
99
 
    this format is initializable, unsupported to aid in testing the
 
97
    this format is initializable, unsupported to aid in testing the 
100
98
    open and open(unsupported=True) routines.
101
99
    """
102
100
 
103
 
    @classmethod
104
 
    def get_format_string(cls):
 
101
    def get_format_string(self):
105
102
        """See RepositoryFormat.get_format_string()."""
106
103
        return "Sample .bzr repository format."
107
104
 
118
115
        return "opened repository."
119
116
 
120
117
 
121
 
class SampleExtraRepositoryFormat(repository.RepositoryFormat):
122
 
    """A sample format that can not be used in a metadir
123
 
 
124
 
    """
125
 
 
126
 
    def get_format_string(self):
127
 
        raise NotImplementedError
128
 
 
129
 
 
130
118
class TestRepositoryFormat(TestCaseWithTransport):
131
119
    """Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
132
120
 
133
121
    def test_find_format(self):
134
122
        # is the right format object found for a repository?
135
123
        # create a branch with a few known format objects.
136
 
        # this is not quite the same as
 
124
        # this is not quite the same as 
137
125
        self.build_tree(["foo/", "bar/"])
138
126
        def check_format(format, url):
139
127
            dir = format._matchingbzrdir.initialize(url)
140
128
            format.initialize(dir)
141
 
            t = transport.get_transport_from_path(url)
142
 
            found_format = repository.RepositoryFormatMetaDir.find_format(dir)
143
 
            self.assertIsInstance(found_format, format.__class__)
144
 
        check_format(repository.format_registry.get_default(), "bar")
145
 
 
 
129
            t = get_transport(url)
 
130
            found_format = repository.RepositoryFormat.find_format(dir)
 
131
            self.failUnless(isinstance(found_format, format.__class__))
 
132
        check_format(weaverepo.RepositoryFormat7(), "bar")
 
133
        
146
134
    def test_find_format_no_repository(self):
147
135
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
148
136
        self.assertRaises(errors.NoRepositoryPresent,
149
 
                          repository.RepositoryFormatMetaDir.find_format,
 
137
                          repository.RepositoryFormat.find_format,
150
138
                          dir)
151
139
 
152
 
    def test_from_string(self):
153
 
        self.assertIsInstance(
154
 
            SampleRepositoryFormat.from_string(
155
 
                "Sample .bzr repository format."),
156
 
            SampleRepositoryFormat)
157
 
        self.assertRaises(AssertionError,
158
 
            SampleRepositoryFormat.from_string,
159
 
                "Different .bzr repository format.")
160
 
 
161
140
    def test_find_format_unknown_format(self):
162
141
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
163
142
        SampleRepositoryFormat().initialize(dir)
164
143
        self.assertRaises(UnknownFormatError,
165
 
                          repository.RepositoryFormatMetaDir.find_format,
 
144
                          repository.RepositoryFormat.find_format,
166
145
                          dir)
167
146
 
168
 
    def test_find_format_with_features(self):
169
 
        tree = self.make_branch_and_tree('.', format='2a')
170
 
        tree.branch.repository.update_feature_flags({"name": "necessity"})
171
 
        found_format = repository.RepositoryFormatMetaDir.find_format(tree.bzrdir)
172
 
        self.assertIsInstance(found_format, repository.RepositoryFormatMetaDir)
173
 
        self.assertEqual(found_format.features.get("name"), "necessity")
174
 
        self.assertRaises(errors.MissingFeature, found_format.check_support_status,
175
 
            True)
176
 
        self.addCleanup(repository.RepositoryFormatMetaDir.unregister_feature,
177
 
            "name")
178
 
        repository.RepositoryFormatMetaDir.register_feature("name")
179
 
        found_format.check_support_status(True)
180
 
 
181
 
 
182
 
class TestRepositoryFormatRegistry(TestCase):
183
 
 
184
 
    def setUp(self):
185
 
        super(TestRepositoryFormatRegistry, self).setUp()
186
 
        self.registry = repository.RepositoryFormatRegistry()
187
 
 
188
147
    def test_register_unregister_format(self):
189
148
        format = SampleRepositoryFormat()
190
 
        self.registry.register(format)
191
 
        self.assertEqual(format, self.registry.get("Sample .bzr repository format."))
192
 
        self.registry.remove(format)
193
 
        self.assertRaises(KeyError, self.registry.get, "Sample .bzr repository format.")
194
 
 
195
 
    def test_get_all(self):
196
 
        format = SampleRepositoryFormat()
197
 
        self.assertEqual([], self.registry._get_all())
198
 
        self.registry.register(format)
199
 
        self.assertEqual([format], self.registry._get_all())
200
 
 
201
 
    def test_register_extra(self):
202
 
        format = SampleExtraRepositoryFormat()
203
 
        self.assertEqual([], self.registry._get_all())
204
 
        self.registry.register_extra(format)
205
 
        self.assertEqual([format], self.registry._get_all())
206
 
 
207
 
    def test_register_extra_lazy(self):
208
 
        self.assertEqual([], self.registry._get_all())
209
 
        self.registry.register_extra_lazy("bzrlib.tests.test_repository",
210
 
            "SampleExtraRepositoryFormat")
211
 
        formats = self.registry._get_all()
212
 
        self.assertEqual(1, len(formats))
213
 
        self.assertIsInstance(formats[0], SampleExtraRepositoryFormat)
 
149
        # make a control dir
 
150
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
151
        # make a repo
 
152
        format.initialize(dir)
 
153
        # register a format for it.
 
154
        repository.RepositoryFormat.register_format(format)
 
155
        # which repository.Open will refuse (not supported)
 
156
        self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
 
157
        # but open(unsupported) will work
 
158
        self.assertEqual(format.open(dir), "opened repository.")
 
159
        # unregister the format
 
160
        repository.RepositoryFormat.unregister_format(format)
 
161
 
 
162
 
 
163
class TestFormat6(TestCaseWithTransport):
 
164
 
 
165
    def test_no_ancestry_weave(self):
 
166
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
 
167
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
168
        # We no longer need to create the ancestry.weave file
 
169
        # since it is *never* used.
 
170
        self.assertRaises(NoSuchFile,
 
171
                          control.transport.get,
 
172
                          'ancestry.weave')
 
173
 
 
174
    def test_exposed_versioned_files_are_marked_dirty(self):
 
175
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
 
176
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
177
        repo.lock_write()
 
178
        inv = repo.get_inventory_weave()
 
179
        repo.unlock()
 
180
        self.assertRaises(errors.OutSideTransaction,
 
181
            inv.add_lines, 'foo', [], [])
 
182
 
 
183
    def test_supports_external_lookups(self):
 
184
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
 
185
        repo = weaverepo.RepositoryFormat6().initialize(control)
 
186
        self.assertFalse(repo._format.supports_external_lookups)
 
187
 
 
188
 
 
189
class TestFormat7(TestCaseWithTransport):
 
190
    
 
191
    def test_disk_layout(self):
 
192
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
193
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
194
        # in case of side effects of locking.
 
195
        repo.lock_write()
 
196
        repo.unlock()
 
197
        # we want:
 
198
        # format 'Bazaar-NG Repository format 7'
 
199
        # lock ''
 
200
        # inventory.weave == empty_weave
 
201
        # empty revision-store directory
 
202
        # empty weaves directory
 
203
        t = control.get_repository_transport(None)
 
204
        self.assertEqualDiff('Bazaar-NG Repository format 7',
 
205
                             t.get('format').read())
 
206
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
 
207
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
 
208
        self.assertEqualDiff('# bzr weave file v5\n'
 
209
                             'w\n'
 
210
                             'W\n',
 
211
                             t.get('inventory.weave').read())
 
212
 
 
213
    def test_shared_disk_layout(self):
 
214
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
215
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
216
        # we want:
 
217
        # format 'Bazaar-NG Repository format 7'
 
218
        # inventory.weave == empty_weave
 
219
        # empty revision-store directory
 
220
        # empty weaves directory
 
221
        # a 'shared-storage' marker file.
 
222
        # lock is not present when unlocked
 
223
        t = control.get_repository_transport(None)
 
224
        self.assertEqualDiff('Bazaar-NG Repository format 7',
 
225
                             t.get('format').read())
 
226
        self.assertEqualDiff('', t.get('shared-storage').read())
 
227
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
 
228
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
 
229
        self.assertEqualDiff('# bzr weave file v5\n'
 
230
                             'w\n'
 
231
                             'W\n',
 
232
                             t.get('inventory.weave').read())
 
233
        self.assertFalse(t.has('branch-lock'))
 
234
 
 
235
    def test_creates_lockdir(self):
 
236
        """Make sure it appears to be controlled by a LockDir existence"""
 
237
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
238
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
239
        t = control.get_repository_transport(None)
 
240
        # TODO: Should check there is a 'lock' toplevel directory, 
 
241
        # regardless of contents
 
242
        self.assertFalse(t.has('lock/held/info'))
 
243
        repo.lock_write()
 
244
        try:
 
245
            self.assertTrue(t.has('lock/held/info'))
 
246
        finally:
 
247
            # unlock so we don't get a warning about failing to do so
 
248
            repo.unlock()
 
249
 
 
250
    def test_uses_lockdir(self):
 
251
        """repo format 7 actually locks on lockdir"""
 
252
        base_url = self.get_url()
 
253
        control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
 
254
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
255
        t = control.get_repository_transport(None)
 
256
        repo.lock_write()
 
257
        repo.unlock()
 
258
        del repo
 
259
        # make sure the same lock is created by opening it
 
260
        repo = repository.Repository.open(base_url)
 
261
        repo.lock_write()
 
262
        self.assertTrue(t.has('lock/held/info'))
 
263
        repo.unlock()
 
264
        self.assertFalse(t.has('lock/held/info'))
 
265
 
 
266
    def test_shared_no_tree_disk_layout(self):
 
267
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
268
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
 
269
        repo.set_make_working_trees(False)
 
270
        # we want:
 
271
        # format 'Bazaar-NG Repository format 7'
 
272
        # lock ''
 
273
        # inventory.weave == empty_weave
 
274
        # empty revision-store directory
 
275
        # empty weaves directory
 
276
        # a 'shared-storage' marker file.
 
277
        t = control.get_repository_transport(None)
 
278
        self.assertEqualDiff('Bazaar-NG Repository format 7',
 
279
                             t.get('format').read())
 
280
        ## self.assertEqualDiff('', t.get('lock').read())
 
281
        self.assertEqualDiff('', t.get('shared-storage').read())
 
282
        self.assertEqualDiff('', t.get('no-working-trees').read())
 
283
        repo.set_make_working_trees(True)
 
284
        self.assertFalse(t.has('no-working-trees'))
 
285
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
 
286
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
 
287
        self.assertEqualDiff('# bzr weave file v5\n'
 
288
                             'w\n'
 
289
                             'W\n',
 
290
                             t.get('inventory.weave').read())
 
291
 
 
292
    def test_exposed_versioned_files_are_marked_dirty(self):
 
293
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
294
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
295
        repo.lock_write()
 
296
        inv = repo.get_inventory_weave()
 
297
        repo.unlock()
 
298
        self.assertRaises(errors.OutSideTransaction,
 
299
            inv.add_lines, 'foo', [], [])
 
300
 
 
301
    def test_supports_external_lookups(self):
 
302
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
303
        repo = weaverepo.RepositoryFormat7().initialize(control)
 
304
        self.assertFalse(repo._format.supports_external_lookups)
214
305
 
215
306
 
216
307
class TestFormatKnit1(TestCaseWithTransport):
217
 
 
218
 
    def test_attribute__fetch_order(self):
219
 
        """Knits need topological data insertion."""
220
 
        repo = self.make_repository('.',
221
 
                format=controldir.format_registry.get('knit')())
222
 
        self.assertEqual('topological', repo._format._fetch_order)
223
 
 
224
 
    def test_attribute__fetch_uses_deltas(self):
225
 
        """Knits reuse deltas."""
226
 
        repo = self.make_repository('.',
227
 
                format=controldir.format_registry.get('knit')())
228
 
        self.assertEqual(True, repo._format._fetch_uses_deltas)
229
 
 
 
308
    
230
309
    def test_disk_layout(self):
231
310
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
232
311
        repo = knitrepo.RepositoryFormatKnit1().initialize(control)
246
325
        # self.assertEqualDiff('', t.get('lock').read())
247
326
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
248
327
        self.check_knits(t)
249
 
        # Check per-file knits.
250
 
        branch = control.create_branch()
251
 
        tree = control.create_workingtree()
252
 
        tree.add(['foo'], ['Nasty-IdC:'], ['file'])
253
 
        tree.put_file_bytes_non_atomic('Nasty-IdC:', '')
254
 
        tree.commit('1st post', rev_id='foo')
255
 
        self.assertHasKnit(t, 'knits/e8/%254easty-%2549d%2543%253a',
256
 
            '\nfoo fulltext 0 81  :')
257
328
 
258
 
    def assertHasKnit(self, t, knit_name, extra_content=''):
 
329
    def assertHasKnit(self, t, knit_name):
259
330
        """Assert that knit_name exists on t."""
260
 
        self.assertEqualDiff('# bzr knit index 8\n' + extra_content,
 
331
        self.assertEqualDiff('# bzr knit index 8\n',
261
332
                             t.get(knit_name + '.kndx').read())
 
333
        # no default content
 
334
        self.assertTrue(t.has(knit_name + '.knit'))
262
335
 
263
336
    def check_knits(self, t):
264
337
        """check knit content for a repository."""
308
381
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
309
382
        self.check_knits(t)
310
383
 
 
384
    def test_exposed_versioned_files_are_marked_dirty(self):
 
385
        format = bzrdir.BzrDirMetaFormat1()
 
386
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
387
        repo = self.make_repository('.', format=format)
 
388
        repo.lock_write()
 
389
        inv = repo.get_inventory_weave()
 
390
        repo.unlock()
 
391
        self.assertRaises(errors.OutSideTransaction,
 
392
            inv.add_lines, 'foo', [], [])
 
393
 
311
394
    def test_deserialise_sets_root_revision(self):
312
395
        """We must have a inventory.root.revision
313
396
 
316
399
        is valid when the api is not being abused.
317
400
        """
318
401
        repo = self.make_repository('.',
319
 
                format=controldir.format_registry.get('knit')())
 
402
                format=bzrdir.format_registry.get('knit')())
320
403
        inv_xml = '<inventory format="5">\n</inventory>\n'
321
 
        inv = repo._deserialise_inventory('test-rev-id', inv_xml)
 
404
        inv = repo.deserialise_inventory('test-rev-id', inv_xml)
322
405
        self.assertEqual('test-rev-id', inv.root.revision)
323
406
 
324
407
    def test_deserialise_uses_global_revision_id(self):
325
408
        """If it is set, then we re-use the global revision id"""
326
409
        repo = self.make_repository('.',
327
 
                format=controldir.format_registry.get('knit')())
 
410
                format=bzrdir.format_registry.get('knit')())
328
411
        inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
329
412
                   '</inventory>\n')
330
413
        # Arguably, the deserialise_inventory should detect a mismatch, and
331
414
        # raise an error, rather than silently using one revision_id over the
332
415
        # other.
333
 
        self.assertRaises(AssertionError, repo._deserialise_inventory,
 
416
        self.assertRaises(AssertionError, repo.deserialise_inventory,
334
417
            'test-rev-id', inv_xml)
335
 
        inv = repo._deserialise_inventory('other-rev-id', inv_xml)
 
418
        inv = repo.deserialise_inventory('other-rev-id', inv_xml)
336
419
        self.assertEqual('other-rev-id', inv.root.revision)
337
420
 
338
421
    def test_supports_external_lookups(self):
339
422
        repo = self.make_repository('.',
340
 
                format=controldir.format_registry.get('knit')())
 
423
                format=bzrdir.format_registry.get('knit')())
341
424
        self.assertFalse(repo._format.supports_external_lookups)
342
425
 
343
426
 
 
427
class KnitRepositoryStreamTests(test_knit.KnitTests):
 
428
    """Tests for knitrepo._get_stream_as_bytes."""
 
429
 
 
430
    def test_get_stream_as_bytes(self):
 
431
        # Make a simple knit
 
432
        k1 = self.make_test_knit()
 
433
        k1.add_lines('text-a', [], test_knit.split_lines(test_knit.TEXT_1))
 
434
        
 
435
        # Serialise it, check the output.
 
436
        bytes = knitrepo._get_stream_as_bytes(k1, ['text-a'])
 
437
        data = bencode.bdecode(bytes)
 
438
        format, record = data
 
439
        self.assertEqual('knit-plain', format)
 
440
        self.assertEqual(['text-a', ['fulltext'], []], record[:3])
 
441
        self.assertRecordContentEqual(k1, 'text-a', record[3])
 
442
 
 
443
    def test_get_stream_as_bytes_all(self):
 
444
        """Get a serialised data stream for all the records in a knit.
 
445
 
 
446
        Much like test_get_stream_all, except for get_stream_as_bytes.
 
447
        """
 
448
        k1 = self.make_test_knit()
 
449
        # Insert the same data as BasicKnitTests.test_knit_join, as they seem
 
450
        # to cover a range of cases (no parents, one parent, multiple parents).
 
451
        test_data = [
 
452
            ('text-a', [], test_knit.TEXT_1),
 
453
            ('text-b', ['text-a'], test_knit.TEXT_1),
 
454
            ('text-c', [], test_knit.TEXT_1),
 
455
            ('text-d', ['text-c'], test_knit.TEXT_1),
 
456
            ('text-m', ['text-b', 'text-d'], test_knit.TEXT_1),
 
457
           ]
 
458
        # This test is actually a bit strict as the order in which they're
 
459
        # returned is not defined.  This matches the current (deterministic)
 
460
        # behaviour.
 
461
        expected_data_list = [
 
462
            # version, options, parents
 
463
            ('text-a', ['fulltext'], []),
 
464
            ('text-b', ['line-delta'], ['text-a']),
 
465
            ('text-m', ['line-delta'], ['text-b', 'text-d']),
 
466
            ('text-c', ['fulltext'], []),
 
467
            ('text-d', ['line-delta'], ['text-c']),
 
468
            ]
 
469
        for version_id, parents, lines in test_data:
 
470
            k1.add_lines(version_id, parents, test_knit.split_lines(lines))
 
471
 
 
472
        bytes = knitrepo._get_stream_as_bytes(
 
473
            k1, ['text-a', 'text-b', 'text-m', 'text-c', 'text-d', ])
 
474
 
 
475
        data = bencode.bdecode(bytes)
 
476
        format = data.pop(0)
 
477
        self.assertEqual('knit-plain', format)
 
478
 
 
479
        for expected, actual in zip(expected_data_list, data):
 
480
            expected_version = expected[0]
 
481
            expected_options = expected[1]
 
482
            expected_parents = expected[2]
 
483
            version, options, parents, bytes = actual
 
484
            self.assertEqual(expected_version, version)
 
485
            self.assertEqual(expected_options, options)
 
486
            self.assertEqual(expected_parents, parents)
 
487
            self.assertRecordContentEqual(k1, version, bytes)
 
488
 
 
489
 
344
490
class DummyRepository(object):
345
491
    """A dummy repository for testing."""
346
492
 
347
 
    _format = None
348
493
    _serializer = None
349
494
 
350
495
    def supports_rich_root(self):
351
 
        if self._format is not None:
352
 
            return self._format.rich_root_data
353
496
        return False
354
497
 
355
 
    def get_graph(self):
356
 
        raise NotImplementedError
357
 
 
358
 
    def get_parent_map(self, revision_ids):
359
 
        raise NotImplementedError
360
 
 
361
498
 
362
499
class InterDummy(repository.InterRepository):
363
500
    """An inter-repository optimised code path for DummyRepository.
370
507
    @staticmethod
371
508
    def is_compatible(repo_source, repo_target):
372
509
        """InterDummy is compatible with DummyRepository."""
373
 
        return (isinstance(repo_source, DummyRepository) and
 
510
        return (isinstance(repo_source, DummyRepository) and 
374
511
            isinstance(repo_target, DummyRepository))
375
512
 
376
513
 
384
521
        # classes do not barf inappropriately when a surprising repository type
385
522
        # is handed to them.
386
523
        dummy_a = DummyRepository()
387
 
        dummy_a._format = RepositoryFormat()
388
 
        dummy_a._format.supports_full_versioned_files = True
389
524
        dummy_b = DummyRepository()
390
 
        dummy_b._format = RepositoryFormat()
391
 
        dummy_b._format.supports_full_versioned_files = True
392
525
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
393
526
 
394
527
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
395
528
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default.
396
 
 
 
529
        
397
530
        The effective default is now InterSameDataRepository because there is
398
531
        no actual sane default in the presence of incompatible data models.
399
532
        """
400
533
        inter_repo = repository.InterRepository.get(repo_a, repo_b)
401
 
        self.assertEqual(vf_repository.InterSameDataRepository,
 
534
        self.assertEqual(repository.InterSameDataRepository,
402
535
                         inter_repo.__class__)
403
536
        self.assertEqual(repo_a, inter_repo.source)
404
537
        self.assertEqual(repo_b, inter_repo.target)
410
543
        # pair that it returns true on for the is_compatible static method
411
544
        # check
412
545
        dummy_a = DummyRepository()
413
 
        dummy_a._format = RepositoryFormat()
414
546
        dummy_b = DummyRepository()
415
 
        dummy_b._format = RepositoryFormat()
416
547
        repo = self.make_repository('.')
417
548
        # hack dummies to look like repo somewhat.
418
549
        dummy_a._serializer = repo._serializer
419
 
        dummy_a._format.supports_tree_reference = repo._format.supports_tree_reference
420
 
        dummy_a._format.rich_root_data = repo._format.rich_root_data
421
 
        dummy_a._format.supports_full_versioned_files = repo._format.supports_full_versioned_files
422
550
        dummy_b._serializer = repo._serializer
423
 
        dummy_b._format.supports_tree_reference = repo._format.supports_tree_reference
424
 
        dummy_b._format.rich_root_data = repo._format.rich_root_data
425
 
        dummy_b._format.supports_full_versioned_files = repo._format.supports_full_versioned_files
426
551
        repository.InterRepository.register_optimiser(InterDummy)
427
552
        try:
428
553
            # we should get the default for something InterDummy returns False
441
566
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
442
567
 
443
568
 
444
 
class TestRepositoryFormat1(knitrepo.RepositoryFormatKnit1):
445
 
 
446
 
    @classmethod
447
 
    def get_format_string(cls):
448
 
        return "Test Format 1"
449
 
 
450
 
 
451
 
class TestRepositoryFormat2(knitrepo.RepositoryFormatKnit1):
452
 
 
453
 
    @classmethod
454
 
    def get_format_string(cls):
455
 
        return "Test Format 2"
 
569
class TestInterWeaveRepo(TestCaseWithTransport):
 
570
 
 
571
    def test_is_compatible_and_registered(self):
 
572
        # InterWeaveRepo is compatible when either side
 
573
        # is a format 5/6/7 branch
 
574
        from bzrlib.repofmt import knitrepo, weaverepo
 
575
        formats = [weaverepo.RepositoryFormat5(),
 
576
                   weaverepo.RepositoryFormat6(),
 
577
                   weaverepo.RepositoryFormat7()]
 
578
        incompatible_formats = [weaverepo.RepositoryFormat4(),
 
579
                                knitrepo.RepositoryFormatKnit1(),
 
580
                                ]
 
581
        repo_a = self.make_repository('a')
 
582
        repo_b = self.make_repository('b')
 
583
        is_compatible = repository.InterWeaveRepo.is_compatible
 
584
        for source in incompatible_formats:
 
585
            # force incompatible left then right
 
586
            repo_a._format = source
 
587
            repo_b._format = formats[0]
 
588
            self.assertFalse(is_compatible(repo_a, repo_b))
 
589
            self.assertFalse(is_compatible(repo_b, repo_a))
 
590
        for source in formats:
 
591
            repo_a._format = source
 
592
            for target in formats:
 
593
                repo_b._format = target
 
594
                self.assertTrue(is_compatible(repo_a, repo_b))
 
595
        self.assertEqual(repository.InterWeaveRepo,
 
596
                         repository.InterRepository.get(repo_a,
 
597
                                                        repo_b).__class__)
 
598
 
 
599
 
 
600
class TestInterRemoteToOther(TestCaseWithTransport):
 
601
 
 
602
    def make_remote_repository(self, path, backing_format=None):
 
603
        """Make a RemoteRepository object backed by a real repository that will
 
604
        be created at the given path."""
 
605
        self.make_repository(path, format=backing_format)
 
606
        smart_server = server.SmartTCPServer_for_testing()
 
607
        smart_server.setUp()
 
608
        remote_transport = get_transport(smart_server.get_url()).clone(path)
 
609
        self.addCleanup(smart_server.tearDown)
 
610
        remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
 
611
        remote_repo = remote_bzrdir.open_repository()
 
612
        return remote_repo
 
613
 
 
614
    def test_is_compatible_same_format(self):
 
615
        """InterRemoteToOther is compatible with a remote repository and a
 
616
        second repository that have the same format."""
 
617
        local_repo = self.make_repository('local')
 
618
        remote_repo = self.make_remote_repository('remote')
 
619
        is_compatible = repository.InterRemoteToOther.is_compatible
 
620
        self.assertTrue(
 
621
            is_compatible(remote_repo, local_repo),
 
622
            "InterRemoteToOther(%r, %r) is false" % (remote_repo, local_repo))
 
623
          
 
624
    def test_is_incompatible_different_format(self):
 
625
        local_repo = self.make_repository('local', 'dirstate')
 
626
        remote_repo = self.make_remote_repository('a', 'dirstate-with-subtree')
 
627
        is_compatible = repository.InterRemoteToOther.is_compatible
 
628
        self.assertFalse(
 
629
            is_compatible(remote_repo, local_repo),
 
630
            "InterRemoteToOther(%r, %r) is true" % (local_repo, remote_repo))
 
631
 
 
632
    def test_is_incompatible_different_format_both_remote(self):
 
633
        remote_repo_a = self.make_remote_repository(
 
634
            'a', 'dirstate-with-subtree')
 
635
        remote_repo_b = self.make_remote_repository('b', 'dirstate')
 
636
        is_compatible = repository.InterRemoteToOther.is_compatible
 
637
        self.assertFalse(
 
638
            is_compatible(remote_repo_a, remote_repo_b),
 
639
            "InterRemoteToOther(%r, %r) is true"
 
640
            % (remote_repo_a, remote_repo_b))
456
641
 
457
642
 
458
643
class TestRepositoryConverter(TestCaseWithTransport):
459
644
 
460
645
    def test_convert_empty(self):
461
 
        source_format = TestRepositoryFormat1()
462
 
        target_format = TestRepositoryFormat2()
463
 
        repository.format_registry.register(source_format)
464
 
        self.addCleanup(repository.format_registry.remove,
465
 
            source_format)
466
 
        repository.format_registry.register(target_format)
467
 
        self.addCleanup(repository.format_registry.remove,
468
 
            target_format)
469
 
        t = self.get_transport()
 
646
        t = get_transport(self.get_url('.'))
470
647
        t.mkdir('repository')
471
648
        repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
472
 
        repo = TestRepositoryFormat1().initialize(repo_dir)
 
649
        repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
 
650
        target_format = knitrepo.RepositoryFormatKnit1()
473
651
        converter = repository.CopyConverter(target_format)
474
652
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
475
653
        try:
480
658
        self.assertTrue(isinstance(target_format, repo._format.__class__))
481
659
 
482
660
 
 
661
class TestMisc(TestCase):
 
662
    
 
663
    def test_unescape_xml(self):
 
664
        """We get some kind of error when malformed entities are passed"""
 
665
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;') 
 
666
 
 
667
 
483
668
class TestRepositoryFormatKnit3(TestCaseWithTransport):
484
669
 
485
 
    def test_attribute__fetch_order(self):
486
 
        """Knits need topological data insertion."""
487
 
        format = bzrdir.BzrDirMetaFormat1()
488
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
489
 
        repo = self.make_repository('.', format=format)
490
 
        self.assertEqual('topological', repo._format._fetch_order)
491
 
 
492
 
    def test_attribute__fetch_uses_deltas(self):
493
 
        """Knits reuse deltas."""
494
 
        format = bzrdir.BzrDirMetaFormat1()
495
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
496
 
        repo = self.make_repository('.', format=format)
497
 
        self.assertEqual(True, repo._format._fetch_uses_deltas)
498
 
 
499
670
    def test_convert(self):
500
671
        """Ensure the upgrade adds weaves for roots"""
501
672
        format = bzrdir.BzrDirMetaFormat1()
503
674
        tree = self.make_branch_and_tree('.', format)
504
675
        tree.commit("Dull commit", rev_id="dull")
505
676
        revision_tree = tree.branch.repository.revision_tree('dull')
506
 
        revision_tree.lock_read()
507
 
        try:
508
 
            self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
509
 
                revision_tree.get_root_id())
510
 
        finally:
511
 
            revision_tree.unlock()
 
677
        self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
 
678
            revision_tree.inventory.root.file_id)
512
679
        format = bzrdir.BzrDirMetaFormat1()
513
680
        format.repository_format = knitrepo.RepositoryFormatKnit3()
514
681
        upgrade.Convert('.', format)
515
682
        tree = workingtree.WorkingTree.open('.')
516
683
        revision_tree = tree.branch.repository.revision_tree('dull')
517
 
        revision_tree.lock_read()
518
 
        try:
519
 
            revision_tree.get_file_lines(revision_tree.get_root_id())
520
 
        finally:
521
 
            revision_tree.unlock()
 
684
        revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
522
685
        tree.commit("Another dull commit", rev_id='dull2')
523
686
        revision_tree = tree.branch.repository.revision_tree('dull2')
524
 
        revision_tree.lock_read()
525
 
        self.addCleanup(revision_tree.unlock)
526
 
        self.assertEqual('dull',
527
 
                revision_tree.get_file_revision(revision_tree.get_root_id()))
 
687
        self.assertEqual('dull', revision_tree.inventory.root.revision)
 
688
 
 
689
    def test_exposed_versioned_files_are_marked_dirty(self):
 
690
        format = bzrdir.BzrDirMetaFormat1()
 
691
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
692
        repo = self.make_repository('.', format=format)
 
693
        repo.lock_write()
 
694
        inv = repo.get_inventory_weave()
 
695
        repo.unlock()
 
696
        self.assertRaises(errors.OutSideTransaction,
 
697
            inv.add_lines, 'foo', [], [])
528
698
 
529
699
    def test_supports_external_lookups(self):
530
700
        format = bzrdir.BzrDirMetaFormat1()
533
703
        self.assertFalse(repo._format.supports_external_lookups)
534
704
 
535
705
 
536
 
class Test2a(tests.TestCaseWithMemoryTransport):
537
 
 
538
 
    def test_chk_bytes_uses_custom_btree_parser(self):
539
 
        mt = self.make_branch_and_memory_tree('test', format='2a')
540
 
        mt.lock_write()
541
 
        self.addCleanup(mt.unlock)
542
 
        mt.add([''], ['root-id'])
543
 
        mt.commit('first')
544
 
        index = mt.branch.repository.chk_bytes._index._graph_index._indices[0]
545
 
        self.assertEqual(btree_index._gcchk_factory, index._leaf_factory)
546
 
        # It should also work if we re-open the repo
547
 
        repo = mt.branch.repository.bzrdir.open_repository()
548
 
        repo.lock_read()
549
 
        self.addCleanup(repo.unlock)
550
 
        index = repo.chk_bytes._index._graph_index._indices[0]
551
 
        self.assertEqual(btree_index._gcchk_factory, index._leaf_factory)
552
 
 
553
 
    def test_fetch_combines_groups(self):
554
 
        builder = self.make_branch_builder('source', format='2a')
555
 
        builder.start_series()
556
 
        builder.build_snapshot('1', None, [
557
 
            ('add', ('', 'root-id', 'directory', '')),
558
 
            ('add', ('file', 'file-id', 'file', 'content\n'))])
559
 
        builder.build_snapshot('2', ['1'], [
560
 
            ('modify', ('file-id', 'content-2\n'))])
561
 
        builder.finish_series()
562
 
        source = builder.get_branch()
563
 
        target = self.make_repository('target', format='2a')
564
 
        target.fetch(source.repository)
565
 
        target.lock_read()
566
 
        self.addCleanup(target.unlock)
567
 
        details = target.texts._index.get_build_details(
568
 
            [('file-id', '1',), ('file-id', '2',)])
569
 
        file_1_details = details[('file-id', '1')]
570
 
        file_2_details = details[('file-id', '2')]
571
 
        # The index, and what to read off disk, should be the same for both
572
 
        # versions of the file.
573
 
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
574
 
 
575
 
    def test_fetch_combines_groups(self):
576
 
        builder = self.make_branch_builder('source', format='2a')
577
 
        builder.start_series()
578
 
        builder.build_snapshot('1', None, [
579
 
            ('add', ('', 'root-id', 'directory', '')),
580
 
            ('add', ('file', 'file-id', 'file', 'content\n'))])
581
 
        builder.build_snapshot('2', ['1'], [
582
 
            ('modify', ('file-id', 'content-2\n'))])
583
 
        builder.finish_series()
584
 
        source = builder.get_branch()
585
 
        target = self.make_repository('target', format='2a')
586
 
        target.fetch(source.repository)
587
 
        target.lock_read()
588
 
        self.addCleanup(target.unlock)
589
 
        details = target.texts._index.get_build_details(
590
 
            [('file-id', '1',), ('file-id', '2',)])
591
 
        file_1_details = details[('file-id', '1')]
592
 
        file_2_details = details[('file-id', '2')]
593
 
        # The index, and what to read off disk, should be the same for both
594
 
        # versions of the file.
595
 
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
596
 
 
597
 
    def test_fetch_combines_groups(self):
598
 
        builder = self.make_branch_builder('source', format='2a')
599
 
        builder.start_series()
600
 
        builder.build_snapshot('1', None, [
601
 
            ('add', ('', 'root-id', 'directory', '')),
602
 
            ('add', ('file', 'file-id', 'file', 'content\n'))])
603
 
        builder.build_snapshot('2', ['1'], [
604
 
            ('modify', ('file-id', 'content-2\n'))])
605
 
        builder.finish_series()
606
 
        source = builder.get_branch()
607
 
        target = self.make_repository('target', format='2a')
608
 
        target.fetch(source.repository)
609
 
        target.lock_read()
610
 
        self.addCleanup(target.unlock)
611
 
        details = target.texts._index.get_build_details(
612
 
            [('file-id', '1',), ('file-id', '2',)])
613
 
        file_1_details = details[('file-id', '1')]
614
 
        file_2_details = details[('file-id', '2')]
615
 
        # The index, and what to read off disk, should be the same for both
616
 
        # versions of the file.
617
 
        self.assertEqual(file_1_details[0][:3], file_2_details[0][:3])
618
 
 
619
 
    def test_format_pack_compresses_True(self):
620
 
        repo = self.make_repository('repo', format='2a')
621
 
        self.assertTrue(repo._format.pack_compresses)
622
 
 
623
 
    def test_inventories_use_chk_map_with_parent_base_dict(self):
624
 
        tree = self.make_branch_and_memory_tree('repo', format="2a")
625
 
        tree.lock_write()
626
 
        tree.add([''], ['TREE_ROOT'])
627
 
        revid = tree.commit("foo")
628
 
        tree.unlock()
629
 
        tree.lock_read()
630
 
        self.addCleanup(tree.unlock)
631
 
        inv = tree.branch.repository.get_inventory(revid)
632
 
        self.assertNotEqual(None, inv.parent_id_basename_to_file_id)
633
 
        inv.parent_id_basename_to_file_id._ensure_root()
634
 
        inv.id_to_entry._ensure_root()
635
 
        self.assertEqual(65536, inv.id_to_entry._root_node.maximum_size)
636
 
        self.assertEqual(65536,
637
 
            inv.parent_id_basename_to_file_id._root_node.maximum_size)
638
 
 
639
 
    def test_autopack_unchanged_chk_nodes(self):
640
 
        # at 20 unchanged commits, chk pages are packed that are split into
641
 
        # two groups such that the new pack being made doesn't have all its
642
 
        # pages in the source packs (though they are in the repository).
643
 
        # Use a memory backed repository, we don't need to hit disk for this
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'])
648
 
        for pos in range(20):
649
 
            tree.commit(str(pos))
650
 
 
651
 
    def test_pack_with_hint(self):
652
 
        tree = self.make_branch_and_memory_tree('tree', format='2a')
653
 
        tree.lock_write()
654
 
        self.addCleanup(tree.unlock)
655
 
        tree.add([''], ['TREE_ROOT'])
656
 
        # 1 commit to leave untouched
657
 
        tree.commit('1')
658
 
        to_keep = tree.branch.repository._pack_collection.names()
659
 
        # 2 to combine
660
 
        tree.commit('2')
661
 
        tree.commit('3')
662
 
        all = tree.branch.repository._pack_collection.names()
663
 
        combine = list(set(all) - set(to_keep))
664
 
        self.assertLength(3, all)
665
 
        self.assertLength(2, combine)
666
 
        tree.branch.repository.pack(hint=combine)
667
 
        final = tree.branch.repository._pack_collection.names()
668
 
        self.assertLength(2, final)
669
 
        self.assertFalse(combine[0] in final)
670
 
        self.assertFalse(combine[1] in final)
671
 
        self.assertSubset(to_keep, final)
672
 
 
673
 
    def test_stream_source_to_gc(self):
674
 
        source = self.make_repository('source', format='2a')
675
 
        target = self.make_repository('target', format='2a')
676
 
        stream = source._get_source(target._format)
677
 
        self.assertIsInstance(stream, groupcompress_repo.GroupCHKStreamSource)
678
 
 
679
 
    def test_stream_source_to_non_gc(self):
680
 
        source = self.make_repository('source', format='2a')
681
 
        target = self.make_repository('target', format='rich-root-pack')
682
 
        stream = source._get_source(target._format)
683
 
        # We don't want the child GroupCHKStreamSource
684
 
        self.assertIs(type(stream), vf_repository.StreamSource)
685
 
 
686
 
    def test_get_stream_for_missing_keys_includes_all_chk_refs(self):
687
 
        source_builder = self.make_branch_builder('source',
688
 
                            format='2a')
689
 
        # We have to build a fairly large tree, so that we are sure the chk
690
 
        # pages will have split into multiple pages.
691
 
        entries = [('add', ('', 'a-root-id', 'directory', None))]
692
 
        for i in 'abcdefghijklmnopqrstuvwxyz123456789':
693
 
            for j in 'abcdefghijklmnopqrstuvwxyz123456789':
694
 
                fname = i + j
695
 
                fid = fname + '-id'
696
 
                content = 'content for %s\n' % (fname,)
697
 
                entries.append(('add', (fname, fid, 'file', content)))
698
 
        source_builder.start_series()
699
 
        source_builder.build_snapshot('rev-1', None, entries)
700
 
        # Now change a few of them, so we get a few new pages for the second
701
 
        # revision
702
 
        source_builder.build_snapshot('rev-2', ['rev-1'], [
703
 
            ('modify', ('aa-id', 'new content for aa-id\n')),
704
 
            ('modify', ('cc-id', 'new content for cc-id\n')),
705
 
            ('modify', ('zz-id', 'new content for zz-id\n')),
706
 
            ])
707
 
        source_builder.finish_series()
708
 
        source_branch = source_builder.get_branch()
709
 
        source_branch.lock_read()
710
 
        self.addCleanup(source_branch.unlock)
711
 
        target = self.make_repository('target', format='2a')
712
 
        source = source_branch.repository._get_source(target._format)
713
 
        self.assertIsInstance(source, groupcompress_repo.GroupCHKStreamSource)
714
 
 
715
 
        # On a regular pass, getting the inventories and chk pages for rev-2
716
 
        # would only get the newly created chk pages
717
 
        search = vf_search.SearchResult(set(['rev-2']), set(['rev-1']), 1,
718
 
                                    set(['rev-2']))
719
 
        simple_chk_records = []
720
 
        for vf_name, substream in source.get_stream(search):
721
 
            if vf_name == 'chk_bytes':
722
 
                for record in substream:
723
 
                    simple_chk_records.append(record.key)
724
 
            else:
725
 
                for _ in substream:
726
 
                    continue
727
 
        # 3 pages, the root (InternalNode), + 2 pages which actually changed
728
 
        self.assertEqual([('sha1:91481f539e802c76542ea5e4c83ad416bf219f73',),
729
 
                          ('sha1:4ff91971043668583985aec83f4f0ab10a907d3f',),
730
 
                          ('sha1:81e7324507c5ca132eedaf2d8414ee4bb2226187',),
731
 
                          ('sha1:b101b7da280596c71a4540e9a1eeba8045985ee0',)],
732
 
                         simple_chk_records)
733
 
        # Now, when we do a similar call using 'get_stream_for_missing_keys'
734
 
        # we should get a much larger set of pages.
735
 
        missing = [('inventories', 'rev-2')]
736
 
        full_chk_records = []
737
 
        for vf_name, substream in source.get_stream_for_missing_keys(missing):
738
 
            if vf_name == 'inventories':
739
 
                for record in substream:
740
 
                    self.assertEqual(('rev-2',), record.key)
741
 
            elif vf_name == 'chk_bytes':
742
 
                for record in substream:
743
 
                    full_chk_records.append(record.key)
744
 
            else:
745
 
                self.fail('Should not be getting a stream of %s' % (vf_name,))
746
 
        # We have 257 records now. This is because we have 1 root page, and 256
747
 
        # leaf pages in a complete listing.
748
 
        self.assertEqual(257, len(full_chk_records))
749
 
        self.assertSubset(simple_chk_records, full_chk_records)
750
 
 
751
 
    def test_inconsistency_fatal(self):
752
 
        repo = self.make_repository('repo', format='2a')
753
 
        self.assertTrue(repo.revisions._index._inconsistency_fatal)
754
 
        self.assertFalse(repo.texts._index._inconsistency_fatal)
755
 
        self.assertFalse(repo.inventories._index._inconsistency_fatal)
756
 
        self.assertFalse(repo.signatures._index._inconsistency_fatal)
757
 
        self.assertFalse(repo.chk_bytes._index._inconsistency_fatal)
758
 
 
759
 
 
760
 
class TestKnitPackStreamSource(tests.TestCaseWithMemoryTransport):
761
 
 
762
 
    def test_source_to_exact_pack_092(self):
763
 
        source = self.make_repository('source', format='pack-0.92')
764
 
        target = self.make_repository('target', format='pack-0.92')
765
 
        stream_source = source._get_source(target._format)
766
 
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
767
 
 
768
 
    def test_source_to_exact_pack_rich_root_pack(self):
769
 
        source = self.make_repository('source', format='rich-root-pack')
770
 
        target = self.make_repository('target', format='rich-root-pack')
771
 
        stream_source = source._get_source(target._format)
772
 
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
773
 
 
774
 
    def test_source_to_exact_pack_19(self):
775
 
        source = self.make_repository('source', format='1.9')
776
 
        target = self.make_repository('target', format='1.9')
777
 
        stream_source = source._get_source(target._format)
778
 
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
779
 
 
780
 
    def test_source_to_exact_pack_19_rich_root(self):
781
 
        source = self.make_repository('source', format='1.9-rich-root')
782
 
        target = self.make_repository('target', format='1.9-rich-root')
783
 
        stream_source = source._get_source(target._format)
784
 
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
785
 
 
786
 
    def test_source_to_remote_exact_pack_19(self):
787
 
        trans = self.make_smart_server('target')
788
 
        trans.ensure_base()
789
 
        source = self.make_repository('source', format='1.9')
790
 
        target = self.make_repository('target', format='1.9')
791
 
        target = repository.Repository.open(trans.base)
792
 
        stream_source = source._get_source(target._format)
793
 
        self.assertIsInstance(stream_source, knitpack_repo.KnitPackStreamSource)
794
 
 
795
 
    def test_stream_source_to_non_exact(self):
796
 
        source = self.make_repository('source', format='pack-0.92')
797
 
        target = self.make_repository('target', format='1.9')
798
 
        stream = source._get_source(target._format)
799
 
        self.assertIs(type(stream), vf_repository.StreamSource)
800
 
 
801
 
    def test_stream_source_to_non_exact_rich_root(self):
802
 
        source = self.make_repository('source', format='1.9')
803
 
        target = self.make_repository('target', format='1.9-rich-root')
804
 
        stream = source._get_source(target._format)
805
 
        self.assertIs(type(stream), vf_repository.StreamSource)
806
 
 
807
 
    def test_source_to_remote_non_exact_pack_19(self):
808
 
        trans = self.make_smart_server('target')
809
 
        trans.ensure_base()
810
 
        source = self.make_repository('source', format='1.9')
811
 
        target = self.make_repository('target', format='1.6')
812
 
        target = repository.Repository.open(trans.base)
813
 
        stream_source = source._get_source(target._format)
814
 
        self.assertIs(type(stream_source), vf_repository.StreamSource)
815
 
 
816
 
    def test_stream_source_to_knit(self):
817
 
        source = self.make_repository('source', format='pack-0.92')
818
 
        target = self.make_repository('target', format='dirstate')
819
 
        stream = source._get_source(target._format)
820
 
        self.assertIs(type(stream), vf_repository.StreamSource)
821
 
 
822
 
 
823
 
class TestDevelopment6FindParentIdsOfRevisions(TestCaseWithTransport):
824
 
    """Tests for _find_parent_ids_of_revisions."""
825
 
 
826
 
    def setUp(self):
827
 
        super(TestDevelopment6FindParentIdsOfRevisions, self).setUp()
828
 
        self.builder = self.make_branch_builder('source')
829
 
        self.builder.start_series()
830
 
        self.builder.build_snapshot('initial', None,
831
 
            [('add', ('', 'tree-root', 'directory', None))])
832
 
        self.repo = self.builder.get_branch().repository
833
 
        self.addCleanup(self.builder.finish_series)
834
 
 
835
 
    def assertParentIds(self, expected_result, rev_set):
836
 
        self.assertEqual(sorted(expected_result),
837
 
            sorted(self.repo._find_parent_ids_of_revisions(rev_set)))
838
 
 
839
 
    def test_simple(self):
840
 
        self.builder.build_snapshot('revid1', None, [])
841
 
        self.builder.build_snapshot('revid2', ['revid1'], [])
842
 
        rev_set = ['revid2']
843
 
        self.assertParentIds(['revid1'], rev_set)
844
 
 
845
 
    def test_not_first_parent(self):
846
 
        self.builder.build_snapshot('revid1', None, [])
847
 
        self.builder.build_snapshot('revid2', ['revid1'], [])
848
 
        self.builder.build_snapshot('revid3', ['revid2'], [])
849
 
        rev_set = ['revid3', 'revid2']
850
 
        self.assertParentIds(['revid1'], rev_set)
851
 
 
852
 
    def test_not_null(self):
853
 
        rev_set = ['initial']
854
 
        self.assertParentIds([], rev_set)
855
 
 
856
 
    def test_not_null_set(self):
857
 
        self.builder.build_snapshot('revid1', None, [])
858
 
        rev_set = [_mod_revision.NULL_REVISION]
859
 
        self.assertParentIds([], rev_set)
860
 
 
861
 
    def test_ghost(self):
862
 
        self.builder.build_snapshot('revid1', None, [])
863
 
        rev_set = ['ghost', 'revid1']
864
 
        self.assertParentIds(['initial'], rev_set)
865
 
 
866
 
    def test_ghost_parent(self):
867
 
        self.builder.build_snapshot('revid1', None, [])
868
 
        self.builder.build_snapshot('revid2', ['revid1', 'ghost'], [])
869
 
        rev_set = ['revid2', 'revid1']
870
 
        self.assertParentIds(['ghost', 'initial'], rev_set)
871
 
 
872
 
    def test_righthand_parent(self):
873
 
        self.builder.build_snapshot('revid1', None, [])
874
 
        self.builder.build_snapshot('revid2a', ['revid1'], [])
875
 
        self.builder.build_snapshot('revid2b', ['revid1'], [])
876
 
        self.builder.build_snapshot('revid3', ['revid2a', 'revid2b'], [])
877
 
        rev_set = ['revid3', 'revid2a']
878
 
        self.assertParentIds(['revid1', 'revid2b'], rev_set)
879
 
 
880
 
 
881
706
class TestWithBrokenRepo(TestCaseWithTransport):
882
707
    """These tests seem to be more appropriate as interface tests?"""
883
708
 
896
721
            inv = inventory.Inventory(revision_id='rev1a')
897
722
            inv.root.revision = 'rev1a'
898
723
            self.add_file(repo, inv, 'file1', 'rev1a', [])
899
 
            repo.texts.add_lines((inv.root.file_id, 'rev1a'), [], [])
900
724
            repo.add_inventory('rev1a', inv, [])
901
725
            revision = _mod_revision.Revision('rev1a',
902
726
                committer='jrandom@example.com', timestamp=0,
903
727
                inventory_sha1='', timezone=0, message='foo', parent_ids=[])
904
 
            repo.add_revision('rev1a', revision, inv)
 
728
            repo.add_revision('rev1a',revision, inv)
905
729
 
906
730
            # make rev1b, which has no Revision, but has an Inventory, and
907
731
            # file1
937
761
    def add_revision(self, repo, revision_id, inv, parent_ids):
938
762
        inv.revision_id = revision_id
939
763
        inv.root.revision = revision_id
940
 
        repo.texts.add_lines((inv.root.file_id, revision_id), [], [])
941
764
        repo.add_inventory(revision_id, inv, parent_ids)
942
765
        revision = _mod_revision.Revision(revision_id,
943
766
            committer='jrandom@example.com', timestamp=0, inventory_sha1='',
944
767
            timezone=0, message='foo', parent_ids=parent_ids)
945
 
        repo.add_revision(revision_id, revision, inv)
 
768
        repo.add_revision(revision_id,revision, inv)
946
769
 
947
770
    def add_file(self, repo, inv, filename, revision, parents):
948
771
        file_id = filename + '-id'
950
773
        entry.revision = revision
951
774
        entry.text_size = 0
952
775
        inv.add(entry)
953
 
        text_key = (file_id, revision)
954
 
        parent_keys = [(file_id, parent) for parent in parents]
955
 
        repo.texts.add_lines(text_key, parent_keys, ['line\n'])
 
776
        vf = repo.weave_store.get_weave_or_empty(file_id,
 
777
                                                 repo.get_transaction())
 
778
        vf.add_lines(revision, parents, ['line\n'])
956
779
 
957
780
    def test_insert_from_broken_repo(self):
958
781
        """Inserting a data stream from a broken repository won't silently
960
783
        """
961
784
        broken_repo = self.make_broken_repository()
962
785
        empty_repo = self.make_repository('empty-repo')
963
 
        try:
964
 
            empty_repo.fetch(broken_repo)
965
 
        except (errors.RevisionNotPresent, errors.BzrCheckError):
966
 
            # Test successful: compression parent not being copied leads to
967
 
            # error.
968
 
            return
969
 
        empty_repo.lock_read()
 
786
        search = graph.SearchResult(set(['rev1a', 'rev2', 'rev3']),
 
787
            set(), 3, ['rev1a', 'rev2', 'rev3'])
 
788
        broken_repo.lock_read()
 
789
        self.addCleanup(broken_repo.unlock)
 
790
        stream = broken_repo.get_data_stream_for_search(search)
 
791
        empty_repo.lock_write()
970
792
        self.addCleanup(empty_repo.unlock)
971
 
        text = empty_repo.texts.get_record_stream(
972
 
            [('file2-id', 'rev3')], 'topological', True).next()
973
 
        self.assertEqual('line\n', text.get_bytes_as('fulltext'))
 
793
        empty_repo.start_write_group()
 
794
        try:
 
795
            self.assertRaises(
 
796
                errors.KnitCorrupt, empty_repo.insert_data_stream, stream)
 
797
        finally:
 
798
            empty_repo.abort_write_group()
 
799
 
 
800
 
 
801
class TestKnitPackNoSubtrees(TestCaseWithTransport):
 
802
 
 
803
    def get_format(self):
 
804
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
 
805
 
 
806
    def test_disk_layout(self):
 
807
        format = self.get_format()
 
808
        repo = self.make_repository('.', format=format)
 
809
        # in case of side effects of locking.
 
810
        repo.lock_write()
 
811
        repo.unlock()
 
812
        t = repo.bzrdir.get_repository_transport(None)
 
813
        self.check_format(t)
 
814
        # XXX: no locks left when unlocked at the moment
 
815
        # self.assertEqualDiff('', t.get('lock').read())
 
816
        self.check_databases(t)
 
817
 
 
818
    def check_format(self, t):
 
819
        self.assertEqualDiff(
 
820
            "Bazaar pack repository format 1 (needs bzr 0.92)\n",
 
821
                             t.get('format').read())
 
822
 
 
823
    def assertHasKndx(self, t, knit_name):
 
824
        """Assert that knit_name exists on t."""
 
825
        self.assertEqualDiff('# bzr knit index 8\n',
 
826
                             t.get(knit_name + '.kndx').read())
 
827
 
 
828
    def assertHasNoKndx(self, t, knit_name):
 
829
        """Assert that knit_name has no index on t."""
 
830
        self.assertFalse(t.has(knit_name + '.kndx'))
 
831
 
 
832
    def assertHasNoKnit(self, t, knit_name):
 
833
        """Assert that knit_name exists on t."""
 
834
        # no default content
 
835
        self.assertFalse(t.has(knit_name + '.knit'))
 
836
 
 
837
    def check_databases(self, t):
 
838
        """check knit content for a repository."""
 
839
        # check conversion worked
 
840
        self.assertHasNoKndx(t, 'inventory')
 
841
        self.assertHasNoKnit(t, 'inventory')
 
842
        self.assertHasNoKndx(t, 'revisions')
 
843
        self.assertHasNoKnit(t, 'revisions')
 
844
        self.assertHasNoKndx(t, 'signatures')
 
845
        self.assertHasNoKnit(t, 'signatures')
 
846
        self.assertFalse(t.has('knits'))
 
847
        # revision-indexes file-container directory
 
848
        self.assertEqual([],
 
849
            list(GraphIndex(t, 'pack-names', None).iter_all_entries()))
 
850
        self.assertTrue(S_ISDIR(t.stat('packs').st_mode))
 
851
        self.assertTrue(S_ISDIR(t.stat('upload').st_mode))
 
852
        self.assertTrue(S_ISDIR(t.stat('indices').st_mode))
 
853
        self.assertTrue(S_ISDIR(t.stat('obsolete_packs').st_mode))
 
854
 
 
855
    def test_shared_disk_layout(self):
 
856
        format = self.get_format()
 
857
        repo = self.make_repository('.', shared=True, format=format)
 
858
        # we want:
 
859
        t = repo.bzrdir.get_repository_transport(None)
 
860
        self.check_format(t)
 
861
        # XXX: no locks left when unlocked at the moment
 
862
        # self.assertEqualDiff('', t.get('lock').read())
 
863
        # We should have a 'shared-storage' marker file.
 
864
        self.assertEqualDiff('', t.get('shared-storage').read())
 
865
        self.check_databases(t)
 
866
 
 
867
    def test_shared_no_tree_disk_layout(self):
 
868
        format = self.get_format()
 
869
        repo = self.make_repository('.', shared=True, format=format)
 
870
        repo.set_make_working_trees(False)
 
871
        # we want:
 
872
        t = repo.bzrdir.get_repository_transport(None)
 
873
        self.check_format(t)
 
874
        # XXX: no locks left when unlocked at the moment
 
875
        # self.assertEqualDiff('', t.get('lock').read())
 
876
        # We should have a 'shared-storage' marker file.
 
877
        self.assertEqualDiff('', t.get('shared-storage').read())
 
878
        # We should have a marker for the no-working-trees flag.
 
879
        self.assertEqualDiff('', t.get('no-working-trees').read())
 
880
        # The marker should go when we toggle the setting.
 
881
        repo.set_make_working_trees(True)
 
882
        self.assertFalse(t.has('no-working-trees'))
 
883
        self.check_databases(t)
 
884
 
 
885
    def test_adding_revision_creates_pack_indices(self):
 
886
        format = self.get_format()
 
887
        tree = self.make_branch_and_tree('.', format=format)
 
888
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
 
889
        self.assertEqual([],
 
890
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
 
891
        tree.commit('foobarbaz')
 
892
        index = GraphIndex(trans, 'pack-names', None)
 
893
        index_nodes = list(index.iter_all_entries())
 
894
        self.assertEqual(1, len(index_nodes))
 
895
        node = index_nodes[0]
 
896
        name = node[1][0]
 
897
        # the pack sizes should be listed in the index
 
898
        pack_value = node[2]
 
899
        sizes = [int(digits) for digits in pack_value.split(' ')]
 
900
        for size, suffix in zip(sizes, ['.rix', '.iix', '.tix', '.six']):
 
901
            stat = trans.stat('indices/%s%s' % (name, suffix))
 
902
            self.assertEqual(size, stat.st_size)
 
903
 
 
904
    def test_pulling_nothing_leads_to_no_new_names(self):
 
905
        format = self.get_format()
 
906
        tree1 = self.make_branch_and_tree('1', format=format)
 
907
        tree2 = self.make_branch_and_tree('2', format=format)
 
908
        tree1.branch.repository.fetch(tree2.branch.repository)
 
909
        trans = tree1.branch.repository.bzrdir.get_repository_transport(None)
 
910
        self.assertEqual([],
 
911
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
 
912
 
 
913
    def test_commit_across_pack_shape_boundary_autopacks(self):
 
914
        format = self.get_format()
 
915
        tree = self.make_branch_and_tree('.', format=format)
 
916
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
 
917
        # This test could be a little cheaper by replacing the packs
 
918
        # attribute on the repository to allow a different pack distribution
 
919
        # and max packs policy - so we are checking the policy is honoured
 
920
        # in the test. But for now 11 commits is not a big deal in a single
 
921
        # test.
 
922
        for x in range(9):
 
923
            tree.commit('commit %s' % x)
 
924
        # there should be 9 packs:
 
925
        index = GraphIndex(trans, 'pack-names', None)
 
926
        self.assertEqual(9, len(list(index.iter_all_entries())))
 
927
        # insert some files in obsolete_packs which should be removed by pack.
 
928
        trans.put_bytes('obsolete_packs/foo', '123')
 
929
        trans.put_bytes('obsolete_packs/bar', '321')
 
930
        # committing one more should coalesce to 1 of 10.
 
931
        tree.commit('commit triggering pack')
 
932
        index = GraphIndex(trans, 'pack-names', None)
 
933
        self.assertEqual(1, len(list(index.iter_all_entries())))
 
934
        # packing should not damage data
 
935
        tree = tree.bzrdir.open_workingtree()
 
936
        check_result = tree.branch.repository.check(
 
937
            [tree.branch.last_revision()])
 
938
        # We should have 50 (10x5) files in the obsolete_packs directory.
 
939
        obsolete_files = list(trans.list_dir('obsolete_packs'))
 
940
        self.assertFalse('foo' in obsolete_files)
 
941
        self.assertFalse('bar' in obsolete_files)
 
942
        self.assertEqual(50, len(obsolete_files))
 
943
        # XXX: Todo check packs obsoleted correctly - old packs and indices
 
944
        # in the obsolete_packs directory.
 
945
        large_pack_name = list(index.iter_all_entries())[0][1][0]
 
946
        # finally, committing again should not touch the large pack.
 
947
        tree.commit('commit not triggering pack')
 
948
        index = GraphIndex(trans, 'pack-names', None)
 
949
        self.assertEqual(2, len(list(index.iter_all_entries())))
 
950
        pack_names = [node[1][0] for node in index.iter_all_entries()]
 
951
        self.assertTrue(large_pack_name in pack_names)
 
952
 
 
953
    def test_fail_obsolete_deletion(self):
 
954
        # failing to delete obsolete packs is not fatal
 
955
        format = self.get_format()
 
956
        server = fakenfs.FakeNFSServer()
 
957
        server.setUp()
 
958
        self.addCleanup(server.tearDown)
 
959
        transport = get_transport(server.get_url())
 
960
        bzrdir = self.get_format().initialize_on_transport(transport)
 
961
        repo = bzrdir.create_repository()
 
962
        repo_transport = bzrdir.get_repository_transport(None)
 
963
        self.assertTrue(repo_transport.has('obsolete_packs'))
 
964
        # these files are in use by another client and typically can't be deleted
 
965
        repo_transport.put_bytes('obsolete_packs/.nfsblahblah', 'contents')
 
966
        repo._pack_collection._clear_obsolete_packs()
 
967
        self.assertTrue(repo_transport.has('obsolete_packs/.nfsblahblah'))
 
968
 
 
969
    def test_pack_after_two_commits_packs_everything(self):
 
970
        format = self.get_format()
 
971
        tree = self.make_branch_and_tree('.', format=format)
 
972
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
 
973
        tree.commit('start')
 
974
        tree.commit('more work')
 
975
        tree.branch.repository.pack()
 
976
        # there should be 1 pack:
 
977
        index = GraphIndex(trans, 'pack-names', None)
 
978
        self.assertEqual(1, len(list(index.iter_all_entries())))
 
979
        self.assertEqual(2, len(tree.branch.repository.all_revision_ids()))
 
980
 
 
981
    def test_pack_layout(self):
 
982
        format = self.get_format()
 
983
        tree = self.make_branch_and_tree('.', format=format)
 
984
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
 
985
        tree.commit('start', rev_id='1')
 
986
        tree.commit('more work', rev_id='2')
 
987
        tree.branch.repository.pack()
 
988
        tree.lock_read()
 
989
        self.addCleanup(tree.unlock)
 
990
        pack = tree.branch.repository._pack_collection.get_pack_by_name(
 
991
            tree.branch.repository._pack_collection.names()[0])
 
992
        # revision access tends to be tip->ancestor, so ordering that way on 
 
993
        # disk is a good idea.
 
994
        for _1, key, val, refs in pack.revision_index.iter_all_entries():
 
995
            if key == ('1',):
 
996
                pos_1 = int(val[1:].split()[0])
 
997
            else:
 
998
                pos_2 = int(val[1:].split()[0])
 
999
        self.assertTrue(pos_2 < pos_1)
 
1000
 
 
1001
    def test_pack_repositories_support_multiple_write_locks(self):
 
1002
        format = self.get_format()
 
1003
        self.make_repository('.', shared=True, format=format)
 
1004
        r1 = repository.Repository.open('.')
 
1005
        r2 = repository.Repository.open('.')
 
1006
        r1.lock_write()
 
1007
        self.addCleanup(r1.unlock)
 
1008
        r2.lock_write()
 
1009
        r2.unlock()
 
1010
 
 
1011
    def _add_text(self, repo, fileid):
 
1012
        """Add a text to the repository within a write group."""
 
1013
        vf =repo.weave_store.get_weave(fileid, repo.get_transaction())
 
1014
        vf.add_lines('samplerev+' + fileid, [], [])
 
1015
 
 
1016
    def test_concurrent_writers_merge_new_packs(self):
 
1017
        format = self.get_format()
 
1018
        self.make_repository('.', shared=True, format=format)
 
1019
        r1 = repository.Repository.open('.')
 
1020
        r2 = repository.Repository.open('.')
 
1021
        r1.lock_write()
 
1022
        try:
 
1023
            # access enough data to load the names list
 
1024
            list(r1.all_revision_ids())
 
1025
            r2.lock_write()
 
1026
            try:
 
1027
                # access enough data to load the names list
 
1028
                list(r2.all_revision_ids())
 
1029
                r1.start_write_group()
 
1030
                try:
 
1031
                    r2.start_write_group()
 
1032
                    try:
 
1033
                        self._add_text(r1, 'fileidr1')
 
1034
                        self._add_text(r2, 'fileidr2')
 
1035
                    except:
 
1036
                        r2.abort_write_group()
 
1037
                        raise
 
1038
                except:
 
1039
                    r1.abort_write_group()
 
1040
                    raise
 
1041
                # both r1 and r2 have open write groups with data in them
 
1042
                # created while the other's write group was open.
 
1043
                # Commit both which requires a merge to the pack-names.
 
1044
                try:
 
1045
                    r1.commit_write_group()
 
1046
                except:
 
1047
                    r1.abort_write_group()
 
1048
                    r2.abort_write_group()
 
1049
                    raise
 
1050
                r2.commit_write_group()
 
1051
                # tell r1 to reload from disk
 
1052
                r1._pack_collection.reset()
 
1053
                # Now both repositories should know about both names
 
1054
                r1._pack_collection.ensure_loaded()
 
1055
                r2._pack_collection.ensure_loaded()
 
1056
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
 
1057
                self.assertEqual(2, len(r1._pack_collection.names()))
 
1058
            finally:
 
1059
                r2.unlock()
 
1060
        finally:
 
1061
            r1.unlock()
 
1062
 
 
1063
    def test_concurrent_writer_second_preserves_dropping_a_pack(self):
 
1064
        format = self.get_format()
 
1065
        self.make_repository('.', shared=True, format=format)
 
1066
        r1 = repository.Repository.open('.')
 
1067
        r2 = repository.Repository.open('.')
 
1068
        # add a pack to drop
 
1069
        r1.lock_write()
 
1070
        try:
 
1071
            r1.start_write_group()
 
1072
            try:
 
1073
                self._add_text(r1, 'fileidr1')
 
1074
            except:
 
1075
                r1.abort_write_group()
 
1076
                raise
 
1077
            else:
 
1078
                r1.commit_write_group()
 
1079
            r1._pack_collection.ensure_loaded()
 
1080
            name_to_drop = r1._pack_collection.all_packs()[0].name
 
1081
        finally:
 
1082
            r1.unlock()
 
1083
        r1.lock_write()
 
1084
        try:
 
1085
            # access enough data to load the names list
 
1086
            list(r1.all_revision_ids())
 
1087
            r2.lock_write()
 
1088
            try:
 
1089
                # access enough data to load the names list
 
1090
                list(r2.all_revision_ids())
 
1091
                r1._pack_collection.ensure_loaded()
 
1092
                try:
 
1093
                    r2.start_write_group()
 
1094
                    try:
 
1095
                        # in r1, drop the pack
 
1096
                        r1._pack_collection._remove_pack_from_memory(
 
1097
                            r1._pack_collection.get_pack_by_name(name_to_drop))
 
1098
                        # in r2, add a pack
 
1099
                        self._add_text(r2, 'fileidr2')
 
1100
                    except:
 
1101
                        r2.abort_write_group()
 
1102
                        raise
 
1103
                except:
 
1104
                    r1._pack_collection.reset()
 
1105
                    raise
 
1106
                # r1 has a changed names list, and r2 an open write groups with
 
1107
                # changes.
 
1108
                # save r1, and then commit the r2 write group, which requires a
 
1109
                # merge to the pack-names, which should not reinstate
 
1110
                # name_to_drop
 
1111
                try:
 
1112
                    r1._pack_collection._save_pack_names()
 
1113
                    r1._pack_collection.reset()
 
1114
                except:
 
1115
                    r2.abort_write_group()
 
1116
                    raise
 
1117
                try:
 
1118
                    r2.commit_write_group()
 
1119
                except:
 
1120
                    r2.abort_write_group()
 
1121
                    raise
 
1122
                # Now both repositories should now about just one name.
 
1123
                r1._pack_collection.ensure_loaded()
 
1124
                r2._pack_collection.ensure_loaded()
 
1125
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
 
1126
                self.assertEqual(1, len(r1._pack_collection.names()))
 
1127
                self.assertFalse(name_to_drop in r1._pack_collection.names())
 
1128
            finally:
 
1129
                r2.unlock()
 
1130
        finally:
 
1131
            r1.unlock()
 
1132
 
 
1133
    def test_lock_write_does_not_physically_lock(self):
 
1134
        repo = self.make_repository('.', format=self.get_format())
 
1135
        repo.lock_write()
 
1136
        self.addCleanup(repo.unlock)
 
1137
        self.assertFalse(repo.get_physical_lock_status())
 
1138
 
 
1139
    def prepare_for_break_lock(self):
 
1140
        # Setup the global ui factory state so that a break-lock method call
 
1141
        # will find usable input in the input stream.
 
1142
        old_factory = bzrlib.ui.ui_factory
 
1143
        def restoreFactory():
 
1144
            bzrlib.ui.ui_factory = old_factory
 
1145
        self.addCleanup(restoreFactory)
 
1146
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
 
1147
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
 
1148
 
 
1149
    def test_break_lock_breaks_physical_lock(self):
 
1150
        repo = self.make_repository('.', format=self.get_format())
 
1151
        repo._pack_collection.lock_names()
 
1152
        repo2 = repository.Repository.open('.')
 
1153
        self.assertTrue(repo.get_physical_lock_status())
 
1154
        self.prepare_for_break_lock()
 
1155
        repo2.break_lock()
 
1156
        self.assertFalse(repo.get_physical_lock_status())
 
1157
 
 
1158
    def test_broken_physical_locks_error_on__unlock_names_lock(self):
 
1159
        repo = self.make_repository('.', format=self.get_format())
 
1160
        repo._pack_collection.lock_names()
 
1161
        self.assertTrue(repo.get_physical_lock_status())
 
1162
        repo2 = repository.Repository.open('.')
 
1163
        self.prepare_for_break_lock()
 
1164
        repo2.break_lock()
 
1165
        self.assertRaises(errors.LockBroken, repo._pack_collection._unlock_names)
 
1166
 
 
1167
    def test_fetch_without_find_ghosts_ignores_ghosts(self):
 
1168
        # we want two repositories at this point:
 
1169
        # one with a revision that is a ghost in the other
 
1170
        # repository.
 
1171
        # 'ghost' is present in has_ghost, 'ghost' is absent in 'missing_ghost'.
 
1172
        # 'references' is present in both repositories, and 'tip' is present
 
1173
        # just in has_ghost.
 
1174
        # has_ghost       missing_ghost
 
1175
        #------------------------------
 
1176
        # 'ghost'             -
 
1177
        # 'references'    'references'
 
1178
        # 'tip'               -
 
1179
        # In this test we fetch 'tip' which should not fetch 'ghost'
 
1180
        has_ghost = self.make_repository('has_ghost', format=self.get_format())
 
1181
        missing_ghost = self.make_repository('missing_ghost',
 
1182
            format=self.get_format())
 
1183
 
 
1184
        def add_commit(repo, revision_id, parent_ids):
 
1185
            repo.lock_write()
 
1186
            repo.start_write_group()
 
1187
            inv = inventory.Inventory(revision_id=revision_id)
 
1188
            inv.root.revision = revision_id
 
1189
            root_id = inv.root.file_id
 
1190
            sha1 = repo.add_inventory(revision_id, inv, [])
 
1191
            vf = repo.weave_store.get_weave_or_empty(root_id,
 
1192
                repo.get_transaction())
 
1193
            vf.add_lines(revision_id, [], [])
 
1194
            rev = bzrlib.revision.Revision(timestamp=0,
 
1195
                                           timezone=None,
 
1196
                                           committer="Foo Bar <foo@example.com>",
 
1197
                                           message="Message",
 
1198
                                           inventory_sha1=sha1,
 
1199
                                           revision_id=revision_id)
 
1200
            rev.parent_ids = parent_ids
 
1201
            repo.add_revision(revision_id, rev)
 
1202
            repo.commit_write_group()
 
1203
            repo.unlock()
 
1204
        add_commit(has_ghost, 'ghost', [])
 
1205
        add_commit(has_ghost, 'references', ['ghost'])
 
1206
        add_commit(missing_ghost, 'references', ['ghost'])
 
1207
        add_commit(has_ghost, 'tip', ['references'])
 
1208
        missing_ghost.fetch(has_ghost, 'tip')
 
1209
        # missing ghost now has tip and not ghost.
 
1210
        rev = missing_ghost.get_revision('tip')
 
1211
        inv = missing_ghost.get_inventory('tip')
 
1212
        self.assertRaises(errors.NoSuchRevision,
 
1213
            missing_ghost.get_revision, 'ghost')
 
1214
        self.assertRaises(errors.RevisionNotPresent,
 
1215
            missing_ghost.get_inventory, 'ghost')
 
1216
 
 
1217
    def test_supports_external_lookups(self):
 
1218
        repo = self.make_repository('.', format=self.get_format())
 
1219
        self.assertFalse(repo._format.supports_external_lookups)
 
1220
 
 
1221
 
 
1222
class TestKnitPackSubtrees(TestKnitPackNoSubtrees):
 
1223
 
 
1224
    def get_format(self):
 
1225
        return bzrdir.format_registry.make_bzrdir(
 
1226
            'pack-0.92-subtree')
 
1227
 
 
1228
    def check_format(self, t):
 
1229
        self.assertEqualDiff(
 
1230
            "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n",
 
1231
            t.get('format').read())
 
1232
 
 
1233
 
 
1234
class TestDevelopment0(TestKnitPackNoSubtrees):
 
1235
 
 
1236
    def get_format(self):
 
1237
        return bzrdir.format_registry.make_bzrdir(
 
1238
            'development')
 
1239
 
 
1240
    def check_format(self, t):
 
1241
        self.assertEqualDiff(
 
1242
            "Bazaar development format 0 (needs bzr.dev from before 1.3)\n",
 
1243
            t.get('format').read())
 
1244
 
 
1245
 
 
1246
class TestDevelopment0Subtree(TestKnitPackNoSubtrees):
 
1247
 
 
1248
    def get_format(self):
 
1249
        return bzrdir.format_registry.make_bzrdir(
 
1250
            'development-subtree')
 
1251
 
 
1252
    def check_format(self, t):
 
1253
        self.assertEqualDiff(
 
1254
            "Bazaar development format 0 with subtree support "
 
1255
            "(needs bzr.dev from before 1.3)\n",
 
1256
            t.get('format').read())
974
1257
 
975
1258
 
976
1259
class TestRepositoryPackCollection(TestCaseWithTransport):
977
1260
 
978
1261
    def get_format(self):
979
 
        return controldir.format_registry.make_bzrdir('pack-0.92')
980
 
 
981
 
    def get_packs(self):
982
 
        format = self.get_format()
983
 
        repo = self.make_repository('.', format=format)
984
 
        return repo._pack_collection
985
 
 
986
 
    def make_packs_and_alt_repo(self, write_lock=False):
987
 
        """Create a pack repo with 3 packs, and access it via a second repo."""
988
 
        tree = self.make_branch_and_tree('.', format=self.get_format())
989
 
        tree.lock_write()
990
 
        self.addCleanup(tree.unlock)
991
 
        rev1 = tree.commit('one')
992
 
        rev2 = tree.commit('two')
993
 
        rev3 = tree.commit('three')
994
 
        r = repository.Repository.open('.')
995
 
        if write_lock:
996
 
            r.lock_write()
997
 
        else:
998
 
            r.lock_read()
999
 
        self.addCleanup(r.unlock)
1000
 
        packs = r._pack_collection
1001
 
        packs.ensure_loaded()
1002
 
        return tree, r, packs, [rev1, rev2, rev3]
1003
 
 
1004
 
    def test__clear_obsolete_packs(self):
1005
 
        packs = self.get_packs()
1006
 
        obsolete_pack_trans = packs.transport.clone('obsolete_packs')
1007
 
        obsolete_pack_trans.put_bytes('a-pack.pack', 'content\n')
1008
 
        obsolete_pack_trans.put_bytes('a-pack.rix', 'content\n')
1009
 
        obsolete_pack_trans.put_bytes('a-pack.iix', 'content\n')
1010
 
        obsolete_pack_trans.put_bytes('another-pack.pack', 'foo\n')
1011
 
        obsolete_pack_trans.put_bytes('not-a-pack.rix', 'foo\n')
1012
 
        res = packs._clear_obsolete_packs()
1013
 
        self.assertEqual(['a-pack', 'another-pack'], sorted(res))
1014
 
        self.assertEqual([], obsolete_pack_trans.list_dir('.'))
1015
 
 
1016
 
    def test__clear_obsolete_packs_preserve(self):
1017
 
        packs = self.get_packs()
1018
 
        obsolete_pack_trans = packs.transport.clone('obsolete_packs')
1019
 
        obsolete_pack_trans.put_bytes('a-pack.pack', 'content\n')
1020
 
        obsolete_pack_trans.put_bytes('a-pack.rix', 'content\n')
1021
 
        obsolete_pack_trans.put_bytes('a-pack.iix', 'content\n')
1022
 
        obsolete_pack_trans.put_bytes('another-pack.pack', 'foo\n')
1023
 
        obsolete_pack_trans.put_bytes('not-a-pack.rix', 'foo\n')
1024
 
        res = packs._clear_obsolete_packs(preserve=set(['a-pack']))
1025
 
        self.assertEqual(['a-pack', 'another-pack'], sorted(res))
1026
 
        self.assertEqual(['a-pack.iix', 'a-pack.pack', 'a-pack.rix'],
1027
 
                         sorted(obsolete_pack_trans.list_dir('.')))
 
1262
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
1028
1263
 
1029
1264
    def test__max_pack_count(self):
1030
1265
        """The maximum pack count is a function of the number of revisions."""
 
1266
        format = self.get_format()
 
1267
        repo = self.make_repository('.', format=format)
 
1268
        packs = repo._pack_collection
1031
1269
        # no revisions - one pack, so that we can have a revision free repo
1032
1270
        # without it blowing up
1033
 
        packs = self.get_packs()
1034
1271
        self.assertEqual(1, packs._max_pack_count(0))
1035
1272
        # after that the sum of the digits, - check the first 1-9
1036
1273
        self.assertEqual(1, packs._max_pack_count(1))
1051
1288
        # check some arbitrary big numbers
1052
1289
        self.assertEqual(25, packs._max_pack_count(112894))
1053
1290
 
1054
 
    def test_repr(self):
1055
 
        packs = self.get_packs()
1056
 
        self.assertContainsRe(repr(packs),
1057
 
            'RepositoryPackCollection(.*Repository(.*))')
1058
 
 
1059
 
    def test__obsolete_packs(self):
1060
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1061
 
        names = packs.names()
1062
 
        pack = packs.get_pack_by_name(names[0])
1063
 
        # Schedule this one for removal
1064
 
        packs._remove_pack_from_memory(pack)
1065
 
        # Simulate a concurrent update by renaming the .pack file and one of
1066
 
        # the indices
1067
 
        packs.transport.rename('packs/%s.pack' % (names[0],),
1068
 
                               'obsolete_packs/%s.pack' % (names[0],))
1069
 
        packs.transport.rename('indices/%s.iix' % (names[0],),
1070
 
                               'obsolete_packs/%s.iix' % (names[0],))
1071
 
        # Now trigger the obsoletion, and ensure that all the remaining files
1072
 
        # are still renamed
1073
 
        packs._obsolete_packs([pack])
1074
 
        self.assertEqual([n + '.pack' for n in names[1:]],
1075
 
                         sorted(packs._pack_transport.list_dir('.')))
1076
 
        # names[0] should not be present in the index anymore
1077
 
        self.assertEqual(names[1:],
1078
 
            sorted(set([osutils.splitext(n)[0] for n in
1079
 
                        packs._index_transport.list_dir('.')])))
1080
 
 
1081
 
    def test__obsolete_packs_missing_directory(self):
1082
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1083
 
        r.control_transport.rmdir('obsolete_packs')
1084
 
        names = packs.names()
1085
 
        pack = packs.get_pack_by_name(names[0])
1086
 
        # Schedule this one for removal
1087
 
        packs._remove_pack_from_memory(pack)
1088
 
        # Now trigger the obsoletion, and ensure that all the remaining files
1089
 
        # are still renamed
1090
 
        packs._obsolete_packs([pack])
1091
 
        self.assertEqual([n + '.pack' for n in names[1:]],
1092
 
                         sorted(packs._pack_transport.list_dir('.')))
1093
 
        # names[0] should not be present in the index anymore
1094
 
        self.assertEqual(names[1:],
1095
 
            sorted(set([osutils.splitext(n)[0] for n in
1096
 
                        packs._index_transport.list_dir('.')])))
1097
 
 
1098
1291
    def test_pack_distribution_zero(self):
1099
 
        packs = self.get_packs()
 
1292
        format = self.get_format()
 
1293
        repo = self.make_repository('.', format=format)
 
1294
        packs = repo._pack_collection
1100
1295
        self.assertEqual([0], packs.pack_distribution(0))
1101
1296
 
1102
1297
    def test_ensure_loaded_unlocked(self):
1103
 
        packs = self.get_packs()
 
1298
        format = self.get_format()
 
1299
        repo = self.make_repository('.', format=format)
1104
1300
        self.assertRaises(errors.ObjectNotLocked,
1105
 
                          packs.ensure_loaded)
 
1301
                          repo._pack_collection.ensure_loaded)
1106
1302
 
1107
1303
    def test_pack_distribution_one_to_nine(self):
1108
 
        packs = self.get_packs()
 
1304
        format = self.get_format()
 
1305
        repo = self.make_repository('.', format=format)
 
1306
        packs = repo._pack_collection
1109
1307
        self.assertEqual([1],
1110
1308
            packs.pack_distribution(1))
1111
1309
        self.assertEqual([1, 1],
1127
1325
 
1128
1326
    def test_pack_distribution_stable_at_boundaries(self):
1129
1327
        """When there are multi-rev packs the counts are stable."""
1130
 
        packs = self.get_packs()
 
1328
        format = self.get_format()
 
1329
        repo = self.make_repository('.', format=format)
 
1330
        packs = repo._pack_collection
1131
1331
        # in 10s:
1132
1332
        self.assertEqual([10], packs.pack_distribution(10))
1133
1333
        self.assertEqual([10, 1], packs.pack_distribution(11))
1142
1342
        self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1143
1343
 
1144
1344
    def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1145
 
        packs = self.get_packs()
 
1345
        format = self.get_format()
 
1346
        repo = self.make_repository('.', format=format)
 
1347
        packs = repo._pack_collection
1146
1348
        existing_packs = [(2000, "big"), (9, "medium")]
1147
1349
        # rev count - 2009 -> 2x1000 + 9x1
1148
1350
        pack_operations = packs.plan_autopack_combinations(
1150
1352
        self.assertEqual([], pack_operations)
1151
1353
 
1152
1354
    def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1153
 
        packs = self.get_packs()
 
1355
        format = self.get_format()
 
1356
        repo = self.make_repository('.', format=format)
 
1357
        packs = repo._pack_collection
1154
1358
        existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1155
1359
        # rev count - 2010 -> 2x1000 + 1x10
1156
1360
        pack_operations = packs.plan_autopack_combinations(
1158
1362
        self.assertEqual([], pack_operations)
1159
1363
 
1160
1364
    def test_plan_pack_operations_2010_combines_smallest_two(self):
1161
 
        packs = self.get_packs()
 
1365
        format = self.get_format()
 
1366
        repo = self.make_repository('.', format=format)
 
1367
        packs = repo._pack_collection
1162
1368
        existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1163
1369
            (1, "single1")]
1164
1370
        # rev count - 2010 -> 2x1000 + 1x10 (3)
1165
1371
        pack_operations = packs.plan_autopack_combinations(
1166
1372
            existing_packs, [1000, 1000, 10])
1167
 
        self.assertEqual([[2, ["single2", "single1"]]], pack_operations)
1168
 
 
1169
 
    def test_plan_pack_operations_creates_a_single_op(self):
1170
 
        packs = self.get_packs()
1171
 
        existing_packs = [(50, 'a'), (40, 'b'), (30, 'c'), (10, 'd'),
1172
 
                          (10, 'e'), (6, 'f'), (4, 'g')]
1173
 
        # rev count 150 -> 1x100 and 5x10
1174
 
        # The two size 10 packs do not need to be touched. The 50, 40, 30 would
1175
 
        # be combined into a single 120 size pack, and the 6 & 4 would
1176
 
        # becombined into a size 10 pack. However, if we have to rewrite them,
1177
 
        # we save a pack file with no increased I/O by putting them into the
1178
 
        # same file.
1179
 
        distribution = packs.pack_distribution(150)
1180
 
        pack_operations = packs.plan_autopack_combinations(existing_packs,
1181
 
                                                           distribution)
1182
 
        self.assertEqual([[130, ['a', 'b', 'c', 'f', 'g']]], pack_operations)
 
1373
        self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
1183
1374
 
1184
1375
    def test_all_packs_none(self):
1185
1376
        format = self.get_format()
1223
1414
        tree.lock_read()
1224
1415
        self.addCleanup(tree.unlock)
1225
1416
        packs = tree.branch.repository._pack_collection
1226
 
        packs.reset()
1227
1417
        packs.ensure_loaded()
1228
1418
        name = packs.names()[0]
1229
1419
        pack_1 = packs.get_pack_by_name(name)
1230
1420
        # the pack should be correctly initialised
1231
 
        sizes = packs._names[name]
1232
 
        rev_index = GraphIndex(packs._index_transport, name + '.rix', sizes[0])
1233
 
        inv_index = GraphIndex(packs._index_transport, name + '.iix', sizes[1])
1234
 
        txt_index = GraphIndex(packs._index_transport, name + '.tix', sizes[2])
1235
 
        sig_index = GraphIndex(packs._index_transport, name + '.six', sizes[3])
 
1421
        rev_index = GraphIndex(packs._index_transport, name + '.rix',
 
1422
            packs._names[name][0])
 
1423
        inv_index = GraphIndex(packs._index_transport, name + '.iix',
 
1424
            packs._names[name][1])
 
1425
        txt_index = GraphIndex(packs._index_transport, name + '.tix',
 
1426
            packs._names[name][2])
 
1427
        sig_index = GraphIndex(packs._index_transport, name + '.six',
 
1428
            packs._names[name][3])
1236
1429
        self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
1237
1430
            name, rev_index, inv_index, txt_index, sig_index), pack_1)
1238
1431
        # and the same instance should be returned on successive calls.
1239
1432
        self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1240
1433
 
1241
 
    def test_reload_pack_names_new_entry(self):
1242
 
        tree, r, packs, revs = self.make_packs_and_alt_repo()
1243
 
        names = packs.names()
1244
 
        # Add a new pack file into the repository
1245
 
        rev4 = tree.commit('four')
1246
 
        new_names = tree.branch.repository._pack_collection.names()
1247
 
        new_name = set(new_names).difference(names)
1248
 
        self.assertEqual(1, len(new_name))
1249
 
        new_name = new_name.pop()
1250
 
        # The old collection hasn't noticed yet
1251
 
        self.assertEqual(names, packs.names())
1252
 
        self.assertTrue(packs.reload_pack_names())
1253
 
        self.assertEqual(new_names, packs.names())
1254
 
        # And the repository can access the new revision
1255
 
        self.assertEqual({rev4:(revs[-1],)}, r.get_parent_map([rev4]))
1256
 
        self.assertFalse(packs.reload_pack_names())
1257
 
 
1258
 
    def test_reload_pack_names_added_and_removed(self):
1259
 
        tree, r, packs, revs = self.make_packs_and_alt_repo()
1260
 
        names = packs.names()
1261
 
        # Now repack the whole thing
1262
 
        tree.branch.repository.pack()
1263
 
        new_names = tree.branch.repository._pack_collection.names()
1264
 
        # The other collection hasn't noticed yet
1265
 
        self.assertEqual(names, packs.names())
1266
 
        self.assertTrue(packs.reload_pack_names())
1267
 
        self.assertEqual(new_names, packs.names())
1268
 
        self.assertEqual({revs[-1]:(revs[-2],)}, r.get_parent_map([revs[-1]]))
1269
 
        self.assertFalse(packs.reload_pack_names())
1270
 
 
1271
 
    def test_reload_pack_names_preserves_pending(self):
1272
 
        # TODO: Update this to also test for pending-deleted names
1273
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1274
 
        # We will add one pack (via start_write_group + insert_record_stream),
1275
 
        # and remove another pack (via _remove_pack_from_memory)
1276
 
        orig_names = packs.names()
1277
 
        orig_at_load = packs._packs_at_load
1278
 
        to_remove_name = iter(orig_names).next()
1279
 
        r.start_write_group()
1280
 
        self.addCleanup(r.abort_write_group)
1281
 
        r.texts.insert_record_stream([versionedfile.FulltextContentFactory(
1282
 
            ('text', 'rev'), (), None, 'content\n')])
1283
 
        new_pack = packs._new_pack
1284
 
        self.assertTrue(new_pack.data_inserted())
1285
 
        new_pack.finish()
1286
 
        packs.allocate(new_pack)
1287
 
        packs._new_pack = None
1288
 
        removed_pack = packs.get_pack_by_name(to_remove_name)
1289
 
        packs._remove_pack_from_memory(removed_pack)
1290
 
        names = packs.names()
1291
 
        all_nodes, deleted_nodes, new_nodes, _ = packs._diff_pack_names()
1292
 
        new_names = set([x[0][0] for x in new_nodes])
1293
 
        self.assertEqual(names, sorted([x[0][0] for x in all_nodes]))
1294
 
        self.assertEqual(set(names) - set(orig_names), new_names)
1295
 
        self.assertEqual(set([new_pack.name]), new_names)
1296
 
        self.assertEqual([to_remove_name],
1297
 
                         sorted([x[0][0] for x in deleted_nodes]))
1298
 
        packs.reload_pack_names()
1299
 
        reloaded_names = packs.names()
1300
 
        self.assertEqual(orig_at_load, packs._packs_at_load)
1301
 
        self.assertEqual(names, reloaded_names)
1302
 
        all_nodes, deleted_nodes, new_nodes, _ = packs._diff_pack_names()
1303
 
        new_names = set([x[0][0] for x in new_nodes])
1304
 
        self.assertEqual(names, sorted([x[0][0] for x in all_nodes]))
1305
 
        self.assertEqual(set(names) - set(orig_names), new_names)
1306
 
        self.assertEqual(set([new_pack.name]), new_names)
1307
 
        self.assertEqual([to_remove_name],
1308
 
                         sorted([x[0][0] for x in deleted_nodes]))
1309
 
 
1310
 
    def test_autopack_obsoletes_new_pack(self):
1311
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1312
 
        packs._max_pack_count = lambda x: 1
1313
 
        packs.pack_distribution = lambda x: [10]
1314
 
        r.start_write_group()
1315
 
        r.revisions.insert_record_stream([versionedfile.FulltextContentFactory(
1316
 
            ('bogus-rev',), (), None, 'bogus-content\n')])
1317
 
        # This should trigger an autopack, which will combine everything into a
1318
 
        # single pack file.
1319
 
        new_names = r.commit_write_group()
1320
 
        names = packs.names()
1321
 
        self.assertEqual(1, len(names))
1322
 
        self.assertEqual([names[0] + '.pack'],
1323
 
                         packs._pack_transport.list_dir('.'))
1324
 
 
1325
 
    def test_autopack_reloads_and_stops(self):
1326
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1327
 
        # After we have determined what needs to be autopacked, trigger a
1328
 
        # full-pack via the other repo which will cause us to re-evaluate and
1329
 
        # decide we don't need to do anything
1330
 
        orig_execute = packs._execute_pack_operations
1331
 
        def _munged_execute_pack_ops(*args, **kwargs):
1332
 
            tree.branch.repository.pack()
1333
 
            return orig_execute(*args, **kwargs)
1334
 
        packs._execute_pack_operations = _munged_execute_pack_ops
1335
 
        packs._max_pack_count = lambda x: 1
1336
 
        packs.pack_distribution = lambda x: [10]
1337
 
        self.assertFalse(packs.autopack())
1338
 
        self.assertEqual(1, len(packs.names()))
1339
 
        self.assertEqual(tree.branch.repository._pack_collection.names(),
1340
 
                         packs.names())
1341
 
 
1342
 
    def test__save_pack_names(self):
1343
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1344
 
        names = packs.names()
1345
 
        pack = packs.get_pack_by_name(names[0])
1346
 
        packs._remove_pack_from_memory(pack)
1347
 
        packs._save_pack_names(obsolete_packs=[pack])
1348
 
        cur_packs = packs._pack_transport.list_dir('.')
1349
 
        self.assertEqual([n + '.pack' for n in names[1:]], sorted(cur_packs))
1350
 
        # obsolete_packs will also have stuff like .rix and .iix present.
1351
 
        obsolete_packs = packs.transport.list_dir('obsolete_packs')
1352
 
        obsolete_names = set([osutils.splitext(n)[0] for n in obsolete_packs])
1353
 
        self.assertEqual([pack.name], sorted(obsolete_names))
1354
 
 
1355
 
    def test__save_pack_names_already_obsoleted(self):
1356
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1357
 
        names = packs.names()
1358
 
        pack = packs.get_pack_by_name(names[0])
1359
 
        packs._remove_pack_from_memory(pack)
1360
 
        # We are going to simulate a concurrent autopack by manually obsoleting
1361
 
        # the pack directly.
1362
 
        packs._obsolete_packs([pack])
1363
 
        packs._save_pack_names(clear_obsolete_packs=True,
1364
 
                               obsolete_packs=[pack])
1365
 
        cur_packs = packs._pack_transport.list_dir('.')
1366
 
        self.assertEqual([n + '.pack' for n in names[1:]], sorted(cur_packs))
1367
 
        # Note that while we set clear_obsolete_packs=True, it should not
1368
 
        # delete a pack file that we have also scheduled for obsoletion.
1369
 
        obsolete_packs = packs.transport.list_dir('obsolete_packs')
1370
 
        obsolete_names = set([osutils.splitext(n)[0] for n in obsolete_packs])
1371
 
        self.assertEqual([pack.name], sorted(obsolete_names))
1372
 
 
1373
 
    def test_pack_no_obsolete_packs_directory(self):
1374
 
        """Bug #314314, don't fail if obsolete_packs directory does
1375
 
        not exist."""
1376
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1377
 
        r.control_transport.rmdir('obsolete_packs')
1378
 
        packs._clear_obsolete_packs()
1379
 
 
1380
1434
 
1381
1435
class TestPack(TestCaseWithTransport):
1382
1436
    """Tests for the Pack object."""
1436
1490
        pack_transport = self.get_transport('pack')
1437
1491
        index_transport = self.get_transport('index')
1438
1492
        upload_transport.mkdir('.')
1439
 
        collection = pack_repo.RepositoryPackCollection(
1440
 
            repo=None,
1441
 
            transport=self.get_transport('.'),
1442
 
            index_transport=index_transport,
1443
 
            upload_transport=upload_transport,
1444
 
            pack_transport=pack_transport,
1445
 
            index_builder_class=BTreeBuilder,
1446
 
            index_class=BTreeGraphIndex,
1447
 
            use_chk_index=False)
1448
 
        pack = pack_repo.NewPack(collection)
1449
 
        self.addCleanup(pack.abort) # Make sure the write stream gets closed
1450
 
        self.assertIsInstance(pack.revision_index, BTreeBuilder)
1451
 
        self.assertIsInstance(pack.inventory_index, BTreeBuilder)
1452
 
        self.assertIsInstance(pack._hash, type(osutils.md5()))
 
1493
        pack = pack_repo.NewPack(upload_transport, index_transport,
 
1494
            pack_transport)
 
1495
        self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
 
1496
        self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
 
1497
        self.assertIsInstance(pack._hash, type(md5.new()))
1453
1498
        self.assertTrue(pack.upload_transport is upload_transport)
1454
1499
        self.assertTrue(pack.index_transport is index_transport)
1455
1500
        self.assertTrue(pack.pack_transport is pack_transport)
1462
1507
class TestPacker(TestCaseWithTransport):
1463
1508
    """Tests for the packs repository Packer class."""
1464
1509
 
1465
 
    def test_pack_optimizes_pack_order(self):
1466
 
        builder = self.make_branch_builder('.', format="1.9")
1467
 
        builder.start_series()
1468
 
        builder.build_snapshot('A', None, [
1469
 
            ('add', ('', 'root-id', 'directory', None)),
1470
 
            ('add', ('f', 'f-id', 'file', 'content\n'))])
1471
 
        builder.build_snapshot('B', ['A'],
1472
 
            [('modify', ('f-id', 'new-content\n'))])
1473
 
        builder.build_snapshot('C', ['B'],
1474
 
            [('modify', ('f-id', 'third-content\n'))])
1475
 
        builder.build_snapshot('D', ['C'],
1476
 
            [('modify', ('f-id', 'fourth-content\n'))])
1477
 
        b = builder.get_branch()
1478
 
        b.lock_read()
1479
 
        builder.finish_series()
1480
 
        self.addCleanup(b.unlock)
1481
 
        # At this point, we should have 4 pack files available
1482
 
        # Because of how they were built, they correspond to
1483
 
        # ['D', 'C', 'B', 'A']
1484
 
        packs = b.repository._pack_collection.packs
1485
 
        packer = knitpack_repo.KnitPacker(b.repository._pack_collection,
1486
 
                                  packs, 'testing',
1487
 
                                  revision_ids=['B', 'C'])
1488
 
        # Now, when we are copying the B & C revisions, their pack files should
1489
 
        # be moved to the front of the stack
1490
 
        # The new ordering moves B & C to the front of the .packs attribute,
1491
 
        # and leaves the others in the original order.
1492
 
        new_packs = [packs[1], packs[2], packs[0], packs[3]]
1493
 
        new_pack = packer.pack()
1494
 
        self.assertEqual(new_packs, packer.packs)
1495
 
 
1496
 
 
1497
 
class TestOptimisingPacker(TestCaseWithTransport):
1498
 
    """Tests for the OptimisingPacker class."""
1499
 
 
1500
 
    def get_pack_collection(self):
1501
 
        repo = self.make_repository('.')
1502
 
        return repo._pack_collection
1503
 
 
1504
 
    def test_open_pack_will_optimise(self):
1505
 
        packer = knitpack_repo.OptimisingKnitPacker(self.get_pack_collection(),
1506
 
                                            [], '.test')
1507
 
        new_pack = packer.open_pack()
1508
 
        self.addCleanup(new_pack.abort) # ensure cleanup
1509
 
        self.assertIsInstance(new_pack, pack_repo.NewPack)
1510
 
        self.assertTrue(new_pack.revision_index._optimize_for_size)
1511
 
        self.assertTrue(new_pack.inventory_index._optimize_for_size)
1512
 
        self.assertTrue(new_pack.text_index._optimize_for_size)
1513
 
        self.assertTrue(new_pack.signature_index._optimize_for_size)
1514
 
 
1515
 
 
1516
 
class TestGCCHKPacker(TestCaseWithTransport):
1517
 
 
1518
 
    def make_abc_branch(self):
1519
 
        builder = self.make_branch_builder('source')
1520
 
        builder.start_series()
1521
 
        builder.build_snapshot('A', None, [
1522
 
            ('add', ('', 'root-id', 'directory', None)),
1523
 
            ('add', ('file', 'file-id', 'file', 'content\n')),
1524
 
            ])
1525
 
        builder.build_snapshot('B', ['A'], [
1526
 
            ('add', ('dir', 'dir-id', 'directory', None))])
1527
 
        builder.build_snapshot('C', ['B'], [
1528
 
            ('modify', ('file-id', 'new content\n'))])
1529
 
        builder.finish_series()
1530
 
        return builder.get_branch()
1531
 
 
1532
 
    def make_branch_with_disjoint_inventory_and_revision(self):
1533
 
        """a repo with separate packs for a revisions Revision and Inventory.
1534
 
 
1535
 
        There will be one pack file that holds the Revision content, and one
1536
 
        for the Inventory content.
1537
 
 
1538
 
        :return: (repository,
1539
 
                  pack_name_with_rev_A_Revision,
1540
 
                  pack_name_with_rev_A_Inventory,
1541
 
                  pack_name_with_rev_C_content)
1542
 
        """
1543
 
        b_source = self.make_abc_branch()
1544
 
        b_base = b_source.bzrdir.sprout('base', revision_id='A').open_branch()
1545
 
        b_stacked = b_base.bzrdir.sprout('stacked', stacked=True).open_branch()
1546
 
        b_stacked.lock_write()
1547
 
        self.addCleanup(b_stacked.unlock)
1548
 
        b_stacked.fetch(b_source, 'B')
1549
 
        # Now re-open the stacked repo directly (no fallbacks) so that we can
1550
 
        # fill in the A rev.
1551
 
        repo_not_stacked = b_stacked.bzrdir.open_repository()
1552
 
        repo_not_stacked.lock_write()
1553
 
        self.addCleanup(repo_not_stacked.unlock)
1554
 
        # Now we should have a pack file with A's inventory, but not its
1555
 
        # Revision
1556
 
        self.assertEqual([('A',), ('B',)],
1557
 
                         sorted(repo_not_stacked.inventories.keys()))
1558
 
        self.assertEqual([('B',)],
1559
 
                         sorted(repo_not_stacked.revisions.keys()))
1560
 
        stacked_pack_names = repo_not_stacked._pack_collection.names()
1561
 
        # We have a couple names here, figure out which has A's inventory
1562
 
        for name in stacked_pack_names:
1563
 
            pack = repo_not_stacked._pack_collection.get_pack_by_name(name)
1564
 
            keys = [n[1] for n in pack.inventory_index.iter_all_entries()]
1565
 
            if ('A',) in keys:
1566
 
                inv_a_pack_name = name
1567
 
                break
1568
 
        else:
1569
 
            self.fail('Could not find pack containing A\'s inventory')
1570
 
        repo_not_stacked.fetch(b_source.repository, 'A')
1571
 
        self.assertEqual([('A',), ('B',)],
1572
 
                         sorted(repo_not_stacked.revisions.keys()))
1573
 
        new_pack_names = set(repo_not_stacked._pack_collection.names())
1574
 
        rev_a_pack_names = new_pack_names.difference(stacked_pack_names)
1575
 
        self.assertEqual(1, len(rev_a_pack_names))
1576
 
        rev_a_pack_name = list(rev_a_pack_names)[0]
1577
 
        # Now fetch 'C', so we have a couple pack files to join
1578
 
        repo_not_stacked.fetch(b_source.repository, 'C')
1579
 
        rev_c_pack_names = set(repo_not_stacked._pack_collection.names())
1580
 
        rev_c_pack_names = rev_c_pack_names.difference(new_pack_names)
1581
 
        self.assertEqual(1, len(rev_c_pack_names))
1582
 
        rev_c_pack_name = list(rev_c_pack_names)[0]
1583
 
        return (repo_not_stacked, rev_a_pack_name, inv_a_pack_name,
1584
 
                rev_c_pack_name)
1585
 
 
1586
 
    def test_pack_with_distant_inventories(self):
1587
 
        # See https://bugs.launchpad.net/bzr/+bug/437003
1588
 
        # When repacking, it is possible to have an inventory in a different
1589
 
        # pack file than the associated revision. An autopack can then come
1590
 
        # along, and miss that inventory, and complain.
1591
 
        (repo, rev_a_pack_name, inv_a_pack_name, rev_c_pack_name
1592
 
         ) = self.make_branch_with_disjoint_inventory_and_revision()
1593
 
        a_pack = repo._pack_collection.get_pack_by_name(rev_a_pack_name)
1594
 
        c_pack = repo._pack_collection.get_pack_by_name(rev_c_pack_name)
1595
 
        packer = groupcompress_repo.GCCHKPacker(repo._pack_collection,
1596
 
                    [a_pack, c_pack], '.test-pack')
1597
 
        # This would raise ValueError in bug #437003, but should not raise an
1598
 
        # error once fixed.
1599
 
        packer.pack()
1600
 
 
1601
 
    def test_pack_with_missing_inventory(self):
1602
 
        # Similar to test_pack_with_missing_inventory, but this time, we force
1603
 
        # the A inventory to actually be gone from the repository.
1604
 
        (repo, rev_a_pack_name, inv_a_pack_name, rev_c_pack_name
1605
 
         ) = self.make_branch_with_disjoint_inventory_and_revision()
1606
 
        inv_a_pack = repo._pack_collection.get_pack_by_name(inv_a_pack_name)
1607
 
        repo._pack_collection._remove_pack_from_memory(inv_a_pack)
1608
 
        packer = groupcompress_repo.GCCHKPacker(repo._pack_collection,
1609
 
            repo._pack_collection.all_packs(), '.test-pack')
1610
 
        e = self.assertRaises(ValueError, packer.pack)
1611
 
        packer.new_pack.abort()
1612
 
        self.assertContainsRe(str(e),
1613
 
            r"We are missing inventories for revisions: .*'A'")
1614
 
 
1615
 
 
1616
 
class TestCrossFormatPacks(TestCaseWithTransport):
1617
 
 
1618
 
    def log_pack(self, hint=None):
1619
 
        self.calls.append(('pack', hint))
1620
 
        self.orig_pack(hint=hint)
1621
 
        if self.expect_hint:
1622
 
            self.assertTrue(hint)
1623
 
 
1624
 
    def run_stream(self, src_fmt, target_fmt, expect_pack_called):
1625
 
        self.expect_hint = expect_pack_called
1626
 
        self.calls = []
1627
 
        source_tree = self.make_branch_and_tree('src', format=src_fmt)
1628
 
        source_tree.lock_write()
1629
 
        self.addCleanup(source_tree.unlock)
1630
 
        tip = source_tree.commit('foo')
1631
 
        target = self.make_repository('target', format=target_fmt)
1632
 
        target.lock_write()
1633
 
        self.addCleanup(target.unlock)
1634
 
        source = source_tree.branch.repository._get_source(target._format)
1635
 
        self.orig_pack = target.pack
1636
 
        self.overrideAttr(target, "pack", self.log_pack)
1637
 
        search = target.search_missing_revision_ids(
1638
 
            source_tree.branch.repository, revision_ids=[tip])
1639
 
        stream = source.get_stream(search)
1640
 
        from_format = source_tree.branch.repository._format
1641
 
        sink = target._get_sink()
1642
 
        sink.insert_stream(stream, from_format, [])
1643
 
        if expect_pack_called:
1644
 
            self.assertLength(1, self.calls)
1645
 
        else:
1646
 
            self.assertLength(0, self.calls)
1647
 
 
1648
 
    def run_fetch(self, src_fmt, target_fmt, expect_pack_called):
1649
 
        self.expect_hint = expect_pack_called
1650
 
        self.calls = []
1651
 
        source_tree = self.make_branch_and_tree('src', format=src_fmt)
1652
 
        source_tree.lock_write()
1653
 
        self.addCleanup(source_tree.unlock)
1654
 
        tip = source_tree.commit('foo')
1655
 
        target = self.make_repository('target', format=target_fmt)
1656
 
        target.lock_write()
1657
 
        self.addCleanup(target.unlock)
1658
 
        source = source_tree.branch.repository
1659
 
        self.orig_pack = target.pack
1660
 
        self.overrideAttr(target, "pack", self.log_pack)
1661
 
        target.fetch(source)
1662
 
        if expect_pack_called:
1663
 
            self.assertLength(1, self.calls)
1664
 
        else:
1665
 
            self.assertLength(0, self.calls)
1666
 
 
1667
 
    def test_sink_format_hint_no(self):
1668
 
        # When the target format says packing makes no difference, pack is not
1669
 
        # called.
1670
 
        self.run_stream('1.9', 'rich-root-pack', False)
1671
 
 
1672
 
    def test_sink_format_hint_yes(self):
1673
 
        # When the target format says packing makes a difference, pack is
1674
 
        # called.
1675
 
        self.run_stream('1.9', '2a', True)
1676
 
 
1677
 
    def test_sink_format_same_no(self):
1678
 
        # When the formats are the same, pack is not called.
1679
 
        self.run_stream('2a', '2a', False)
1680
 
 
1681
 
    def test_IDS_format_hint_no(self):
1682
 
        # When the target format says packing makes no difference, pack is not
1683
 
        # called.
1684
 
        self.run_fetch('1.9', 'rich-root-pack', False)
1685
 
 
1686
 
    def test_IDS_format_hint_yes(self):
1687
 
        # When the target format says packing makes a difference, pack is
1688
 
        # called.
1689
 
        self.run_fetch('1.9', '2a', True)
1690
 
 
1691
 
    def test_IDS_format_same_no(self):
1692
 
        # When the formats are the same, pack is not called.
1693
 
        self.run_fetch('2a', '2a', False)
1694
 
 
1695
 
 
1696
 
class Test_LazyListJoin(tests.TestCase):
1697
 
 
1698
 
    def test__repr__(self):
1699
 
        lazy = repository._LazyListJoin(['a'], ['b'])
1700
 
        self.assertEqual("bzrlib.repository._LazyListJoin((['a'], ['b']))",
1701
 
                         repr(lazy))
1702
 
 
1703
 
 
1704
 
class TestFeatures(tests.TestCaseWithTransport):
1705
 
 
1706
 
    def test_open_with_present_feature(self):
1707
 
        self.addCleanup(
1708
 
            repository.RepositoryFormatMetaDir.unregister_feature,
1709
 
            "makes-cheese-sandwich")
1710
 
        repository.RepositoryFormatMetaDir.register_feature(
1711
 
            "makes-cheese-sandwich")
1712
 
        repo = self.make_repository('.')
1713
 
        repo.lock_write()
1714
 
        repo._format.features["makes-cheese-sandwich"] = "required"
1715
 
        repo._format.check_support_status(False)
1716
 
        repo.unlock()
1717
 
 
1718
 
    def test_open_with_missing_required_feature(self):
1719
 
        repo = self.make_repository('.')
1720
 
        repo.lock_write()
1721
 
        repo._format.features["makes-cheese-sandwich"] = "required"
1722
 
        self.assertRaises(errors.MissingFeature,
1723
 
            repo._format.check_support_status, False)
 
1510
    # To date, this class has been factored out and nothing new added to it;
 
1511
    # thus there are not yet any tests.
 
1512
 
 
1513
 
 
1514
class TestInterDifferingSerializer(TestCaseWithTransport):
 
1515
 
 
1516
    def test_progress_bar(self):
 
1517
        tree = self.make_branch_and_tree('tree')
 
1518
        tree.commit('rev1', rev_id='rev-1')
 
1519
        tree.commit('rev2', rev_id='rev-2')
 
1520
        tree.commit('rev3', rev_id='rev-3')
 
1521
        repo = self.make_repository('repo')
 
1522
        inter_repo = repository.InterDifferingSerializer(
 
1523
            tree.branch.repository, repo)
 
1524
        pb = progress.InstrumentedProgress(to_file=StringIO())
 
1525
        pb.never_throttle = True
 
1526
        inter_repo.fetch('rev-1', pb)
 
1527
        self.assertEqual('Transferring revisions', pb.last_msg)
 
1528
        self.assertEqual(1, pb.last_cnt)
 
1529
        self.assertEqual(1, pb.last_total)
 
1530
        inter_repo.fetch('rev-3', pb)
 
1531
        self.assertEqual(2, pb.last_cnt)
 
1532
        self.assertEqual(2, pb.last_total)