~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitrepo.py

  • Committer: Martin Pool
  • Date: 2005-05-16 02:19:13 UTC
  • Revision ID: mbp@sourcefrog.net-20050516021913-3a933f871079e3fe
- patch from ddaa to create api/ directory 
  before building API docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
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
16
 
 
17
 
from bzrlib.lazy_import import lazy_import
18
 
lazy_import(globals(), """
19
 
from bzrlib import (
20
 
    debug,
21
 
    )
22
 
from bzrlib.store import revision
23
 
from bzrlib.store.revision.knit import KnitRevisionStore
24
 
""")
25
 
from bzrlib import (
26
 
    bzrdir,
27
 
    deprecated_graph,
28
 
    errors,
29
 
    knit,
30
 
    lockable_files,
31
 
    lockdir,
32
 
    osutils,
33
 
    transactions,
34
 
    xml5,
35
 
    xml7,
36
 
    )
37
 
 
38
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
 
from bzrlib.repository import (
40
 
    CommitBuilder,
41
 
    MetaDirRepository,
42
 
    MetaDirRepositoryFormat,
43
 
    RepositoryFormat,
44
 
    RootCommitBuilder,
45
 
    )
46
 
import bzrlib.revision as _mod_revision
47
 
from bzrlib.store.versioned import VersionedFileStore
48
 
from bzrlib.trace import mutter, mutter_callsite
49
 
from bzrlib.util import bencode
50
 
 
51
 
 
52
 
class _KnitParentsProvider(object):
53
 
 
54
 
    def __init__(self, knit):
55
 
        self._knit = knit
56
 
 
57
 
    def __repr__(self):
58
 
        return 'KnitParentsProvider(%r)' % self._knit
59
 
 
60
 
    def get_parents(self, revision_ids):
61
 
        parents_list = []
62
 
        for revision_id in revision_ids:
63
 
            if revision_id == _mod_revision.NULL_REVISION:
64
 
                parents = []
65
 
            else:
66
 
                try:
67
 
                    parents = self._knit.get_parents_with_ghosts(revision_id)
68
 
                except errors.RevisionNotPresent:
69
 
                    parents = None
70
 
                else:
71
 
                    if len(parents) == 0:
72
 
                        parents = [_mod_revision.NULL_REVISION]
73
 
            parents_list.append(parents)
74
 
        return parents_list
75
 
 
76
 
 
77
 
class KnitRepository(MetaDirRepository):
78
 
    """Knit format repository."""
79
 
 
80
 
    # These attributes are inherited from the Repository base class. Setting
81
 
    # them to None ensures that if the constructor is changed to not initialize
82
 
    # them, or a subclass fails to call the constructor, that an error will
83
 
    # occur rather than the system working but generating incorrect data.
84
 
    _commit_builder_class = None
85
 
    _serializer = None
86
 
 
87
 
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
88
 
        control_store, text_store, _commit_builder_class, _serializer):
89
 
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
90
 
            _revision_store, control_store, text_store)
91
 
        self._commit_builder_class = _commit_builder_class
92
 
        self._serializer = _serializer
93
 
        self._reconcile_fixes_text_parents = True
94
 
 
95
 
    def _warn_if_deprecated(self):
96
 
        # This class isn't deprecated
97
 
        pass
98
 
 
99
 
    def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
100
 
        return inv_vf.add_lines_with_ghosts(revid, parents, lines,
101
 
            check_content=check_content)[0]
102
 
 
103
 
    @needs_read_lock
104
 
    def _all_revision_ids(self):
105
 
        """See Repository.all_revision_ids()."""
106
 
        # Knits get the revision graph from the index of the revision knit, so
107
 
        # it's always possible even if they're on an unlistable transport.
108
 
        return self._revision_store.all_revision_ids(self.get_transaction())
109
 
 
110
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
111
 
        """Find file_id(s) which are involved in the changes between revisions.
112
 
 
113
 
        This determines the set of revisions which are involved, and then
114
 
        finds all file ids affected by those revisions.
115
 
        """
116
 
        vf = self._get_revision_vf()
117
 
        from_set = set(vf.get_ancestry(from_revid))
118
 
        to_set = set(vf.get_ancestry(to_revid))
119
 
        changed = to_set.difference(from_set)
120
 
        return self._fileid_involved_by_set(changed)
121
 
 
122
 
    def fileid_involved(self, last_revid=None):
123
 
        """Find all file_ids modified in the ancestry of last_revid.
124
 
 
125
 
        :param last_revid: If None, last_revision() will be used.
126
 
        """
127
 
        if not last_revid:
128
 
            changed = set(self.all_revision_ids())
129
 
        else:
130
 
            changed = set(self.get_ancestry(last_revid))
131
 
        if None in changed:
132
 
            changed.remove(None)
133
 
        return self._fileid_involved_by_set(changed)
134
 
 
135
 
    @needs_read_lock
136
 
    def get_ancestry(self, revision_id, topo_sorted=True):
137
 
        """Return a list of revision-ids integrated by a revision.
138
 
        
139
 
        This is topologically sorted, unless 'topo_sorted' is specified as
140
 
        False.
141
 
        """
142
 
        if _mod_revision.is_null(revision_id):
143
 
            return [None]
144
 
        vf = self._get_revision_vf()
145
 
        try:
146
 
            return [None] + vf.get_ancestry(revision_id, topo_sorted)
147
 
        except errors.RevisionNotPresent:
148
 
            raise errors.NoSuchRevision(self, revision_id)
149
 
 
150
 
    @needs_read_lock
151
 
    def get_data_stream(self, revision_ids):
152
 
        """See Repository.get_data_stream."""
153
 
        item_keys = self.item_keys_introduced_by(revision_ids)
154
 
        for knit_kind, file_id, versions in item_keys:
155
 
            name = (knit_kind,)
156
 
            if knit_kind == 'file':
157
 
                name = ('file', file_id)
158
 
                knit = self.weave_store.get_weave_or_empty(
159
 
                    file_id, self.get_transaction())
160
 
            elif knit_kind == 'inventory':
161
 
                knit = self.get_inventory_weave()
162
 
            elif knit_kind == 'revisions':
163
 
                knit = self._revision_store.get_revision_file(
164
 
                    self.get_transaction())
165
 
            elif knit_kind == 'signatures':
166
 
                knit = self._revision_store.get_signature_file(
167
 
                    self.get_transaction())
168
 
            else:
169
 
                raise AssertionError('Unknown knit kind %r' % (knit_kind,))
170
 
            yield name, _get_stream_as_bytes(knit, versions)
171
 
 
172
 
    @needs_read_lock
173
 
    def get_revision(self, revision_id):
174
 
        """Return the Revision object for a named revision"""
175
 
        revision_id = osutils.safe_revision_id(revision_id)
176
 
        return self.get_revision_reconcile(revision_id)
177
 
 
178
 
    @needs_read_lock
179
 
    def get_revision_graph(self, revision_id=None):
180
 
        """Return a dictionary containing the revision graph.
181
 
 
182
 
        :param revision_id: The revision_id to get a graph from. If None, then
183
 
        the entire revision graph is returned. This is a deprecated mode of
184
 
        operation and will be removed in the future.
185
 
        :return: a dictionary of revision_id->revision_parents_list.
186
 
        """
187
 
        if 'evil' in debug.debug_flags:
188
 
            mutter_callsite(3,
189
 
                "get_revision_graph scales with size of history.")
190
 
        # special case NULL_REVISION
191
 
        if revision_id == _mod_revision.NULL_REVISION:
192
 
            return {}
193
 
        a_weave = self._get_revision_vf()
194
 
        if revision_id is None:
195
 
            return a_weave.get_graph()
196
 
        if revision_id not in a_weave:
197
 
            raise errors.NoSuchRevision(self, revision_id)
198
 
        else:
199
 
            # add what can be reached from revision_id
200
 
            return a_weave.get_graph([revision_id])
201
 
 
202
 
    @needs_read_lock
203
 
    def get_revision_graph_with_ghosts(self, revision_ids=None):
204
 
        """Return a graph of the revisions with ghosts marked as applicable.
205
 
 
206
 
        :param revision_ids: an iterable of revisions to graph or None for all.
207
 
        :return: a Graph object with the graph reachable from revision_ids.
208
 
        """
209
 
        if 'evil' in debug.debug_flags:
210
 
            mutter_callsite(3,
211
 
                "get_revision_graph_with_ghosts scales with size of history.")
212
 
        result = deprecated_graph.Graph()
213
 
        vf = self._get_revision_vf()
214
 
        versions = set(vf.versions())
215
 
        if not revision_ids:
216
 
            pending = set(self.all_revision_ids())
217
 
            required = set([])
218
 
        else:
219
 
            pending = set(revision_ids)
220
 
            # special case NULL_REVISION
221
 
            if _mod_revision.NULL_REVISION in pending:
222
 
                pending.remove(_mod_revision.NULL_REVISION)
223
 
            required = set(pending)
224
 
        done = set([])
225
 
        while len(pending):
226
 
            revision_id = pending.pop()
227
 
            if not revision_id in versions:
228
 
                if revision_id in required:
229
 
                    raise errors.NoSuchRevision(self, revision_id)
230
 
                # a ghost
231
 
                result.add_ghost(revision_id)
232
 
                # mark it as done so we don't try for it again.
233
 
                done.add(revision_id)
234
 
                continue
235
 
            parent_ids = vf.get_parents_with_ghosts(revision_id)
236
 
            for parent_id in parent_ids:
237
 
                # is this queued or done ?
238
 
                if (parent_id not in pending and
239
 
                    parent_id not in done):
240
 
                    # no, queue it.
241
 
                    pending.add(parent_id)
242
 
            result.add_node(revision_id, parent_ids)
243
 
            done.add(revision_id)
244
 
        return result
245
 
 
246
 
    def _get_revision_vf(self):
247
 
        """:return: a versioned file containing the revisions."""
248
 
        vf = self._revision_store.get_revision_file(self.get_transaction())
249
 
        return vf
250
 
 
251
 
    def _get_history_vf(self):
252
 
        """Get a versionedfile whose history graph reflects all revisions.
253
 
 
254
 
        For knit repositories, this is the revision knit.
255
 
        """
256
 
        return self._get_revision_vf()
257
 
 
258
 
    @needs_write_lock
259
 
    def reconcile(self, other=None, thorough=False):
260
 
        """Reconcile this repository."""
261
 
        from bzrlib.reconcile import KnitReconciler
262
 
        reconciler = KnitReconciler(self, thorough=thorough)
263
 
        reconciler.reconcile()
264
 
        return reconciler
265
 
    
266
 
    def revision_parents(self, revision_id):
267
 
        return self._get_revision_vf().get_parents(revision_id)
268
 
 
269
 
    def _make_parents_provider(self):
270
 
        return _KnitParentsProvider(self._get_revision_vf())
271
 
 
272
 
    def _find_inconsistent_revision_parents(self):
273
 
        """Find revisions with different parent lists in the revision object
274
 
        and in the index graph.
275
 
 
276
 
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
277
 
            parents-in-revision).
278
 
        """
279
 
        assert self.is_locked()
280
 
        vf = self._get_revision_vf()
281
 
        for index_version in vf.versions():
282
 
            parents_according_to_index = tuple(vf.get_parents_with_ghosts(
283
 
                index_version))
284
 
            revision = self.get_revision(index_version)
285
 
            parents_according_to_revision = tuple(revision.parent_ids)
286
 
            if parents_according_to_index != parents_according_to_revision:
287
 
                yield (index_version, parents_according_to_index,
288
 
                    parents_according_to_revision)
289
 
 
290
 
    def _check_for_inconsistent_revision_parents(self):
291
 
        inconsistencies = list(self._find_inconsistent_revision_parents())
292
 
        if inconsistencies:
293
 
            raise errors.BzrCheckError(
294
 
                "Revision knit has inconsistent parents.")
295
 
 
296
 
    def revision_graph_can_have_wrong_parents(self):
297
 
        # The revision.kndx could potentially claim a revision has a different
298
 
        # parent to the revision text.
299
 
        return True
300
 
 
301
 
 
302
 
class RepositoryFormatKnit(MetaDirRepositoryFormat):
303
 
    """Bzr repository knit format (generalized). 
304
 
 
305
 
    This repository format has:
306
 
     - knits for file texts and inventory
307
 
     - hash subdirectory based stores.
308
 
     - knits for revisions and signatures
309
 
     - TextStores for revisions and signatures.
310
 
     - a format marker of its own
311
 
     - an optional 'shared-storage' flag
312
 
     - an optional 'no-working-trees' flag
313
 
     - a LockDir lock
314
 
    """
315
 
 
316
 
    # Set this attribute in derived classes to control the repository class
317
 
    # created by open and initialize.
318
 
    repository_class = None
319
 
    # Set this attribute in derived classes to control the
320
 
    # _commit_builder_class that the repository objects will have passed to
321
 
    # their constructor.
322
 
    _commit_builder_class = None
323
 
    # Set this attribute in derived clases to control the _serializer that the
324
 
    # repository objects will have passed to their constructor.
325
 
    _serializer = xml5.serializer_v5
326
 
 
327
 
    def _get_control_store(self, repo_transport, control_files):
328
 
        """Return the control store for this repository."""
329
 
        return VersionedFileStore(
330
 
            repo_transport,
331
 
            prefixed=False,
332
 
            file_mode=control_files._file_mode,
333
 
            versionedfile_class=knit.KnitVersionedFile,
334
 
            versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
335
 
            )
336
 
 
337
 
    def _get_revision_store(self, repo_transport, control_files):
338
 
        """See RepositoryFormat._get_revision_store()."""
339
 
        versioned_file_store = VersionedFileStore(
340
 
            repo_transport,
341
 
            file_mode=control_files._file_mode,
342
 
            prefixed=False,
343
 
            precious=True,
344
 
            versionedfile_class=knit.KnitVersionedFile,
345
 
            versionedfile_kwargs={'delta':False,
346
 
                                  'factory':knit.KnitPlainFactory(),
347
 
                                 },
348
 
            escaped=True,
349
 
            )
350
 
        return KnitRevisionStore(versioned_file_store)
351
 
 
352
 
    def _get_text_store(self, transport, control_files):
353
 
        """See RepositoryFormat._get_text_store()."""
354
 
        return self._get_versioned_file_store('knits',
355
 
                                  transport,
356
 
                                  control_files,
357
 
                                  versionedfile_class=knit.KnitVersionedFile,
358
 
                                  versionedfile_kwargs={
359
 
                                      'create_parent_dir':True,
360
 
                                      'delay_create':True,
361
 
                                      'dir_mode':control_files._dir_mode,
362
 
                                  },
363
 
                                  escaped=True)
364
 
 
365
 
    def initialize(self, a_bzrdir, shared=False):
366
 
        """Create a knit format 1 repository.
367
 
 
368
 
        :param a_bzrdir: bzrdir to contain the new repository; must already
369
 
            be initialized.
370
 
        :param shared: If true the repository will be initialized as a shared
371
 
                       repository.
372
 
        """
373
 
        mutter('creating repository in %s.', a_bzrdir.transport.base)
374
 
        dirs = ['knits']
375
 
        files = []
376
 
        utf8_files = [('format', self.get_format_string())]
377
 
        
378
 
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
379
 
        repo_transport = a_bzrdir.get_repository_transport(None)
380
 
        control_files = lockable_files.LockableFiles(repo_transport,
381
 
                                'lock', lockdir.LockDir)
382
 
        control_store = self._get_control_store(repo_transport, control_files)
383
 
        transaction = transactions.WriteTransaction()
384
 
        # trigger a write of the inventory store.
385
 
        control_store.get_weave_or_empty('inventory', transaction)
386
 
        _revision_store = self._get_revision_store(repo_transport, control_files)
387
 
        # the revision id here is irrelevant: it will not be stored, and cannot
388
 
        # already exist.
389
 
        _revision_store.has_revision_id('A', transaction)
390
 
        _revision_store.get_signature_file(transaction)
391
 
        return self.open(a_bzrdir=a_bzrdir, _found=True)
392
 
 
393
 
    def open(self, a_bzrdir, _found=False, _override_transport=None):
394
 
        """See RepositoryFormat.open().
395
 
        
396
 
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
397
 
                                    repository at a slightly different url
398
 
                                    than normal. I.e. during 'upgrade'.
399
 
        """
400
 
        if not _found:
401
 
            format = RepositoryFormat.find_format(a_bzrdir)
402
 
            assert format.__class__ ==  self.__class__
403
 
        if _override_transport is not None:
404
 
            repo_transport = _override_transport
405
 
        else:
406
 
            repo_transport = a_bzrdir.get_repository_transport(None)
407
 
        control_files = lockable_files.LockableFiles(repo_transport,
408
 
                                'lock', lockdir.LockDir)
409
 
        text_store = self._get_text_store(repo_transport, control_files)
410
 
        control_store = self._get_control_store(repo_transport, control_files)
411
 
        _revision_store = self._get_revision_store(repo_transport, control_files)
412
 
        return self.repository_class(_format=self,
413
 
                              a_bzrdir=a_bzrdir,
414
 
                              control_files=control_files,
415
 
                              _revision_store=_revision_store,
416
 
                              control_store=control_store,
417
 
                              text_store=text_store,
418
 
                              _commit_builder_class=self._commit_builder_class,
419
 
                              _serializer=self._serializer)
420
 
 
421
 
 
422
 
class RepositoryFormatKnit1(RepositoryFormatKnit):
423
 
    """Bzr repository knit format 1.
424
 
 
425
 
    This repository format has:
426
 
     - knits for file texts and inventory
427
 
     - hash subdirectory based stores.
428
 
     - knits for revisions and signatures
429
 
     - TextStores for revisions and signatures.
430
 
     - a format marker of its own
431
 
     - an optional 'shared-storage' flag
432
 
     - an optional 'no-working-trees' flag
433
 
     - a LockDir lock
434
 
 
435
 
    This format was introduced in bzr 0.8.
436
 
    """
437
 
 
438
 
    repository_class = KnitRepository
439
 
    _commit_builder_class = CommitBuilder
440
 
    _serializer = xml5.serializer_v5
441
 
 
442
 
    def __ne__(self, other):
443
 
        return self.__class__ is not other.__class__
444
 
 
445
 
    def get_format_string(self):
446
 
        """See RepositoryFormat.get_format_string()."""
447
 
        return "Bazaar-NG Knit Repository Format 1"
448
 
 
449
 
    def get_format_description(self):
450
 
        """See RepositoryFormat.get_format_description()."""
451
 
        return "Knit repository format 1"
452
 
 
453
 
    def check_conversion_target(self, target_format):
454
 
        pass
455
 
 
456
 
 
457
 
class RepositoryFormatKnit3(RepositoryFormatKnit):
458
 
    """Bzr repository knit format 2.
459
 
 
460
 
    This repository format has:
461
 
     - knits for file texts and inventory
462
 
     - hash subdirectory based stores.
463
 
     - knits for revisions and signatures
464
 
     - TextStores for revisions and signatures.
465
 
     - a format marker of its own
466
 
     - an optional 'shared-storage' flag
467
 
     - an optional 'no-working-trees' flag
468
 
     - a LockDir lock
469
 
     - support for recording full info about the tree root
470
 
     - support for recording tree-references
471
 
    """
472
 
 
473
 
    repository_class = KnitRepository
474
 
    _commit_builder_class = RootCommitBuilder
475
 
    rich_root_data = True
476
 
    supports_tree_reference = True
477
 
    _serializer = xml7.serializer_v7
478
 
 
479
 
    def _get_matching_bzrdir(self):
480
 
        return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
481
 
 
482
 
    def _ignore_setting_bzrdir(self, format):
483
 
        pass
484
 
 
485
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
486
 
 
487
 
    def check_conversion_target(self, target_format):
488
 
        if not target_format.rich_root_data:
489
 
            raise errors.BadConversionTarget(
490
 
                'Does not support rich root data.', target_format)
491
 
        if not getattr(target_format, 'supports_tree_reference', False):
492
 
            raise errors.BadConversionTarget(
493
 
                'Does not support nested trees', target_format)
494
 
            
495
 
    def get_format_string(self):
496
 
        """See RepositoryFormat.get_format_string()."""
497
 
        return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
498
 
 
499
 
    def get_format_description(self):
500
 
        """See RepositoryFormat.get_format_description()."""
501
 
        return "Knit repository format 3"
502
 
 
503
 
 
504
 
def _get_stream_as_bytes(knit, required_versions):
505
 
    """Generate a serialised data stream.
506
 
 
507
 
    The format is a bencoding of a list.  The first element of the list is a
508
 
    string of the format signature, then each subsequent element is a list
509
 
    corresponding to a record.  Those lists contain:
510
 
 
511
 
      * a version id
512
 
      * a list of options
513
 
      * a list of parents
514
 
      * the bytes
515
 
 
516
 
    :returns: a bencoded list.
517
 
    """
518
 
    knit_stream = knit.get_data_stream(required_versions)
519
 
    format_signature, data_list, callable = knit_stream
520
 
    data = []
521
 
    data.append(format_signature)
522
 
    for version, options, length, parents in data_list:
523
 
        data.append([version, options, parents, callable(length)])
524
 
    return bencode.bencode(data)