/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-08 14:55:19 UTC
  • mfrom: (3530 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3532.
  • Revision ID: john@arbash-meinel.com-20080708145519-paqg4kjwbpgs2xmq
Merge bzr.dev 3530

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