~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitrepo.py

merge merge tweaks from aaron, which includes latest .dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
from bzrlib.lazy_import import lazy_import
18
 
lazy_import(globals(), """
19
 
from bzrlib import (
20
 
    bzrdir,
21
 
    errors,
22
 
    knit as _mod_knit,
23
 
    lockable_files,
24
 
    lockdir,
25
 
    osutils,
26
 
    revision as _mod_revision,
27
 
    transactions,
28
 
    versionedfile,
29
 
    xml5,
30
 
    xml6,
31
 
    xml7,
32
 
    )
33
 
""")
34
 
from bzrlib import (
35
 
    symbol_versioning,
36
 
    )
37
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
 
from bzrlib.repository import (
39
 
    CommitBuilder,
40
 
    MetaDirRepository,
41
 
    MetaDirRepositoryFormat,
42
 
    RepositoryFormat,
43
 
    RootCommitBuilder,
44
 
    )
45
 
from bzrlib.trace import mutter, mutter_callsite
46
 
 
47
 
 
48
 
class _KnitParentsProvider(object):
49
 
 
50
 
    def __init__(self, knit):
51
 
        self._knit = knit
52
 
 
53
 
    def __repr__(self):
54
 
        return 'KnitParentsProvider(%r)' % self._knit
55
 
 
56
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
57
 
    def get_parents(self, revision_ids):
58
 
        """See graph._StackedParentsProvider.get_parents"""
59
 
        parent_map = self.get_parent_map(revision_ids)
60
 
        return [parent_map.get(r, None) for r in revision_ids]
61
 
 
62
 
    def get_parent_map(self, keys):
63
 
        """See graph._StackedParentsProvider.get_parent_map"""
64
 
        parent_map = {}
65
 
        for revision_id in keys:
66
 
            if revision_id is None:
67
 
                raise ValueError('get_parent_map(None) is not valid')
68
 
            if revision_id == _mod_revision.NULL_REVISION:
69
 
                parent_map[revision_id] = ()
70
 
            else:
71
 
                try:
72
 
                    parents = tuple(
73
 
                        self._knit.get_parents_with_ghosts(revision_id))
74
 
                except errors.RevisionNotPresent:
75
 
                    continue
76
 
                else:
77
 
                    if len(parents) == 0:
78
 
                        parents = (_mod_revision.NULL_REVISION,)
79
 
                parent_map[revision_id] = parents
80
 
        return parent_map
81
 
 
82
 
 
83
 
class _KnitsParentsProvider(object):
84
 
 
85
 
    def __init__(self, knit, prefix=()):
86
 
        """Create a parent provider for string keys mapped to tuple keys."""
87
 
        self._knit = knit
88
 
        self._prefix = prefix
89
 
 
90
 
    def __repr__(self):
91
 
        return 'KnitsParentsProvider(%r)' % self._knit
92
 
 
93
 
    def get_parent_map(self, keys):
94
 
        """See graph._StackedParentsProvider.get_parent_map"""
95
 
        parent_map = self._knit.get_parent_map(
96
 
            [self._prefix + (key,) for key in keys])
97
 
        result = {}
98
 
        for key, parents in parent_map.items():
99
 
            revid = key[-1]
100
 
            if len(parents) == 0:
101
 
                parents = (_mod_revision.NULL_REVISION,)
102
 
            else:
103
 
                parents = tuple(parent[-1] for parent in parents)
104
 
            result[revid] = parents
105
 
        for revision_id in keys:
106
 
            if revision_id == _mod_revision.NULL_REVISION:
107
 
                result[revision_id] = ()
108
 
        return result
109
 
 
110
 
 
111
 
class KnitRepository(MetaDirRepository):
112
 
    """Knit format repository."""
113
 
 
114
 
    # These attributes are inherited from the Repository base class. Setting
115
 
    # them to None ensures that if the constructor is changed to not initialize
116
 
    # them, or a subclass fails to call the constructor, that an error will
117
 
    # occur rather than the system working but generating incorrect data.
118
 
    _commit_builder_class = None
119
 
    _serializer = None
120
 
 
121
 
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
122
 
        _serializer):
123
 
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
124
 
        self._commit_builder_class = _commit_builder_class
125
 
        self._serializer = _serializer
126
 
        self._reconcile_fixes_text_parents = True
127
 
        self._fetch_uses_deltas = True
128
 
        self._fetch_order = 'topological'
129
 
 
130
 
    @needs_read_lock
131
 
    def _all_revision_ids(self):
132
 
        """See Repository.all_revision_ids()."""
133
 
        return [key[0] for key in self.revisions.keys()]
134
 
 
135
 
    def _activate_new_inventory(self):
136
 
        """Put a replacement inventory.new into use as inventories."""
137
 
        # Copy the content across
138
 
        t = self._transport
139
 
        t.copy('inventory.new.kndx', 'inventory.kndx')
140
 
        try:
141
 
            t.copy('inventory.new.knit', 'inventory.knit')
142
 
        except errors.NoSuchFile:
143
 
            # empty inventories knit
144
 
            t.delete('inventory.knit')
145
 
        # delete the temp inventory
146
 
        t.delete('inventory.new.kndx')
147
 
        try:
148
 
            t.delete('inventory.new.knit')
149
 
        except errors.NoSuchFile:
150
 
            # empty inventories knit
151
 
            pass
152
 
        # Force index reload (sanity check)
153
 
        self.inventories._index._reset_cache()
154
 
        self.inventories.keys()
155
 
 
156
 
    def _backup_inventory(self):
157
 
        t = self._transport
158
 
        t.copy('inventory.kndx', 'inventory.backup.kndx')
159
 
        t.copy('inventory.knit', 'inventory.backup.knit')
160
 
 
161
 
    def _move_file_id(self, from_id, to_id):
162
 
        t = self._transport.clone('knits')
163
 
        from_rel_url = self.texts._index._mapper.map((from_id, None))
164
 
        to_rel_url = self.texts._index._mapper.map((to_id, None))
165
 
        # We expect both files to always exist in this case.
166
 
        for suffix in ('.knit', '.kndx'):
167
 
            t.rename(from_rel_url + suffix, to_rel_url + suffix)
168
 
 
169
 
    def _remove_file_id(self, file_id):
170
 
        t = self._transport.clone('knits')
171
 
        rel_url = self.texts._index._mapper.map((file_id, None))
172
 
        for suffix in ('.kndx', '.knit'):
173
 
            try:
174
 
                t.delete(rel_url + suffix)
175
 
            except errors.NoSuchFile:
176
 
                pass
177
 
 
178
 
    def _temp_inventories(self):
179
 
        result = self._format._get_inventories(self._transport, self,
180
 
            'inventory.new')
181
 
        # Reconciling when the output has no revisions would result in no
182
 
        # writes - but we want to ensure there is an inventory for
183
 
        # compatibility with older clients that don't lazy-load.
184
 
        result.get_parent_map([('A',)])
185
 
        return result
186
 
 
187
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
188
 
        """Find file_id(s) which are involved in the changes between revisions.
189
 
 
190
 
        This determines the set of revisions which are involved, and then
191
 
        finds all file ids affected by those revisions.
192
 
        """
193
 
        vf = self._get_revision_vf()
194
 
        from_set = set(vf.get_ancestry(from_revid))
195
 
        to_set = set(vf.get_ancestry(to_revid))
196
 
        changed = to_set.difference(from_set)
197
 
        return self._fileid_involved_by_set(changed)
198
 
 
199
 
    def fileid_involved(self, last_revid=None):
200
 
        """Find all file_ids modified in the ancestry of last_revid.
201
 
 
202
 
        :param last_revid: If None, last_revision() will be used.
203
 
        """
204
 
        if not last_revid:
205
 
            changed = set(self.all_revision_ids())
206
 
        else:
207
 
            changed = set(self.get_ancestry(last_revid))
208
 
        if None in changed:
209
 
            changed.remove(None)
210
 
        return self._fileid_involved_by_set(changed)
211
 
 
212
 
    @needs_read_lock
213
 
    def get_revision(self, revision_id):
214
 
        """Return the Revision object for a named revision"""
215
 
        revision_id = osutils.safe_revision_id(revision_id)
216
 
        return self.get_revision_reconcile(revision_id)
217
 
 
218
 
    @needs_write_lock
219
 
    def reconcile(self, other=None, thorough=False):
220
 
        """Reconcile this repository."""
221
 
        from bzrlib.reconcile import KnitReconciler
222
 
        reconciler = KnitReconciler(self, thorough=thorough)
223
 
        reconciler.reconcile()
224
 
        return reconciler
225
 
    
226
 
    def _make_parents_provider(self):
227
 
        return _KnitsParentsProvider(self.revisions)
228
 
 
229
 
    def _find_inconsistent_revision_parents(self):
230
 
        """Find revisions with different parent lists in the revision object
231
 
        and in the index graph.
232
 
 
233
 
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
234
 
            parents-in-revision).
235
 
        """
236
 
        if not self.is_locked():
237
 
            raise AssertionError()
238
 
        vf = self.revisions
239
 
        for index_version in vf.keys():
240
 
            parent_map = vf.get_parent_map([index_version])
241
 
            parents_according_to_index = tuple(parent[-1] for parent in
242
 
                parent_map[index_version])
243
 
            revision = self.get_revision(index_version[-1])
244
 
            parents_according_to_revision = tuple(revision.parent_ids)
245
 
            if parents_according_to_index != parents_according_to_revision:
246
 
                yield (index_version[-1], parents_according_to_index,
247
 
                    parents_according_to_revision)
248
 
 
249
 
    def _check_for_inconsistent_revision_parents(self):
250
 
        inconsistencies = list(self._find_inconsistent_revision_parents())
251
 
        if inconsistencies:
252
 
            raise errors.BzrCheckError(
253
 
                "Revision knit has inconsistent parents.")
254
 
 
255
 
    def revision_graph_can_have_wrong_parents(self):
256
 
        # The revision.kndx could potentially claim a revision has a different
257
 
        # parent to the revision text.
258
 
        return True
259
 
 
260
 
 
261
 
class RepositoryFormatKnit(MetaDirRepositoryFormat):
262
 
    """Bzr repository knit format (generalized). 
263
 
 
264
 
    This repository format has:
265
 
     - knits for file texts and inventory
266
 
     - hash subdirectory based stores.
267
 
     - knits for revisions and signatures
268
 
     - TextStores for revisions and signatures.
269
 
     - a format marker of its own
270
 
     - an optional 'shared-storage' flag
271
 
     - an optional 'no-working-trees' flag
272
 
     - a LockDir lock
273
 
    """
274
 
 
275
 
    # Set this attribute in derived classes to control the repository class
276
 
    # created by open and initialize.
277
 
    repository_class = None
278
 
    # Set this attribute in derived classes to control the
279
 
    # _commit_builder_class that the repository objects will have passed to
280
 
    # their constructor.
281
 
    _commit_builder_class = None
282
 
    # Set this attribute in derived clases to control the _serializer that the
283
 
    # repository objects will have passed to their constructor.
284
 
    @property
285
 
    def _serializer(self):
286
 
        return xml5.serializer_v5
287
 
    # Knit based repositories handle ghosts reasonably well.
288
 
    supports_ghosts = True
289
 
    # External lookups are not supported in this format.
290
 
    supports_external_lookups = False
291
 
 
292
 
    def _get_inventories(self, repo_transport, repo, name='inventory'):
293
 
        mapper = versionedfile.ConstantMapper(name)
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, annotated=False)
298
 
 
299
 
    def _get_revisions(self, repo_transport, repo):
300
 
        mapper = versionedfile.ConstantMapper('revisions')
301
 
        index = _mod_knit._KndxIndex(repo_transport, mapper,
302
 
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
303
 
        access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
304
 
        return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
305
 
            annotated=False)
306
 
 
307
 
    def _get_signatures(self, repo_transport, repo):
308
 
        mapper = versionedfile.ConstantMapper('signatures')
309
 
        index = _mod_knit._KndxIndex(repo_transport, mapper,
310
 
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
311
 
        access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
312
 
        return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
313
 
            annotated=False)
314
 
 
315
 
    def _get_texts(self, repo_transport, repo):
316
 
        mapper = versionedfile.HashEscapedPrefixMapper()
317
 
        base_transport = repo_transport.clone('knits')
318
 
        index = _mod_knit._KndxIndex(base_transport, mapper,
319
 
            repo.get_transaction, repo.is_write_locked, repo.is_locked)
320
 
        access = _mod_knit._KnitKeyAccess(base_transport, mapper)
321
 
        return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=200,
322
 
            annotated=True)
323
 
 
324
 
    def initialize(self, a_bzrdir, shared=False):
325
 
        """Create a knit format 1 repository.
326
 
 
327
 
        :param a_bzrdir: bzrdir to contain the new repository; must already
328
 
            be initialized.
329
 
        :param shared: If true the repository will be initialized as a shared
330
 
                       repository.
331
 
        """
332
 
        mutter('creating repository in %s.', a_bzrdir.transport.base)
333
 
        dirs = ['knits']
334
 
        files = []
335
 
        utf8_files = [('format', self.get_format_string())]
336
 
        
337
 
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
338
 
        repo_transport = a_bzrdir.get_repository_transport(None)
339
 
        control_files = lockable_files.LockableFiles(repo_transport,
340
 
                                'lock', lockdir.LockDir)
341
 
        transaction = transactions.WriteTransaction()
342
 
        result = self.open(a_bzrdir=a_bzrdir, _found=True)
343
 
        result.lock_write()
344
 
        # the revision id here is irrelevant: it will not be stored, and cannot
345
 
        # already exist, we do this to create files on disk for older clients.
346
 
        result.inventories.get_parent_map([('A',)])
347
 
        result.revisions.get_parent_map([('A',)])
348
 
        result.signatures.get_parent_map([('A',)])
349
 
        result.unlock()
350
 
        return result
351
 
 
352
 
    def open(self, a_bzrdir, _found=False, _override_transport=None):
353
 
        """See RepositoryFormat.open().
354
 
        
355
 
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
356
 
                                    repository at a slightly different url
357
 
                                    than normal. I.e. during 'upgrade'.
358
 
        """
359
 
        if not _found:
360
 
            format = RepositoryFormat.find_format(a_bzrdir)
361
 
        if _override_transport is not None:
362
 
            repo_transport = _override_transport
363
 
        else:
364
 
            repo_transport = a_bzrdir.get_repository_transport(None)
365
 
        control_files = lockable_files.LockableFiles(repo_transport,
366
 
                                'lock', lockdir.LockDir)
367
 
        repo = self.repository_class(_format=self,
368
 
                              a_bzrdir=a_bzrdir,
369
 
                              control_files=control_files,
370
 
                              _commit_builder_class=self._commit_builder_class,
371
 
                              _serializer=self._serializer)
372
 
        repo.revisions = self._get_revisions(repo_transport, repo)
373
 
        repo.signatures = self._get_signatures(repo_transport, repo)
374
 
        repo.inventories = self._get_inventories(repo_transport, repo)
375
 
        repo.texts = self._get_texts(repo_transport, repo)
376
 
        repo._transport = repo_transport
377
 
        return repo
378
 
 
379
 
 
380
 
class RepositoryFormatKnit1(RepositoryFormatKnit):
381
 
    """Bzr repository knit format 1.
382
 
 
383
 
    This repository format has:
384
 
     - knits for file texts and inventory
385
 
     - hash subdirectory based stores.
386
 
     - knits for revisions and signatures
387
 
     - TextStores for revisions and signatures.
388
 
     - a format marker of its own
389
 
     - an optional 'shared-storage' flag
390
 
     - an optional 'no-working-trees' flag
391
 
     - a LockDir lock
392
 
 
393
 
    This format was introduced in bzr 0.8.
394
 
    """
395
 
 
396
 
    repository_class = KnitRepository
397
 
    _commit_builder_class = CommitBuilder
398
 
    @property
399
 
    def _serializer(self):
400
 
        return xml5.serializer_v5
401
 
 
402
 
    def __ne__(self, other):
403
 
        return self.__class__ is not other.__class__
404
 
 
405
 
    def get_format_string(self):
406
 
        """See RepositoryFormat.get_format_string()."""
407
 
        return "Bazaar-NG Knit Repository Format 1"
408
 
 
409
 
    def get_format_description(self):
410
 
        """See RepositoryFormat.get_format_description()."""
411
 
        return "Knit repository format 1"
412
 
 
413
 
    def check_conversion_target(self, target_format):
414
 
        pass
415
 
 
416
 
 
417
 
class RepositoryFormatKnit3(RepositoryFormatKnit):
418
 
    """Bzr repository knit format 3.
419
 
 
420
 
    This repository format has:
421
 
     - knits for file texts and inventory
422
 
     - hash subdirectory based stores.
423
 
     - knits for revisions and signatures
424
 
     - TextStores for revisions and signatures.
425
 
     - a format marker of its own
426
 
     - an optional 'shared-storage' flag
427
 
     - an optional 'no-working-trees' flag
428
 
     - a LockDir lock
429
 
     - support for recording full info about the tree root
430
 
     - support for recording tree-references
431
 
    """
432
 
 
433
 
    repository_class = KnitRepository
434
 
    _commit_builder_class = RootCommitBuilder
435
 
    rich_root_data = True
436
 
    supports_tree_reference = True
437
 
    @property
438
 
    def _serializer(self):
439
 
        return xml7.serializer_v7
440
 
 
441
 
    def _get_matching_bzrdir(self):
442
 
        return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
443
 
 
444
 
    def _ignore_setting_bzrdir(self, format):
445
 
        pass
446
 
 
447
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
448
 
 
449
 
    def check_conversion_target(self, target_format):
450
 
        if not target_format.rich_root_data:
451
 
            raise errors.BadConversionTarget(
452
 
                'Does not support rich root data.', target_format)
453
 
        if not getattr(target_format, 'supports_tree_reference', False):
454
 
            raise errors.BadConversionTarget(
455
 
                'Does not support nested trees', target_format)
456
 
            
457
 
    def get_format_string(self):
458
 
        """See RepositoryFormat.get_format_string()."""
459
 
        return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
460
 
 
461
 
    def get_format_description(self):
462
 
        """See RepositoryFormat.get_format_description()."""
463
 
        return "Knit repository format 3"
464
 
 
465
 
 
466
 
class RepositoryFormatKnit4(RepositoryFormatKnit):
467
 
    """Bzr repository knit format 4.
468
 
 
469
 
    This repository format has everything in format 3, except for
470
 
    tree-references:
471
 
     - knits for file texts and inventory
472
 
     - hash subdirectory based stores.
473
 
     - knits for revisions and signatures
474
 
     - TextStores for revisions and signatures.
475
 
     - a format marker of its own
476
 
     - an optional 'shared-storage' flag
477
 
     - an optional 'no-working-trees' flag
478
 
     - a LockDir lock
479
 
     - support for recording full info about the tree root
480
 
    """
481
 
 
482
 
    repository_class = KnitRepository
483
 
    _commit_builder_class = RootCommitBuilder
484
 
    rich_root_data = True
485
 
    supports_tree_reference = False
486
 
    @property
487
 
    def _serializer(self):
488
 
        return xml6.serializer_v6
489
 
 
490
 
    def _get_matching_bzrdir(self):
491
 
        return bzrdir.format_registry.make_bzrdir('rich-root')
492
 
 
493
 
    def _ignore_setting_bzrdir(self, format):
494
 
        pass
495
 
 
496
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
497
 
 
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
 
 
503
 
    def get_format_string(self):
504
 
        """See RepositoryFormat.get_format_string()."""
505
 
        return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
506
 
 
507
 
    def get_format_description(self):
508
 
        """See RepositoryFormat.get_format_description()."""
509
 
        return "Knit repository format 4"