1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
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.
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.
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
17
from bzrlib.lazy_import import lazy_import
18
lazy_import(globals(), """
22
from bzrlib.store import revision
23
from bzrlib.store.revision.knit import KnitRevisionStore
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.knit import KnitVersionedFiles, _KndxIndex, _KnitKeyAccess
40
from bzrlib.repository import (
43
MetaDirRepositoryFormat,
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
54
class _KnitParentsProvider(object):
56
def __init__(self, knit):
60
return 'KnitParentsProvider(%r)' % self._knit
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]
68
def get_parent_map(self, keys):
69
"""See graph._StackedParentsProvider.get_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] = ()
79
self._knit.get_parents_with_ghosts(revision_id))
80
except errors.RevisionNotPresent:
84
parents = (_mod_revision.NULL_REVISION,)
85
parent_map[revision_id] = parents
89
class _KnitsParentsProvider(object):
91
def __init__(self, knit, prefix=()):
92
"""Create a parent provider for string keys mapped to tuple keys."""
97
return 'KnitsParentsProvider(%r)' % self._knit
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])
104
for key, parents in parent_map.items():
106
if len(parents) == 0:
107
parents = (_mod_revision.NULL_REVISION,)
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] = ()
117
class KnitRepository(MetaDirRepository):
118
"""Knit format repository."""
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
127
def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
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
135
def _all_revision_ids(self):
136
"""See Repository.all_revision_ids()."""
137
return [key[0] for key in self.revisions.keys()]
139
def _activate_new_inventory(self):
140
"""Put a replacement inventory.new into use as inventories."""
141
# Copy the content across
143
t.copy('inventory.new.kndx', 'inventory.kndx')
145
t.copy('inventory.new.knit', 'inventory.knit')
146
except errors.NoSuchFile:
147
# empty inventories knit
148
t.delete('inventory.knit')
149
# delete the temp inventory
150
t.delete('inventory.new.kndx')
152
t.delete('inventory.new.knit')
153
except errors.NoSuchFile:
154
# empty inventories knit
156
# Force index reload (sanity check)
157
self.inventories._index._reset_cache()
158
self.inventories.keys()
160
def _backup_inventory(self):
162
t.copy('inventory.kndx', 'inventory.backup.kndx')
163
t.copy('inventory.knit', 'inventory.backup.knit')
165
def _move_file_id(self, from_id, to_id):
166
t = self._transport.clone('knits')
167
from_rel_url = self.texts._index._mapper.map((from_id, None))
168
to_rel_url = self.texts._index._mapper.map((to_id, None))
169
# We expect both files to always exist in this case.
170
for suffix in ('.knit', '.kndx'):
171
t.rename(from_rel_url + suffix, to_rel_url + suffix)
173
def _remove_file_id(self, file_id):
174
t = self._transport.clone('knits')
175
rel_url = self.texts._index._mapper.map((file_id, None))
176
for suffix in ('.kndx', '.knit'):
178
t.delete(rel_url + suffix)
179
except errors.NoSuchFile:
182
def _temp_inventories(self):
183
result = self._format._get_inventories(self._transport, self,
185
# Reconciling when the output has no revisions would result in no
186
# writes - but we want to ensure there is an inventory for
187
# compatibility with older clients that don't lazy-load.
188
result.get_parent_map([('A',)])
191
def fileid_involved_between_revs(self, from_revid, to_revid):
192
"""Find file_id(s) which are involved in the changes between revisions.
194
This determines the set of revisions which are involved, and then
195
finds all file ids affected by those revisions.
197
vf = self._get_revision_vf()
198
from_set = set(vf.get_ancestry(from_revid))
199
to_set = set(vf.get_ancestry(to_revid))
200
changed = to_set.difference(from_set)
201
return self._fileid_involved_by_set(changed)
203
def fileid_involved(self, last_revid=None):
204
"""Find all file_ids modified in the ancestry of last_revid.
206
:param last_revid: If None, last_revision() will be used.
209
changed = set(self.all_revision_ids())
211
changed = set(self.get_ancestry(last_revid))
214
return self._fileid_involved_by_set(changed)
217
def get_revision(self, revision_id):
218
"""Return the Revision object for a named revision"""
219
revision_id = osutils.safe_revision_id(revision_id)
220
return self.get_revision_reconcile(revision_id)
223
def reconcile(self, other=None, thorough=False):
224
"""Reconcile this repository."""
225
from bzrlib.reconcile import KnitReconciler
226
reconciler = KnitReconciler(self, thorough=thorough)
227
reconciler.reconcile()
230
def _make_parents_provider(self):
231
return _KnitsParentsProvider(self.revisions)
233
def _find_inconsistent_revision_parents(self):
234
"""Find revisions with different parent lists in the revision object
235
and in the index graph.
237
:returns: an iterator yielding tuples of (revison-id, parents-in-index,
238
parents-in-revision).
240
if not self.is_locked():
241
raise AssertionError()
243
for index_version in vf.keys():
244
parent_map = vf.get_parent_map([index_version])
245
parents_according_to_index = tuple(parent[-1] for parent in
246
parent_map[index_version])
247
revision = self.get_revision(index_version[-1])
248
parents_according_to_revision = tuple(revision.parent_ids)
249
if parents_according_to_index != parents_according_to_revision:
250
yield (index_version[-1], parents_according_to_index,
251
parents_according_to_revision)
253
def _check_for_inconsistent_revision_parents(self):
254
inconsistencies = list(self._find_inconsistent_revision_parents())
256
raise errors.BzrCheckError(
257
"Revision knit has inconsistent parents.")
259
def revision_graph_can_have_wrong_parents(self):
260
# The revision.kndx could potentially claim a revision has a different
261
# parent to the revision text.
265
class RepositoryFormatKnit(MetaDirRepositoryFormat):
266
"""Bzr repository knit format (generalized).
268
This repository format has:
269
- knits for file texts and inventory
270
- hash subdirectory based stores.
271
- knits for revisions and signatures
272
- TextStores for revisions and signatures.
273
- a format marker of its own
274
- an optional 'shared-storage' flag
275
- an optional 'no-working-trees' flag
279
# Set this attribute in derived classes to control the repository class
280
# created by open and initialize.
281
repository_class = None
282
# Set this attribute in derived classes to control the
283
# _commit_builder_class that the repository objects will have passed to
285
_commit_builder_class = None
286
# Set this attribute in derived clases to control the _serializer that the
287
# repository objects will have passed to their constructor.
288
_serializer = xml5.serializer_v5
289
# Knit based repositories handle ghosts reasonably well.
290
supports_ghosts = True
291
# External lookups are not supported in this format.
292
supports_external_lookups = False
294
def _get_inventories(self, repo_transport, repo, name='inventory'):
295
mapper = ConstantMapper(name)
296
index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
297
repo.is_write_locked, repo.is_locked)
298
access = _KnitKeyAccess(repo_transport, mapper)
299
return KnitVersionedFiles(index, access, annotated=False)
301
def _get_revisions(self, repo_transport, repo):
302
mapper = ConstantMapper('revisions')
303
index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
304
repo.is_write_locked, repo.is_locked)
305
access = _KnitKeyAccess(repo_transport, mapper)
306
return KnitVersionedFiles(index, access, max_delta_chain=0,
309
def _get_signatures(self, repo_transport, repo):
310
mapper = ConstantMapper('signatures')
311
index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
312
repo.is_write_locked, repo.is_locked)
313
access = _KnitKeyAccess(repo_transport, mapper)
314
return KnitVersionedFiles(index, access, max_delta_chain=0,
317
def _get_texts(self, repo_transport, repo):
318
mapper = HashEscapedPrefixMapper()
319
base_transport = repo_transport.clone('knits')
320
index = _KndxIndex(base_transport, mapper, repo.get_transaction,
321
repo.is_write_locked, repo.is_locked)
322
access = _KnitKeyAccess(base_transport, mapper)
323
return KnitVersionedFiles(index, access, max_delta_chain=200,
326
def initialize(self, a_bzrdir, shared=False):
327
"""Create a knit format 1 repository.
329
:param a_bzrdir: bzrdir to contain the new repository; must already
331
:param shared: If true the repository will be initialized as a shared
334
mutter('creating repository in %s.', a_bzrdir.transport.base)
337
utf8_files = [('format', self.get_format_string())]
339
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
340
repo_transport = a_bzrdir.get_repository_transport(None)
341
control_files = lockable_files.LockableFiles(repo_transport,
342
'lock', lockdir.LockDir)
343
transaction = transactions.WriteTransaction()
344
result = self.open(a_bzrdir=a_bzrdir, _found=True)
346
# the revision id here is irrelevant: it will not be stored, and cannot
347
# already exist, we do this to create files on disk for older clients.
348
result.inventories.get_parent_map([('A',)])
349
result.revisions.get_parent_map([('A',)])
350
result.signatures.get_parent_map([('A',)])
354
def open(self, a_bzrdir, _found=False, _override_transport=None):
355
"""See RepositoryFormat.open().
357
:param _override_transport: INTERNAL USE ONLY. Allows opening the
358
repository at a slightly different url
359
than normal. I.e. during 'upgrade'.
362
format = RepositoryFormat.find_format(a_bzrdir)
363
if _override_transport is not None:
364
repo_transport = _override_transport
366
repo_transport = a_bzrdir.get_repository_transport(None)
367
control_files = lockable_files.LockableFiles(repo_transport,
368
'lock', lockdir.LockDir)
369
repo = self.repository_class(_format=self,
371
control_files=control_files,
372
_commit_builder_class=self._commit_builder_class,
373
_serializer=self._serializer)
374
repo.revisions = self._get_revisions(repo_transport, repo)
375
repo.signatures = self._get_signatures(repo_transport, repo)
376
repo.inventories = self._get_inventories(repo_transport, repo)
377
repo.texts = self._get_texts(repo_transport, repo)
378
repo._transport = repo_transport
382
class RepositoryFormatKnit1(RepositoryFormatKnit):
383
"""Bzr repository knit format 1.
385
This repository format has:
386
- knits for file texts and inventory
387
- hash subdirectory based stores.
388
- knits for revisions and signatures
389
- TextStores for revisions and signatures.
390
- a format marker of its own
391
- an optional 'shared-storage' flag
392
- an optional 'no-working-trees' flag
395
This format was introduced in bzr 0.8.
398
repository_class = KnitRepository
399
_commit_builder_class = CommitBuilder
400
_serializer = xml5.serializer_v5
402
def __ne__(self, other):
403
return self.__class__ is not other.__class__
405
def get_format_string(self):
406
"""See RepositoryFormat.get_format_string()."""
407
return "Bazaar-NG Knit Repository Format 1"
409
def get_format_description(self):
410
"""See RepositoryFormat.get_format_description()."""
411
return "Knit repository format 1"
413
def check_conversion_target(self, target_format):
417
class RepositoryFormatKnit3(RepositoryFormatKnit):
418
"""Bzr repository knit format 3.
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
429
- support for recording full info about the tree root
430
- support for recording tree-references
433
repository_class = KnitRepository
434
_commit_builder_class = RootCommitBuilder
435
rich_root_data = True
436
supports_tree_reference = True
437
_serializer = xml7.serializer_v7
439
def _get_matching_bzrdir(self):
440
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
442
def _ignore_setting_bzrdir(self, format):
445
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
447
def check_conversion_target(self, target_format):
448
if not target_format.rich_root_data:
449
raise errors.BadConversionTarget(
450
'Does not support rich root data.', target_format)
451
if not getattr(target_format, 'supports_tree_reference', False):
452
raise errors.BadConversionTarget(
453
'Does not support nested trees', target_format)
455
def get_format_string(self):
456
"""See RepositoryFormat.get_format_string()."""
457
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
459
def get_format_description(self):
460
"""See RepositoryFormat.get_format_description()."""
461
return "Knit repository format 3"
464
class RepositoryFormatKnit4(RepositoryFormatKnit):
465
"""Bzr repository knit format 4.
467
This repository format has everything in format 3, except for
469
- knits for file texts and inventory
470
- hash subdirectory based stores.
471
- knits for revisions and signatures
472
- TextStores for revisions and signatures.
473
- a format marker of its own
474
- an optional 'shared-storage' flag
475
- an optional 'no-working-trees' flag
477
- support for recording full info about the tree root
480
repository_class = KnitRepository
481
_commit_builder_class = RootCommitBuilder
482
rich_root_data = True
483
supports_tree_reference = False
484
_serializer = xml6.serializer_v6
486
def _get_matching_bzrdir(self):
487
return bzrdir.format_registry.make_bzrdir('rich-root')
489
def _ignore_setting_bzrdir(self, format):
492
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
494
def check_conversion_target(self, target_format):
495
if not target_format.rich_root_data:
496
raise errors.BadConversionTarget(
497
'Does not support rich root data.', target_format)
499
def get_format_string(self):
500
"""See RepositoryFormat.get_format_string()."""
501
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
503
def get_format_description(self):
504
"""See RepositoryFormat.get_format_description()."""
505
return "Knit repository format 4"