~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

Just embed a struct st in the python result object, avoids converting things we don't need converted, and copying values around always.

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