/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

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