~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-24 05:21:02 UTC
  • mfrom: (4192 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4202.
  • Revision ID: mbp@sourcefrog.net-20090324052102-8kk087b32tep3d9h
merge trunk

Show diffs side-by-side

added added

removed removed

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