/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/bzr/knitrepo.py

  • Committer: Jelmer Vernooij
  • Date: 2017-08-07 11:49:46 UTC
  • mto: (6747.3.4 avoid-set-revid-3)
  • mto: This revision was merged to the branch mainline in revision 6750.
  • Revision ID: jelmer@jelmer.uk-20170807114946-luclmxuawyzhpiot
Avoid setting revision_ids.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007-2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from __future__ import absolute_import
 
18
 
 
19
from ..lazy_import import lazy_import
 
20
lazy_import(globals(), """
 
21
import itertools
 
22
 
 
23
from breezy import (
 
24
    controldir,
 
25
    errors,
 
26
    lockable_files,
 
27
    lockdir,
 
28
    osutils,
 
29
    revision as _mod_revision,
 
30
    trace,
 
31
    transactions,
 
32
    )
 
33
from breezy.bzr import (
 
34
    knit as _mod_knit,
 
35
    versionedfile,
 
36
    xml5,
 
37
    xml6,
 
38
    xml7,
 
39
    )
 
40
""")
 
41
from ..decorators import needs_read_lock, needs_write_lock
 
42
from ..repository import (
 
43
    InterRepository,
 
44
    IsInWriteGroupError,
 
45
    )
 
46
from ..bzr.repository import (
 
47
    RepositoryFormatMetaDir,
 
48
    )
 
49
from ..bzr.vf_repository import (
 
50
    InterSameDataRepository,
 
51
    MetaDirVersionedFileRepository,
 
52
    MetaDirVersionedFileRepositoryFormat,
 
53
    VersionedFileCommitBuilder,
 
54
    VersionedFileRootCommitBuilder,
 
55
    )
 
56
 
 
57
 
 
58
class _KnitParentsProvider(object):
 
59
 
 
60
    def __init__(self, knit):
 
61
        self._knit = knit
 
62
 
 
63
    def __repr__(self):
 
64
        return 'KnitParentsProvider(%r)' % self._knit
 
65
 
 
66
    def get_parent_map(self, keys):
 
67
        """See graph.StackedParentsProvider.get_parent_map"""
 
68
        parent_map = {}
 
69
        for revision_id in keys:
 
70
            if revision_id is None:
 
71
                raise ValueError('get_parent_map(None) is not valid')
 
72
            if revision_id == _mod_revision.NULL_REVISION:
 
73
                parent_map[revision_id] = ()
 
74
            else:
 
75
                try:
 
76
                    parents = tuple(
 
77
                        self._knit.get_parents_with_ghosts(revision_id))
 
78
                except errors.RevisionNotPresent:
 
79
                    continue
 
80
                else:
 
81
                    if len(parents) == 0:
 
82
                        parents = (_mod_revision.NULL_REVISION,)
 
83
                parent_map[revision_id] = parents
 
84
        return parent_map
 
85
 
 
86
 
 
87
class _KnitsParentsProvider(object):
 
88
 
 
89
    def __init__(self, knit, prefix=()):
 
90
        """Create a parent provider for string keys mapped to tuple keys."""
 
91
        self._knit = knit
 
92
        self._prefix = prefix
 
93
 
 
94
    def __repr__(self):
 
95
        return 'KnitsParentsProvider(%r)' % self._knit
 
96
 
 
97
    def get_parent_map(self, keys):
 
98
        """See graph.StackedParentsProvider.get_parent_map"""
 
99
        parent_map = self._knit.get_parent_map(
 
100
            [self._prefix + (key,) for key in keys])
 
101
        result = {}
 
102
        for key, parents in parent_map.items():
 
103
            revid = key[-1]
 
104
            if len(parents) == 0:
 
105
                parents = (_mod_revision.NULL_REVISION,)
 
106
            else:
 
107
                parents = tuple(parent[-1] for parent in parents)
 
108
            result[revid] = parents
 
109
        for revision_id in keys:
 
110
            if revision_id == _mod_revision.NULL_REVISION:
 
111
                result[revision_id] = ()
 
112
        return result
 
113
 
 
114
 
 
115
class KnitRepository(MetaDirVersionedFileRepository):
 
116
    """Knit format repository."""
 
117
 
 
118
    # These attributes are inherited from the Repository base class. Setting
 
119
    # them to None ensures that if the constructor is changed to not initialize
 
120
    # them, or a subclass fails to call the constructor, that an error will
 
121
    # occur rather than the system working but generating incorrect data.
 
122
    _commit_builder_class = None
 
123
    _serializer = None
 
124
 
 
125
    def __init__(self, _format, a_controldir, control_files, _commit_builder_class,
 
126
        _serializer):
 
127
        super(KnitRepository, self).__init__(_format, a_controldir, control_files)
 
128
        self._commit_builder_class = _commit_builder_class
 
129
        self._serializer = _serializer
 
130
        self._reconcile_fixes_text_parents = True
 
131
 
 
132
    @needs_read_lock
 
133
    def _all_revision_ids(self):
 
134
        """See Repository.all_revision_ids()."""
 
135
        return [key[0] for key in self.revisions.keys()]
 
136
 
 
137
    def _activate_new_inventory(self):
 
138
        """Put a replacement inventory.new into use as inventories."""
 
139
        # Copy the content across
 
140
        t = self._transport
 
141
        t.copy('inventory.new.kndx', 'inventory.kndx')
 
142
        try:
 
143
            t.copy('inventory.new.knit', 'inventory.knit')
 
144
        except errors.NoSuchFile:
 
145
            # empty inventories knit
 
146
            t.delete('inventory.knit')
 
147
        # delete the temp inventory
 
148
        t.delete('inventory.new.kndx')
 
149
        try:
 
150
            t.delete('inventory.new.knit')
 
151
        except errors.NoSuchFile:
 
152
            # empty inventories knit
 
153
            pass
 
154
        # Force index reload (sanity check)
 
155
        self.inventories._index._reset_cache()
 
156
        self.inventories.keys()
 
157
 
 
158
    def _backup_inventory(self):
 
159
        t = self._transport
 
160
        t.copy('inventory.kndx', 'inventory.backup.kndx')
 
161
        t.copy('inventory.knit', 'inventory.backup.knit')
 
162
 
 
163
    def _move_file_id(self, from_id, to_id):
 
164
        t = self._transport.clone('knits')
 
165
        from_rel_url = self.texts._index._mapper.map((from_id, None))
 
166
        to_rel_url = self.texts._index._mapper.map((to_id, None))
 
167
        # We expect both files to always exist in this case.
 
168
        for suffix in ('.knit', '.kndx'):
 
169
            t.rename(from_rel_url + suffix, to_rel_url + suffix)
 
170
 
 
171
    def _remove_file_id(self, file_id):
 
172
        t = self._transport.clone('knits')
 
173
        rel_url = self.texts._index._mapper.map((file_id, None))
 
174
        for suffix in ('.kndx', '.knit'):
 
175
            try:
 
176
                t.delete(rel_url + suffix)
 
177
            except errors.NoSuchFile:
 
178
                pass
 
179
 
 
180
    def _temp_inventories(self):
 
181
        result = self._format._get_inventories(self._transport, self,
 
182
            'inventory.new')
 
183
        # Reconciling when the output has no revisions would result in no
 
184
        # writes - but we want to ensure there is an inventory for
 
185
        # compatibility with older clients that don't lazy-load.
 
186
        result.get_parent_map([('A',)])
 
187
        return result
 
188
 
 
189
    @needs_read_lock
 
190
    def get_revision(self, revision_id):
 
191
        """Return the Revision object for a named revision"""
 
192
        revision_id = osutils.safe_revision_id(revision_id)
 
193
        return self.get_revision_reconcile(revision_id)
 
194
 
 
195
    def _refresh_data(self):
 
196
        if not self.is_locked():
 
197
            return
 
198
        if self.is_in_write_group():
 
199
            raise IsInWriteGroupError(self)
 
200
        # Create a new transaction to force all knits to see the scope change.
 
201
        # This is safe because we're outside a write group.
 
202
        self.control_files._finish_transaction()
 
203
        if self.is_write_locked():
 
204
            self.control_files._set_write_transaction()
 
205
        else:
 
206
            self.control_files._set_read_transaction()
 
207
 
 
208
    @needs_write_lock
 
209
    def reconcile(self, other=None, thorough=False):
 
210
        """Reconcile this repository."""
 
211
        from breezy.reconcile import KnitReconciler
 
212
        reconciler = KnitReconciler(self, thorough=thorough)
 
213
        reconciler.reconcile()
 
214
        return reconciler
 
215
 
 
216
    def _make_parents_provider(self):
 
217
        return _KnitsParentsProvider(self.revisions)
 
218
 
 
219
 
 
220
class RepositoryFormatKnit(MetaDirVersionedFileRepositoryFormat):
 
221
    """Bzr repository knit format (generalized).
 
222
 
 
223
    This repository format has:
 
224
     - knits for file texts and inventory
 
225
     - hash subdirectory based stores.
 
226
     - knits for revisions and signatures
 
227
     - TextStores for revisions and signatures.
 
228
     - a format marker of its own
 
229
     - an optional 'shared-storage' flag
 
230
     - an optional 'no-working-trees' flag
 
231
     - a LockDir lock
 
232
    """
 
233
 
 
234
    # Set this attribute in derived classes to control the repository class
 
235
    # created by open and initialize.
 
236
    repository_class = None
 
237
    # Set this attribute in derived classes to control the
 
238
    # _commit_builder_class that the repository objects will have passed to
 
239
    # their constructor.
 
240
    _commit_builder_class = None
 
241
    # Set this attribute in derived clases to control the _serializer that the
 
242
    # repository objects will have passed to their constructor.
 
243
    @property
 
244
    def _serializer(self):
 
245
        return xml5.serializer_v5
 
246
    # Knit based repositories handle ghosts reasonably well.
 
247
    supports_ghosts = True
 
248
    # External lookups are not supported in this format.
 
249
    supports_external_lookups = False
 
250
    # No CHK support.
 
251
    supports_chks = False
 
252
    _fetch_order = 'topological'
 
253
    _fetch_uses_deltas = True
 
254
    fast_deltas = False
 
255
    supports_funky_characters = True
 
256
    # The revision.kndx could potentially claim a revision has a different
 
257
    # parent to the revision text.
 
258
    revision_graph_can_have_wrong_parents = True
 
259
 
 
260
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
261
        mapper = versionedfile.ConstantMapper(name)
 
262
        index = _mod_knit._KndxIndex(repo_transport, mapper,
 
263
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
 
264
        access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
 
265
        return _mod_knit.KnitVersionedFiles(index, access, annotated=False)
 
266
 
 
267
    def _get_revisions(self, repo_transport, repo):
 
268
        mapper = versionedfile.ConstantMapper('revisions')
 
269
        index = _mod_knit._KndxIndex(repo_transport, mapper,
 
270
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
 
271
        access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
 
272
        return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
 
273
            annotated=False)
 
274
 
 
275
    def _get_signatures(self, repo_transport, repo):
 
276
        mapper = versionedfile.ConstantMapper('signatures')
 
277
        index = _mod_knit._KndxIndex(repo_transport, mapper,
 
278
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
 
279
        access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
 
280
        return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
 
281
            annotated=False)
 
282
 
 
283
    def _get_texts(self, repo_transport, repo):
 
284
        mapper = versionedfile.HashEscapedPrefixMapper()
 
285
        base_transport = repo_transport.clone('knits')
 
286
        index = _mod_knit._KndxIndex(base_transport, mapper,
 
287
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
 
288
        access = _mod_knit._KnitKeyAccess(base_transport, mapper)
 
289
        return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=200,
 
290
            annotated=True)
 
291
 
 
292
    def initialize(self, a_controldir, shared=False):
 
293
        """Create a knit format 1 repository.
 
294
 
 
295
        :param a_controldir: bzrdir to contain the new repository; must already
 
296
            be initialized.
 
297
        :param shared: If true the repository will be initialized as a shared
 
298
                       repository.
 
299
        """
 
300
        trace.mutter('creating repository in %s.', a_controldir.transport.base)
 
301
        dirs = ['knits']
 
302
        files = []
 
303
        utf8_files = [('format', self.get_format_string())]
 
304
 
 
305
        self._upload_blank_content(a_controldir, dirs, files, utf8_files, shared)
 
306
        repo_transport = a_controldir.get_repository_transport(None)
 
307
        control_files = lockable_files.LockableFiles(repo_transport,
 
308
                                'lock', lockdir.LockDir)
 
309
        transaction = transactions.WriteTransaction()
 
310
        result = self.open(a_controldir=a_controldir, _found=True)
 
311
        result.lock_write()
 
312
        # the revision id here is irrelevant: it will not be stored, and cannot
 
313
        # already exist, we do this to create files on disk for older clients.
 
314
        result.inventories.get_parent_map([('A',)])
 
315
        result.revisions.get_parent_map([('A',)])
 
316
        result.signatures.get_parent_map([('A',)])
 
317
        result.unlock()
 
318
        self._run_post_repo_init_hooks(result, a_controldir, shared)
 
319
        return result
 
320
 
 
321
    def open(self, a_controldir, _found=False, _override_transport=None):
 
322
        """See RepositoryFormat.open().
 
323
 
 
324
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
325
                                    repository at a slightly different url
 
326
                                    than normal. I.e. during 'upgrade'.
 
327
        """
 
328
        if not _found:
 
329
            format = RepositoryFormatMetaDir.find_format(a_controldir)
 
330
        if _override_transport is not None:
 
331
            repo_transport = _override_transport
 
332
        else:
 
333
            repo_transport = a_controldir.get_repository_transport(None)
 
334
        control_files = lockable_files.LockableFiles(repo_transport,
 
335
                                'lock', lockdir.LockDir)
 
336
        repo = self.repository_class(_format=self,
 
337
                              a_controldir=a_controldir,
 
338
                              control_files=control_files,
 
339
                              _commit_builder_class=self._commit_builder_class,
 
340
                              _serializer=self._serializer)
 
341
        repo.revisions = self._get_revisions(repo_transport, repo)
 
342
        repo.signatures = self._get_signatures(repo_transport, repo)
 
343
        repo.inventories = self._get_inventories(repo_transport, repo)
 
344
        repo.texts = self._get_texts(repo_transport, repo)
 
345
        repo.chk_bytes = None
 
346
        repo._transport = repo_transport
 
347
        return repo
 
348
 
 
349
 
 
350
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
351
    """Bzr repository knit format 1.
 
352
 
 
353
    This repository format has:
 
354
     - knits for file texts and inventory
 
355
     - hash subdirectory based stores.
 
356
     - knits for revisions and signatures
 
357
     - TextStores for revisions and signatures.
 
358
     - a format marker of its own
 
359
     - an optional 'shared-storage' flag
 
360
     - an optional 'no-working-trees' flag
 
361
     - a LockDir lock
 
362
 
 
363
    This format was introduced in bzr 0.8.
 
364
    """
 
365
 
 
366
    repository_class = KnitRepository
 
367
    _commit_builder_class = VersionedFileCommitBuilder
 
368
    @property
 
369
    def _serializer(self):
 
370
        return xml5.serializer_v5
 
371
 
 
372
    def __ne__(self, other):
 
373
        return self.__class__ is not other.__class__
 
374
 
 
375
    @classmethod
 
376
    def get_format_string(cls):
 
377
        """See RepositoryFormat.get_format_string()."""
 
378
        return "Bazaar-NG Knit Repository Format 1"
 
379
 
 
380
    def get_format_description(self):
 
381
        """See RepositoryFormat.get_format_description()."""
 
382
        return "Knit repository format 1"
 
383
 
 
384
 
 
385
class RepositoryFormatKnit3(RepositoryFormatKnit):
 
386
    """Bzr repository knit format 3.
 
387
 
 
388
    This repository format has:
 
389
     - knits for file texts and inventory
 
390
     - hash subdirectory based stores.
 
391
     - knits for revisions and signatures
 
392
     - TextStores for revisions and signatures.
 
393
     - a format marker of its own
 
394
     - an optional 'shared-storage' flag
 
395
     - an optional 'no-working-trees' flag
 
396
     - a LockDir lock
 
397
     - support for recording full info about the tree root
 
398
     - support for recording tree-references
 
399
    """
 
400
 
 
401
    repository_class = KnitRepository
 
402
    _commit_builder_class = VersionedFileRootCommitBuilder
 
403
    rich_root_data = True
 
404
    experimental = True
 
405
    supports_tree_reference = True
 
406
    @property
 
407
    def _serializer(self):
 
408
        return xml7.serializer_v7
 
409
 
 
410
    def _get_matching_bzrdir(self):
 
411
        return controldir.format_registry.make_controldir('dirstate-with-subtree')
 
412
 
 
413
    def _ignore_setting_bzrdir(self, format):
 
414
        pass
 
415
 
 
416
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
417
 
 
418
    @classmethod
 
419
    def get_format_string(cls):
 
420
        """See RepositoryFormat.get_format_string()."""
 
421
        return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
 
422
 
 
423
    def get_format_description(self):
 
424
        """See RepositoryFormat.get_format_description()."""
 
425
        return "Knit repository format 3"
 
426
 
 
427
 
 
428
class RepositoryFormatKnit4(RepositoryFormatKnit):
 
429
    """Bzr repository knit format 4.
 
430
 
 
431
    This repository format has everything in format 3, except for
 
432
    tree-references:
 
433
     - knits for file texts and inventory
 
434
     - hash subdirectory based stores.
 
435
     - knits for revisions and signatures
 
436
     - TextStores for revisions and signatures.
 
437
     - a format marker of its own
 
438
     - an optional 'shared-storage' flag
 
439
     - an optional 'no-working-trees' flag
 
440
     - a LockDir lock
 
441
     - support for recording full info about the tree root
 
442
    """
 
443
 
 
444
    repository_class = KnitRepository
 
445
    _commit_builder_class = VersionedFileRootCommitBuilder
 
446
    rich_root_data = True
 
447
    supports_tree_reference = False
 
448
    @property
 
449
    def _serializer(self):
 
450
        return xml6.serializer_v6
 
451
 
 
452
    def _get_matching_bzrdir(self):
 
453
        return controldir.format_registry.make_controldir('rich-root')
 
454
 
 
455
    def _ignore_setting_bzrdir(self, format):
 
456
        pass
 
457
 
 
458
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
459
 
 
460
    @classmethod
 
461
    def get_format_string(cls):
 
462
        """See RepositoryFormat.get_format_string()."""
 
463
        return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
 
464
 
 
465
    def get_format_description(self):
 
466
        """See RepositoryFormat.get_format_description()."""
 
467
        return "Knit repository format 4"
 
468
 
 
469
 
 
470
class InterKnitRepo(InterSameDataRepository):
 
471
    """Optimised code paths between Knit based repositories."""
 
472
 
 
473
    @classmethod
 
474
    def _get_repo_format_to_test(self):
 
475
        return RepositoryFormatKnit1()
 
476
 
 
477
    @staticmethod
 
478
    def is_compatible(source, target):
 
479
        """Be compatible with known Knit formats.
 
480
 
 
481
        We don't test for the stores being of specific types because that
 
482
        could lead to confusing results, and there is no need to be
 
483
        overly general.
 
484
        """
 
485
        try:
 
486
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
487
                isinstance(target._format, RepositoryFormatKnit))
 
488
        except AttributeError:
 
489
            return False
 
490
        return are_knits and InterRepository._same_model(source, target)
 
491
 
 
492
    @needs_read_lock
 
493
    def search_missing_revision_ids(self,
 
494
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
495
            limit=None):
 
496
        """See InterRepository.search_missing_revision_ids()."""
 
497
        source_ids_set = self._present_source_revisions_for(
 
498
            revision_ids, if_present_ids)
 
499
        # source_ids is the worst possible case we may need to pull.
 
500
        # now we want to filter source_ids against what we actually
 
501
        # have in target, but don't try to check for existence where we know
 
502
        # we do not have a revision as that would be pointless.
 
503
        target_ids = set(self.target.all_revision_ids())
 
504
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
505
        actually_present_revisions = set(
 
506
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
507
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
508
        if revision_ids is not None:
 
509
            # we used get_ancestry to determine source_ids then we are assured all
 
510
            # revisions referenced are present as they are installed in topological order.
 
511
            # and the tip revision was validated by get_ancestry.
 
512
            result_set = required_revisions
 
513
        else:
 
514
            # if we just grabbed the possibly available ids, then
 
515
            # we only have an estimate of whats available and need to validate
 
516
            # that against the revision records.
 
517
            result_set = set(
 
518
                self.source._eliminate_revisions_not_present(required_revisions))
 
519
        if limit is not None:
 
520
            topo_ordered = self.source.get_graph().iter_topo_order(result_set)
 
521
            result_set = set(itertools.islice(topo_ordered, limit))
 
522
        return self.source.revision_ids_to_search_result(result_set)
 
523
 
 
524
 
 
525
InterRepository.register_optimiser(InterKnitRepo)