~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Aaron Bentley
  • Date: 2008-12-03 05:31:27 UTC
  • mto: This revision was merged to the branch mainline in revision 3893.
  • Revision ID: aaron@aaronbentley.com-20081203053127-vozu5rmsixaadw0v
Change ls-shelf to shelve --list

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