/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: Martin Albisetti
  • Date: 2008-04-08 22:56:38 UTC
  • mto: (3350.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 3351.
  • Revision ID: argentina@gmail.com-20080408225638-ghmlbx6afr7fy0g1
 * Change to module imports to alphabetical order
 * Small tweak in indention

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Deprecated weave-based repository formats.
18
18
 
20
20
ghosts.
21
21
"""
22
22
 
23
 
import os
24
 
from cStringIO import StringIO
25
 
import urllib
 
23
from StringIO import StringIO
26
24
 
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
 
""")
34
25
from bzrlib import (
35
26
    bzrdir,
36
27
    debug,
39
30
    lockdir,
40
31
    osutils,
41
32
    revision as _mod_revision,
42
 
    trace,
43
 
    urlutils,
44
 
    versionedfile,
45
33
    weave,
46
34
    weavefile,
 
35
    xml5,
47
36
    )
48
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
49
38
from bzrlib.repository import (
50
39
    CommitBuilder,
51
 
    MetaDirVersionedFileRepository,
 
40
    MetaDirRepository,
52
41
    MetaDirRepositoryFormat,
53
42
    Repository,
54
43
    RepositoryFormat,
55
44
    )
56
45
from bzrlib.store.text import TextStore
57
 
from bzrlib.tuned_gzip import GzipFile, bytes_to_gzip
58
 
from bzrlib.versionedfile import (
59
 
    AbsentContentFactory,
60
 
    FulltextContentFactory,
61
 
    VersionedFiles,
62
 
    )
 
46
from bzrlib.symbol_versioning import deprecated_method, one_four
 
47
from bzrlib.trace import mutter
63
48
 
64
49
 
65
50
class AllInOneRepository(Repository):
66
51
    """Legacy support - the repository behaviour for all-in-one branches."""
67
52
 
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):
 
53
    _serializer = xml5.serializer_v5
 
54
 
 
55
    def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
80
56
        # we reuse one control files instance.
81
 
        dir_mode = a_bzrdir._get_dir_mode()
82
 
        file_mode = a_bzrdir._get_file_mode()
 
57
        dir_mode = a_bzrdir._control_files._dir_mode
 
58
        file_mode = a_bzrdir._control_files._file_mode
83
59
 
84
60
        def get_store(name, compressed=True, prefixed=False):
85
61
            # FIXME: This approach of assuming stores are all entirely compressed
86
 
            # or entirely uncompressed is tidy, but breaks upgrade from
87
 
            # some existing branches where there's a mixture; we probably
 
62
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
63
            # some existing branches where there's a mixture; we probably 
88
64
            # still want the option to look for both.
89
 
            relpath = self._escape(name)
90
 
            store = TextStore(a_bzrdir.transport.clone(relpath),
 
65
            relpath = a_bzrdir._control_files._escape(name)
 
66
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
91
67
                              prefixed=prefixed, compressed=compressed,
92
68
                              dir_mode=dir_mode,
93
69
                              file_mode=file_mode)
94
70
            return store
95
71
 
96
72
        # not broken out yet because the controlweaves|inventory_store
97
 
        # and texts bits are still different.
 
73
        # and text_store | weave_store bits are still different.
98
74
        if isinstance(_format, RepositoryFormat4):
99
 
            # cannot remove these - there is still no consistent api
 
75
            # cannot remove these - there is still no consistent api 
100
76
            # which allows access to this old info.
101
77
            self.inventory_store = get_store('inventory-store')
102
 
            self._text_store = get_store('text-store')
103
 
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files)
 
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)
104
80
 
105
81
    @needs_read_lock
106
82
    def _all_possible_ids(self):
107
83
        """Return all the possible revisions that we could find."""
108
84
        if 'evil' in debug.debug_flags:
109
 
            trace.mutter_callsite(
110
 
                3, "_all_possible_ids scales with size of history.")
111
 
        return [key[-1] for key in self.inventories.keys()]
 
85
            mutter_callsite(3, "_all_possible_ids scales with size of history.")
 
86
        return self.get_inventory_weave().versions()
112
87
 
113
88
    @needs_read_lock
114
89
    def _all_revision_ids(self):
115
 
        """Returns a list of all the revision ids in the repository.
 
90
        """Returns a list of all the revision ids in the repository. 
116
91
 
117
 
        These are in as much topological order as the underlying store can
 
92
        These are in as much topological order as the underlying store can 
118
93
        present: for weaves ghosts may lead to a lack of correctness until
119
94
        the reweave updates the parents list.
120
95
        """
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')
 
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)
140
120
 
141
121
    def get_commit_builder(self, branch, parents, config, timestamp=None,
142
122
                           timezone=None, committer=None, revprops=None,
143
123
                           revision_id=None):
144
124
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
145
 
        result = CommitBuilder(self, parents, config, timestamp, timezone,
 
125
        result = WeaveCommitBuilder(self, parents, config, timestamp, timezone,
146
126
                              committer, revprops, revision_id)
147
127
        self.start_write_group()
148
128
        return result
150
130
    @needs_read_lock
151
131
    def get_revisions(self, revision_ids):
152
132
        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)
153
143
        return revs
154
144
 
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
 
 
 
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
166
191
    def is_shared(self):
167
192
        """AllInOne repositories cannot be shared."""
168
193
        return False
177
202
        :param new_value: True to restore the default, False to disable making
178
203
                          working trees.
179
204
        """
180
 
        raise errors.RepositoryUpgradeRequired(self.user_url)
181
 
 
 
205
        raise NotImplementedError(self.set_make_working_trees)
 
206
    
182
207
    def make_working_trees(self):
183
208
        """Returns the policy for making working trees on new branches."""
184
209
        return True
189
214
        return False
190
215
 
191
216
 
192
 
class WeaveMetaDirRepository(MetaDirVersionedFileRepository):
 
217
class WeaveMetaDirRepository(MetaDirRepository):
193
218
    """A subclass of MetaDirRepository to set weave specific policy."""
194
219
 
195
 
    def __init__(self, _format, a_bzrdir, control_files):
196
 
        super(WeaveMetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
197
 
        self._serializer = _format._serializer
 
220
    _serializer = xml5.serializer_v5
198
221
 
199
222
    @needs_read_lock
200
223
    def _all_possible_ids(self):
201
224
        """Return all the possible revisions that we could find."""
202
225
        if 'evil' in debug.debug_flags:
203
 
            trace.mutter_callsite(
204
 
                3, "_all_possible_ids scales with size of history.")
205
 
        return [key[-1] for key in self.inventories.keys()]
 
226
            mutter_callsite(3, "_all_possible_ids scales with size of history.")
 
227
        return self.get_inventory_weave().versions()
206
228
 
207
229
    @needs_read_lock
208
230
    def _all_revision_ids(self):
209
 
        """Returns a list of all the revision ids in the repository.
 
231
        """Returns a list of all the revision ids in the repository. 
210
232
 
211
 
        These are in as much topological order as the underlying store can
 
233
        These are in as much topological order as the underlying store can 
212
234
        present: for weaves ghosts may lead to a lack of correctness until
213
235
        the reweave updates the parents list.
214
236
        """
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')
 
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)
234
261
 
235
262
    def get_commit_builder(self, branch, parents, config, timestamp=None,
236
263
                           timezone=None, committer=None, revprops=None,
237
264
                           revision_id=None):
238
265
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
239
 
        result = CommitBuilder(self, parents, config, timestamp, timezone,
 
266
        result = WeaveCommitBuilder(self, parents, config, timestamp, timezone,
240
267
                              committer, revprops, revision_id)
241
268
        self.start_write_group()
242
269
        return result
244
271
    @needs_read_lock
245
272
    def get_revision(self, revision_id):
246
273
        """Return the Revision object for a named revision"""
 
274
        # TODO: jam 20070210 get_revision_reconcile should do this for us
247
275
        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)
248
285
        return r
249
286
 
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]
 
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
260
331
 
261
332
    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.
262
335
        return False
263
336
 
264
337
 
269
342
    supports_tree_reference = False
270
343
    supports_ghosts = False
271
344
    supports_external_lookups = False
272
 
    supports_chks = False
273
 
    _fetch_order = 'topological'
274
 
    _fetch_reconcile = True
275
 
    fast_deltas = False
276
345
 
277
346
    def initialize(self, a_bzrdir, shared=False, _internal=False):
278
347
        """Create a weave repository."""
282
351
        if not _internal:
283
352
            # always initialized when the bzrdir is.
284
353
            return self.open(a_bzrdir, _found=True)
285
 
 
 
354
        
286
355
        # Create an empty weave
287
356
        sio = StringIO()
288
357
        weavefile.write_weave_v5(weave.Weave(), sio)
289
358
        empty_weave = sio.getvalue()
290
359
 
291
 
        trace.mutter('creating repository in %s.', a_bzrdir.transport.base)
292
 
 
 
360
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
361
        dirs = ['revision-store', 'weaves']
 
362
        files = [('inventory.weave', StringIO(empty_weave)),
 
363
                 ]
 
364
        
293
365
        # FIXME: RBC 20060125 don't peek under the covers
294
366
        # NB: no need to escape relative paths that are url safe.
295
367
        control_files = lockable_files.LockableFiles(a_bzrdir.transport,
296
 
            'branch-lock', lockable_files.TransportLock)
 
368
                                'branch-lock', lockable_files.TransportLock)
297
369
        control_files.create_lock()
298
370
        control_files.lock_write()
299
 
        transport = a_bzrdir.transport
 
371
        control_files._transport.mkdir_multi(dirs,
 
372
                mode=control_files._dir_mode)
300
373
        try:
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())
 
374
            for file, content in files:
 
375
                control_files.put(file, content)
305
376
        finally:
306
377
            control_files.unlock()
307
 
        repository = self.open(a_bzrdir, _found=True)
308
 
        self._run_post_repo_init_hooks(repository, a_bzrdir, shared)
309
 
        return repository
 
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)
310
390
 
311
391
    def open(self, a_bzrdir, _found=False):
312
392
        """See RepositoryFormat.open()."""
316
396
 
317
397
        repo_transport = a_bzrdir.get_repository_transport(None)
318
398
        control_files = a_bzrdir._control_files
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
 
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
326
410
 
327
411
 
328
412
class RepositoryFormat4(PreSplitOutRepositoryFormat):
339
423
 
340
424
    _matchingbzrdir = bzrdir.BzrDirFormat4()
341
425
 
 
426
    def __init__(self):
 
427
        super(RepositoryFormat4, self).__init__()
 
428
 
342
429
    def get_format_description(self):
343
430
        """See RepositoryFormat.get_format_description()."""
344
431
        return "Repository format 4"
351
438
        """Format 4 is not supported.
352
439
 
353
440
        It is not supported because the model changed from 4 to 5 and the
354
 
        conversion logic is expensive - so doing it on the fly was not
 
441
        conversion logic is expensive - so doing it on the fly was not 
355
442
        feasible.
356
443
        """
357
444
        return False
358
445
 
359
 
    def _get_inventories(self, repo_transport, repo, name='inventory'):
360
 
        # No inventories store written so far.
 
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
        """
361
451
        return None
362
 
 
363
 
    def _get_revisions(self, repo_transport, repo):
 
452
    
 
453
    def _get_revision_store(self, repo_transport, control_files):
 
454
        """See RepositoryFormat._get_revision_store()."""
364
455
        from bzrlib.xml4 import serializer_v4
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
 
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()."""
376
463
 
377
464
 
378
465
class RepositoryFormat5(PreSplitOutRepositoryFormat):
386
473
 
387
474
    _versionedfile_class = weave.WeaveFile
388
475
    _matchingbzrdir = bzrdir.BzrDirFormat5()
389
 
    @property
390
 
    def _serializer(self):
391
 
        return xml5.serializer_v5
 
476
 
 
477
    def __init__(self):
 
478
        super(RepositoryFormat5, self).__init__()
392
479
 
393
480
    def get_format_description(self):
394
481
        """See RepositoryFormat.get_format_description()."""
395
482
        return "Weave repository format 5"
396
483
 
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)
 
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)
421
495
 
422
496
 
423
497
class RepositoryFormat6(PreSplitOutRepositoryFormat):
431
505
 
432
506
    _versionedfile_class = weave.WeaveFile
433
507
    _matchingbzrdir = bzrdir.BzrDirFormat6()
434
 
    @property
435
 
    def _serializer(self):
436
 
        return xml5.serializer_v5
 
508
 
 
509
    def __init__(self):
 
510
        super(RepositoryFormat6, self).__init__()
437
511
 
438
512
    def get_format_description(self):
439
513
        """See RepositoryFormat.get_format_description()."""
440
514
        return "Weave repository format 6"
441
515
 
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
 
 
 
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)
467
527
 
468
528
class RepositoryFormat7(MetaDirRepositoryFormat):
469
529
    """Bzr repository 7.
479
539
 
480
540
    _versionedfile_class = weave.WeaveFile
481
541
    supports_ghosts = False
482
 
    supports_chks = False
483
542
 
484
 
    _fetch_order = 'topological'
485
 
    _fetch_reconcile = True
486
 
    fast_deltas = False
487
 
    @property
488
 
    def _serializer(self):
489
 
        return xml5.serializer_v5
 
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)
490
549
 
491
550
    def get_format_string(self):
492
551
        """See RepositoryFormat.get_format_string()."""
496
555
        """See RepositoryFormat.get_format_description()."""
497
556
        return "Weave repository format 7"
498
557
 
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)
 
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)
519
575
 
520
576
    def initialize(self, a_bzrdir, shared=False):
521
577
        """Create a weave repository.
528
584
        weavefile.write_weave_v5(weave.Weave(), sio)
529
585
        empty_weave = sio.getvalue()
530
586
 
531
 
        trace.mutter('creating repository in %s.', a_bzrdir.transport.base)
 
587
        mutter('creating repository in %s.', a_bzrdir.transport.base)
532
588
        dirs = ['revision-store', 'weaves']
533
 
        files = [('inventory.weave', StringIO(empty_weave)),
 
589
        files = [('inventory.weave', StringIO(empty_weave)), 
534
590
                 ]
535
591
        utf8_files = [('format', self.get_format_string())]
536
 
 
 
592
 
537
593
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
538
594
        return self.open(a_bzrdir=a_bzrdir, _found=True)
539
595
 
540
596
    def open(self, a_bzrdir, _found=False, _override_transport=None):
541
597
        """See RepositoryFormat.open().
542
 
 
 
598
        
543
599
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
544
600
                                    repository at a slightly different url
545
601
                                    than normal. I.e. during 'upgrade'.
546
602
        """
547
603
        if not _found:
548
604
            format = RepositoryFormat.find_format(a_bzrdir)
 
605
            assert format.__class__ ==  self.__class__
549
606
        if _override_transport is not None:
550
607
            repo_transport = _override_transport
551
608
        else:
552
609
            repo_transport = a_bzrdir.get_repository_transport(None)
553
610
        control_files = lockable_files.LockableFiles(repo_transport,
554
611
                                'lock', lockdir.LockDir)
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])
 
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
 
740
635
 
741
636
_legacy_formats = [RepositoryFormat4(),
742
637
                   RepositoryFormat5(),