~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Robert Collins
  • Date: 2009-07-07 04:32:13 UTC
  • mto: This revision was merged to the branch mainline in revision 4524.
  • Revision ID: robertc@robertcollins.net-20090707043213-4hjjhgr40iq7gk2d
More informative assertions in xml serialisation.

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