~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_repository.py

  • Committer: Aaron Bentley
  • Date: 2012-07-19 16:57:16 UTC
  • mto: This revision was merged to the branch mainline in revision 6540.
  • Revision ID: aaron@aaronbentley.com-20120719165716-b4iupzkb17b9l9wx
Avoid branch write lock to preserve VFS call count.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for the Repository facility that are not interface tests.
18
18
 
19
 
For interface tests see tests/repository_implementations/*.py.
 
19
For interface tests see tests/per_repository/*.py.
20
20
 
21
21
For concrete class tests see this file, and for storage formats tests
22
22
also see this file.
23
23
"""
24
24
 
25
 
import md5
26
25
from stat import S_ISDIR
27
 
from StringIO import StringIO
28
26
 
29
27
import bzrlib
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
 
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
37
41
from bzrlib.repository import RepositoryFormat
38
 
from bzrlib.smart import server
39
42
from bzrlib.tests import (
40
43
    TestCase,
41
44
    TestCaseWithTransport,
42
 
    test_knit,
43
45
    )
44
 
from bzrlib.transport import get_transport
45
 
from bzrlib.transport.memory import MemoryServer
46
 
from bzrlib.util import bencode
47
46
from bzrlib import (
48
47
    bzrdir,
 
48
    controldir,
49
49
    errors,
50
50
    inventory,
51
 
    progress,
 
51
    osutils,
52
52
    repository,
53
53
    revision as _mod_revision,
54
 
    symbol_versioning,
55
54
    upgrade,
 
55
    versionedfile,
 
56
    vf_repository,
56
57
    workingtree,
57
58
    )
58
 
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
 
59
from bzrlib.repofmt import (
 
60
    groupcompress_repo,
 
61
    knitrepo,
 
62
    knitpack_repo,
 
63
    pack_repo,
 
64
    )
59
65
 
60
66
 
61
67
class TestDefaultFormat(TestCase):
62
68
 
63
69
    def test_get_set_default_format(self):
64
 
        old_default = bzrdir.format_registry.get('default')
 
70
        old_default = controldir.format_registry.get('default')
65
71
        private_default = old_default().repository_format.__class__
66
 
        old_format = repository.RepositoryFormat.get_default_format()
 
72
        old_format = repository.format_registry.get_default()
67
73
        self.assertTrue(isinstance(old_format, private_default))
68
74
        def make_sample_bzrdir():
69
75
            my_bzrdir = bzrdir.BzrDirMetaFormat1()
70
76
            my_bzrdir.repository_format = SampleRepositoryFormat()
71
77
            return my_bzrdir
72
 
        bzrdir.format_registry.remove('default')
73
 
        bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
74
 
        bzrdir.format_registry.set_default('sample')
 
78
        controldir.format_registry.remove('default')
 
79
        controldir.format_registry.register('sample', make_sample_bzrdir, '')
 
80
        controldir.format_registry.set_default('sample')
75
81
        # creating a repository should now create an instrumented dir.
76
82
        try:
77
83
            # the default branch format is used by the meta dir format
80
86
            result = dir.create_repository()
81
87
            self.assertEqual(result, 'A bzr repository dir')
82
88
        finally:
83
 
            bzrdir.format_registry.remove('default')
84
 
            bzrdir.format_registry.remove('sample')
85
 
            bzrdir.format_registry.register('default', old_default, '')
86
 
        self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
 
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
93
                              old_format.__class__)
88
94
 
89
95
 
90
 
class SampleRepositoryFormat(repository.RepositoryFormat):
 
96
class SampleRepositoryFormat(repository.RepositoryFormatMetaDir):
91
97
    """A sample format
92
98
 
93
 
    this format is initializable, unsupported to aid in testing the 
 
99
    this format is initializable, unsupported to aid in testing the
94
100
    open and open(unsupported=True) routines.
95
101
    """
96
102
 
97
 
    def get_format_string(self):
 
103
    @classmethod
 
104
    def get_format_string(cls):
98
105
        """See RepositoryFormat.get_format_string()."""
99
106
        return "Sample .bzr repository format."
100
107
 
111
118
        return "opened repository."
112
119
 
113
120
 
 
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
 
114
130
class TestRepositoryFormat(TestCaseWithTransport):
115
131
    """Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
116
132
 
117
133
    def test_find_format(self):
118
134
        # is the right format object found for a repository?
119
135
        # create a branch with a few known format objects.
120
 
        # this is not quite the same as 
 
136
        # this is not quite the same as
121
137
        self.build_tree(["foo/", "bar/"])
122
138
        def check_format(format, url):
123
139
            dir = format._matchingbzrdir.initialize(url)
124
140
            format.initialize(dir)
125
 
            t = get_transport(url)
126
 
            found_format = repository.RepositoryFormat.find_format(dir)
127
 
            self.failUnless(isinstance(found_format, format.__class__))
128
 
        check_format(weaverepo.RepositoryFormat7(), "bar")
129
 
        
 
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
 
130
146
    def test_find_format_no_repository(self):
131
147
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
132
148
        self.assertRaises(errors.NoRepositoryPresent,
133
 
                          repository.RepositoryFormat.find_format,
 
149
                          repository.RepositoryFormatMetaDir.find_format,
134
150
                          dir)
135
151
 
 
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
 
136
161
    def test_find_format_unknown_format(self):
137
162
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
138
163
        SampleRepositoryFormat().initialize(dir)
139
164
        self.assertRaises(UnknownFormatError,
140
 
                          repository.RepositoryFormat.find_format,
 
165
                          repository.RepositoryFormatMetaDir.find_format,
141
166
                          dir)
142
167
 
 
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.assertEquals(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
 
143
188
    def test_register_unregister_format(self):
144
189
        format = SampleRepositoryFormat()
145
 
        # make a control dir
146
 
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
147
 
        # make a repo
148
 
        format.initialize(dir)
149
 
        # register a format for it.
150
 
        repository.RepositoryFormat.register_format(format)
151
 
        # which repository.Open will refuse (not supported)
152
 
        self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
153
 
        # but open(unsupported) will work
154
 
        self.assertEqual(format.open(dir), "opened repository.")
155
 
        # unregister the format
156
 
        repository.RepositoryFormat.unregister_format(format)
157
 
 
158
 
 
159
 
class TestFormat6(TestCaseWithTransport):
160
 
 
161
 
    def test_no_ancestry_weave(self):
162
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
163
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
164
 
        # We no longer need to create the ancestry.weave file
165
 
        # since it is *never* used.
166
 
        self.assertRaises(NoSuchFile,
167
 
                          control.transport.get,
168
 
                          'ancestry.weave')
169
 
 
170
 
    def test_exposed_versioned_files_are_marked_dirty(self):
171
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
172
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
173
 
        repo.lock_write()
174
 
        inv = repo.get_inventory_weave()
175
 
        repo.unlock()
176
 
        self.assertRaises(errors.OutSideTransaction,
177
 
            inv.add_lines, 'foo', [], [])
178
 
 
179
 
    def test_supports_external_lookups(self):
180
 
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
181
 
        repo = weaverepo.RepositoryFormat6().initialize(control)
182
 
        self.assertFalse(repo._format.supports_external_lookups)
183
 
 
184
 
 
185
 
class TestFormat7(TestCaseWithTransport):
186
 
    
187
 
    def test_disk_layout(self):
188
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
189
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
190
 
        # in case of side effects of locking.
191
 
        repo.lock_write()
192
 
        repo.unlock()
193
 
        # we want:
194
 
        # format 'Bazaar-NG Repository format 7'
195
 
        # lock ''
196
 
        # inventory.weave == empty_weave
197
 
        # empty revision-store directory
198
 
        # empty weaves directory
199
 
        t = control.get_repository_transport(None)
200
 
        self.assertEqualDiff('Bazaar-NG Repository format 7',
201
 
                             t.get('format').read())
202
 
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
203
 
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
204
 
        self.assertEqualDiff('# bzr weave file v5\n'
205
 
                             'w\n'
206
 
                             'W\n',
207
 
                             t.get('inventory.weave').read())
208
 
 
209
 
    def test_shared_disk_layout(self):
210
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
211
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
212
 
        # we want:
213
 
        # format 'Bazaar-NG Repository format 7'
214
 
        # inventory.weave == empty_weave
215
 
        # empty revision-store directory
216
 
        # empty weaves directory
217
 
        # a 'shared-storage' marker file.
218
 
        # lock is not present when unlocked
219
 
        t = control.get_repository_transport(None)
220
 
        self.assertEqualDiff('Bazaar-NG Repository format 7',
221
 
                             t.get('format').read())
222
 
        self.assertEqualDiff('', t.get('shared-storage').read())
223
 
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
224
 
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
225
 
        self.assertEqualDiff('# bzr weave file v5\n'
226
 
                             'w\n'
227
 
                             'W\n',
228
 
                             t.get('inventory.weave').read())
229
 
        self.assertFalse(t.has('branch-lock'))
230
 
 
231
 
    def test_creates_lockdir(self):
232
 
        """Make sure it appears to be controlled by a LockDir existence"""
233
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
234
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
235
 
        t = control.get_repository_transport(None)
236
 
        # TODO: Should check there is a 'lock' toplevel directory, 
237
 
        # regardless of contents
238
 
        self.assertFalse(t.has('lock/held/info'))
239
 
        repo.lock_write()
240
 
        try:
241
 
            self.assertTrue(t.has('lock/held/info'))
242
 
        finally:
243
 
            # unlock so we don't get a warning about failing to do so
244
 
            repo.unlock()
245
 
 
246
 
    def test_uses_lockdir(self):
247
 
        """repo format 7 actually locks on lockdir"""
248
 
        base_url = self.get_url()
249
 
        control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
250
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
251
 
        t = control.get_repository_transport(None)
252
 
        repo.lock_write()
253
 
        repo.unlock()
254
 
        del repo
255
 
        # make sure the same lock is created by opening it
256
 
        repo = repository.Repository.open(base_url)
257
 
        repo.lock_write()
258
 
        self.assertTrue(t.has('lock/held/info'))
259
 
        repo.unlock()
260
 
        self.assertFalse(t.has('lock/held/info'))
261
 
 
262
 
    def test_shared_no_tree_disk_layout(self):
263
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
264
 
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
265
 
        repo.set_make_working_trees(False)
266
 
        # we want:
267
 
        # format 'Bazaar-NG Repository format 7'
268
 
        # lock ''
269
 
        # inventory.weave == empty_weave
270
 
        # empty revision-store directory
271
 
        # empty weaves directory
272
 
        # a 'shared-storage' marker file.
273
 
        t = control.get_repository_transport(None)
274
 
        self.assertEqualDiff('Bazaar-NG Repository format 7',
275
 
                             t.get('format').read())
276
 
        ## self.assertEqualDiff('', t.get('lock').read())
277
 
        self.assertEqualDiff('', t.get('shared-storage').read())
278
 
        self.assertEqualDiff('', t.get('no-working-trees').read())
279
 
        repo.set_make_working_trees(True)
280
 
        self.assertFalse(t.has('no-working-trees'))
281
 
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
282
 
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
283
 
        self.assertEqualDiff('# bzr weave file v5\n'
284
 
                             'w\n'
285
 
                             'W\n',
286
 
                             t.get('inventory.weave').read())
287
 
 
288
 
    def test_exposed_versioned_files_are_marked_dirty(self):
289
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
290
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
291
 
        repo.lock_write()
292
 
        inv = repo.get_inventory_weave()
293
 
        repo.unlock()
294
 
        self.assertRaises(errors.OutSideTransaction,
295
 
            inv.add_lines, 'foo', [], [])
296
 
 
297
 
    def test_supports_external_lookups(self):
298
 
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
299
 
        repo = weaverepo.RepositoryFormat7().initialize(control)
300
 
        self.assertFalse(repo._format.supports_external_lookups)
 
190
        self.registry.register(format)
 
191
        self.assertEquals(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.assertEquals([], self.registry._get_all())
 
198
        self.registry.register(format)
 
199
        self.assertEquals([format], self.registry._get_all())
 
200
 
 
201
    def test_register_extra(self):
 
202
        format = SampleExtraRepositoryFormat()
 
203
        self.assertEquals([], self.registry._get_all())
 
204
        self.registry.register_extra(format)
 
205
        self.assertEquals([format], self.registry._get_all())
 
206
 
 
207
    def test_register_extra_lazy(self):
 
208
        self.assertEquals([], self.registry._get_all())
 
209
        self.registry.register_extra_lazy("bzrlib.tests.test_repository",
 
210
            "SampleExtraRepositoryFormat")
 
211
        formats = self.registry._get_all()
 
212
        self.assertEquals(1, len(formats))
 
213
        self.assertIsInstance(formats[0], SampleExtraRepositoryFormat)
301
214
 
302
215
 
303
216
class TestFormatKnit1(TestCaseWithTransport):
304
 
    
 
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
 
305
230
    def test_disk_layout(self):
306
231
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
307
232
        repo = knitrepo.RepositoryFormatKnit1().initialize(control)
321
246
        # self.assertEqualDiff('', t.get('lock').read())
322
247
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
323
248
        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  :')
324
257
 
325
 
    def assertHasKnit(self, t, knit_name):
 
258
    def assertHasKnit(self, t, knit_name, extra_content=''):
326
259
        """Assert that knit_name exists on t."""
327
 
        self.assertEqualDiff('# bzr knit index 8\n',
 
260
        self.assertEqualDiff('# bzr knit index 8\n' + extra_content,
328
261
                             t.get(knit_name + '.kndx').read())
329
 
        # no default content
330
 
        self.assertTrue(t.has(knit_name + '.knit'))
331
262
 
332
263
    def check_knits(self, t):
333
264
        """check knit content for a repository."""
377
308
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
378
309
        self.check_knits(t)
379
310
 
380
 
    def test_exposed_versioned_files_are_marked_dirty(self):
381
 
        format = bzrdir.BzrDirMetaFormat1()
382
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
383
 
        repo = self.make_repository('.', format=format)
384
 
        repo.lock_write()
385
 
        inv = repo.get_inventory_weave()
386
 
        repo.unlock()
387
 
        self.assertRaises(errors.OutSideTransaction,
388
 
            inv.add_lines, 'foo', [], [])
389
 
 
390
311
    def test_deserialise_sets_root_revision(self):
391
312
        """We must have a inventory.root.revision
392
313
 
395
316
        is valid when the api is not being abused.
396
317
        """
397
318
        repo = self.make_repository('.',
398
 
                format=bzrdir.format_registry.get('knit')())
 
319
                format=controldir.format_registry.get('knit')())
399
320
        inv_xml = '<inventory format="5">\n</inventory>\n'
400
 
        inv = repo.deserialise_inventory('test-rev-id', inv_xml)
 
321
        inv = repo._deserialise_inventory('test-rev-id', inv_xml)
401
322
        self.assertEqual('test-rev-id', inv.root.revision)
402
323
 
403
324
    def test_deserialise_uses_global_revision_id(self):
404
325
        """If it is set, then we re-use the global revision id"""
405
326
        repo = self.make_repository('.',
406
 
                format=bzrdir.format_registry.get('knit')())
 
327
                format=controldir.format_registry.get('knit')())
407
328
        inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
408
329
                   '</inventory>\n')
409
330
        # Arguably, the deserialise_inventory should detect a mismatch, and
410
331
        # raise an error, rather than silently using one revision_id over the
411
332
        # other.
412
 
        self.assertRaises(AssertionError, repo.deserialise_inventory,
 
333
        self.assertRaises(AssertionError, repo._deserialise_inventory,
413
334
            'test-rev-id', inv_xml)
414
 
        inv = repo.deserialise_inventory('other-rev-id', inv_xml)
 
335
        inv = repo._deserialise_inventory('other-rev-id', inv_xml)
415
336
        self.assertEqual('other-rev-id', inv.root.revision)
416
337
 
417
338
    def test_supports_external_lookups(self):
418
339
        repo = self.make_repository('.',
419
 
                format=bzrdir.format_registry.get('knit')())
 
340
                format=controldir.format_registry.get('knit')())
420
341
        self.assertFalse(repo._format.supports_external_lookups)
421
342
 
422
343
 
423
 
class KnitRepositoryStreamTests(test_knit.KnitTests):
424
 
    """Tests for knitrepo._get_stream_as_bytes."""
425
 
 
426
 
    def test_get_stream_as_bytes(self):
427
 
        # Make a simple knit
428
 
        k1 = self.make_test_knit()
429
 
        k1.add_lines('text-a', [], test_knit.split_lines(test_knit.TEXT_1))
430
 
        
431
 
        # Serialise it, check the output.
432
 
        bytes = knitrepo._get_stream_as_bytes(k1, ['text-a'])
433
 
        data = bencode.bdecode(bytes)
434
 
        format, record = data
435
 
        self.assertEqual('knit-plain', format)
436
 
        self.assertEqual(['text-a', ['fulltext'], []], record[:3])
437
 
        self.assertRecordContentEqual(k1, 'text-a', record[3])
438
 
 
439
 
    def test_get_stream_as_bytes_all(self):
440
 
        """Get a serialised data stream for all the records in a knit.
441
 
 
442
 
        Much like test_get_stream_all, except for get_stream_as_bytes.
443
 
        """
444
 
        k1 = self.make_test_knit()
445
 
        # Insert the same data as BasicKnitTests.test_knit_join, as they seem
446
 
        # to cover a range of cases (no parents, one parent, multiple parents).
447
 
        test_data = [
448
 
            ('text-a', [], test_knit.TEXT_1),
449
 
            ('text-b', ['text-a'], test_knit.TEXT_1),
450
 
            ('text-c', [], test_knit.TEXT_1),
451
 
            ('text-d', ['text-c'], test_knit.TEXT_1),
452
 
            ('text-m', ['text-b', 'text-d'], test_knit.TEXT_1),
453
 
           ]
454
 
        # This test is actually a bit strict as the order in which they're
455
 
        # returned is not defined.  This matches the current (deterministic)
456
 
        # behaviour.
457
 
        expected_data_list = [
458
 
            # version, options, parents
459
 
            ('text-a', ['fulltext'], []),
460
 
            ('text-b', ['line-delta'], ['text-a']),
461
 
            ('text-m', ['line-delta'], ['text-b', 'text-d']),
462
 
            ('text-c', ['fulltext'], []),
463
 
            ('text-d', ['line-delta'], ['text-c']),
464
 
            ]
465
 
        for version_id, parents, lines in test_data:
466
 
            k1.add_lines(version_id, parents, test_knit.split_lines(lines))
467
 
 
468
 
        bytes = knitrepo._get_stream_as_bytes(
469
 
            k1, ['text-a', 'text-b', 'text-m', 'text-c', 'text-d', ])
470
 
 
471
 
        data = bencode.bdecode(bytes)
472
 
        format = data.pop(0)
473
 
        self.assertEqual('knit-plain', format)
474
 
 
475
 
        for expected, actual in zip(expected_data_list, data):
476
 
            expected_version = expected[0]
477
 
            expected_options = expected[1]
478
 
            expected_parents = expected[2]
479
 
            version, options, parents, bytes = actual
480
 
            self.assertEqual(expected_version, version)
481
 
            self.assertEqual(expected_options, options)
482
 
            self.assertEqual(expected_parents, parents)
483
 
            self.assertRecordContentEqual(k1, version, bytes)
484
 
 
485
 
 
486
344
class DummyRepository(object):
487
345
    """A dummy repository for testing."""
488
346
 
 
347
    _format = None
489
348
    _serializer = None
490
349
 
491
350
    def supports_rich_root(self):
 
351
        if self._format is not None:
 
352
            return self._format.rich_root_data
492
353
        return False
493
354
 
 
355
    def get_graph(self):
 
356
        raise NotImplementedError
 
357
 
 
358
    def get_parent_map(self, revision_ids):
 
359
        raise NotImplementedError
 
360
 
494
361
 
495
362
class InterDummy(repository.InterRepository):
496
363
    """An inter-repository optimised code path for DummyRepository.
503
370
    @staticmethod
504
371
    def is_compatible(repo_source, repo_target):
505
372
        """InterDummy is compatible with DummyRepository."""
506
 
        return (isinstance(repo_source, DummyRepository) and 
 
373
        return (isinstance(repo_source, DummyRepository) and
507
374
            isinstance(repo_target, DummyRepository))
508
375
 
509
376
 
517
384
        # classes do not barf inappropriately when a surprising repository type
518
385
        # is handed to them.
519
386
        dummy_a = DummyRepository()
 
387
        dummy_a._format = RepositoryFormat()
 
388
        dummy_a._format.supports_full_versioned_files = True
520
389
        dummy_b = DummyRepository()
 
390
        dummy_b._format = RepositoryFormat()
 
391
        dummy_b._format.supports_full_versioned_files = True
521
392
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
522
393
 
523
394
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
524
395
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default.
525
 
        
 
396
 
526
397
        The effective default is now InterSameDataRepository because there is
527
398
        no actual sane default in the presence of incompatible data models.
528
399
        """
529
400
        inter_repo = repository.InterRepository.get(repo_a, repo_b)
530
 
        self.assertEqual(repository.InterSameDataRepository,
 
401
        self.assertEqual(vf_repository.InterSameDataRepository,
531
402
                         inter_repo.__class__)
532
403
        self.assertEqual(repo_a, inter_repo.source)
533
404
        self.assertEqual(repo_b, inter_repo.target)
539
410
        # pair that it returns true on for the is_compatible static method
540
411
        # check
541
412
        dummy_a = DummyRepository()
 
413
        dummy_a._format = RepositoryFormat()
542
414
        dummy_b = DummyRepository()
 
415
        dummy_b._format = RepositoryFormat()
543
416
        repo = self.make_repository('.')
544
417
        # hack dummies to look like repo somewhat.
545
418
        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
546
422
        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
547
426
        repository.InterRepository.register_optimiser(InterDummy)
548
427
        try:
549
428
            # we should get the default for something InterDummy returns False
562
441
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
563
442
 
564
443
 
565
 
class TestInterWeaveRepo(TestCaseWithTransport):
566
 
 
567
 
    def test_is_compatible_and_registered(self):
568
 
        # InterWeaveRepo is compatible when either side
569
 
        # is a format 5/6/7 branch
570
 
        from bzrlib.repofmt import knitrepo, weaverepo
571
 
        formats = [weaverepo.RepositoryFormat5(),
572
 
                   weaverepo.RepositoryFormat6(),
573
 
                   weaverepo.RepositoryFormat7()]
574
 
        incompatible_formats = [weaverepo.RepositoryFormat4(),
575
 
                                knitrepo.RepositoryFormatKnit1(),
576
 
                                ]
577
 
        repo_a = self.make_repository('a')
578
 
        repo_b = self.make_repository('b')
579
 
        is_compatible = repository.InterWeaveRepo.is_compatible
580
 
        for source in incompatible_formats:
581
 
            # force incompatible left then right
582
 
            repo_a._format = source
583
 
            repo_b._format = formats[0]
584
 
            self.assertFalse(is_compatible(repo_a, repo_b))
585
 
            self.assertFalse(is_compatible(repo_b, repo_a))
586
 
        for source in formats:
587
 
            repo_a._format = source
588
 
            for target in formats:
589
 
                repo_b._format = target
590
 
                self.assertTrue(is_compatible(repo_a, repo_b))
591
 
        self.assertEqual(repository.InterWeaveRepo,
592
 
                         repository.InterRepository.get(repo_a,
593
 
                                                        repo_b).__class__)
594
 
 
595
 
 
596
 
class TestInterRemoteToOther(TestCaseWithTransport):
597
 
 
598
 
    def make_remote_repository(self, path, backing_format=None):
599
 
        """Make a RemoteRepository object backed by a real repository that will
600
 
        be created at the given path."""
601
 
        self.make_repository(path, format=backing_format)
602
 
        smart_server = server.SmartTCPServer_for_testing()
603
 
        smart_server.setUp()
604
 
        remote_transport = get_transport(smart_server.get_url()).clone(path)
605
 
        self.addCleanup(smart_server.tearDown)
606
 
        remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
607
 
        remote_repo = remote_bzrdir.open_repository()
608
 
        return remote_repo
609
 
 
610
 
    def test_is_compatible_same_format(self):
611
 
        """InterRemoteToOther is compatible with a remote repository and a
612
 
        second repository that have the same format."""
613
 
        local_repo = self.make_repository('local')
614
 
        remote_repo = self.make_remote_repository('remote')
615
 
        is_compatible = repository.InterRemoteToOther.is_compatible
616
 
        self.assertTrue(
617
 
            is_compatible(remote_repo, local_repo),
618
 
            "InterRemoteToOther(%r, %r) is false" % (remote_repo, local_repo))
619
 
          
620
 
    def test_is_incompatible_different_format(self):
621
 
        local_repo = self.make_repository('local', 'dirstate')
622
 
        remote_repo = self.make_remote_repository('a', 'dirstate-with-subtree')
623
 
        is_compatible = repository.InterRemoteToOther.is_compatible
624
 
        self.assertFalse(
625
 
            is_compatible(remote_repo, local_repo),
626
 
            "InterRemoteToOther(%r, %r) is true" % (local_repo, remote_repo))
627
 
 
628
 
    def test_is_incompatible_different_format_both_remote(self):
629
 
        remote_repo_a = self.make_remote_repository(
630
 
            'a', 'dirstate-with-subtree')
631
 
        remote_repo_b = self.make_remote_repository('b', 'dirstate')
632
 
        is_compatible = repository.InterRemoteToOther.is_compatible
633
 
        self.assertFalse(
634
 
            is_compatible(remote_repo_a, remote_repo_b),
635
 
            "InterRemoteToOther(%r, %r) is true"
636
 
            % (remote_repo_a, remote_repo_b))
 
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"
637
456
 
638
457
 
639
458
class TestRepositoryConverter(TestCaseWithTransport):
640
459
 
641
460
    def test_convert_empty(self):
642
 
        t = get_transport(self.get_url('.'))
 
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()
643
470
        t.mkdir('repository')
644
471
        repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
645
 
        repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
646
 
        target_format = knitrepo.RepositoryFormatKnit1()
 
472
        repo = TestRepositoryFormat1().initialize(repo_dir)
647
473
        converter = repository.CopyConverter(target_format)
648
474
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
649
475
        try:
654
480
        self.assertTrue(isinstance(target_format, repo._format.__class__))
655
481
 
656
482
 
657
 
class TestMisc(TestCase):
658
 
    
659
 
    def test_unescape_xml(self):
660
 
        """We get some kind of error when malformed entities are passed"""
661
 
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;') 
662
 
 
663
 
 
664
483
class TestRepositoryFormatKnit3(TestCaseWithTransport):
665
484
 
 
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
 
666
499
    def test_convert(self):
667
500
        """Ensure the upgrade adds weaves for roots"""
668
501
        format = bzrdir.BzrDirMetaFormat1()
670
503
        tree = self.make_branch_and_tree('.', format)
671
504
        tree.commit("Dull commit", rev_id="dull")
672
505
        revision_tree = tree.branch.repository.revision_tree('dull')
673
 
        self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
674
 
            revision_tree.inventory.root.file_id)
 
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()
675
512
        format = bzrdir.BzrDirMetaFormat1()
676
513
        format.repository_format = knitrepo.RepositoryFormatKnit3()
677
514
        upgrade.Convert('.', format)
678
515
        tree = workingtree.WorkingTree.open('.')
679
516
        revision_tree = tree.branch.repository.revision_tree('dull')
680
 
        revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
 
517
        revision_tree.lock_read()
 
518
        try:
 
519
            revision_tree.get_file_lines(revision_tree.get_root_id())
 
520
        finally:
 
521
            revision_tree.unlock()
681
522
        tree.commit("Another dull commit", rev_id='dull2')
682
523
        revision_tree = tree.branch.repository.revision_tree('dull2')
683
 
        self.assertEqual('dull', revision_tree.inventory.root.revision)
684
 
 
685
 
    def test_exposed_versioned_files_are_marked_dirty(self):
686
 
        format = bzrdir.BzrDirMetaFormat1()
687
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
688
 
        repo = self.make_repository('.', format=format)
689
 
        repo.lock_write()
690
 
        inv = repo.get_inventory_weave()
691
 
        repo.unlock()
692
 
        self.assertRaises(errors.OutSideTransaction,
693
 
            inv.add_lines, 'foo', [], [])
 
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()))
694
528
 
695
529
    def test_supports_external_lookups(self):
696
530
        format = bzrdir.BzrDirMetaFormat1()
699
533
        self.assertFalse(repo._format.supports_external_lookups)
700
534
 
701
535
 
 
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
 
702
881
class TestWithBrokenRepo(TestCaseWithTransport):
703
882
    """These tests seem to be more appropriate as interface tests?"""
704
883
 
717
896
            inv = inventory.Inventory(revision_id='rev1a')
718
897
            inv.root.revision = 'rev1a'
719
898
            self.add_file(repo, inv, 'file1', 'rev1a', [])
 
899
            repo.texts.add_lines((inv.root.file_id, 'rev1a'), [], [])
720
900
            repo.add_inventory('rev1a', inv, [])
721
901
            revision = _mod_revision.Revision('rev1a',
722
902
                committer='jrandom@example.com', timestamp=0,
723
903
                inventory_sha1='', timezone=0, message='foo', parent_ids=[])
724
 
            repo.add_revision('rev1a',revision, inv)
 
904
            repo.add_revision('rev1a', revision, inv)
725
905
 
726
906
            # make rev1b, which has no Revision, but has an Inventory, and
727
907
            # file1
757
937
    def add_revision(self, repo, revision_id, inv, parent_ids):
758
938
        inv.revision_id = revision_id
759
939
        inv.root.revision = revision_id
 
940
        repo.texts.add_lines((inv.root.file_id, revision_id), [], [])
760
941
        repo.add_inventory(revision_id, inv, parent_ids)
761
942
        revision = _mod_revision.Revision(revision_id,
762
943
            committer='jrandom@example.com', timestamp=0, inventory_sha1='',
763
944
            timezone=0, message='foo', parent_ids=parent_ids)
764
 
        repo.add_revision(revision_id,revision, inv)
 
945
        repo.add_revision(revision_id, revision, inv)
765
946
 
766
947
    def add_file(self, repo, inv, filename, revision, parents):
767
948
        file_id = filename + '-id'
769
950
        entry.revision = revision
770
951
        entry.text_size = 0
771
952
        inv.add(entry)
772
 
        vf = repo.weave_store.get_weave_or_empty(file_id,
773
 
                                                 repo.get_transaction())
774
 
        vf.add_lines(revision, parents, ['line\n'])
 
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'])
775
956
 
776
957
    def test_insert_from_broken_repo(self):
777
958
        """Inserting a data stream from a broken repository won't silently
779
960
        """
780
961
        broken_repo = self.make_broken_repository()
781
962
        empty_repo = self.make_repository('empty-repo')
782
 
        search = graph.SearchResult(set(['rev1a', 'rev2', 'rev3']),
783
 
            set(), 3, ['rev1a', 'rev2', 'rev3'])
784
 
        broken_repo.lock_read()
785
 
        self.addCleanup(broken_repo.unlock)
786
 
        stream = broken_repo.get_data_stream_for_search(search)
787
 
        empty_repo.lock_write()
 
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()
788
970
        self.addCleanup(empty_repo.unlock)
789
 
        empty_repo.start_write_group()
790
 
        try:
791
 
            self.assertRaises(
792
 
                errors.KnitCorrupt, empty_repo.insert_data_stream, stream)
793
 
        finally:
794
 
            empty_repo.abort_write_group()
795
 
 
796
 
 
797
 
class TestKnitPackNoSubtrees(TestCaseWithTransport):
798
 
 
799
 
    def get_format(self):
800
 
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
801
 
 
802
 
    def test_disk_layout(self):
803
 
        format = self.get_format()
804
 
        repo = self.make_repository('.', format=format)
805
 
        # in case of side effects of locking.
806
 
        repo.lock_write()
807
 
        repo.unlock()
808
 
        t = repo.bzrdir.get_repository_transport(None)
809
 
        self.check_format(t)
810
 
        # XXX: no locks left when unlocked at the moment
811
 
        # self.assertEqualDiff('', t.get('lock').read())
812
 
        self.check_databases(t)
813
 
 
814
 
    def check_format(self, t):
815
 
        self.assertEqualDiff(
816
 
            "Bazaar pack repository format 1 (needs bzr 0.92)\n",
817
 
                             t.get('format').read())
818
 
 
819
 
    def assertHasKndx(self, t, knit_name):
820
 
        """Assert that knit_name exists on t."""
821
 
        self.assertEqualDiff('# bzr knit index 8\n',
822
 
                             t.get(knit_name + '.kndx').read())
823
 
 
824
 
    def assertHasNoKndx(self, t, knit_name):
825
 
        """Assert that knit_name has no index on t."""
826
 
        self.assertFalse(t.has(knit_name + '.kndx'))
827
 
 
828
 
    def assertHasNoKnit(self, t, knit_name):
829
 
        """Assert that knit_name exists on t."""
830
 
        # no default content
831
 
        self.assertFalse(t.has(knit_name + '.knit'))
832
 
 
833
 
    def check_databases(self, t):
834
 
        """check knit content for a repository."""
835
 
        # check conversion worked
836
 
        self.assertHasNoKndx(t, 'inventory')
837
 
        self.assertHasNoKnit(t, 'inventory')
838
 
        self.assertHasNoKndx(t, 'revisions')
839
 
        self.assertHasNoKnit(t, 'revisions')
840
 
        self.assertHasNoKndx(t, 'signatures')
841
 
        self.assertHasNoKnit(t, 'signatures')
842
 
        self.assertFalse(t.has('knits'))
843
 
        # revision-indexes file-container directory
844
 
        self.assertEqual([],
845
 
            list(GraphIndex(t, 'pack-names', None).iter_all_entries()))
846
 
        self.assertTrue(S_ISDIR(t.stat('packs').st_mode))
847
 
        self.assertTrue(S_ISDIR(t.stat('upload').st_mode))
848
 
        self.assertTrue(S_ISDIR(t.stat('indices').st_mode))
849
 
        self.assertTrue(S_ISDIR(t.stat('obsolete_packs').st_mode))
850
 
 
851
 
    def test_shared_disk_layout(self):
852
 
        format = self.get_format()
853
 
        repo = self.make_repository('.', shared=True, format=format)
854
 
        # we want:
855
 
        t = repo.bzrdir.get_repository_transport(None)
856
 
        self.check_format(t)
857
 
        # XXX: no locks left when unlocked at the moment
858
 
        # self.assertEqualDiff('', t.get('lock').read())
859
 
        # We should have a 'shared-storage' marker file.
860
 
        self.assertEqualDiff('', t.get('shared-storage').read())
861
 
        self.check_databases(t)
862
 
 
863
 
    def test_shared_no_tree_disk_layout(self):
864
 
        format = self.get_format()
865
 
        repo = self.make_repository('.', shared=True, format=format)
866
 
        repo.set_make_working_trees(False)
867
 
        # we want:
868
 
        t = repo.bzrdir.get_repository_transport(None)
869
 
        self.check_format(t)
870
 
        # XXX: no locks left when unlocked at the moment
871
 
        # self.assertEqualDiff('', t.get('lock').read())
872
 
        # We should have a 'shared-storage' marker file.
873
 
        self.assertEqualDiff('', t.get('shared-storage').read())
874
 
        # We should have a marker for the no-working-trees flag.
875
 
        self.assertEqualDiff('', t.get('no-working-trees').read())
876
 
        # The marker should go when we toggle the setting.
877
 
        repo.set_make_working_trees(True)
878
 
        self.assertFalse(t.has('no-working-trees'))
879
 
        self.check_databases(t)
880
 
 
881
 
    def test_adding_revision_creates_pack_indices(self):
882
 
        format = self.get_format()
883
 
        tree = self.make_branch_and_tree('.', format=format)
884
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
885
 
        self.assertEqual([],
886
 
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
887
 
        tree.commit('foobarbaz')
888
 
        index = GraphIndex(trans, 'pack-names', None)
889
 
        index_nodes = list(index.iter_all_entries())
890
 
        self.assertEqual(1, len(index_nodes))
891
 
        node = index_nodes[0]
892
 
        name = node[1][0]
893
 
        # the pack sizes should be listed in the index
894
 
        pack_value = node[2]
895
 
        sizes = [int(digits) for digits in pack_value.split(' ')]
896
 
        for size, suffix in zip(sizes, ['.rix', '.iix', '.tix', '.six']):
897
 
            stat = trans.stat('indices/%s%s' % (name, suffix))
898
 
            self.assertEqual(size, stat.st_size)
899
 
 
900
 
    def test_pulling_nothing_leads_to_no_new_names(self):
901
 
        format = self.get_format()
902
 
        tree1 = self.make_branch_and_tree('1', format=format)
903
 
        tree2 = self.make_branch_and_tree('2', format=format)
904
 
        tree1.branch.repository.fetch(tree2.branch.repository)
905
 
        trans = tree1.branch.repository.bzrdir.get_repository_transport(None)
906
 
        self.assertEqual([],
907
 
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
908
 
 
909
 
    def test_commit_across_pack_shape_boundary_autopacks(self):
910
 
        format = self.get_format()
911
 
        tree = self.make_branch_and_tree('.', format=format)
912
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
913
 
        # This test could be a little cheaper by replacing the packs
914
 
        # attribute on the repository to allow a different pack distribution
915
 
        # and max packs policy - so we are checking the policy is honoured
916
 
        # in the test. But for now 11 commits is not a big deal in a single
917
 
        # test.
918
 
        for x in range(9):
919
 
            tree.commit('commit %s' % x)
920
 
        # there should be 9 packs:
921
 
        index = GraphIndex(trans, 'pack-names', None)
922
 
        self.assertEqual(9, len(list(index.iter_all_entries())))
923
 
        # insert some files in obsolete_packs which should be removed by pack.
924
 
        trans.put_bytes('obsolete_packs/foo', '123')
925
 
        trans.put_bytes('obsolete_packs/bar', '321')
926
 
        # committing one more should coalesce to 1 of 10.
927
 
        tree.commit('commit triggering pack')
928
 
        index = GraphIndex(trans, 'pack-names', None)
929
 
        self.assertEqual(1, len(list(index.iter_all_entries())))
930
 
        # packing should not damage data
931
 
        tree = tree.bzrdir.open_workingtree()
932
 
        check_result = tree.branch.repository.check(
933
 
            [tree.branch.last_revision()])
934
 
        # We should have 50 (10x5) files in the obsolete_packs directory.
935
 
        obsolete_files = list(trans.list_dir('obsolete_packs'))
936
 
        self.assertFalse('foo' in obsolete_files)
937
 
        self.assertFalse('bar' in obsolete_files)
938
 
        self.assertEqual(50, len(obsolete_files))
939
 
        # XXX: Todo check packs obsoleted correctly - old packs and indices
940
 
        # in the obsolete_packs directory.
941
 
        large_pack_name = list(index.iter_all_entries())[0][1][0]
942
 
        # finally, committing again should not touch the large pack.
943
 
        tree.commit('commit not triggering pack')
944
 
        index = GraphIndex(trans, 'pack-names', None)
945
 
        self.assertEqual(2, len(list(index.iter_all_entries())))
946
 
        pack_names = [node[1][0] for node in index.iter_all_entries()]
947
 
        self.assertTrue(large_pack_name in pack_names)
948
 
 
949
 
    def test_pack_after_two_commits_packs_everything(self):
950
 
        format = self.get_format()
951
 
        tree = self.make_branch_and_tree('.', format=format)
952
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
953
 
        tree.commit('start')
954
 
        tree.commit('more work')
955
 
        tree.branch.repository.pack()
956
 
        # there should be 1 pack:
957
 
        index = GraphIndex(trans, 'pack-names', None)
958
 
        self.assertEqual(1, len(list(index.iter_all_entries())))
959
 
        self.assertEqual(2, len(tree.branch.repository.all_revision_ids()))
960
 
 
961
 
    def test_pack_layout(self):
962
 
        format = self.get_format()
963
 
        tree = self.make_branch_and_tree('.', format=format)
964
 
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
965
 
        tree.commit('start', rev_id='1')
966
 
        tree.commit('more work', rev_id='2')
967
 
        tree.branch.repository.pack()
968
 
        tree.lock_read()
969
 
        self.addCleanup(tree.unlock)
970
 
        pack = tree.branch.repository._pack_collection.get_pack_by_name(
971
 
            tree.branch.repository._pack_collection.names()[0])
972
 
        # revision access tends to be tip->ancestor, so ordering that way on 
973
 
        # disk is a good idea.
974
 
        for _1, key, val, refs in pack.revision_index.iter_all_entries():
975
 
            if key == ('1',):
976
 
                pos_1 = int(val[1:].split()[0])
977
 
            else:
978
 
                pos_2 = int(val[1:].split()[0])
979
 
        self.assertTrue(pos_2 < pos_1)
980
 
 
981
 
    def test_pack_repositories_support_multiple_write_locks(self):
982
 
        format = self.get_format()
983
 
        self.make_repository('.', shared=True, format=format)
984
 
        r1 = repository.Repository.open('.')
985
 
        r2 = repository.Repository.open('.')
986
 
        r1.lock_write()
987
 
        self.addCleanup(r1.unlock)
988
 
        r2.lock_write()
989
 
        r2.unlock()
990
 
 
991
 
    def _add_text(self, repo, fileid):
992
 
        """Add a text to the repository within a write group."""
993
 
        vf =repo.weave_store.get_weave(fileid, repo.get_transaction())
994
 
        vf.add_lines('samplerev+' + fileid, [], [])
995
 
 
996
 
    def test_concurrent_writers_merge_new_packs(self):
997
 
        format = self.get_format()
998
 
        self.make_repository('.', shared=True, format=format)
999
 
        r1 = repository.Repository.open('.')
1000
 
        r2 = repository.Repository.open('.')
1001
 
        r1.lock_write()
1002
 
        try:
1003
 
            # access enough data to load the names list
1004
 
            list(r1.all_revision_ids())
1005
 
            r2.lock_write()
1006
 
            try:
1007
 
                # access enough data to load the names list
1008
 
                list(r2.all_revision_ids())
1009
 
                r1.start_write_group()
1010
 
                try:
1011
 
                    r2.start_write_group()
1012
 
                    try:
1013
 
                        self._add_text(r1, 'fileidr1')
1014
 
                        self._add_text(r2, 'fileidr2')
1015
 
                    except:
1016
 
                        r2.abort_write_group()
1017
 
                        raise
1018
 
                except:
1019
 
                    r1.abort_write_group()
1020
 
                    raise
1021
 
                # both r1 and r2 have open write groups with data in them
1022
 
                # created while the other's write group was open.
1023
 
                # Commit both which requires a merge to the pack-names.
1024
 
                try:
1025
 
                    r1.commit_write_group()
1026
 
                except:
1027
 
                    r1.abort_write_group()
1028
 
                    r2.abort_write_group()
1029
 
                    raise
1030
 
                r2.commit_write_group()
1031
 
                # tell r1 to reload from disk
1032
 
                r1._pack_collection.reset()
1033
 
                # Now both repositories should know about both names
1034
 
                r1._pack_collection.ensure_loaded()
1035
 
                r2._pack_collection.ensure_loaded()
1036
 
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1037
 
                self.assertEqual(2, len(r1._pack_collection.names()))
1038
 
            finally:
1039
 
                r2.unlock()
1040
 
        finally:
1041
 
            r1.unlock()
1042
 
 
1043
 
    def test_concurrent_writer_second_preserves_dropping_a_pack(self):
1044
 
        format = self.get_format()
1045
 
        self.make_repository('.', shared=True, format=format)
1046
 
        r1 = repository.Repository.open('.')
1047
 
        r2 = repository.Repository.open('.')
1048
 
        # add a pack to drop
1049
 
        r1.lock_write()
1050
 
        try:
1051
 
            r1.start_write_group()
1052
 
            try:
1053
 
                self._add_text(r1, 'fileidr1')
1054
 
            except:
1055
 
                r1.abort_write_group()
1056
 
                raise
1057
 
            else:
1058
 
                r1.commit_write_group()
1059
 
            r1._pack_collection.ensure_loaded()
1060
 
            name_to_drop = r1._pack_collection.all_packs()[0].name
1061
 
        finally:
1062
 
            r1.unlock()
1063
 
        r1.lock_write()
1064
 
        try:
1065
 
            # access enough data to load the names list
1066
 
            list(r1.all_revision_ids())
1067
 
            r2.lock_write()
1068
 
            try:
1069
 
                # access enough data to load the names list
1070
 
                list(r2.all_revision_ids())
1071
 
                r1._pack_collection.ensure_loaded()
1072
 
                try:
1073
 
                    r2.start_write_group()
1074
 
                    try:
1075
 
                        # in r1, drop the pack
1076
 
                        r1._pack_collection._remove_pack_from_memory(
1077
 
                            r1._pack_collection.get_pack_by_name(name_to_drop))
1078
 
                        # in r2, add a pack
1079
 
                        self._add_text(r2, 'fileidr2')
1080
 
                    except:
1081
 
                        r2.abort_write_group()
1082
 
                        raise
1083
 
                except:
1084
 
                    r1._pack_collection.reset()
1085
 
                    raise
1086
 
                # r1 has a changed names list, and r2 an open write groups with
1087
 
                # changes.
1088
 
                # save r1, and then commit the r2 write group, which requires a
1089
 
                # merge to the pack-names, which should not reinstate
1090
 
                # name_to_drop
1091
 
                try:
1092
 
                    r1._pack_collection._save_pack_names()
1093
 
                    r1._pack_collection.reset()
1094
 
                except:
1095
 
                    r2.abort_write_group()
1096
 
                    raise
1097
 
                try:
1098
 
                    r2.commit_write_group()
1099
 
                except:
1100
 
                    r2.abort_write_group()
1101
 
                    raise
1102
 
                # Now both repositories should now about just one name.
1103
 
                r1._pack_collection.ensure_loaded()
1104
 
                r2._pack_collection.ensure_loaded()
1105
 
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1106
 
                self.assertEqual(1, len(r1._pack_collection.names()))
1107
 
                self.assertFalse(name_to_drop in r1._pack_collection.names())
1108
 
            finally:
1109
 
                r2.unlock()
1110
 
        finally:
1111
 
            r1.unlock()
1112
 
 
1113
 
    def test_lock_write_does_not_physically_lock(self):
1114
 
        repo = self.make_repository('.', format=self.get_format())
1115
 
        repo.lock_write()
1116
 
        self.addCleanup(repo.unlock)
1117
 
        self.assertFalse(repo.get_physical_lock_status())
1118
 
 
1119
 
    def prepare_for_break_lock(self):
1120
 
        # Setup the global ui factory state so that a break-lock method call
1121
 
        # will find usable input in the input stream.
1122
 
        old_factory = bzrlib.ui.ui_factory
1123
 
        def restoreFactory():
1124
 
            bzrlib.ui.ui_factory = old_factory
1125
 
        self.addCleanup(restoreFactory)
1126
 
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1127
 
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
1128
 
 
1129
 
    def test_break_lock_breaks_physical_lock(self):
1130
 
        repo = self.make_repository('.', format=self.get_format())
1131
 
        repo._pack_collection.lock_names()
1132
 
        repo2 = repository.Repository.open('.')
1133
 
        self.assertTrue(repo.get_physical_lock_status())
1134
 
        self.prepare_for_break_lock()
1135
 
        repo2.break_lock()
1136
 
        self.assertFalse(repo.get_physical_lock_status())
1137
 
 
1138
 
    def test_broken_physical_locks_error_on__unlock_names_lock(self):
1139
 
        repo = self.make_repository('.', format=self.get_format())
1140
 
        repo._pack_collection.lock_names()
1141
 
        self.assertTrue(repo.get_physical_lock_status())
1142
 
        repo2 = repository.Repository.open('.')
1143
 
        self.prepare_for_break_lock()
1144
 
        repo2.break_lock()
1145
 
        self.assertRaises(errors.LockBroken, repo._pack_collection._unlock_names)
1146
 
 
1147
 
    def test_fetch_without_find_ghosts_ignores_ghosts(self):
1148
 
        # we want two repositories at this point:
1149
 
        # one with a revision that is a ghost in the other
1150
 
        # repository.
1151
 
        # 'ghost' is present in has_ghost, 'ghost' is absent in 'missing_ghost'.
1152
 
        # 'references' is present in both repositories, and 'tip' is present
1153
 
        # just in has_ghost.
1154
 
        # has_ghost       missing_ghost
1155
 
        #------------------------------
1156
 
        # 'ghost'             -
1157
 
        # 'references'    'references'
1158
 
        # 'tip'               -
1159
 
        # In this test we fetch 'tip' which should not fetch 'ghost'
1160
 
        has_ghost = self.make_repository('has_ghost', format=self.get_format())
1161
 
        missing_ghost = self.make_repository('missing_ghost',
1162
 
            format=self.get_format())
1163
 
 
1164
 
        def add_commit(repo, revision_id, parent_ids):
1165
 
            repo.lock_write()
1166
 
            repo.start_write_group()
1167
 
            inv = inventory.Inventory(revision_id=revision_id)
1168
 
            inv.root.revision = revision_id
1169
 
            root_id = inv.root.file_id
1170
 
            sha1 = repo.add_inventory(revision_id, inv, [])
1171
 
            vf = repo.weave_store.get_weave_or_empty(root_id,
1172
 
                repo.get_transaction())
1173
 
            vf.add_lines(revision_id, [], [])
1174
 
            rev = bzrlib.revision.Revision(timestamp=0,
1175
 
                                           timezone=None,
1176
 
                                           committer="Foo Bar <foo@example.com>",
1177
 
                                           message="Message",
1178
 
                                           inventory_sha1=sha1,
1179
 
                                           revision_id=revision_id)
1180
 
            rev.parent_ids = parent_ids
1181
 
            repo.add_revision(revision_id, rev)
1182
 
            repo.commit_write_group()
1183
 
            repo.unlock()
1184
 
        add_commit(has_ghost, 'ghost', [])
1185
 
        add_commit(has_ghost, 'references', ['ghost'])
1186
 
        add_commit(missing_ghost, 'references', ['ghost'])
1187
 
        add_commit(has_ghost, 'tip', ['references'])
1188
 
        missing_ghost.fetch(has_ghost, 'tip')
1189
 
        # missing ghost now has tip and not ghost.
1190
 
        rev = missing_ghost.get_revision('tip')
1191
 
        inv = missing_ghost.get_inventory('tip')
1192
 
        self.assertRaises(errors.NoSuchRevision,
1193
 
            missing_ghost.get_revision, 'ghost')
1194
 
        self.assertRaises(errors.RevisionNotPresent,
1195
 
            missing_ghost.get_inventory, 'ghost')
1196
 
 
1197
 
    def test_supports_external_lookups(self):
1198
 
        repo = self.make_repository('.', format=self.get_format())
1199
 
        self.assertFalse(repo._format.supports_external_lookups)
1200
 
 
1201
 
 
1202
 
class TestKnitPackSubtrees(TestKnitPackNoSubtrees):
1203
 
 
1204
 
    def get_format(self):
1205
 
        return bzrdir.format_registry.make_bzrdir(
1206
 
            'pack-0.92-subtree')
1207
 
 
1208
 
    def check_format(self, t):
1209
 
        self.assertEqualDiff(
1210
 
            "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n",
1211
 
            t.get('format').read())
1212
 
 
1213
 
 
1214
 
class TestDevelopment0(TestKnitPackNoSubtrees):
1215
 
 
1216
 
    def get_format(self):
1217
 
        return bzrdir.format_registry.make_bzrdir(
1218
 
            'development')
1219
 
 
1220
 
    def check_format(self, t):
1221
 
        self.assertEqualDiff(
1222
 
            "Bazaar development format 0 (needs bzr.dev from before 1.3)\n",
1223
 
            t.get('format').read())
1224
 
 
1225
 
 
1226
 
class TestDevelopment0Subtree(TestKnitPackNoSubtrees):
1227
 
 
1228
 
    def get_format(self):
1229
 
        return bzrdir.format_registry.make_bzrdir(
1230
 
            'development-subtree')
1231
 
 
1232
 
    def check_format(self, t):
1233
 
        self.assertEqualDiff(
1234
 
            "Bazaar development format 0 with subtree support "
1235
 
            "(needs bzr.dev from before 1.3)\n",
1236
 
            t.get('format').read())
 
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'))
1237
974
 
1238
975
 
1239
976
class TestRepositoryPackCollection(TestCaseWithTransport):
1240
977
 
1241
978
    def get_format(self):
1242
 
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
 
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('.')))
1243
1028
 
1244
1029
    def test__max_pack_count(self):
1245
1030
        """The maximum pack count is a function of the number of revisions."""
1246
 
        format = self.get_format()
1247
 
        repo = self.make_repository('.', format=format)
1248
 
        packs = repo._pack_collection
1249
1031
        # no revisions - one pack, so that we can have a revision free repo
1250
1032
        # without it blowing up
 
1033
        packs = self.get_packs()
1251
1034
        self.assertEqual(1, packs._max_pack_count(0))
1252
1035
        # after that the sum of the digits, - check the first 1-9
1253
1036
        self.assertEqual(1, packs._max_pack_count(1))
1268
1051
        # check some arbitrary big numbers
1269
1052
        self.assertEqual(25, packs._max_pack_count(112894))
1270
1053
 
 
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
 
1271
1098
    def test_pack_distribution_zero(self):
1272
 
        format = self.get_format()
1273
 
        repo = self.make_repository('.', format=format)
1274
 
        packs = repo._pack_collection
 
1099
        packs = self.get_packs()
1275
1100
        self.assertEqual([0], packs.pack_distribution(0))
1276
1101
 
1277
1102
    def test_ensure_loaded_unlocked(self):
1278
 
        format = self.get_format()
1279
 
        repo = self.make_repository('.', format=format)
 
1103
        packs = self.get_packs()
1280
1104
        self.assertRaises(errors.ObjectNotLocked,
1281
 
                          repo._pack_collection.ensure_loaded)
 
1105
                          packs.ensure_loaded)
1282
1106
 
1283
1107
    def test_pack_distribution_one_to_nine(self):
1284
 
        format = self.get_format()
1285
 
        repo = self.make_repository('.', format=format)
1286
 
        packs = repo._pack_collection
 
1108
        packs = self.get_packs()
1287
1109
        self.assertEqual([1],
1288
1110
            packs.pack_distribution(1))
1289
1111
        self.assertEqual([1, 1],
1305
1127
 
1306
1128
    def test_pack_distribution_stable_at_boundaries(self):
1307
1129
        """When there are multi-rev packs the counts are stable."""
1308
 
        format = self.get_format()
1309
 
        repo = self.make_repository('.', format=format)
1310
 
        packs = repo._pack_collection
 
1130
        packs = self.get_packs()
1311
1131
        # in 10s:
1312
1132
        self.assertEqual([10], packs.pack_distribution(10))
1313
1133
        self.assertEqual([10, 1], packs.pack_distribution(11))
1322
1142
        self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1323
1143
 
1324
1144
    def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1325
 
        format = self.get_format()
1326
 
        repo = self.make_repository('.', format=format)
1327
 
        packs = repo._pack_collection
 
1145
        packs = self.get_packs()
1328
1146
        existing_packs = [(2000, "big"), (9, "medium")]
1329
1147
        # rev count - 2009 -> 2x1000 + 9x1
1330
1148
        pack_operations = packs.plan_autopack_combinations(
1332
1150
        self.assertEqual([], pack_operations)
1333
1151
 
1334
1152
    def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1335
 
        format = self.get_format()
1336
 
        repo = self.make_repository('.', format=format)
1337
 
        packs = repo._pack_collection
 
1153
        packs = self.get_packs()
1338
1154
        existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1339
1155
        # rev count - 2010 -> 2x1000 + 1x10
1340
1156
        pack_operations = packs.plan_autopack_combinations(
1342
1158
        self.assertEqual([], pack_operations)
1343
1159
 
1344
1160
    def test_plan_pack_operations_2010_combines_smallest_two(self):
1345
 
        format = self.get_format()
1346
 
        repo = self.make_repository('.', format=format)
1347
 
        packs = repo._pack_collection
 
1161
        packs = self.get_packs()
1348
1162
        existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1349
1163
            (1, "single1")]
1350
1164
        # rev count - 2010 -> 2x1000 + 1x10 (3)
1351
1165
        pack_operations = packs.plan_autopack_combinations(
1352
1166
            existing_packs, [1000, 1000, 10])
1353
 
        self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
 
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)
1354
1183
 
1355
1184
    def test_all_packs_none(self):
1356
1185
        format = self.get_format()
1394
1223
        tree.lock_read()
1395
1224
        self.addCleanup(tree.unlock)
1396
1225
        packs = tree.branch.repository._pack_collection
 
1226
        packs.reset()
1397
1227
        packs.ensure_loaded()
1398
1228
        name = packs.names()[0]
1399
1229
        pack_1 = packs.get_pack_by_name(name)
1400
1230
        # the pack should be correctly initialised
1401
 
        rev_index = GraphIndex(packs._index_transport, name + '.rix',
1402
 
            packs._names[name][0])
1403
 
        inv_index = GraphIndex(packs._index_transport, name + '.iix',
1404
 
            packs._names[name][1])
1405
 
        txt_index = GraphIndex(packs._index_transport, name + '.tix',
1406
 
            packs._names[name][2])
1407
 
        sig_index = GraphIndex(packs._index_transport, name + '.six',
1408
 
            packs._names[name][3])
 
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])
1409
1236
        self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
1410
1237
            name, rev_index, inv_index, txt_index, sig_index), pack_1)
1411
1238
        # and the same instance should be returned on successive calls.
1412
1239
        self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1413
1240
 
 
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
 
1414
1380
 
1415
1381
class TestPack(TestCaseWithTransport):
1416
1382
    """Tests for the Pack object."""
1470
1436
        pack_transport = self.get_transport('pack')
1471
1437
        index_transport = self.get_transport('index')
1472
1438
        upload_transport.mkdir('.')
1473
 
        pack = pack_repo.NewPack(upload_transport, index_transport,
1474
 
            pack_transport)
1475
 
        self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
1476
 
        self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
1477
 
        self.assertIsInstance(pack._hash, type(md5.new()))
 
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()))
1478
1453
        self.assertTrue(pack.upload_transport is upload_transport)
1479
1454
        self.assertTrue(pack.index_transport is index_transport)
1480
1455
        self.assertTrue(pack.pack_transport is pack_transport)
1487
1462
class TestPacker(TestCaseWithTransport):
1488
1463
    """Tests for the packs repository Packer class."""
1489
1464
 
1490
 
    # To date, this class has been factored out and nothing new added to it;
1491
 
    # thus there are not yet any tests.
1492
 
 
1493
 
 
1494
 
class TestInterDifferingSerializer(TestCaseWithTransport):
1495
 
 
1496
 
    def test_progress_bar(self):
1497
 
        tree = self.make_branch_and_tree('tree')
1498
 
        tree.commit('rev1', rev_id='rev-1')
1499
 
        tree.commit('rev2', rev_id='rev-2')
1500
 
        tree.commit('rev3', rev_id='rev-3')
1501
 
        repo = self.make_repository('repo')
1502
 
        inter_repo = repository.InterDifferingSerializer(
1503
 
            tree.branch.repository, repo)
1504
 
        pb = progress.InstrumentedProgress(to_file=StringIO())
1505
 
        pb.never_throttle = True
1506
 
        inter_repo.fetch('rev-1', pb)
1507
 
        self.assertEqual('Transferring revisions', pb.last_msg)
1508
 
        self.assertEqual(1, pb.last_cnt)
1509
 
        self.assertEqual(1, pb.last_total)
1510
 
        inter_repo.fetch('rev-3', pb)
1511
 
        self.assertEqual(2, pb.last_cnt)
1512
 
        self.assertEqual(2, pb.last_total)
 
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)