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
134
def _warn_if_deprecated(self):
135
# This class isn't deprecated
139
def _all_revision_ids(self):
140
"""See Repository.all_revision_ids()."""
141
return [key[0] for key in self.revisions.keys()]
143
def _activate_new_inventory(self):
144
"""Put a replacement inventory.new into use as inventories."""
145
# Copy the content across
147
t.copy('inventory.new.kndx', 'inventory.kndx')
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')
156
t.delete('inventory.new.knit')
157
except errors.NoSuchFile:
158
# empty inventories knit
160
# Force index reload (sanity check)
161
self.inventories._index._reset_cache()
162
self.inventories.keys()
164
def _backup_inventory(self):
166
t.copy('inventory.kndx', 'inventory.backup.kndx')
167
t.copy('inventory.knit', 'inventory.backup.knit')
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)
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'):
182
t.delete(rel_url + suffix)
183
except errors.NoSuchFile:
186
def _temp_inventories(self):
187
result = self._format._get_inventories(self._transport, self,
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',)])
195
def fileid_involved_between_revs(self, from_revid, to_revid):
196
"""Find file_id(s) which are involved in the changes between revisions.
198
This determines the set of revisions which are involved, and then
199
finds all file ids affected by those revisions.
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)
207
def fileid_involved(self, last_revid=None):
208
"""Find all file_ids modified in the ancestry of last_revid.
210
:param last_revid: If None, last_revision() will be used.
213
changed = set(self.all_revision_ids())
215
changed = set(self.get_ancestry(last_revid))
218
return self._fileid_involved_by_set(changed)
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)
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()
234
def _make_parents_provider(self):
235
return _KnitsParentsProvider(self.revisions)
237
def _find_inconsistent_revision_parents(self):
238
"""Find revisions with different parent lists in the revision object
239
and in the index graph.
241
:returns: an iterator yielding tuples of (revison-id, parents-in-index,
242
parents-in-revision).
244
if not self.is_locked():
245
raise AssertionError()
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)
257
def _check_for_inconsistent_revision_parents(self):
258
inconsistencies = list(self._find_inconsistent_revision_parents())
260
raise errors.BzrCheckError(
261
"Revision knit has inconsistent parents.")
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.
269
class RepositoryFormatKnit(MetaDirRepositoryFormat):
270
"""Bzr repository knit format (generalized).
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
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
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
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)
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,
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,
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,
330
def initialize(self, a_bzrdir, shared=False):
331
"""Create a knit format 1 repository.
333
:param a_bzrdir: bzrdir to contain the new repository; must already
335
:param shared: If true the repository will be initialized as a shared
338
mutter('creating repository in %s.', a_bzrdir.transport.base)
341
utf8_files = [('format', self.get_format_string())]
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)
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',)])
358
def open(self, a_bzrdir, _found=False, _override_transport=None):
359
"""See RepositoryFormat.open().
361
:param _override_transport: INTERNAL USE ONLY. Allows opening the
362
repository at a slightly different url
363
than normal. I.e. during 'upgrade'.
366
format = RepositoryFormat.find_format(a_bzrdir)
367
if _override_transport is not None:
368
repo_transport = _override_transport
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,
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
386
class RepositoryFormatKnit1(RepositoryFormatKnit):
387
"""Bzr repository knit format 1.
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
399
This format was introduced in bzr 0.8.
402
repository_class = KnitRepository
403
_commit_builder_class = CommitBuilder
404
_serializer = xml5.serializer_v5
406
def __ne__(self, other):
407
return self.__class__ is not other.__class__
409
def get_format_string(self):
410
"""See RepositoryFormat.get_format_string()."""
411
return "Bazaar-NG Knit Repository Format 1"
413
def get_format_description(self):
414
"""See RepositoryFormat.get_format_description()."""
415
return "Knit repository format 1"
417
def check_conversion_target(self, target_format):
421
class RepositoryFormatKnit3(RepositoryFormatKnit):
422
"""Bzr repository knit format 3.
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
433
- support for recording full info about the tree root
434
- support for recording tree-references
437
repository_class = KnitRepository
438
_commit_builder_class = RootCommitBuilder
439
rich_root_data = True
440
supports_tree_reference = True
441
_serializer = xml7.serializer_v7
443
def _get_matching_bzrdir(self):
444
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
446
def _ignore_setting_bzrdir(self, format):
449
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
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)
459
def get_format_string(self):
460
"""See RepositoryFormat.get_format_string()."""
461
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
463
def get_format_description(self):
464
"""See RepositoryFormat.get_format_description()."""
465
return "Knit repository format 3"
468
class RepositoryFormatKnit4(RepositoryFormatKnit):
469
"""Bzr repository knit format 4.
471
This repository format has everything in format 3, except for
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
481
- support for recording full info about the tree root
484
repository_class = KnitRepository
485
_commit_builder_class = RootCommitBuilder
486
rich_root_data = True
487
supports_tree_reference = False
488
_serializer = xml6.serializer_v6
490
def _get_matching_bzrdir(self):
491
return bzrdir.format_registry.make_bzrdir('rich-root')
493
def _ignore_setting_bzrdir(self, format):
496
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
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)
503
def get_format_string(self):
504
"""See RepositoryFormat.get_format_string()."""
505
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
507
def get_format_description(self):
508
"""See RepositoryFormat.get_format_description()."""
509
return "Knit repository format 4"