~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/weaverepo.py

  • Committer: Vincent Ladeuil
  • Date: 2007-10-09 20:32:29 UTC
  • mto: (2903.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 2904.
  • Revision ID: v.ladeuil+lp@free.fr-20071009203229-5k200m1g7mf4jf9l
Fix 149019 by using a proper line number when reporting errors.

* bzrlib/util/configobj/configobj.py:
(ConfigObj._handle_error): Trivial fix. Since cur_index is
0-based, line number was off by one.

Show diffs side-by-side

added added

removed removed

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