~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Robert Collins
  • Date: 2010-04-08 04:34:03 UTC
  • mfrom: (5138 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5139.
  • Revision ID: robertc@robertcollins.net-20100408043403-56z0d07vdqrx7f3t
Update bugfix for 528114 to 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
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Deprecated weave-based repository formats.
18
18
 
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
    graph as _mod_graph,
 
32
    )
 
33
""")
25
34
from bzrlib import (
26
35
    bzrdir,
27
36
    debug,
30
39
    lockdir,
31
40
    osutils,
32
41
    revision as _mod_revision,
 
42
    urlutils,
 
43
    versionedfile,
33
44
    weave,
34
45
    weavefile,
35
 
    xml5,
36
46
    )
37
47
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
48
from bzrlib.repository import (
44
54
    )
45
55
from bzrlib.store.text import TextStore
46
56
from bzrlib.trace import mutter
 
57
from bzrlib.tuned_gzip import GzipFile, bytes_to_gzip
 
58
from bzrlib.versionedfile import (
 
59
    AbsentContentFactory,
 
60
    FulltextContentFactory,
 
61
    VersionedFiles,
 
62
    )
47
63
 
48
64
 
49
65
class AllInOneRepository(Repository):
50
66
    """Legacy support - the repository behaviour for all-in-one branches."""
51
67
 
52
 
    _serializer = xml5.serializer_v5
53
 
 
54
 
    def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
 
68
    @property
 
69
    def _serializer(self):
 
70
        return xml5.serializer_v5
 
71
 
 
72
    def _escape(self, file_or_path):
 
73
        if not isinstance(file_or_path, basestring):
 
74
            file_or_path = '/'.join(file_or_path)
 
75
        if file_or_path == '':
 
76
            return u''
 
77
        return urlutils.escape(osutils.safe_unicode(file_or_path))
 
78
 
 
79
    def __init__(self, _format, a_bzrdir):
55
80
        # we reuse one control files instance.
56
 
        dir_mode = a_bzrdir._control_files._dir_mode
57
 
        file_mode = a_bzrdir._control_files._file_mode
 
81
        dir_mode = a_bzrdir._get_dir_mode()
 
82
        file_mode = a_bzrdir._get_file_mode()
58
83
 
59
84
        def get_store(name, compressed=True, prefixed=False):
60
85
            # FIXME: This approach of assuming stores are all entirely compressed
61
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
62
 
            # some existing branches where there's a mixture; we probably 
 
86
            # or entirely uncompressed is tidy, but breaks upgrade from
 
87
            # some existing branches where there's a mixture; we probably
63
88
            # still want the option to look for both.
64
 
            relpath = a_bzrdir._control_files._escape(name)
65
 
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
 
89
            relpath = self._escape(name)
 
90
            store = TextStore(a_bzrdir.transport.clone(relpath),
66
91
                              prefixed=prefixed, compressed=compressed,
67
92
                              dir_mode=dir_mode,
68
93
                              file_mode=file_mode)
69
94
            return store
70
95
 
71
96
        # not broken out yet because the controlweaves|inventory_store
72
 
        # and text_store | weave_store bits are still different.
 
97
        # and texts bits are still different.
73
98
        if isinstance(_format, RepositoryFormat4):
74
 
            # cannot remove these - there is still no consistent api 
 
99
            # cannot remove these - there is still no consistent api
75
100
            # which allows access to this old info.
76
101
            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)
79
 
        if control_store is not None:
80
 
            control_store.get_scope = self.get_transaction
81
 
        text_store.get_scope = self.get_transaction
 
102
            self._text_store = get_store('text-store')
 
103
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files)
82
104
 
83
105
    @needs_read_lock
84
106
    def _all_possible_ids(self):
85
107
        """Return all the possible revisions that we could find."""
86
108
        if 'evil' in debug.debug_flags:
87
109
            mutter_callsite(3, "_all_possible_ids scales with size of history.")
88
 
        return self.get_inventory_weave().versions()
 
110
        return [key[-1] for key in self.inventories.keys()]
89
111
 
90
112
    @needs_read_lock
91
113
    def _all_revision_ids(self):
92
 
        """Returns a list of all the revision ids in the repository. 
 
114
        """Returns a list of all the revision ids in the repository.
93
115
 
94
 
        These are in as much topological order as the underlying store can 
 
116
        These are in as much topological order as the underlying store can
95
117
        present: for weaves ghosts may lead to a lack of correctness until
96
118
        the reweave updates the parents list.
97
119
        """
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)
 
120
        return [key[-1] for key in self.revisions.keys()]
 
121
 
 
122
    def _activate_new_inventory(self):
 
123
        """Put a replacement inventory.new into use as inventories."""
 
124
        # Copy the content across
 
125
        t = self.bzrdir._control_files._transport
 
126
        t.copy('inventory.new.weave', 'inventory.weave')
 
127
        # delete the temp inventory
 
128
        t.delete('inventory.new.weave')
 
129
        # Check we can parse the new weave properly as a sanity check
 
130
        self.inventories.keys()
 
131
 
 
132
    def _backup_inventory(self):
 
133
        t = self.bzrdir._control_files._transport
 
134
        t.copy('inventory.weave', 'inventory.backup.weave')
 
135
 
 
136
    def _temp_inventories(self):
 
137
        t = self.bzrdir._control_files._transport
 
138
        return self._format._get_inventories(t, self, 'inventory.new')
122
139
 
123
140
    def get_commit_builder(self, branch, parents, config, timestamp=None,
124
141
                           timezone=None, committer=None, revprops=None,
125
142
                           revision_id=None):
126
143
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
127
 
        result = WeaveCommitBuilder(self, parents, config, timestamp, timezone,
 
144
        result = CommitBuilder(self, parents, config, timestamp, timezone,
128
145
                              committer, revprops, revision_id)
129
146
        self.start_write_group()
130
147
        return result
132
149
    @needs_read_lock
133
150
    def get_revisions(self, revision_ids):
134
151
        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
152
        return revs
146
153
 
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
 
154
    def _inventory_add_lines(self, revision_id, parents, lines,
 
155
        check_content=True):
 
156
        """Store lines in inv_vf and return the sha1 of the inventory."""
 
157
        present_parents = self.get_graph().get_parent_map(parents)
 
158
        final_parents = []
 
159
        for parent in parents:
 
160
            if parent in present_parents:
 
161
                final_parents.append((parent,))
 
162
        return self.inventories.add_lines((revision_id,), final_parents, lines,
 
163
            check_content=check_content)[0]
155
164
 
156
 
    @needs_read_lock
157
165
    def is_shared(self):
158
166
        """AllInOne repositories cannot be shared."""
159
167
        return False
183
191
class WeaveMetaDirRepository(MetaDirVersionedFileRepository):
184
192
    """A subclass of MetaDirRepository to set weave specific policy."""
185
193
 
186
 
    _serializer = xml5.serializer_v5
 
194
    def __init__(self, _format, a_bzrdir, control_files):
 
195
        super(WeaveMetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
 
196
        self._serializer = _format._serializer
187
197
 
188
198
    @needs_read_lock
189
199
    def _all_possible_ids(self):
190
200
        """Return all the possible revisions that we could find."""
191
201
        if 'evil' in debug.debug_flags:
192
202
            mutter_callsite(3, "_all_possible_ids scales with size of history.")
193
 
        return self.get_inventory_weave().versions()
 
203
        return [key[-1] for key in self.inventories.keys()]
194
204
 
195
205
    @needs_read_lock
196
206
    def _all_revision_ids(self):
197
 
        """Returns a list of all the revision ids in the repository. 
 
207
        """Returns a list of all the revision ids in the repository.
198
208
 
199
 
        These are in as much topological order as the underlying store can 
 
209
        These are in as much topological order as the underlying store can
200
210
        present: for weaves ghosts may lead to a lack of correctness until
201
211
        the reweave updates the parents list.
202
212
        """
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)
 
213
        return [key[-1] for key in self.revisions.keys()]
 
214
 
 
215
    def _activate_new_inventory(self):
 
216
        """Put a replacement inventory.new into use as inventories."""
 
217
        # Copy the content across
 
218
        t = self._transport
 
219
        t.copy('inventory.new.weave', 'inventory.weave')
 
220
        # delete the temp inventory
 
221
        t.delete('inventory.new.weave')
 
222
        # Check we can parse the new weave properly as a sanity check
 
223
        self.inventories.keys()
 
224
 
 
225
    def _backup_inventory(self):
 
226
        t = self._transport
 
227
        t.copy('inventory.weave', 'inventory.backup.weave')
 
228
 
 
229
    def _temp_inventories(self):
 
230
        t = self._transport
 
231
        return self._format._get_inventories(t, self, 'inventory.new')
227
232
 
228
233
    def get_commit_builder(self, branch, parents, config, timestamp=None,
229
234
                           timezone=None, committer=None, revprops=None,
230
235
                           revision_id=None):
231
236
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
232
 
        result = WeaveCommitBuilder(self, parents, config, timestamp, timezone,
 
237
        result = CommitBuilder(self, parents, config, timestamp, timezone,
233
238
                              committer, revprops, revision_id)
234
239
        self.start_write_group()
235
240
        return result
237
242
    @needs_read_lock
238
243
    def get_revision(self, revision_id):
239
244
        """Return the Revision object for a named revision"""
240
 
        # TODO: jam 20070210 get_revision_reconcile should do this for us
241
245
        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
246
        return r
252
247
 
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
 
248
    def _inventory_add_lines(self, revision_id, parents, lines,
 
249
        check_content=True):
 
250
        """Store lines in inv_vf and return the sha1 of the inventory."""
 
251
        present_parents = self.get_graph().get_parent_map(parents)
 
252
        final_parents = []
 
253
        for parent in parents:
 
254
            if parent in present_parents:
 
255
                final_parents.append((parent,))
 
256
        return self.inventories.add_lines((revision_id,), final_parents, lines,
 
257
            check_content=check_content)[0]
261
258
 
262
259
    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
260
        return False
266
261
 
267
262
 
272
267
    supports_tree_reference = False
273
268
    supports_ghosts = False
274
269
    supports_external_lookups = False
 
270
    supports_chks = False
 
271
    _fetch_order = 'topological'
 
272
    _fetch_reconcile = True
 
273
    fast_deltas = False
275
274
 
276
275
    def initialize(self, a_bzrdir, shared=False, _internal=False):
277
276
        """Create a weave repository."""
281
280
        if not _internal:
282
281
            # always initialized when the bzrdir is.
283
282
            return self.open(a_bzrdir, _found=True)
284
 
        
 
283
 
285
284
        # Create an empty weave
286
285
        sio = StringIO()
287
286
        weavefile.write_weave_v5(weave.Weave(), sio)
288
287
        empty_weave = sio.getvalue()
289
288
 
290
289
        mutter('creating repository in %s.', a_bzrdir.transport.base)
291
 
        dirs = ['revision-store', 'weaves']
292
 
        files = [('inventory.weave', StringIO(empty_weave)),
293
 
                 ]
294
 
        
 
290
 
295
291
        # FIXME: RBC 20060125 don't peek under the covers
296
292
        # NB: no need to escape relative paths that are url safe.
297
293
        control_files = lockable_files.LockableFiles(a_bzrdir.transport,
298
 
                                'branch-lock', lockable_files.TransportLock)
 
294
            'branch-lock', lockable_files.TransportLock)
299
295
        control_files.create_lock()
300
296
        control_files.lock_write()
301
 
        control_files._transport.mkdir_multi(dirs,
302
 
                mode=control_files._dir_mode)
 
297
        transport = a_bzrdir.transport
303
298
        try:
304
 
            for file, content in files:
305
 
                control_files.put(file, content)
 
299
            transport.mkdir_multi(['revision-store', 'weaves'],
 
300
                mode=a_bzrdir._get_dir_mode())
 
301
            transport.put_bytes_non_atomic('inventory.weave', empty_weave,
 
302
                mode=a_bzrdir._get_file_mode())
306
303
        finally:
307
304
            control_files.unlock()
308
305
        return self.open(a_bzrdir, _found=True)
309
306
 
310
 
    def _get_control_store(self, repo_transport, control_files):
311
 
        """Return the control store for this repository."""
312
 
        return self._get_versioned_file_store('',
313
 
                                              repo_transport,
314
 
                                              control_files,
315
 
                                              prefixed=False)
316
 
 
317
 
    def _get_text_store(self, transport, control_files):
318
 
        """Get a store for file texts for this format."""
319
 
        raise NotImplementedError(self._get_text_store)
320
 
 
321
307
    def open(self, a_bzrdir, _found=False):
322
308
        """See RepositoryFormat.open()."""
323
309
        if not _found:
326
312
 
327
313
        repo_transport = a_bzrdir.get_repository_transport(None)
328
314
        control_files = a_bzrdir._control_files
329
 
        text_store = self._get_text_store(repo_transport, control_files)
330
 
        control_store = self._get_control_store(repo_transport, control_files)
331
 
        _revision_store = self._get_revision_store(repo_transport, control_files)
332
 
        return AllInOneRepository(_format=self,
333
 
                                  a_bzrdir=a_bzrdir,
334
 
                                  _revision_store=_revision_store,
335
 
                                  control_store=control_store,
336
 
                                  text_store=text_store)
337
 
 
338
 
    def check_conversion_target(self, target_format):
339
 
        pass
 
315
        result = AllInOneRepository(_format=self, a_bzrdir=a_bzrdir)
 
316
        result.revisions = self._get_revisions(repo_transport, result)
 
317
        result.signatures = self._get_signatures(repo_transport, result)
 
318
        result.inventories = self._get_inventories(repo_transport, result)
 
319
        result.texts = self._get_texts(repo_transport, result)
 
320
        result.chk_bytes = None
 
321
        return result
340
322
 
341
323
 
342
324
class RepositoryFormat4(PreSplitOutRepositoryFormat):
353
335
 
354
336
    _matchingbzrdir = bzrdir.BzrDirFormat4()
355
337
 
356
 
    def __init__(self):
357
 
        super(RepositoryFormat4, self).__init__()
358
 
 
359
338
    def get_format_description(self):
360
339
        """See RepositoryFormat.get_format_description()."""
361
340
        return "Repository format 4"
368
347
        """Format 4 is not supported.
369
348
 
370
349
        It is not supported because the model changed from 4 to 5 and the
371
 
        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
372
351
        feasible.
373
352
        """
374
353
        return False
375
354
 
376
 
    def _get_control_store(self, repo_transport, control_files):
377
 
        """Format 4 repositories have no formal control store at this point.
378
 
        
379
 
        This will cause any control-file-needing apis to fail - this is desired.
380
 
        """
 
355
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
356
        # No inventories store written so far.
381
357
        return None
382
 
    
383
 
    def _get_revision_store(self, repo_transport, control_files):
384
 
        """See RepositoryFormat._get_revision_store()."""
 
358
 
 
359
    def _get_revisions(self, repo_transport, repo):
385
360
        from bzrlib.xml4 import serializer_v4
386
 
        return self._get_text_rev_store(repo_transport,
387
 
                                        control_files,
388
 
                                        'revision-store',
389
 
                                        serializer=serializer_v4)
390
 
 
391
 
    def _get_text_store(self, transport, control_files):
392
 
        """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
393
372
 
394
373
 
395
374
class RepositoryFormat5(PreSplitOutRepositoryFormat):
403
382
 
404
383
    _versionedfile_class = weave.WeaveFile
405
384
    _matchingbzrdir = bzrdir.BzrDirFormat5()
406
 
 
407
 
    def __init__(self):
408
 
        super(RepositoryFormat5, self).__init__()
 
385
    @property
 
386
    def _serializer(self):
 
387
        return xml5.serializer_v5
409
388
 
410
389
    def get_format_description(self):
411
390
        """See RepositoryFormat.get_format_description()."""
412
391
        return "Weave repository format 5"
413
392
 
414
 
    def _get_revision_store(self, repo_transport, control_files):
415
 
        """See RepositoryFormat._get_revision_store()."""
416
 
        """Return the revision store object for this a_bzrdir."""
417
 
        return self._get_text_rev_store(repo_transport,
418
 
                                        control_files,
419
 
                                        'revision-store',
420
 
                                        compressed=False)
421
 
 
422
 
    def _get_text_store(self, transport, control_files):
423
 
        """See RepositoryFormat._get_text_store()."""
424
 
        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)
425
417
 
426
418
 
427
419
class RepositoryFormat6(PreSplitOutRepositoryFormat):
435
427
 
436
428
    _versionedfile_class = weave.WeaveFile
437
429
    _matchingbzrdir = bzrdir.BzrDirFormat6()
438
 
 
439
 
    def __init__(self):
440
 
        super(RepositoryFormat6, self).__init__()
 
430
    @property
 
431
    def _serializer(self):
 
432
        return xml5.serializer_v5
441
433
 
442
434
    def get_format_description(self):
443
435
        """See RepositoryFormat.get_format_description()."""
444
436
        return "Weave repository format 6"
445
437
 
446
 
    def _get_revision_store(self, repo_transport, control_files):
447
 
        """See RepositoryFormat._get_revision_store()."""
448
 
        return self._get_text_rev_store(repo_transport,
449
 
                                        control_files,
450
 
                                        'revision-store',
451
 
                                        compressed=False,
452
 
                                        prefixed=True)
453
 
 
454
 
    def _get_text_store(self, transport, control_files):
455
 
        """See RepositoryFormat._get_text_store()."""
456
 
        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
 
457
463
 
458
464
class RepositoryFormat7(MetaDirRepositoryFormat):
459
465
    """Bzr repository 7.
469
475
 
470
476
    _versionedfile_class = weave.WeaveFile
471
477
    supports_ghosts = False
 
478
    supports_chks = False
472
479
 
473
 
    def _get_control_store(self, repo_transport, control_files):
474
 
        """Return the control store for this repository."""
475
 
        return self._get_versioned_file_store('',
476
 
                                              repo_transport,
477
 
                                              control_files,
478
 
                                              prefixed=False)
 
480
    _fetch_order = 'topological'
 
481
    _fetch_reconcile = True
 
482
    fast_deltas = False
 
483
    @property
 
484
    def _serializer(self):
 
485
        return xml5.serializer_v5
479
486
 
480
487
    def get_format_string(self):
481
488
        """See RepositoryFormat.get_format_string()."""
485
492
        """See RepositoryFormat.get_format_description()."""
486
493
        return "Weave repository format 7"
487
494
 
488
 
    def check_conversion_target(self, target_format):
489
 
        pass
490
 
 
491
 
    def _get_revision_store(self, repo_transport, control_files):
492
 
        """See RepositoryFormat._get_revision_store()."""
493
 
        return self._get_text_rev_store(repo_transport,
494
 
                                        control_files,
495
 
                                        'revision-store',
496
 
                                        compressed=False,
497
 
                                        prefixed=True,
498
 
                                        )
499
 
 
500
 
    def _get_text_store(self, transport, control_files):
501
 
        """See RepositoryFormat._get_text_store()."""
502
 
        return self._get_versioned_file_store('weaves',
503
 
                                              transport,
504
 
                                              control_files)
 
495
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
496
        mapper = versionedfile.ConstantMapper(name)
 
497
        return versionedfile.ThunkedVersionedFiles(repo_transport,
 
498
            weave.WeaveFile, mapper, repo.is_locked)
 
499
 
 
500
    def _get_revisions(self, repo_transport, repo):
 
501
        return RevisionTextStore(repo_transport.clone('revision-store'),
 
502
            xml5.serializer_v5, True, versionedfile.HashPrefixMapper(),
 
503
            repo.is_locked, repo.is_write_locked)
 
504
 
 
505
    def _get_signatures(self, repo_transport, repo):
 
506
        return SignatureTextStore(repo_transport.clone('revision-store'),
 
507
            True, versionedfile.HashPrefixMapper(),
 
508
            repo.is_locked, repo.is_write_locked)
 
509
 
 
510
    def _get_texts(self, repo_transport, repo):
 
511
        mapper = versionedfile.HashPrefixMapper()
 
512
        base_transport = repo_transport.clone('weaves')
 
513
        return versionedfile.ThunkedVersionedFiles(base_transport,
 
514
            weave.WeaveFile, mapper, repo.is_locked)
505
515
 
506
516
    def initialize(self, a_bzrdir, shared=False):
507
517
        """Create a weave repository.
516
526
 
517
527
        mutter('creating repository in %s.', a_bzrdir.transport.base)
518
528
        dirs = ['revision-store', 'weaves']
519
 
        files = [('inventory.weave', StringIO(empty_weave)), 
 
529
        files = [('inventory.weave', StringIO(empty_weave)),
520
530
                 ]
521
531
        utf8_files = [('format', self.get_format_string())]
522
 
 
 
532
 
523
533
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
524
534
        return self.open(a_bzrdir=a_bzrdir, _found=True)
525
535
 
526
536
    def open(self, a_bzrdir, _found=False, _override_transport=None):
527
537
        """See RepositoryFormat.open().
528
 
        
 
538
 
529
539
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
530
540
                                    repository at a slightly different url
531
541
                                    than normal. I.e. during 'upgrade'.
538
548
            repo_transport = a_bzrdir.get_repository_transport(None)
539
549
        control_files = lockable_files.LockableFiles(repo_transport,
540
550
                                'lock', lockdir.LockDir)
541
 
        text_store = self._get_text_store(repo_transport, control_files)
542
 
        control_store = self._get_control_store(repo_transport, control_files)
543
 
        _revision_store = self._get_revision_store(repo_transport, control_files)
544
 
        return WeaveMetaDirRepository(_format=self,
545
 
            a_bzrdir=a_bzrdir,
546
 
            control_files=control_files,
547
 
            _revision_store=_revision_store,
548
 
            control_store=control_store,
549
 
            text_store=text_store)
550
 
 
551
 
 
552
 
class WeaveCommitBuilder(CommitBuilder):
553
 
    """A builder for weave based repos that don't support ghosts."""
554
 
 
555
 
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
556
 
        versionedfile = self.repository.weave_store.get_weave_or_empty(
557
 
            file_id, self.repository.get_transaction())
558
 
        result = versionedfile.add_lines(
559
 
            self._new_revision_id, parents, new_lines,
560
 
            nostore_sha=nostore_sha)[0:2]
561
 
        return result
562
 
 
 
551
        result = WeaveMetaDirRepository(_format=self, a_bzrdir=a_bzrdir,
 
552
            control_files=control_files)
 
553
        result.revisions = self._get_revisions(repo_transport, result)
 
554
        result.signatures = self._get_signatures(repo_transport, result)
 
555
        result.inventories = self._get_inventories(repo_transport, result)
 
556
        result.texts = self._get_texts(repo_transport, result)
 
557
        result.chk_bytes = None
 
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_known_graph_ancestry(self, keys):
 
668
        """Get a KnownGraph instance with the ancestry of keys."""
 
669
        keys = self.keys()
 
670
        parent_map = self.get_parent_map(keys)
 
671
        kg = _mod_graph.KnownGraph(parent_map)
 
672
        return kg
 
673
 
 
674
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
675
        for key in keys:
 
676
            text, parents = self._load_text_parents(key)
 
677
            if text is None:
 
678
                yield AbsentContentFactory(key)
 
679
            else:
 
680
                yield FulltextContentFactory(key, parents, None, text)
 
681
 
 
682
    def keys(self):
 
683
        if not self._is_locked():
 
684
            raise errors.ObjectNotLocked(self)
 
685
        relpaths = set()
 
686
        for quoted_relpath in self._transport.iter_files_recursive():
 
687
            relpath = urllib.unquote(quoted_relpath)
 
688
            path, ext = os.path.splitext(relpath)
 
689
            if ext == '.gz':
 
690
                relpath = path
 
691
            if not relpath.endswith('.sig'):
 
692
                relpaths.add(relpath)
 
693
        paths = list(relpaths)
 
694
        return set([self._mapper.unmap(path) for path in paths])
 
695
 
 
696
 
 
697
class SignatureTextStore(TextVersionedFiles):
 
698
    """Legacy thunk for format 4-7 repositories."""
 
699
 
 
700
    def __init__(self, transport, compressed, mapper, is_locked, can_write):
 
701
        TextVersionedFiles.__init__(self, transport, compressed, mapper,
 
702
            is_locked, can_write)
 
703
        self._ext = '.sig' + self._ext
 
704
 
 
705
    def get_parent_map(self, keys):
 
706
        result = {}
 
707
        for key in keys:
 
708
            text = self._load_text(key)
 
709
            if text is None:
 
710
                continue
 
711
            result[key] = None
 
712
        return result
 
713
 
 
714
    def get_record_stream(self, keys, sort_order, include_delta_closure):
 
715
        for key in keys:
 
716
            text = self._load_text(key)
 
717
            if text is None:
 
718
                yield AbsentContentFactory(key)
 
719
            else:
 
720
                yield FulltextContentFactory(key, None, None, text)
 
721
 
 
722
    def keys(self):
 
723
        if not self._is_locked():
 
724
            raise errors.ObjectNotLocked(self)
 
725
        relpaths = set()
 
726
        for quoted_relpath in self._transport.iter_files_recursive():
 
727
            relpath = urllib.unquote(quoted_relpath)
 
728
            path, ext = os.path.splitext(relpath)
 
729
            if ext == '.gz':
 
730
                relpath = path
 
731
            if not relpath.endswith('.sig'):
 
732
                continue
 
733
            relpaths.add(relpath[:-4])
 
734
        paths = list(relpaths)
 
735
        return set([self._mapper.unmap(path) for path in paths])
563
736
 
564
737
_legacy_formats = [RepositoryFormat4(),
565
738
                   RepositoryFormat5(),