~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitrepo.py

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