~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

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