/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 bzrlib/repofmt/knitrepo.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-22 14:12:22 UTC
  • mfrom: (5809 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110422141222-nx2j0hbkihcb8j16
Merge newer bzr.dev and resolve conflicts.
Try to write some documentation about how the _dirblock_state works.
Fix up the tests so that they pass again.

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