~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Andrew Bennetts
  • Date: 2009-08-13 00:20:29 UTC
  • mto: This revision was merged to the branch mainline in revision 4608.
  • Revision ID: andrew.bennetts@canonical.com-20090813002029-akc5x2mtxa8rq068
Raise InventoryDeltaErrors, not generic BzrErrors, from inventory_delta.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Deprecated weave-based repository formats.
 
18
 
 
19
Weave based formats scaled linearly with history size and could not represent
 
20
ghosts.
 
21
"""
 
22
 
 
23
import os
 
24
from cStringIO import StringIO
 
25
import urllib
 
26
 
 
27
from bzrlib.lazy_import import lazy_import
 
28
lazy_import(globals(), """
 
29
from bzrlib import (
 
30
    xml5,
 
31
    )
 
32
""")
 
33
from bzrlib import (
 
34
    bzrdir,
 
35
    debug,
 
36
    errors,
 
37
    lockable_files,
 
38
    lockdir,
 
39
    osutils,
 
40
    revision as _mod_revision,
 
41
    urlutils,
 
42
    versionedfile,
 
43
    weave,
 
44
    weavefile,
 
45
    )
 
46
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
47
from bzrlib.repository import (
 
48
    CommitBuilder,
 
49
    MetaDirVersionedFileRepository,
 
50
    MetaDirRepositoryFormat,
 
51
    Repository,
 
52
    RepositoryFormat,
 
53
    )
 
54
from bzrlib.store.text import TextStore
 
55
from bzrlib.trace import mutter
 
56
from bzrlib.tuned_gzip import GzipFile, bytes_to_gzip
 
57
from bzrlib.versionedfile import (
 
58
    AbsentContentFactory,
 
59
    FulltextContentFactory,
 
60
    VersionedFiles,
 
61
    )
 
62
 
 
63
 
 
64
class AllInOneRepository(Repository):
 
65
    """Legacy support - the repository behaviour for all-in-one branches."""
 
66
 
 
67
    @property
 
68
    def _serializer(self):
 
69
        return xml5.serializer_v5
 
70
 
 
71
    def _escape(self, file_or_path):
 
72
        if not isinstance(file_or_path, basestring):
 
73
            file_or_path = '/'.join(file_or_path)
 
74
        if file_or_path == '':
 
75
            return u''
 
76
        return urlutils.escape(osutils.safe_unicode(file_or_path))
 
77
 
 
78
    def __init__(self, _format, a_bzrdir):
 
79
        # we reuse one control files instance.
 
80
        dir_mode = a_bzrdir._get_dir_mode()
 
81
        file_mode = a_bzrdir._get_file_mode()
 
82
 
 
83
        def get_store(name, compressed=True, prefixed=False):
 
84
            # FIXME: This approach of assuming stores are all entirely compressed
 
85
            # or entirely uncompressed is tidy, but breaks upgrade from
 
86
            # some existing branches where there's a mixture; we probably
 
87
            # still want the option to look for both.
 
88
            relpath = self._escape(name)
 
89
            store = TextStore(a_bzrdir.transport.clone(relpath),
 
90
                              prefixed=prefixed, compressed=compressed,
 
91
                              dir_mode=dir_mode,
 
92
                              file_mode=file_mode)
 
93
            return store
 
94
 
 
95
        # not broken out yet because the controlweaves|inventory_store
 
96
        # and texts bits are still different.
 
97
        if isinstance(_format, RepositoryFormat4):
 
98
            # cannot remove these - there is still no consistent api
 
99
            # which allows access to this old info.
 
100
            self.inventory_store = get_store('inventory-store')
 
101
            self._text_store = get_store('text-store')
 
102
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files)
 
103
 
 
104
    @needs_read_lock
 
105
    def _all_possible_ids(self):
 
106
        """Return all the possible revisions that we could find."""
 
107
        if 'evil' in debug.debug_flags:
 
108
            mutter_callsite(3, "_all_possible_ids scales with size of history.")
 
109
        return [key[-1] for key in self.inventories.keys()]
 
110
 
 
111
    @needs_read_lock
 
112
    def _all_revision_ids(self):
 
113
        """Returns a list of all the revision ids in the repository.
 
114
 
 
115
        These are in as much topological order as the underlying store can
 
116
        present: for weaves ghosts may lead to a lack of correctness until
 
117
        the reweave updates the parents list.
 
118
        """
 
119
        return [key[-1] for key in self.revisions.keys()]
 
120
 
 
121
    def _activate_new_inventory(self):
 
122
        """Put a replacement inventory.new into use as inventories."""
 
123
        # Copy the content across
 
124
        t = self.bzrdir._control_files._transport
 
125
        t.copy('inventory.new.weave', 'inventory.weave')
 
126
        # delete the temp inventory
 
127
        t.delete('inventory.new.weave')
 
128
        # Check we can parse the new weave properly as a sanity check
 
129
        self.inventories.keys()
 
130
 
 
131
    def _backup_inventory(self):
 
132
        t = self.bzrdir._control_files._transport
 
133
        t.copy('inventory.weave', 'inventory.backup.weave')
 
134
 
 
135
    def _temp_inventories(self):
 
136
        t = self.bzrdir._control_files._transport
 
137
        return self._format._get_inventories(t, self, 'inventory.new')
 
138
 
 
139
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
140
                           timezone=None, committer=None, revprops=None,
 
141
                           revision_id=None):
 
142
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
 
143
        result = CommitBuilder(self, parents, config, timestamp, timezone,
 
144
                              committer, revprops, revision_id)
 
145
        self.start_write_group()
 
146
        return result
 
147
 
 
148
    @needs_read_lock
 
149
    def get_revisions(self, revision_ids):
 
150
        revs = self._get_revisions(revision_ids)
 
151
        return revs
 
152
 
 
153
    def _inventory_add_lines(self, revision_id, parents, lines,
 
154
        check_content=True):
 
155
        """Store lines in inv_vf and return the sha1 of the inventory."""
 
156
        present_parents = self.get_graph().get_parent_map(parents)
 
157
        final_parents = []
 
158
        for parent in parents:
 
159
            if parent in present_parents:
 
160
                final_parents.append((parent,))
 
161
        return self.inventories.add_lines((revision_id,), final_parents, lines,
 
162
            check_content=check_content)[0]
 
163
 
 
164
    def is_shared(self):
 
165
        """AllInOne repositories cannot be shared."""
 
166
        return False
 
167
 
 
168
    @needs_write_lock
 
169
    def set_make_working_trees(self, new_value):
 
170
        """Set the policy flag for making working trees when creating branches.
 
171
 
 
172
        This only applies to branches that use this repository.
 
173
 
 
174
        The default is 'True'.
 
175
        :param new_value: True to restore the default, False to disable making
 
176
                          working trees.
 
177
        """
 
178
        raise errors.RepositoryUpgradeRequired(self.bzrdir.root_transport.base)
 
179
 
 
180
    def make_working_trees(self):
 
181
        """Returns the policy for making working trees on new branches."""
 
182
        return True
 
183
 
 
184
    def revision_graph_can_have_wrong_parents(self):
 
185
        # XXX: This is an old format that we don't support full checking on, so
 
186
        # just claim that checking for this inconsistency is not required.
 
187
        return False
 
188
 
 
189
 
 
190
class WeaveMetaDirRepository(MetaDirVersionedFileRepository):
 
191
    """A subclass of MetaDirRepository to set weave specific policy."""
 
192
 
 
193
    def __init__(self, _format, a_bzrdir, control_files):
 
194
        super(WeaveMetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
 
195
        self._serializer = _format._serializer
 
196
 
 
197
    @needs_read_lock
 
198
    def _all_possible_ids(self):
 
199
        """Return all the possible revisions that we could find."""
 
200
        if 'evil' in debug.debug_flags:
 
201
            mutter_callsite(3, "_all_possible_ids scales with size of history.")
 
202
        return [key[-1] for key in self.inventories.keys()]
 
203
 
 
204
    @needs_read_lock
 
205
    def _all_revision_ids(self):
 
206
        """Returns a list of all the revision ids in the repository.
 
207
 
 
208
        These are in as much topological order as the underlying store can
 
209
        present: for weaves ghosts may lead to a lack of correctness until
 
210
        the reweave updates the parents list.
 
211
        """
 
212
        return [key[-1] for key in self.revisions.keys()]
 
213
 
 
214
    def _activate_new_inventory(self):
 
215
        """Put a replacement inventory.new into use as inventories."""
 
216
        # Copy the content across
 
217
        t = self._transport
 
218
        t.copy('inventory.new.weave', 'inventory.weave')
 
219
        # delete the temp inventory
 
220
        t.delete('inventory.new.weave')
 
221
        # Check we can parse the new weave properly as a sanity check
 
222
        self.inventories.keys()
 
223
 
 
224
    def _backup_inventory(self):
 
225
        t = self._transport
 
226
        t.copy('inventory.weave', 'inventory.backup.weave')
 
227
 
 
228
    def _temp_inventories(self):
 
229
        t = self._transport
 
230
        return self._format._get_inventories(t, self, 'inventory.new')
 
231
 
 
232
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
233
                           timezone=None, committer=None, revprops=None,
 
234
                           revision_id=None):
 
235
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
 
236
        result = CommitBuilder(self, parents, config, timestamp, timezone,
 
237
                              committer, revprops, revision_id)
 
238
        self.start_write_group()
 
239
        return result
 
240
 
 
241
    @needs_read_lock
 
242
    def get_revision(self, revision_id):
 
243
        """Return the Revision object for a named revision"""
 
244
        r = self.get_revision_reconcile(revision_id)
 
245
        return r
 
246
 
 
247
    def _inventory_add_lines(self, revision_id, parents, lines,
 
248
        check_content=True):
 
249
        """Store lines in inv_vf and return the sha1 of the inventory."""
 
250
        present_parents = self.get_graph().get_parent_map(parents)
 
251
        final_parents = []
 
252
        for parent in parents:
 
253
            if parent in present_parents:
 
254
                final_parents.append((parent,))
 
255
        return self.inventories.add_lines((revision_id,), final_parents, lines,
 
256
            check_content=check_content)[0]
 
257
 
 
258
    def revision_graph_can_have_wrong_parents(self):
 
259
        return False
 
260
 
 
261
 
 
262
class PreSplitOutRepositoryFormat(RepositoryFormat):
 
263
    """Base class for the pre split out repository formats."""
 
264
 
 
265
    rich_root_data = False
 
266
    supports_tree_reference = False
 
267
    supports_ghosts = False
 
268
    supports_external_lookups = False
 
269
    supports_chks = False
 
270
    _fetch_order = 'topological'
 
271
    _fetch_reconcile = True
 
272
    fast_deltas = False
 
273
 
 
274
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
275
        """Create a weave repository."""
 
276
        if shared:
 
277
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
278
 
 
279
        if not _internal:
 
280
            # always initialized when the bzrdir is.
 
281
            return self.open(a_bzrdir, _found=True)
 
282
 
 
283
        # Create an empty weave
 
284
        sio = StringIO()
 
285
        weavefile.write_weave_v5(weave.Weave(), sio)
 
286
        empty_weave = sio.getvalue()
 
287
 
 
288
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
289
 
 
290
        # FIXME: RBC 20060125 don't peek under the covers
 
291
        # NB: no need to escape relative paths that are url safe.
 
292
        control_files = lockable_files.LockableFiles(a_bzrdir.transport,
 
293
            'branch-lock', lockable_files.TransportLock)
 
294
        control_files.create_lock()
 
295
        control_files.lock_write()
 
296
        transport = a_bzrdir.transport
 
297
        try:
 
298
            transport.mkdir_multi(['revision-store', 'weaves'],
 
299
                mode=a_bzrdir._get_dir_mode())
 
300
            transport.put_bytes_non_atomic('inventory.weave', empty_weave,
 
301
                mode=a_bzrdir._get_file_mode())
 
302
        finally:
 
303
            control_files.unlock()
 
304
        return self.open(a_bzrdir, _found=True)
 
305
 
 
306
    def open(self, a_bzrdir, _found=False):
 
307
        """See RepositoryFormat.open()."""
 
308
        if not _found:
 
309
            # we are being called directly and must probe.
 
310
            raise NotImplementedError
 
311
 
 
312
        repo_transport = a_bzrdir.get_repository_transport(None)
 
313
        control_files = a_bzrdir._control_files
 
314
        result = AllInOneRepository(_format=self, a_bzrdir=a_bzrdir)
 
315
        result.revisions = self._get_revisions(repo_transport, result)
 
316
        result.signatures = self._get_signatures(repo_transport, result)
 
317
        result.inventories = self._get_inventories(repo_transport, result)
 
318
        result.texts = self._get_texts(repo_transport, result)
 
319
        result.chk_bytes = None
 
320
        return result
 
321
 
 
322
    def check_conversion_target(self, target_format):
 
323
        pass
 
324
 
 
325
 
 
326
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
327
    """Bzr repository format 4.
 
328
 
 
329
    This repository format has:
 
330
     - flat stores
 
331
     - TextStores for texts, inventories,revisions.
 
332
 
 
333
    This format is deprecated: it indexes texts using a text id which is
 
334
    removed in format 5; initialization and write support for this format
 
335
    has been removed.
 
336
    """
 
337
 
 
338
    _matchingbzrdir = bzrdir.BzrDirFormat4()
 
339
 
 
340
    def get_format_description(self):
 
341
        """See RepositoryFormat.get_format_description()."""
 
342
        return "Repository format 4"
 
343
 
 
344
    def initialize(self, url, shared=False, _internal=False):
 
345
        """Format 4 branches cannot be created."""
 
346
        raise errors.UninitializableFormat(self)
 
347
 
 
348
    def is_supported(self):
 
349
        """Format 4 is not supported.
 
350
 
 
351
        It is not supported because the model changed from 4 to 5 and the
 
352
        conversion logic is expensive - so doing it on the fly was not
 
353
        feasible.
 
354
        """
 
355
        return False
 
356
 
 
357
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
358
        # No inventories store written so far.
 
359
        return None
 
360
 
 
361
    def _get_revisions(self, repo_transport, repo):
 
362
        from bzrlib.xml4 import serializer_v4
 
363
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
364
            serializer_v4, True, versionedfile.PrefixMapper(),
 
365
            repo.is_locked, repo.is_write_locked)
 
366
 
 
367
    def _get_signatures(self, repo_transport, repo):
 
368
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
369
            False, versionedfile.PrefixMapper(),
 
370
            repo.is_locked, repo.is_write_locked)
 
371
 
 
372
    def _get_texts(self, repo_transport, repo):
 
373
        return None
 
374
 
 
375
 
 
376
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
377
    """Bzr control format 5.
 
378
 
 
379
    This repository format has:
 
380
     - weaves for file texts and inventory
 
381
     - flat stores
 
382
     - TextStores for revisions and signatures.
 
383
    """
 
384
 
 
385
    _versionedfile_class = weave.WeaveFile
 
386
    _matchingbzrdir = bzrdir.BzrDirFormat5()
 
387
    @property
 
388
    def _serializer(self):
 
389
        return xml5.serializer_v5
 
390
 
 
391
    def get_format_description(self):
 
392
        """See RepositoryFormat.get_format_description()."""
 
393
        return "Weave repository format 5"
 
394
 
 
395
    def network_name(self):
 
396
        """The network name for this format is the control dirs disk label."""
 
397
        return self._matchingbzrdir.get_format_string()
 
398
 
 
399
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
400
        mapper = versionedfile.ConstantMapper(name)
 
401
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
402
            weave.WeaveFile, mapper, repo.is_locked)
 
403
 
 
404
    def _get_revisions(self, repo_transport, repo):
 
405
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
406
            xml5.serializer_v5, False, versionedfile.PrefixMapper(),
 
407
            repo.is_locked, repo.is_write_locked)
 
408
 
 
409
    def _get_signatures(self, repo_transport, repo):
 
410
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
411
            False, versionedfile.PrefixMapper(),
 
412
            repo.is_locked, repo.is_write_locked)
 
413
 
 
414
    def _get_texts(self, repo_transport, repo):
 
415
        mapper = versionedfile.PrefixMapper()
 
416
        base_transport = repo_transport.clone('weaves')
 
417
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
418
            weave.WeaveFile, mapper, repo.is_locked)
 
419
 
 
420
 
 
421
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
422
    """Bzr control format 6.
 
423
 
 
424
    This repository format has:
 
425
     - weaves for file texts and inventory
 
426
     - hash subdirectory based stores.
 
427
     - TextStores for revisions and signatures.
 
428
    """
 
429
 
 
430
    _versionedfile_class = weave.WeaveFile
 
431
    _matchingbzrdir = bzrdir.BzrDirFormat6()
 
432
    @property
 
433
    def _serializer(self):
 
434
        return xml5.serializer_v5
 
435
 
 
436
    def get_format_description(self):
 
437
        """See RepositoryFormat.get_format_description()."""
 
438
        return "Weave repository format 6"
 
439
 
 
440
    def network_name(self):
 
441
        """The network name for this format is the control dirs disk label."""
 
442
        return self._matchingbzrdir.get_format_string()
 
443
 
 
444
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
445
        mapper = versionedfile.ConstantMapper(name)
 
446
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
447
            weave.WeaveFile, mapper, repo.is_locked)
 
448
 
 
449
    def _get_revisions(self, repo_transport, repo):
 
450
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
451
            xml5.serializer_v5, False, versionedfile.HashPrefixMapper(),
 
452
            repo.is_locked, repo.is_write_locked)
 
453
 
 
454
    def _get_signatures(self, repo_transport, repo):
 
455
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
456
            False, versionedfile.HashPrefixMapper(),
 
457
            repo.is_locked, repo.is_write_locked)
 
458
 
 
459
    def _get_texts(self, repo_transport, repo):
 
460
        mapper = versionedfile.HashPrefixMapper()
 
461
        base_transport = repo_transport.clone('weaves')
 
462
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
463
            weave.WeaveFile, mapper, repo.is_locked)
 
464
 
 
465
 
 
466
class RepositoryFormat7(MetaDirRepositoryFormat):
 
467
    """Bzr repository 7.
 
468
 
 
469
    This repository format has:
 
470
     - weaves for file texts and inventory
 
471
     - hash subdirectory based stores.
 
472
     - TextStores for revisions and signatures.
 
473
     - a format marker of its own
 
474
     - an optional 'shared-storage' flag
 
475
     - an optional 'no-working-trees' flag
 
476
    """
 
477
 
 
478
    _versionedfile_class = weave.WeaveFile
 
479
    supports_ghosts = False
 
480
    supports_chks = False
 
481
 
 
482
    _fetch_order = 'topological'
 
483
    _fetch_reconcile = True
 
484
    fast_deltas = False
 
485
    @property
 
486
    def _serializer(self):
 
487
        return xml5.serializer_v5
 
488
 
 
489
    def get_format_string(self):
 
490
        """See RepositoryFormat.get_format_string()."""
 
491
        return "Bazaar-NG Repository format 7"
 
492
 
 
493
    def get_format_description(self):
 
494
        """See RepositoryFormat.get_format_description()."""
 
495
        return "Weave repository format 7"
 
496
 
 
497
    def check_conversion_target(self, target_format):
 
498
        pass
 
499
 
 
500
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
501
        mapper = versionedfile.ConstantMapper(name)
 
502
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
503
            weave.WeaveFile, mapper, repo.is_locked)
 
504
 
 
505
    def _get_revisions(self, repo_transport, repo):
 
506
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
507
            xml5.serializer_v5, True, versionedfile.HashPrefixMapper(),
 
508
            repo.is_locked, repo.is_write_locked)
 
509
 
 
510
    def _get_signatures(self, repo_transport, repo):
 
511
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
512
            True, versionedfile.HashPrefixMapper(),
 
513
            repo.is_locked, repo.is_write_locked)
 
514
 
 
515
    def _get_texts(self, repo_transport, repo):
 
516
        mapper = versionedfile.HashPrefixMapper()
 
517
        base_transport = repo_transport.clone('weaves')
 
518
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
519
            weave.WeaveFile, mapper, repo.is_locked)
 
520
 
 
521
    def initialize(self, a_bzrdir, shared=False):
 
522
        """Create a weave repository.
 
523
 
 
524
        :param shared: If true the repository will be initialized as a shared
 
525
                       repository.
 
526
        """
 
527
        # Create an empty weave
 
528
        sio = StringIO()
 
529
        weavefile.write_weave_v5(weave.Weave(), sio)
 
530
        empty_weave = sio.getvalue()
 
531
 
 
532
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
533
        dirs = ['revision-store', 'weaves']
 
534
        files = [('inventory.weave', StringIO(empty_weave)),
 
535
                 ]
 
536
        utf8_files = [('format', self.get_format_string())]
 
537
 
 
538
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
539
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
540
 
 
541
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
542
        """See RepositoryFormat.open().
 
543
 
 
544
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
545
                                    repository at a slightly different url
 
546
                                    than normal. I.e. during 'upgrade'.
 
547
        """
 
548
        if not _found:
 
549
            format = RepositoryFormat.find_format(a_bzrdir)
 
550
        if _override_transport is not None:
 
551
            repo_transport = _override_transport
 
552
        else:
 
553
            repo_transport = a_bzrdir.get_repository_transport(None)
 
554
        control_files = lockable_files.LockableFiles(repo_transport,
 
555
                                'lock', lockdir.LockDir)
 
556
        result = WeaveMetaDirRepository(_format=self, a_bzrdir=a_bzrdir,
 
557
            control_files=control_files)
 
558
        result.revisions = self._get_revisions(repo_transport, result)
 
559
        result.signatures = self._get_signatures(repo_transport, result)
 
560
        result.inventories = self._get_inventories(repo_transport, result)
 
561
        result.texts = self._get_texts(repo_transport, result)
 
562
        result.chk_bytes = None
 
563
        result._transport = repo_transport
 
564
        return result
 
565
 
 
566
 
 
567
class TextVersionedFiles(VersionedFiles):
 
568
    """Just-a-bunch-of-files based VersionedFile stores."""
 
569
 
 
570
    def __init__(self, transport, compressed, mapper, is_locked, can_write):
 
571
        self._compressed = compressed
 
572
        self._transport = transport
 
573
        self._mapper = mapper
 
574
        if self._compressed:
 
575
            self._ext = '.gz'
 
576
        else:
 
577
            self._ext = ''
 
578
        self._is_locked = is_locked
 
579
        self._can_write = can_write
 
580
 
 
581
    def add_lines(self, key, parents, lines):
 
582
        """Add a revision to the store."""
 
583
        if not self._is_locked():
 
584
            raise errors.ObjectNotLocked(self)
 
585
        if not self._can_write():
 
586
            raise errors.ReadOnlyError(self)
 
587
        if '/' in key[-1]:
 
588
            raise ValueError('bad idea to put / in %r' % (key,))
 
589
        text = ''.join(lines)
 
590
        if self._compressed:
 
591
            text = bytes_to_gzip(text)
 
592
        path = self._map(key)
 
593
        self._transport.put_bytes_non_atomic(path, text, create_parent_dir=True)
 
594
 
 
595
    def insert_record_stream(self, stream):
 
596
        adapters = {}
 
597
        for record in stream:
 
598
            # Raise an error when a record is missing.
 
599
            if record.storage_kind == 'absent':
 
600
                raise errors.RevisionNotPresent([record.key[0]], self)
 
601
            # adapt to non-tuple interface
 
602
            if record.storage_kind == 'fulltext':
 
603
                self.add_lines(record.key, None,
 
604
                    osutils.split_lines(record.get_bytes_as('fulltext')))
 
605
            else:
 
606
                adapter_key = record.storage_kind, 'fulltext'
 
607
                try:
 
608
                    adapter = adapters[adapter_key]
 
609
                except KeyError:
 
610
                    adapter_factory = adapter_registry.get(adapter_key)
 
611
                    adapter = adapter_factory(self)
 
612
                    adapters[adapter_key] = adapter
 
613
                lines = osutils.split_lines(adapter.get_bytes(
 
614
                    record, record.get_bytes_as(record.storage_kind)))
 
615
                try:
 
616
                    self.add_lines(record.key, None, lines)
 
617
                except RevisionAlreadyPresent:
 
618
                    pass
 
619
 
 
620
    def _load_text(self, key):
 
621
        if not self._is_locked():
 
622
            raise errors.ObjectNotLocked(self)
 
623
        path = self._map(key)
 
624
        try:
 
625
            text = self._transport.get_bytes(path)
 
626
            compressed = self._compressed
 
627
        except errors.NoSuchFile:
 
628
            if self._compressed:
 
629
                # try without the .gz
 
630
                path = path[:-3]
 
631
                try:
 
632
                    text = self._transport.get_bytes(path)
 
633
                    compressed = False
 
634
                except errors.NoSuchFile:
 
635
                    return None
 
636
            else:
 
637
                return None
 
638
        if compressed:
 
639
            text = GzipFile(mode='rb', fileobj=StringIO(text)).read()
 
640
        return text
 
641
 
 
642
    def _map(self, key):
 
643
        return self._mapper.map(key) + self._ext
 
644
 
 
645
 
 
646
class RevisionTextStore(TextVersionedFiles):
 
647
    """Legacy thunk for format 4 repositories."""
 
648
 
 
649
    def __init__(self, transport, serializer, compressed, mapper, is_locked,
 
650
        can_write):
 
651
        """Create a RevisionTextStore at transport with serializer."""
 
652
        TextVersionedFiles.__init__(self, transport, compressed, mapper,
 
653
            is_locked, can_write)
 
654
        self._serializer = serializer
 
655
 
 
656
    def _load_text_parents(self, key):
 
657
        text = self._load_text(key)
 
658
        if text is None:
 
659
            return None, None
 
660
        parents = self._serializer.read_revision_from_string(text).parent_ids
 
661
        return text, tuple((parent,) for parent in parents)
 
662
 
 
663
    def get_parent_map(self, keys):
 
664
        result = {}
 
665
        for key in keys:
 
666
            parents = self._load_text_parents(key)[1]
 
667
            if parents is None:
 
668
                continue
 
669
            result[key] = parents
 
670
        return result
 
671
 
 
672
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
673
        for key in keys:
 
674
            text, parents = self._load_text_parents(key)
 
675
            if text is None:
 
676
                yield AbsentContentFactory(key)
 
677
            else:
 
678
                yield FulltextContentFactory(key, parents, None, text)
 
679
 
 
680
    def keys(self):
 
681
        if not self._is_locked():
 
682
            raise errors.ObjectNotLocked(self)
 
683
        relpaths = set()
 
684
        for quoted_relpath in self._transport.iter_files_recursive():
 
685
            relpath = urllib.unquote(quoted_relpath)
 
686
            path, ext = os.path.splitext(relpath)
 
687
            if ext == '.gz':
 
688
                relpath = path
 
689
            if '.sig' not in relpath:
 
690
                relpaths.add(relpath)
 
691
        paths = list(relpaths)
 
692
        return set([self._mapper.unmap(path) for path in paths])
 
693
 
 
694
 
 
695
class SignatureTextStore(TextVersionedFiles):
 
696
    """Legacy thunk for format 4-7 repositories."""
 
697
 
 
698
    def __init__(self, transport, compressed, mapper, is_locked, can_write):
 
699
        TextVersionedFiles.__init__(self, transport, compressed, mapper,
 
700
            is_locked, can_write)
 
701
        self._ext = '.sig' + self._ext
 
702
 
 
703
    def get_parent_map(self, keys):
 
704
        result = {}
 
705
        for key in keys:
 
706
            text = self._load_text(key)
 
707
            if text is None:
 
708
                continue
 
709
            result[key] = None
 
710
        return result
 
711
 
 
712
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
713
        for key in keys:
 
714
            text = self._load_text(key)
 
715
            if text is None:
 
716
                yield AbsentContentFactory(key)
 
717
            else:
 
718
                yield FulltextContentFactory(key, None, None, text)
 
719
 
 
720
    def keys(self):
 
721
        if not self._is_locked():
 
722
            raise errors.ObjectNotLocked(self)
 
723
        relpaths = set()
 
724
        for quoted_relpath in self._transport.iter_files_recursive():
 
725
            relpath = urllib.unquote(quoted_relpath)
 
726
            path, ext = os.path.splitext(relpath)
 
727
            if ext == '.gz':
 
728
                relpath = path
 
729
            if not relpath.endswith('.sig'):
 
730
                continue
 
731
            relpaths.add(relpath[:-4])
 
732
        paths = list(relpaths)
 
733
        return set([self._mapper.unmap(path) for path in paths])
 
734
 
 
735
_legacy_formats = [RepositoryFormat4(),
 
736
                   RepositoryFormat5(),
 
737
                   RepositoryFormat6()]