/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: 2018-05-07 15:27:39 UTC
  • mto: This revision was merged to the branch mainline in revision 6958.
  • Revision ID: jelmer@jelmer.uk-20180507152739-fuv9z9r0yzi7ln3t
Specify source in .coveragerc.

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