1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
17
from bzrlib.lazy_import import lazy_import
 
 
18
lazy_import(globals(), """
 
 
22
from bzrlib.store import revision
 
 
23
from bzrlib.store.revision.knit import KnitRevisionStore
 
 
40
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
 
41
from bzrlib.repository import (
 
 
44
    MetaDirRepositoryFormat,
 
 
48
import bzrlib.revision as _mod_revision
 
 
49
from bzrlib.store.versioned import VersionedFileStore
 
 
50
from bzrlib.trace import mutter, mutter_callsite
 
 
51
from bzrlib.util import bencode
 
 
54
class _KnitParentsProvider(object):
 
 
56
    def __init__(self, knit):
 
 
60
        return 'KnitParentsProvider(%r)' % self._knit
 
 
62
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
 
 
63
    def get_parents(self, revision_ids):
 
 
64
        """See graph._StackedParentsProvider.get_parents"""
 
 
65
        parent_map = self.get_parent_map(revision_ids)
 
 
66
        return [parent_map.get(r, None) for r in revision_ids]
 
 
68
    def get_parent_map(self, keys):
 
 
69
        """See graph._StackedParentsProvider.get_parent_map"""
 
 
71
        for revision_id in keys:
 
 
72
            if revision_id == _mod_revision.NULL_REVISION:
 
 
73
                parent_map[revision_id] = []
 
 
76
                    parents = self._knit.get_parents_with_ghosts(revision_id)
 
 
77
                except errors.RevisionNotPresent:
 
 
81
                        parents = [_mod_revision.NULL_REVISION]
 
 
82
                parent_map[revision_id] = parents
 
 
86
class KnitRepository(MetaDirRepository):
 
 
87
    """Knit format repository."""
 
 
89
    # These attributes are inherited from the Repository base class. Setting
 
 
90
    # them to None ensures that if the constructor is changed to not initialize
 
 
91
    # them, or a subclass fails to call the constructor, that an error will
 
 
92
    # occur rather than the system working but generating incorrect data.
 
 
93
    _commit_builder_class = None
 
 
96
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
 
 
97
        control_store, text_store, _commit_builder_class, _serializer):
 
 
98
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
 
 
99
            _revision_store, control_store, text_store)
 
 
100
        self._commit_builder_class = _commit_builder_class
 
 
101
        self._serializer = _serializer
 
 
102
        self._reconcile_fixes_text_parents = True
 
 
104
    def _warn_if_deprecated(self):
 
 
105
        # This class isn't deprecated
 
 
108
    def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
 
 
109
        return inv_vf.add_lines_with_ghosts(revid, parents, lines,
 
 
110
            check_content=check_content)[0]
 
 
113
    def _all_revision_ids(self):
 
 
114
        """See Repository.all_revision_ids()."""
 
 
115
        # Knits get the revision graph from the index of the revision knit, so
 
 
116
        # it's always possible even if they're on an unlistable transport.
 
 
117
        return self._revision_store.all_revision_ids(self.get_transaction())
 
 
119
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
 
120
        """Find file_id(s) which are involved in the changes between revisions.
 
 
122
        This determines the set of revisions which are involved, and then
 
 
123
        finds all file ids affected by those revisions.
 
 
125
        vf = self._get_revision_vf()
 
 
126
        from_set = set(vf.get_ancestry(from_revid))
 
 
127
        to_set = set(vf.get_ancestry(to_revid))
 
 
128
        changed = to_set.difference(from_set)
 
 
129
        return self._fileid_involved_by_set(changed)
 
 
131
    def fileid_involved(self, last_revid=None):
 
 
132
        """Find all file_ids modified in the ancestry of last_revid.
 
 
134
        :param last_revid: If None, last_revision() will be used.
 
 
137
            changed = set(self.all_revision_ids())
 
 
139
            changed = set(self.get_ancestry(last_revid))
 
 
142
        return self._fileid_involved_by_set(changed)
 
 
145
    def get_ancestry(self, revision_id, topo_sorted=True):
 
 
146
        """Return a list of revision-ids integrated by a revision.
 
 
148
        This is topologically sorted, unless 'topo_sorted' is specified as
 
 
151
        if _mod_revision.is_null(revision_id):
 
 
153
        vf = self._get_revision_vf()
 
 
155
            return [None] + vf.get_ancestry(revision_id, topo_sorted)
 
 
156
        except errors.RevisionNotPresent:
 
 
157
            raise errors.NoSuchRevision(self, revision_id)
 
 
160
    def get_data_stream(self, revision_ids):
 
 
161
        """See Repository.get_data_stream."""
 
 
162
        item_keys = self.item_keys_introduced_by(revision_ids)
 
 
163
        for knit_kind, file_id, versions in item_keys:
 
 
165
            if knit_kind == 'file':
 
 
166
                name = ('file', file_id)
 
 
167
                knit = self.weave_store.get_weave_or_empty(
 
 
168
                    file_id, self.get_transaction())
 
 
169
            elif knit_kind == 'inventory':
 
 
170
                knit = self.get_inventory_weave()
 
 
171
            elif knit_kind == 'revisions':
 
 
172
                knit = self._revision_store.get_revision_file(
 
 
173
                    self.get_transaction())
 
 
174
            elif knit_kind == 'signatures':
 
 
175
                knit = self._revision_store.get_signature_file(
 
 
176
                    self.get_transaction())
 
 
178
                raise AssertionError('Unknown knit kind %r' % (knit_kind,))
 
 
179
            yield name, _get_stream_as_bytes(knit, versions)
 
 
182
    def get_revision(self, revision_id):
 
 
183
        """Return the Revision object for a named revision"""
 
 
184
        revision_id = osutils.safe_revision_id(revision_id)
 
 
185
        return self.get_revision_reconcile(revision_id)
 
 
188
    def get_revision_graph(self, revision_id=None):
 
 
189
        """Return a dictionary containing the revision graph.
 
 
191
        :param revision_id: The revision_id to get a graph from. If None, then
 
 
192
        the entire revision graph is returned. This is a deprecated mode of
 
 
193
        operation and will be removed in the future.
 
 
194
        :return: a dictionary of revision_id->revision_parents_list.
 
 
196
        if 'evil' in debug.debug_flags:
 
 
198
                "get_revision_graph scales with size of history.")
 
 
199
        # special case NULL_REVISION
 
 
200
        if revision_id == _mod_revision.NULL_REVISION:
 
 
202
        a_weave = self._get_revision_vf()
 
 
203
        if revision_id is None:
 
 
204
            return a_weave.get_graph()
 
 
205
        if revision_id not in a_weave:
 
 
206
            raise errors.NoSuchRevision(self, revision_id)
 
 
208
            # add what can be reached from revision_id
 
 
209
            return a_weave.get_graph([revision_id])
 
 
212
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
 
213
        """Return a graph of the revisions with ghosts marked as applicable.
 
 
215
        :param revision_ids: an iterable of revisions to graph or None for all.
 
 
216
        :return: a Graph object with the graph reachable from revision_ids.
 
 
218
        if 'evil' in debug.debug_flags:
 
 
220
                "get_revision_graph_with_ghosts scales with size of history.")
 
 
221
        result = deprecated_graph.Graph()
 
 
222
        vf = self._get_revision_vf()
 
 
223
        versions = set(vf.versions())
 
 
225
            pending = set(self.all_revision_ids())
 
 
228
            pending = set(revision_ids)
 
 
229
            # special case NULL_REVISION
 
 
230
            if _mod_revision.NULL_REVISION in pending:
 
 
231
                pending.remove(_mod_revision.NULL_REVISION)
 
 
232
            required = set(pending)
 
 
235
            revision_id = pending.pop()
 
 
236
            if not revision_id in versions:
 
 
237
                if revision_id in required:
 
 
238
                    raise errors.NoSuchRevision(self, revision_id)
 
 
240
                result.add_ghost(revision_id)
 
 
241
                # mark it as done so we don't try for it again.
 
 
242
                done.add(revision_id)
 
 
244
            parent_ids = vf.get_parents_with_ghosts(revision_id)
 
 
245
            for parent_id in parent_ids:
 
 
246
                # is this queued or done ?
 
 
247
                if (parent_id not in pending and
 
 
248
                    parent_id not in done):
 
 
250
                    pending.add(parent_id)
 
 
251
            result.add_node(revision_id, parent_ids)
 
 
252
            done.add(revision_id)
 
 
255
    def _get_revision_vf(self):
 
 
256
        """:return: a versioned file containing the revisions."""
 
 
257
        vf = self._revision_store.get_revision_file(self.get_transaction())
 
 
260
    def _get_history_vf(self):
 
 
261
        """Get a versionedfile whose history graph reflects all revisions.
 
 
263
        For knit repositories, this is the revision knit.
 
 
265
        return self._get_revision_vf()
 
 
268
    def reconcile(self, other=None, thorough=False):
 
 
269
        """Reconcile this repository."""
 
 
270
        from bzrlib.reconcile import KnitReconciler
 
 
271
        reconciler = KnitReconciler(self, thorough=thorough)
 
 
272
        reconciler.reconcile()
 
 
275
    def revision_parents(self, revision_id):
 
 
276
        return self._get_revision_vf().get_parents(revision_id)
 
 
278
    def _make_parents_provider(self):
 
 
279
        return _KnitParentsProvider(self._get_revision_vf())
 
 
281
    def _find_inconsistent_revision_parents(self):
 
 
282
        """Find revisions with different parent lists in the revision object
 
 
283
        and in the index graph.
 
 
285
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
 
 
286
            parents-in-revision).
 
 
288
        assert self.is_locked()
 
 
289
        vf = self._get_revision_vf()
 
 
290
        for index_version in vf.versions():
 
 
291
            parents_according_to_index = tuple(vf.get_parents_with_ghosts(
 
 
293
            revision = self.get_revision(index_version)
 
 
294
            parents_according_to_revision = tuple(revision.parent_ids)
 
 
295
            if parents_according_to_index != parents_according_to_revision:
 
 
296
                yield (index_version, parents_according_to_index,
 
 
297
                    parents_according_to_revision)
 
 
299
    def _check_for_inconsistent_revision_parents(self):
 
 
300
        inconsistencies = list(self._find_inconsistent_revision_parents())
 
 
302
            raise errors.BzrCheckError(
 
 
303
                "Revision knit has inconsistent parents.")
 
 
305
    def revision_graph_can_have_wrong_parents(self):
 
 
306
        # The revision.kndx could potentially claim a revision has a different
 
 
307
        # parent to the revision text.
 
 
311
class RepositoryFormatKnit(MetaDirRepositoryFormat):
 
 
312
    """Bzr repository knit format (generalized). 
 
 
314
    This repository format has:
 
 
315
     - knits for file texts and inventory
 
 
316
     - hash subdirectory based stores.
 
 
317
     - knits for revisions and signatures
 
 
318
     - TextStores for revisions and signatures.
 
 
319
     - a format marker of its own
 
 
320
     - an optional 'shared-storage' flag
 
 
321
     - an optional 'no-working-trees' flag
 
 
325
    # Set this attribute in derived classes to control the repository class
 
 
326
    # created by open and initialize.
 
 
327
    repository_class = None
 
 
328
    # Set this attribute in derived classes to control the
 
 
329
    # _commit_builder_class that the repository objects will have passed to
 
 
331
    _commit_builder_class = None
 
 
332
    # Set this attribute in derived clases to control the _serializer that the
 
 
333
    # repository objects will have passed to their constructor.
 
 
334
    _serializer = xml5.serializer_v5
 
 
335
    # Knit based repositories handle ghosts reasonably well.
 
 
336
    supports_ghosts = True
 
 
338
    def _get_control_store(self, repo_transport, control_files):
 
 
339
        """Return the control store for this repository."""
 
 
340
        return VersionedFileStore(
 
 
343
            file_mode=control_files._file_mode,
 
 
344
            versionedfile_class=knit.KnitVersionedFile,
 
 
345
            versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
 
 
348
    def _get_revision_store(self, repo_transport, control_files):
 
 
349
        """See RepositoryFormat._get_revision_store()."""
 
 
350
        versioned_file_store = VersionedFileStore(
 
 
352
            file_mode=control_files._file_mode,
 
 
355
            versionedfile_class=knit.KnitVersionedFile,
 
 
356
            versionedfile_kwargs={'delta':False,
 
 
357
                                  'factory':knit.KnitPlainFactory(),
 
 
361
        return KnitRevisionStore(versioned_file_store)
 
 
363
    def _get_text_store(self, transport, control_files):
 
 
364
        """See RepositoryFormat._get_text_store()."""
 
 
365
        return self._get_versioned_file_store('knits',
 
 
368
                                  versionedfile_class=knit.KnitVersionedFile,
 
 
369
                                  versionedfile_kwargs={
 
 
370
                                      'create_parent_dir':True,
 
 
372
                                      'dir_mode':control_files._dir_mode,
 
 
376
    def initialize(self, a_bzrdir, shared=False):
 
 
377
        """Create a knit format 1 repository.
 
 
379
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
 
381
        :param shared: If true the repository will be initialized as a shared
 
 
384
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
 
387
        utf8_files = [('format', self.get_format_string())]
 
 
389
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
 
390
        repo_transport = a_bzrdir.get_repository_transport(None)
 
 
391
        control_files = lockable_files.LockableFiles(repo_transport,
 
 
392
                                'lock', lockdir.LockDir)
 
 
393
        control_store = self._get_control_store(repo_transport, control_files)
 
 
394
        transaction = transactions.WriteTransaction()
 
 
395
        # trigger a write of the inventory store.
 
 
396
        control_store.get_weave_or_empty('inventory', transaction)
 
 
397
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
 
398
        # the revision id here is irrelevant: it will not be stored, and cannot
 
 
400
        _revision_store.has_revision_id('A', transaction)
 
 
401
        _revision_store.get_signature_file(transaction)
 
 
402
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
 
404
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
 
405
        """See RepositoryFormat.open().
 
 
407
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
 
408
                                    repository at a slightly different url
 
 
409
                                    than normal. I.e. during 'upgrade'.
 
 
412
            format = RepositoryFormat.find_format(a_bzrdir)
 
 
413
            assert format.__class__ ==  self.__class__
 
 
414
        if _override_transport is not None:
 
 
415
            repo_transport = _override_transport
 
 
417
            repo_transport = a_bzrdir.get_repository_transport(None)
 
 
418
        control_files = lockable_files.LockableFiles(repo_transport,
 
 
419
                                'lock', lockdir.LockDir)
 
 
420
        text_store = self._get_text_store(repo_transport, control_files)
 
 
421
        control_store = self._get_control_store(repo_transport, control_files)
 
 
422
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
 
423
        return self.repository_class(_format=self,
 
 
425
                              control_files=control_files,
 
 
426
                              _revision_store=_revision_store,
 
 
427
                              control_store=control_store,
 
 
428
                              text_store=text_store,
 
 
429
                              _commit_builder_class=self._commit_builder_class,
 
 
430
                              _serializer=self._serializer)
 
 
433
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
 
434
    """Bzr repository knit format 1.
 
 
436
    This repository format has:
 
 
437
     - knits for file texts and inventory
 
 
438
     - hash subdirectory based stores.
 
 
439
     - knits for revisions and signatures
 
 
440
     - TextStores for revisions and signatures.
 
 
441
     - a format marker of its own
 
 
442
     - an optional 'shared-storage' flag
 
 
443
     - an optional 'no-working-trees' flag
 
 
446
    This format was introduced in bzr 0.8.
 
 
449
    repository_class = KnitRepository
 
 
450
    _commit_builder_class = CommitBuilder
 
 
451
    _serializer = xml5.serializer_v5
 
 
453
    def __ne__(self, other):
 
 
454
        return self.__class__ is not other.__class__
 
 
456
    def get_format_string(self):
 
 
457
        """See RepositoryFormat.get_format_string()."""
 
 
458
        return "Bazaar-NG Knit Repository Format 1"
 
 
460
    def get_format_description(self):
 
 
461
        """See RepositoryFormat.get_format_description()."""
 
 
462
        return "Knit repository format 1"
 
 
464
    def check_conversion_target(self, target_format):
 
 
468
class RepositoryFormatKnit3(RepositoryFormatKnit):
 
 
469
    """Bzr repository knit format 3.
 
 
471
    This repository format has:
 
 
472
     - knits for file texts and inventory
 
 
473
     - hash subdirectory based stores.
 
 
474
     - knits for revisions and signatures
 
 
475
     - TextStores for revisions and signatures.
 
 
476
     - a format marker of its own
 
 
477
     - an optional 'shared-storage' flag
 
 
478
     - an optional 'no-working-trees' flag
 
 
480
     - support for recording full info about the tree root
 
 
481
     - support for recording tree-references
 
 
484
    repository_class = KnitRepository
 
 
485
    _commit_builder_class = RootCommitBuilder
 
 
486
    rich_root_data = True
 
 
487
    supports_tree_reference = True
 
 
488
    _serializer = xml7.serializer_v7
 
 
490
    def _get_matching_bzrdir(self):
 
 
491
        return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
 
 
493
    def _ignore_setting_bzrdir(self, format):
 
 
496
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
 
498
    def check_conversion_target(self, target_format):
 
 
499
        if not target_format.rich_root_data:
 
 
500
            raise errors.BadConversionTarget(
 
 
501
                'Does not support rich root data.', target_format)
 
 
502
        if not getattr(target_format, 'supports_tree_reference', False):
 
 
503
            raise errors.BadConversionTarget(
 
 
504
                'Does not support nested trees', target_format)
 
 
506
    def get_format_string(self):
 
 
507
        """See RepositoryFormat.get_format_string()."""
 
 
508
        return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
 
 
510
    def get_format_description(self):
 
 
511
        """See RepositoryFormat.get_format_description()."""
 
 
512
        return "Knit repository format 3"
 
 
515
class RepositoryFormatKnit4(RepositoryFormatKnit):
 
 
516
    """Bzr repository knit format 4.
 
 
518
    This repository format has everything in format 3, except for
 
 
520
     - knits for file texts and inventory
 
 
521
     - hash subdirectory based stores.
 
 
522
     - knits for revisions and signatures
 
 
523
     - TextStores for revisions and signatures.
 
 
524
     - a format marker of its own
 
 
525
     - an optional 'shared-storage' flag
 
 
526
     - an optional 'no-working-trees' flag
 
 
528
     - support for recording full info about the tree root
 
 
531
    repository_class = KnitRepository
 
 
532
    _commit_builder_class = RootCommitBuilder
 
 
533
    rich_root_data = True
 
 
534
    supports_tree_reference = False
 
 
535
    _serializer = xml6.serializer_v6
 
 
537
    def _get_matching_bzrdir(self):
 
 
538
        return bzrdir.format_registry.make_bzrdir('rich-root')
 
 
540
    def _ignore_setting_bzrdir(self, format):
 
 
543
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
 
545
    def check_conversion_target(self, target_format):
 
 
546
        if not target_format.rich_root_data:
 
 
547
            raise errors.BadConversionTarget(
 
 
548
                'Does not support rich root data.', target_format)
 
 
550
    def get_format_string(self):
 
 
551
        """See RepositoryFormat.get_format_string()."""
 
 
552
        return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
 
 
554
    def get_format_description(self):
 
 
555
        """See RepositoryFormat.get_format_description()."""
 
 
556
        return "Knit repository format 4"
 
 
559
def _get_stream_as_bytes(knit, required_versions):
 
 
560
    """Generate a serialised data stream.
 
 
562
    The format is a bencoding of a list.  The first element of the list is a
 
 
563
    string of the format signature, then each subsequent element is a list
 
 
564
    corresponding to a record.  Those lists contain:
 
 
571
    :returns: a bencoded list.
 
 
573
    knit_stream = knit.get_data_stream(required_versions)
 
 
574
    format_signature, data_list, callable = knit_stream
 
 
576
    data.append(format_signature)
 
 
577
    for version, options, length, parents in data_list:
 
 
578
        data.append([version, options, parents, callable(length)])
 
 
579
    return bencode.bencode(data)