~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Jelmer Vernooij
  • Date: 2010-12-20 11:57:14 UTC
  • mto: This revision was merged to the branch mainline in revision 5577.
  • Revision ID: jelmer@samba.org-20101220115714-2ru3hfappjweeg7q
Don't use no-plugins.

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