~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitrepo.py

  • Committer: Martin Pool
  • Date: 2008-07-03 10:44:34 UTC
  • mfrom: (3512.3.2 setlocale.mini)
  • mto: This revision was merged to the branch mainline in revision 3518.
  • Revision ID: mbp@sourcefrog.net-20080703104434-v4qgzvxd2wxg8etl
Set locale from environment for third party libs and day of week.

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
 
 
134
    def _warn_if_deprecated(self):
 
135
        # This class isn't deprecated
 
136
        pass
 
137
 
 
138
    @needs_read_lock
 
139
    def _all_revision_ids(self):
 
140
        """See Repository.all_revision_ids()."""
 
141
        return [key[0] for key in self.revisions.keys()]
 
142
 
 
143
    def _activate_new_inventory(self):
 
144
        """Put a replacement inventory.new into use as inventories."""
 
145
        # Copy the content across
 
146
        t = self._transport
 
147
        t.copy('inventory.new.kndx', 'inventory.kndx')
 
148
        try:
 
149
            t.copy('inventory.new.knit', 'inventory.knit')
 
150
        except errors.NoSuchFile:
 
151
            # empty inventories knit
 
152
            t.delete('inventory.knit')
 
153
        # delete the temp inventory
 
154
        t.delete('inventory.new.kndx')
 
155
        try:
 
156
            t.delete('inventory.new.knit')
 
157
        except errors.NoSuchFile:
 
158
            # empty inventories knit
 
159
            pass
 
160
        # Force index reload (sanity check)
 
161
        self.inventories._index._reset_cache()
 
162
        self.inventories.keys()
 
163
 
 
164
    def _backup_inventory(self):
 
165
        t = self._transport
 
166
        t.copy('inventory.kndx', 'inventory.backup.kndx')
 
167
        t.copy('inventory.knit', 'inventory.backup.knit')
 
168
 
 
169
    def _move_file_id(self, from_id, to_id):
 
170
        t = self._transport.clone('knits')
 
171
        from_rel_url = self.texts._index._mapper.map((from_id, None))
 
172
        to_rel_url = self.texts._index._mapper.map((to_id, None))
 
173
        # We expect both files to always exist in this case.
 
174
        for suffix in ('.knit', '.kndx'):
 
175
            t.rename(from_rel_url + suffix, to_rel_url + suffix)
 
176
 
 
177
    def _remove_file_id(self, file_id):
 
178
        t = self._transport.clone('knits')
 
179
        rel_url = self.texts._index._mapper.map((file_id, None))
 
180
        for suffix in ('.kndx', '.knit'):
 
181
            try:
 
182
                t.delete(rel_url + suffix)
 
183
            except errors.NoSuchFile:
 
184
                pass
 
185
 
 
186
    def _temp_inventories(self):
 
187
        result = self._format._get_inventories(self._transport, self,
 
188
            'inventory.new')
 
189
        # Reconciling when the output has no revisions would result in no
 
190
        # writes - but we want to ensure there is an inventory for
 
191
        # compatibility with older clients that don't lazy-load.
 
192
        result.get_parent_map([('A',)])
 
193
        return result
 
194
 
 
195
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
196
        """Find file_id(s) which are involved in the changes between revisions.
 
197
 
 
198
        This determines the set of revisions which are involved, and then
 
199
        finds all file ids affected by those revisions.
 
200
        """
 
201
        vf = self._get_revision_vf()
 
202
        from_set = set(vf.get_ancestry(from_revid))
 
203
        to_set = set(vf.get_ancestry(to_revid))
 
204
        changed = to_set.difference(from_set)
 
205
        return self._fileid_involved_by_set(changed)
 
206
 
 
207
    def fileid_involved(self, last_revid=None):
 
208
        """Find all file_ids modified in the ancestry of last_revid.
 
209
 
 
210
        :param last_revid: If None, last_revision() will be used.
 
211
        """
 
212
        if not last_revid:
 
213
            changed = set(self.all_revision_ids())
 
214
        else:
 
215
            changed = set(self.get_ancestry(last_revid))
 
216
        if None in changed:
 
217
            changed.remove(None)
 
218
        return self._fileid_involved_by_set(changed)
 
219
 
 
220
    @needs_read_lock
 
221
    def get_revision(self, revision_id):
 
222
        """Return the Revision object for a named revision"""
 
223
        revision_id = osutils.safe_revision_id(revision_id)
 
224
        return self.get_revision_reconcile(revision_id)
 
225
 
 
226
    @needs_write_lock
 
227
    def reconcile(self, other=None, thorough=False):
 
228
        """Reconcile this repository."""
 
229
        from bzrlib.reconcile import KnitReconciler
 
230
        reconciler = KnitReconciler(self, thorough=thorough)
 
231
        reconciler.reconcile()
 
232
        return reconciler
 
233
    
 
234
    def _make_parents_provider(self):
 
235
        return _KnitsParentsProvider(self.revisions)
 
236
 
 
237
    def _find_inconsistent_revision_parents(self):
 
238
        """Find revisions with different parent lists in the revision object
 
239
        and in the index graph.
 
240
 
 
241
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
 
242
            parents-in-revision).
 
243
        """
 
244
        if not self.is_locked():
 
245
            raise AssertionError()
 
246
        vf = self.revisions
 
247
        for index_version in vf.keys():
 
248
            parent_map = vf.get_parent_map([index_version])
 
249
            parents_according_to_index = tuple(parent[-1] for parent in
 
250
                parent_map[index_version])
 
251
            revision = self.get_revision(index_version[-1])
 
252
            parents_according_to_revision = tuple(revision.parent_ids)
 
253
            if parents_according_to_index != parents_according_to_revision:
 
254
                yield (index_version[-1], parents_according_to_index,
 
255
                    parents_according_to_revision)
 
256
 
 
257
    def _check_for_inconsistent_revision_parents(self):
 
258
        inconsistencies = list(self._find_inconsistent_revision_parents())
 
259
        if inconsistencies:
 
260
            raise errors.BzrCheckError(
 
261
                "Revision knit has inconsistent parents.")
 
262
 
 
263
    def revision_graph_can_have_wrong_parents(self):
 
264
        # The revision.kndx could potentially claim a revision has a different
 
265
        # parent to the revision text.
 
266
        return True
 
267
 
 
268
 
 
269
class RepositoryFormatKnit(MetaDirRepositoryFormat):
 
270
    """Bzr repository knit format (generalized). 
 
271
 
 
272
    This repository format has:
 
273
     - knits for file texts and inventory
 
274
     - hash subdirectory based stores.
 
275
     - knits for revisions and signatures
 
276
     - TextStores for revisions and signatures.
 
277
     - a format marker of its own
 
278
     - an optional 'shared-storage' flag
 
279
     - an optional 'no-working-trees' flag
 
280
     - a LockDir lock
 
281
    """
 
282
 
 
283
    # Set this attribute in derived classes to control the repository class
 
284
    # created by open and initialize.
 
285
    repository_class = None
 
286
    # Set this attribute in derived classes to control the
 
287
    # _commit_builder_class that the repository objects will have passed to
 
288
    # their constructor.
 
289
    _commit_builder_class = None
 
290
    # Set this attribute in derived clases to control the _serializer that the
 
291
    # repository objects will have passed to their constructor.
 
292
    _serializer = xml5.serializer_v5
 
293
    # Knit based repositories handle ghosts reasonably well.
 
294
    supports_ghosts = True
 
295
    # External lookups are not supported in this format.
 
296
    supports_external_lookups = False
 
297
 
 
298
    def _get_inventories(self, repo_transport, repo, name='inventory'):
 
299
        mapper = ConstantMapper(name)
 
300
        index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
 
301
            repo.is_write_locked, repo.is_locked)
 
302
        access = _KnitKeyAccess(repo_transport, mapper)
 
303
        return KnitVersionedFiles(index, access, annotated=False)
 
304
 
 
305
    def _get_revisions(self, repo_transport, repo):
 
306
        mapper = ConstantMapper('revisions')
 
307
        index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
 
308
            repo.is_write_locked, repo.is_locked)
 
309
        access = _KnitKeyAccess(repo_transport, mapper)
 
310
        return KnitVersionedFiles(index, access, max_delta_chain=0,
 
311
            annotated=False)
 
312
 
 
313
    def _get_signatures(self, repo_transport, repo):
 
314
        mapper = ConstantMapper('signatures')
 
315
        index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
 
316
            repo.is_write_locked, repo.is_locked)
 
317
        access = _KnitKeyAccess(repo_transport, mapper)
 
318
        return KnitVersionedFiles(index, access, max_delta_chain=0,
 
319
            annotated=False)
 
320
 
 
321
    def _get_texts(self, repo_transport, repo):
 
322
        mapper = HashEscapedPrefixMapper()
 
323
        base_transport = repo_transport.clone('knits')
 
324
        index = _KndxIndex(base_transport, mapper, repo.get_transaction,
 
325
            repo.is_write_locked, repo.is_locked)
 
326
        access = _KnitKeyAccess(base_transport, mapper)
 
327
        return KnitVersionedFiles(index, access, max_delta_chain=200,
 
328
            annotated=True)
 
329
 
 
330
    def initialize(self, a_bzrdir, shared=False):
 
331
        """Create a knit format 1 repository.
 
332
 
 
333
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
334
            be initialized.
 
335
        :param shared: If true the repository will be initialized as a shared
 
336
                       repository.
 
337
        """
 
338
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
339
        dirs = ['knits']
 
340
        files = []
 
341
        utf8_files = [('format', self.get_format_string())]
 
342
        
 
343
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
344
        repo_transport = a_bzrdir.get_repository_transport(None)
 
345
        control_files = lockable_files.LockableFiles(repo_transport,
 
346
                                'lock', lockdir.LockDir)
 
347
        transaction = transactions.WriteTransaction()
 
348
        result = self.open(a_bzrdir=a_bzrdir, _found=True)
 
349
        result.lock_write()
 
350
        # the revision id here is irrelevant: it will not be stored, and cannot
 
351
        # already exist, we do this to create files on disk for older clients.
 
352
        result.inventories.get_parent_map([('A',)])
 
353
        result.revisions.get_parent_map([('A',)])
 
354
        result.signatures.get_parent_map([('A',)])
 
355
        result.unlock()
 
356
        return result
 
357
 
 
358
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
359
        """See RepositoryFormat.open().
 
360
        
 
361
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
362
                                    repository at a slightly different url
 
363
                                    than normal. I.e. during 'upgrade'.
 
364
        """
 
365
        if not _found:
 
366
            format = RepositoryFormat.find_format(a_bzrdir)
 
367
        if _override_transport is not None:
 
368
            repo_transport = _override_transport
 
369
        else:
 
370
            repo_transport = a_bzrdir.get_repository_transport(None)
 
371
        control_files = lockable_files.LockableFiles(repo_transport,
 
372
                                'lock', lockdir.LockDir)
 
373
        repo = self.repository_class(_format=self,
 
374
                              a_bzrdir=a_bzrdir,
 
375
                              control_files=control_files,
 
376
                              _commit_builder_class=self._commit_builder_class,
 
377
                              _serializer=self._serializer)
 
378
        repo.revisions = self._get_revisions(repo_transport, repo)
 
379
        repo.signatures = self._get_signatures(repo_transport, repo)
 
380
        repo.inventories = self._get_inventories(repo_transport, repo)
 
381
        repo.texts = self._get_texts(repo_transport, repo)
 
382
        repo._transport = repo_transport
 
383
        return repo
 
384
 
 
385
 
 
386
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
387
    """Bzr repository knit format 1.
 
388
 
 
389
    This repository format has:
 
390
     - knits for file texts and inventory
 
391
     - hash subdirectory based stores.
 
392
     - knits for revisions and signatures
 
393
     - TextStores for revisions and signatures.
 
394
     - a format marker of its own
 
395
     - an optional 'shared-storage' flag
 
396
     - an optional 'no-working-trees' flag
 
397
     - a LockDir lock
 
398
 
 
399
    This format was introduced in bzr 0.8.
 
400
    """
 
401
 
 
402
    repository_class = KnitRepository
 
403
    _commit_builder_class = CommitBuilder
 
404
    _serializer = xml5.serializer_v5
 
405
 
 
406
    def __ne__(self, other):
 
407
        return self.__class__ is not other.__class__
 
408
 
 
409
    def get_format_string(self):
 
410
        """See RepositoryFormat.get_format_string()."""
 
411
        return "Bazaar-NG Knit Repository Format 1"
 
412
 
 
413
    def get_format_description(self):
 
414
        """See RepositoryFormat.get_format_description()."""
 
415
        return "Knit repository format 1"
 
416
 
 
417
    def check_conversion_target(self, target_format):
 
418
        pass
 
419
 
 
420
 
 
421
class RepositoryFormatKnit3(RepositoryFormatKnit):
 
422
    """Bzr repository knit format 3.
 
423
 
 
424
    This repository format has:
 
425
     - knits for file texts and inventory
 
426
     - hash subdirectory based stores.
 
427
     - knits for revisions and signatures
 
428
     - TextStores for revisions and signatures.
 
429
     - a format marker of its own
 
430
     - an optional 'shared-storage' flag
 
431
     - an optional 'no-working-trees' flag
 
432
     - a LockDir lock
 
433
     - support for recording full info about the tree root
 
434
     - support for recording tree-references
 
435
    """
 
436
 
 
437
    repository_class = KnitRepository
 
438
    _commit_builder_class = RootCommitBuilder
 
439
    rich_root_data = True
 
440
    supports_tree_reference = True
 
441
    _serializer = xml7.serializer_v7
 
442
 
 
443
    def _get_matching_bzrdir(self):
 
444
        return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
 
445
 
 
446
    def _ignore_setting_bzrdir(self, format):
 
447
        pass
 
448
 
 
449
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
450
 
 
451
    def check_conversion_target(self, target_format):
 
452
        if not target_format.rich_root_data:
 
453
            raise errors.BadConversionTarget(
 
454
                'Does not support rich root data.', target_format)
 
455
        if not getattr(target_format, 'supports_tree_reference', False):
 
456
            raise errors.BadConversionTarget(
 
457
                'Does not support nested trees', target_format)
 
458
            
 
459
    def get_format_string(self):
 
460
        """See RepositoryFormat.get_format_string()."""
 
461
        return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
 
462
 
 
463
    def get_format_description(self):
 
464
        """See RepositoryFormat.get_format_description()."""
 
465
        return "Knit repository format 3"
 
466
 
 
467
 
 
468
class RepositoryFormatKnit4(RepositoryFormatKnit):
 
469
    """Bzr repository knit format 4.
 
470
 
 
471
    This repository format has everything in format 3, except for
 
472
    tree-references:
 
473
     - knits for file texts and inventory
 
474
     - hash subdirectory based stores.
 
475
     - knits for revisions and signatures
 
476
     - TextStores for revisions and signatures.
 
477
     - a format marker of its own
 
478
     - an optional 'shared-storage' flag
 
479
     - an optional 'no-working-trees' flag
 
480
     - a LockDir lock
 
481
     - support for recording full info about the tree root
 
482
    """
 
483
 
 
484
    repository_class = KnitRepository
 
485
    _commit_builder_class = RootCommitBuilder
 
486
    rich_root_data = True
 
487
    supports_tree_reference = False
 
488
    _serializer = 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"