~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Martin Pool
  • Date: 2009-03-03 01:45:32 UTC
  • mto: (4070.4.5 gnu-changelog)
  • mto: This revision was merged to the branch mainline in revision 4081.
  • Revision ID: mbp@sourcefrog.net-20090303014532-d223fxy97cb1og8f
Recommend setting timestamp in BranchBuilder

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