~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Aaron Bentley
  • Date: 2009-03-24 15:47:32 UTC
  • mto: This revision was merged to the branch mainline in revision 4241.
  • Revision ID: aaron@aaronbentley.com-20090324154732-bwkvi4dx3o90a7dq
Add output, emit minimal inventory delta.

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
    _fetch_order = 'topological'
 
270
    _fetch_reconcile = True
 
271
    fast_deltas = False
 
272
 
 
273
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
274
        """Create a weave repository."""
 
275
        if shared:
 
276
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
277
 
 
278
        if not _internal:
 
279
            # always initialized when the bzrdir is.
 
280
            return self.open(a_bzrdir, _found=True)
 
281
 
 
282
        # Create an empty weave
 
283
        sio = StringIO()
 
284
        weavefile.write_weave_v5(weave.Weave(), sio)
 
285
        empty_weave = sio.getvalue()
 
286
 
 
287
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
288
 
 
289
        # FIXME: RBC 20060125 don't peek under the covers
 
290
        # NB: no need to escape relative paths that are url safe.
 
291
        control_files = lockable_files.LockableFiles(a_bzrdir.transport,
 
292
            'branch-lock', lockable_files.TransportLock)
 
293
        control_files.create_lock()
 
294
        control_files.lock_write()
 
295
        transport = a_bzrdir.transport
 
296
        try:
 
297
            transport.mkdir_multi(['revision-store', 'weaves'],
 
298
                mode=a_bzrdir._get_dir_mode())
 
299
            transport.put_bytes_non_atomic('inventory.weave', empty_weave)
 
300
        finally:
 
301
            control_files.unlock()
 
302
        return self.open(a_bzrdir, _found=True)
 
303
 
 
304
    def open(self, a_bzrdir, _found=False):
 
305
        """See RepositoryFormat.open()."""
 
306
        if not _found:
 
307
            # we are being called directly and must probe.
 
308
            raise NotImplementedError
 
309
 
 
310
        repo_transport = a_bzrdir.get_repository_transport(None)
 
311
        control_files = a_bzrdir._control_files
 
312
        result = AllInOneRepository(_format=self, a_bzrdir=a_bzrdir)
 
313
        result.revisions = self._get_revisions(repo_transport, result)
 
314
        result.signatures = self._get_signatures(repo_transport, result)
 
315
        result.inventories = self._get_inventories(repo_transport, result)
 
316
        result.texts = self._get_texts(repo_transport, result)
 
317
        return result
 
318
 
 
319
    def check_conversion_target(self, target_format):
 
320
        pass
 
321
 
 
322
 
 
323
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
324
    """Bzr repository format 4.
 
325
 
 
326
    This repository format has:
 
327
     - flat stores
 
328
     - TextStores for texts, inventories,revisions.
 
329
 
 
330
    This format is deprecated: it indexes texts using a text id which is
 
331
    removed in format 5; initialization and write support for this format
 
332
    has been removed.
 
333
    """
 
334
 
 
335
    _matchingbzrdir = bzrdir.BzrDirFormat4()
 
336
 
 
337
    def get_format_description(self):
 
338
        """See RepositoryFormat.get_format_description()."""
 
339
        return "Repository format 4"
 
340
 
 
341
    def initialize(self, url, shared=False, _internal=False):
 
342
        """Format 4 branches cannot be created."""
 
343
        raise errors.UninitializableFormat(self)
 
344
 
 
345
    def is_supported(self):
 
346
        """Format 4 is not supported.
 
347
 
 
348
        It is not supported because the model changed from 4 to 5 and the
 
349
        conversion logic is expensive - so doing it on the fly was not
 
350
        feasible.
 
351
        """
 
352
        return False
 
353
 
 
354
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
355
        # No inventories store written so far.
 
356
        return None
 
357
 
 
358
    def _get_revisions(self, repo_transport, repo):
 
359
        from bzrlib.xml4 import serializer_v4
 
360
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
361
            serializer_v4, True, versionedfile.PrefixMapper(),
 
362
            repo.is_locked, repo.is_write_locked)
 
363
 
 
364
    def _get_signatures(self, repo_transport, repo):
 
365
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
366
            False, versionedfile.PrefixMapper(),
 
367
            repo.is_locked, repo.is_write_locked)
 
368
 
 
369
    def _get_texts(self, repo_transport, repo):
 
370
        return None
 
371
 
 
372
 
 
373
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
374
    """Bzr control format 5.
 
375
 
 
376
    This repository format has:
 
377
     - weaves for file texts and inventory
 
378
     - flat stores
 
379
     - TextStores for revisions and signatures.
 
380
    """
 
381
 
 
382
    _versionedfile_class = weave.WeaveFile
 
383
    _matchingbzrdir = bzrdir.BzrDirFormat5()
 
384
    @property
 
385
    def _serializer(self):
 
386
        return xml5.serializer_v5
 
387
 
 
388
    def get_format_description(self):
 
389
        """See RepositoryFormat.get_format_description()."""
 
390
        return "Weave repository format 5"
 
391
 
 
392
    def network_name(self):
 
393
        """The network name for this format is the control dirs disk label."""
 
394
        return self._matchingbzrdir.get_format_string()
 
395
 
 
396
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
397
        mapper = versionedfile.ConstantMapper(name)
 
398
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
399
            weave.WeaveFile, mapper, repo.is_locked)
 
400
 
 
401
    def _get_revisions(self, repo_transport, repo):
 
402
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
403
            xml5.serializer_v5, False, versionedfile.PrefixMapper(),
 
404
            repo.is_locked, repo.is_write_locked)
 
405
 
 
406
    def _get_signatures(self, repo_transport, repo):
 
407
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
408
            False, versionedfile.PrefixMapper(),
 
409
            repo.is_locked, repo.is_write_locked)
 
410
 
 
411
    def _get_texts(self, repo_transport, repo):
 
412
        mapper = versionedfile.PrefixMapper()
 
413
        base_transport = repo_transport.clone('weaves')
 
414
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
415
            weave.WeaveFile, mapper, repo.is_locked)
 
416
 
 
417
 
 
418
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
419
    """Bzr control format 6.
 
420
 
 
421
    This repository format has:
 
422
     - weaves for file texts and inventory
 
423
     - hash subdirectory based stores.
 
424
     - TextStores for revisions and signatures.
 
425
    """
 
426
 
 
427
    _versionedfile_class = weave.WeaveFile
 
428
    _matchingbzrdir = bzrdir.BzrDirFormat6()
 
429
    @property
 
430
    def _serializer(self):
 
431
        return xml5.serializer_v5
 
432
 
 
433
    def get_format_description(self):
 
434
        """See RepositoryFormat.get_format_description()."""
 
435
        return "Weave repository format 6"
 
436
 
 
437
    def network_name(self):
 
438
        """The network name for this format is the control dirs disk label."""
 
439
        return self._matchingbzrdir.get_format_string()
 
440
 
 
441
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
442
        mapper = versionedfile.ConstantMapper(name)
 
443
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
444
            weave.WeaveFile, mapper, repo.is_locked)
 
445
 
 
446
    def _get_revisions(self, repo_transport, repo):
 
447
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
448
            xml5.serializer_v5, False, versionedfile.HashPrefixMapper(),
 
449
            repo.is_locked, repo.is_write_locked)
 
450
 
 
451
    def _get_signatures(self, repo_transport, repo):
 
452
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
453
            False, versionedfile.HashPrefixMapper(),
 
454
            repo.is_locked, repo.is_write_locked)
 
455
 
 
456
    def _get_texts(self, repo_transport, repo):
 
457
        mapper = versionedfile.HashPrefixMapper()
 
458
        base_transport = repo_transport.clone('weaves')
 
459
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
460
            weave.WeaveFile, mapper, repo.is_locked)
 
461
 
 
462
 
 
463
class RepositoryFormat7(MetaDirRepositoryFormat):
 
464
    """Bzr repository 7.
 
465
 
 
466
    This repository format has:
 
467
     - weaves for file texts and inventory
 
468
     - hash subdirectory based stores.
 
469
     - TextStores for revisions and signatures.
 
470
     - a format marker of its own
 
471
     - an optional 'shared-storage' flag
 
472
     - an optional 'no-working-trees' flag
 
473
    """
 
474
 
 
475
    _versionedfile_class = weave.WeaveFile
 
476
    supports_ghosts = False
 
477
    _fetch_order = 'topological'
 
478
    _fetch_reconcile = True
 
479
    fast_deltas = False
 
480
    @property
 
481
    def _serializer(self):
 
482
        return xml5.serializer_v5
 
483
 
 
484
    def get_format_string(self):
 
485
        """See RepositoryFormat.get_format_string()."""
 
486
        return "Bazaar-NG Repository format 7"
 
487
 
 
488
    def get_format_description(self):
 
489
        """See RepositoryFormat.get_format_description()."""
 
490
        return "Weave repository format 7"
 
491
 
 
492
    def check_conversion_target(self, target_format):
 
493
        pass
 
494
 
 
495
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
496
        mapper = versionedfile.ConstantMapper(name)
 
497
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
498
            weave.WeaveFile, mapper, repo.is_locked)
 
499
 
 
500
    def _get_revisions(self, repo_transport, repo):
 
501
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
502
            xml5.serializer_v5, True, versionedfile.HashPrefixMapper(),
 
503
            repo.is_locked, repo.is_write_locked)
 
504
 
 
505
    def _get_signatures(self, repo_transport, repo):
 
506
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
507
            True, versionedfile.HashPrefixMapper(),
 
508
            repo.is_locked, repo.is_write_locked)
 
509
 
 
510
    def _get_texts(self, repo_transport, repo):
 
511
        mapper = versionedfile.HashPrefixMapper()
 
512
        base_transport = repo_transport.clone('weaves')
 
513
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
514
            weave.WeaveFile, mapper, repo.is_locked)
 
515
 
 
516
    def initialize(self, a_bzrdir, shared=False):
 
517
        """Create a weave repository.
 
518
 
 
519
        :param shared: If true the repository will be initialized as a shared
 
520
                       repository.
 
521
        """
 
522
        # Create an empty weave
 
523
        sio = StringIO()
 
524
        weavefile.write_weave_v5(weave.Weave(), sio)
 
525
        empty_weave = sio.getvalue()
 
526
 
 
527
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
528
        dirs = ['revision-store', 'weaves']
 
529
        files = [('inventory.weave', StringIO(empty_weave)),
 
530
                 ]
 
531
        utf8_files = [('format', self.get_format_string())]
 
532
 
 
533
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
534
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
535
 
 
536
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
537
        """See RepositoryFormat.open().
 
538
 
 
539
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
540
                                    repository at a slightly different url
 
541
                                    than normal. I.e. during 'upgrade'.
 
542
        """
 
543
        if not _found:
 
544
            format = RepositoryFormat.find_format(a_bzrdir)
 
545
        if _override_transport is not None:
 
546
            repo_transport = _override_transport
 
547
        else:
 
548
            repo_transport = a_bzrdir.get_repository_transport(None)
 
549
        control_files = lockable_files.LockableFiles(repo_transport,
 
550
                                'lock', lockdir.LockDir)
 
551
        result = WeaveMetaDirRepository(_format=self, a_bzrdir=a_bzrdir,
 
552
            control_files=control_files)
 
553
        result.revisions = self._get_revisions(repo_transport, result)
 
554
        result.signatures = self._get_signatures(repo_transport, result)
 
555
        result.inventories = self._get_inventories(repo_transport, result)
 
556
        result.texts = self._get_texts(repo_transport, result)
 
557
        result._transport = repo_transport
 
558
        return result
 
559
 
 
560
 
 
561
class TextVersionedFiles(VersionedFiles):
 
562
    """Just-a-bunch-of-files based VersionedFile stores."""
 
563
 
 
564
    def __init__(self, transport, compressed, mapper, is_locked, can_write):
 
565
        self._compressed = compressed
 
566
        self._transport = transport
 
567
        self._mapper = mapper
 
568
        if self._compressed:
 
569
            self._ext = '.gz'
 
570
        else:
 
571
            self._ext = ''
 
572
        self._is_locked = is_locked
 
573
        self._can_write = can_write
 
574
 
 
575
    def add_lines(self, key, parents, lines):
 
576
        """Add a revision to the store."""
 
577
        if not self._is_locked():
 
578
            raise errors.ObjectNotLocked(self)
 
579
        if not self._can_write():
 
580
            raise errors.ReadOnlyError(self)
 
581
        if '/' in key[-1]:
 
582
            raise ValueError('bad idea to put / in %r' % (key,))
 
583
        text = ''.join(lines)
 
584
        if self._compressed:
 
585
            text = bytes_to_gzip(text)
 
586
        path = self._map(key)
 
587
        self._transport.put_bytes_non_atomic(path, text, create_parent_dir=True)
 
588
 
 
589
    def insert_record_stream(self, stream):
 
590
        adapters = {}
 
591
        for record in stream:
 
592
            # Raise an error when a record is missing.
 
593
            if record.storage_kind == 'absent':
 
594
                raise errors.RevisionNotPresent([record.key[0]], self)
 
595
            # adapt to non-tuple interface
 
596
            if record.storage_kind == 'fulltext':
 
597
                self.add_lines(record.key, None,
 
598
                    osutils.split_lines(record.get_bytes_as('fulltext')))
 
599
            else:
 
600
                adapter_key = record.storage_kind, 'fulltext'
 
601
                try:
 
602
                    adapter = adapters[adapter_key]
 
603
                except KeyError:
 
604
                    adapter_factory = adapter_registry.get(adapter_key)
 
605
                    adapter = adapter_factory(self)
 
606
                    adapters[adapter_key] = adapter
 
607
                lines = osutils.split_lines(adapter.get_bytes(
 
608
                    record, record.get_bytes_as(record.storage_kind)))
 
609
                try:
 
610
                    self.add_lines(record.key, None, lines)
 
611
                except RevisionAlreadyPresent:
 
612
                    pass
 
613
 
 
614
    def _load_text(self, key):
 
615
        if not self._is_locked():
 
616
            raise errors.ObjectNotLocked(self)
 
617
        path = self._map(key)
 
618
        try:
 
619
            text = self._transport.get_bytes(path)
 
620
            compressed = self._compressed
 
621
        except errors.NoSuchFile:
 
622
            if self._compressed:
 
623
                # try without the .gz
 
624
                path = path[:-3]
 
625
                try:
 
626
                    text = self._transport.get_bytes(path)
 
627
                    compressed = False
 
628
                except errors.NoSuchFile:
 
629
                    return None
 
630
            else:
 
631
                return None
 
632
        if compressed:
 
633
            text = GzipFile(mode='rb', fileobj=StringIO(text)).read()
 
634
        return text
 
635
 
 
636
    def _map(self, key):
 
637
        return self._mapper.map(key) + self._ext
 
638
 
 
639
 
 
640
class RevisionTextStore(TextVersionedFiles):
 
641
    """Legacy thunk for format 4 repositories."""
 
642
 
 
643
    def __init__(self, transport, serializer, compressed, mapper, is_locked,
 
644
        can_write):
 
645
        """Create a RevisionTextStore at transport with serializer."""
 
646
        TextVersionedFiles.__init__(self, transport, compressed, mapper,
 
647
            is_locked, can_write)
 
648
        self._serializer = serializer
 
649
 
 
650
    def _load_text_parents(self, key):
 
651
        text = self._load_text(key)
 
652
        if text is None:
 
653
            return None, None
 
654
        parents = self._serializer.read_revision_from_string(text).parent_ids
 
655
        return text, tuple((parent,) for parent in parents)
 
656
 
 
657
    def get_parent_map(self, keys):
 
658
        result = {}
 
659
        for key in keys:
 
660
            parents = self._load_text_parents(key)[1]
 
661
            if parents is None:
 
662
                continue
 
663
            result[key] = parents
 
664
        return result
 
665
 
 
666
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
667
        for key in keys:
 
668
            text, parents = self._load_text_parents(key)
 
669
            if text is None:
 
670
                yield AbsentContentFactory(key)
 
671
            else:
 
672
                yield FulltextContentFactory(key, parents, None, text)
 
673
 
 
674
    def keys(self):
 
675
        if not self._is_locked():
 
676
            raise errors.ObjectNotLocked(self)
 
677
        relpaths = set()
 
678
        for quoted_relpath in self._transport.iter_files_recursive():
 
679
            relpath = urllib.unquote(quoted_relpath)
 
680
            path, ext = os.path.splitext(relpath)
 
681
            if ext == '.gz':
 
682
                relpath = path
 
683
            if '.sig' not in relpath:
 
684
                relpaths.add(relpath)
 
685
        paths = list(relpaths)
 
686
        return set([self._mapper.unmap(path) for path in paths])
 
687
 
 
688
 
 
689
class SignatureTextStore(TextVersionedFiles):
 
690
    """Legacy thunk for format 4-7 repositories."""
 
691
 
 
692
    def __init__(self, transport, compressed, mapper, is_locked, can_write):
 
693
        TextVersionedFiles.__init__(self, transport, compressed, mapper,
 
694
            is_locked, can_write)
 
695
        self._ext = '.sig' + self._ext
 
696
 
 
697
    def get_parent_map(self, keys):
 
698
        result = {}
 
699
        for key in keys:
 
700
            text = self._load_text(key)
 
701
            if text is None:
 
702
                continue
 
703
            result[key] = None
 
704
        return result
 
705
 
 
706
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
707
        for key in keys:
 
708
            text = self._load_text(key)
 
709
            if text is None:
 
710
                yield AbsentContentFactory(key)
 
711
            else:
 
712
                yield FulltextContentFactory(key, None, None, text)
 
713
 
 
714
    def keys(self):
 
715
        if not self._is_locked():
 
716
            raise errors.ObjectNotLocked(self)
 
717
        relpaths = set()
 
718
        for quoted_relpath in self._transport.iter_files_recursive():
 
719
            relpath = urllib.unquote(quoted_relpath)
 
720
            path, ext = os.path.splitext(relpath)
 
721
            if ext == '.gz':
 
722
                relpath = path
 
723
            if not relpath.endswith('.sig'):
 
724
                continue
 
725
            relpaths.add(relpath[:-4])
 
726
        paths = list(relpaths)
 
727
        return set([self._mapper.unmap(path) for path in paths])
 
728
 
 
729
_legacy_formats = [RepositoryFormat4(),
 
730
                   RepositoryFormat5(),
 
731
                   RepositoryFormat6()]