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(), """
26
from bzrlib.store import revision
27
from bzrlib.store.revision.knit import KnitRevisionStore
39
from bzrlib.decorators import needs_read_lock, needs_write_lock
40
from bzrlib.knit import KnitVersionedFiles, _KndxIndex, _KnitKeyAccess
41
from bzrlib.repository import (
44
MetaDirRepositoryFormat,
48
import bzrlib.revision as _mod_revision
49
from bzrlib.store.versioned import VersionedFileStore
50
from bzrlib.trace import mutter, mutter_callsite
51
from bzrlib.util import bencode
52
from bzrlib.versionedfile import ConstantMapper, HashEscapedPrefixMapper
55
class _KnitParentsProvider(object):
57
def __init__(self, knit):
61
return 'KnitParentsProvider(%r)' % self._knit
63
@symbol_versioning.deprecated_method(symbol_versioning.one_one)
64
def get_parents(self, revision_ids):
65
"""See graph._StackedParentsProvider.get_parents"""
66
parent_map = self.get_parent_map(revision_ids)
67
return [parent_map.get(r, None) for r in revision_ids]
69
def get_parent_map(self, keys):
70
"""See graph._StackedParentsProvider.get_parent_map"""
72
for revision_id in keys:
73
if revision_id is None:
74
raise ValueError('get_parent_map(None) is not valid')
75
if revision_id == _mod_revision.NULL_REVISION:
76
parent_map[revision_id] = ()
80
self._knit.get_parents_with_ghosts(revision_id))
81
except errors.RevisionNotPresent:
85
parents = (_mod_revision.NULL_REVISION,)
86
parent_map[revision_id] = parents
90
class _KnitsParentsProvider(object):
92
def __init__(self, knit, prefix=()):
93
"""Create a parent provider for string keys mapped to tuple keys."""
98
return 'KnitsParentsProvider(%r)' % self._knit
100
def get_parent_map(self, keys):
101
"""See graph._StackedParentsProvider.get_parent_map"""
102
parent_map = self._knit.get_parent_map(
103
[self._prefix + (key,) for key in keys])
105
for key, parents in parent_map.items():
107
if len(parents) == 0:
108
parents = (_mod_revision.NULL_REVISION,)
110
parents = tuple(parent[-1] for parent in parents)
111
result[revid] = parents
112
for revision_id in keys:
113
if revision_id == _mod_revision.NULL_REVISION:
114
result[revision_id] = ()
118
class KnitRepository(MetaDirRepository):
119
"""Knit format repository."""
121
# These attributes are inherited from the Repository base class. Setting
122
# them to None ensures that if the constructor is changed to not initialize
123
# them, or a subclass fails to call the constructor, that an error will
124
# occur rather than the system working but generating incorrect data.
125
_commit_builder_class = None
128
def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
130
MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
131
self._commit_builder_class = _commit_builder_class
132
self._serializer = _serializer
133
self._reconcile_fixes_text_parents = True
134
self._fetch_uses_deltas = True
135
self._fetch_order = 'topological'
138
def _all_revision_ids(self):
139
"""See Repository.all_revision_ids()."""
140
return [key[0] for key in self.revisions.keys()]
142
def _activate_new_inventory(self):
143
"""Put a replacement inventory.new into use as inventories."""
144
# Copy the content across
146
t.copy('inventory.new.kndx', 'inventory.kndx')
148
t.copy('inventory.new.knit', 'inventory.knit')
149
except errors.NoSuchFile:
150
# empty inventories knit
151
t.delete('inventory.knit')
152
# delete the temp inventory
153
t.delete('inventory.new.kndx')
155
t.delete('inventory.new.knit')
156
except errors.NoSuchFile:
157
# empty inventories knit
159
# Force index reload (sanity check)
160
self.inventories._index._reset_cache()
161
self.inventories.keys()
163
def _backup_inventory(self):
165
t.copy('inventory.kndx', 'inventory.backup.kndx')
166
t.copy('inventory.knit', 'inventory.backup.knit')
168
def _move_file_id(self, from_id, to_id):
169
t = self._transport.clone('knits')
170
from_rel_url = self.texts._index._mapper.map((from_id, None))
171
to_rel_url = self.texts._index._mapper.map((to_id, None))
172
# We expect both files to always exist in this case.
173
for suffix in ('.knit', '.kndx'):
174
t.rename(from_rel_url + suffix, to_rel_url + suffix)
176
def _remove_file_id(self, file_id):
177
t = self._transport.clone('knits')
178
rel_url = self.texts._index._mapper.map((file_id, None))
179
for suffix in ('.kndx', '.knit'):
181
t.delete(rel_url + suffix)
182
except errors.NoSuchFile:
185
def _temp_inventories(self):
186
result = self._format._get_inventories(self._transport, self,
188
# Reconciling when the output has no revisions would result in no
189
# writes - but we want to ensure there is an inventory for
190
# compatibility with older clients that don't lazy-load.
191
result.get_parent_map([('A',)])
194
def fileid_involved_between_revs(self, from_revid, to_revid):
195
"""Find file_id(s) which are involved in the changes between revisions.
197
This determines the set of revisions which are involved, and then
198
finds all file ids affected by those revisions.
200
vf = self._get_revision_vf()
201
from_set = set(vf.get_ancestry(from_revid))
202
to_set = set(vf.get_ancestry(to_revid))
203
changed = to_set.difference(from_set)
204
return self._fileid_involved_by_set(changed)
206
def fileid_involved(self, last_revid=None):
207
"""Find all file_ids modified in the ancestry of last_revid.
209
:param last_revid: If None, last_revision() will be used.
212
changed = set(self.all_revision_ids())
214
changed = set(self.get_ancestry(last_revid))
217
return self._fileid_involved_by_set(changed)
220
def get_revision(self, revision_id):
221
"""Return the Revision object for a named revision"""
222
revision_id = osutils.safe_revision_id(revision_id)
223
return self.get_revision_reconcile(revision_id)
226
def reconcile(self, other=None, thorough=False):
227
"""Reconcile this repository."""
228
from bzrlib.reconcile import KnitReconciler
229
reconciler = KnitReconciler(self, thorough=thorough)
230
reconciler.reconcile()
233
def _make_parents_provider(self):
234
return _KnitsParentsProvider(self.revisions)
236
def _find_inconsistent_revision_parents(self):
237
"""Find revisions with different parent lists in the revision object
238
and in the index graph.
240
:returns: an iterator yielding tuples of (revison-id, parents-in-index,
241
parents-in-revision).
243
if not self.is_locked():
244
raise AssertionError()
246
for index_version in vf.keys():
247
parent_map = vf.get_parent_map([index_version])
248
parents_according_to_index = tuple(parent[-1] for parent in
249
parent_map[index_version])
250
revision = self.get_revision(index_version[-1])
251
parents_according_to_revision = tuple(revision.parent_ids)
252
if parents_according_to_index != parents_according_to_revision:
253
yield (index_version[-1], parents_according_to_index,
254
parents_according_to_revision)
256
def _check_for_inconsistent_revision_parents(self):
257
inconsistencies = list(self._find_inconsistent_revision_parents())
259
raise errors.BzrCheckError(
260
"Revision knit has inconsistent parents.")
262
def revision_graph_can_have_wrong_parents(self):
263
# The revision.kndx could potentially claim a revision has a different
264
# parent to the revision text.
268
class RepositoryFormatKnit(MetaDirRepositoryFormat):
269
"""Bzr repository knit format (generalized).
271
This repository format has:
272
- knits for file texts and inventory
273
- hash subdirectory based stores.
274
- knits for revisions and signatures
275
- TextStores for revisions and signatures.
276
- a format marker of its own
277
- an optional 'shared-storage' flag
278
- an optional 'no-working-trees' flag
282
# Set this attribute in derived classes to control the repository class
283
# created by open and initialize.
284
repository_class = None
285
# Set this attribute in derived classes to control the
286
# _commit_builder_class that the repository objects will have passed to
288
_commit_builder_class = None
289
# Set this attribute in derived clases to control the _serializer that the
290
# repository objects will have passed to their constructor.
292
def _serializer(self):
293
return xml5.serializer_v5
294
# Knit based repositories handle ghosts reasonably well.
295
supports_ghosts = True
296
# External lookups are not supported in this format.
297
supports_external_lookups = False
299
def _get_inventories(self, repo_transport, repo, name='inventory'):
300
mapper = ConstantMapper(name)
301
index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
302
repo.is_write_locked, repo.is_locked)
303
access = _KnitKeyAccess(repo_transport, mapper)
304
return KnitVersionedFiles(index, access, annotated=False)
306
def _get_revisions(self, repo_transport, repo):
307
mapper = ConstantMapper('revisions')
308
index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
309
repo.is_write_locked, repo.is_locked)
310
access = _KnitKeyAccess(repo_transport, mapper)
311
return KnitVersionedFiles(index, access, max_delta_chain=0,
314
def _get_signatures(self, repo_transport, repo):
315
mapper = ConstantMapper('signatures')
316
index = _KndxIndex(repo_transport, mapper, repo.get_transaction,
317
repo.is_write_locked, repo.is_locked)
318
access = _KnitKeyAccess(repo_transport, mapper)
319
return KnitVersionedFiles(index, access, max_delta_chain=0,
322
def _get_texts(self, repo_transport, repo):
323
mapper = HashEscapedPrefixMapper()
324
base_transport = repo_transport.clone('knits')
325
index = _KndxIndex(base_transport, mapper, repo.get_transaction,
326
repo.is_write_locked, repo.is_locked)
327
access = _KnitKeyAccess(base_transport, mapper)
328
return KnitVersionedFiles(index, access, max_delta_chain=200,
331
def initialize(self, a_bzrdir, shared=False):
332
"""Create a knit format 1 repository.
334
:param a_bzrdir: bzrdir to contain the new repository; must already
336
:param shared: If true the repository will be initialized as a shared
339
mutter('creating repository in %s.', a_bzrdir.transport.base)
342
utf8_files = [('format', self.get_format_string())]
344
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
345
repo_transport = a_bzrdir.get_repository_transport(None)
346
control_files = lockable_files.LockableFiles(repo_transport,
347
'lock', lockdir.LockDir)
348
transaction = transactions.WriteTransaction()
349
result = self.open(a_bzrdir=a_bzrdir, _found=True)
351
# the revision id here is irrelevant: it will not be stored, and cannot
352
# already exist, we do this to create files on disk for older clients.
353
result.inventories.get_parent_map([('A',)])
354
result.revisions.get_parent_map([('A',)])
355
result.signatures.get_parent_map([('A',)])
359
def open(self, a_bzrdir, _found=False, _override_transport=None):
360
"""See RepositoryFormat.open().
362
:param _override_transport: INTERNAL USE ONLY. Allows opening the
363
repository at a slightly different url
364
than normal. I.e. during 'upgrade'.
367
format = RepositoryFormat.find_format(a_bzrdir)
368
if _override_transport is not None:
369
repo_transport = _override_transport
371
repo_transport = a_bzrdir.get_repository_transport(None)
372
control_files = lockable_files.LockableFiles(repo_transport,
373
'lock', lockdir.LockDir)
374
repo = self.repository_class(_format=self,
376
control_files=control_files,
377
_commit_builder_class=self._commit_builder_class,
378
_serializer=self._serializer)
379
repo.revisions = self._get_revisions(repo_transport, repo)
380
repo.signatures = self._get_signatures(repo_transport, repo)
381
repo.inventories = self._get_inventories(repo_transport, repo)
382
repo.texts = self._get_texts(repo_transport, repo)
383
repo._transport = repo_transport
387
class RepositoryFormatKnit1(RepositoryFormatKnit):
388
"""Bzr repository knit format 1.
390
This repository format has:
391
- knits for file texts and inventory
392
- hash subdirectory based stores.
393
- knits for revisions and signatures
394
- TextStores for revisions and signatures.
395
- a format marker of its own
396
- an optional 'shared-storage' flag
397
- an optional 'no-working-trees' flag
400
This format was introduced in bzr 0.8.
403
repository_class = KnitRepository
404
_commit_builder_class = CommitBuilder
406
def _serializer(self):
407
return xml5.serializer_v5
409
def __ne__(self, other):
410
return self.__class__ is not other.__class__
412
def get_format_string(self):
413
"""See RepositoryFormat.get_format_string()."""
414
return "Bazaar-NG Knit Repository Format 1"
416
def get_format_description(self):
417
"""See RepositoryFormat.get_format_description()."""
418
return "Knit repository format 1"
420
def check_conversion_target(self, target_format):
424
class RepositoryFormatKnit3(RepositoryFormatKnit):
425
"""Bzr repository knit format 3.
427
This repository format has:
428
- knits for file texts and inventory
429
- hash subdirectory based stores.
430
- knits for revisions and signatures
431
- TextStores for revisions and signatures.
432
- a format marker of its own
433
- an optional 'shared-storage' flag
434
- an optional 'no-working-trees' flag
436
- support for recording full info about the tree root
437
- support for recording tree-references
440
repository_class = KnitRepository
441
_commit_builder_class = RootCommitBuilder
442
rich_root_data = True
443
supports_tree_reference = True
445
def _serializer(self):
446
return xml7.serializer_v7
448
def _get_matching_bzrdir(self):
449
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
451
def _ignore_setting_bzrdir(self, format):
454
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
456
def check_conversion_target(self, target_format):
457
if not target_format.rich_root_data:
458
raise errors.BadConversionTarget(
459
'Does not support rich root data.', target_format)
460
if not getattr(target_format, 'supports_tree_reference', False):
461
raise errors.BadConversionTarget(
462
'Does not support nested trees', target_format)
464
def get_format_string(self):
465
"""See RepositoryFormat.get_format_string()."""
466
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
468
def get_format_description(self):
469
"""See RepositoryFormat.get_format_description()."""
470
return "Knit repository format 3"
473
class RepositoryFormatKnit4(RepositoryFormatKnit):
474
"""Bzr repository knit format 4.
476
This repository format has everything in format 3, except for
478
- knits for file texts and inventory
479
- hash subdirectory based stores.
480
- knits for revisions and signatures
481
- TextStores for revisions and signatures.
482
- a format marker of its own
483
- an optional 'shared-storage' flag
484
- an optional 'no-working-trees' flag
486
- support for recording full info about the tree root
489
repository_class = KnitRepository
490
_commit_builder_class = RootCommitBuilder
491
rich_root_data = True
492
supports_tree_reference = False
494
def _serializer(self):
495
return xml6.serializer_v6
497
def _get_matching_bzrdir(self):
498
return bzrdir.format_registry.make_bzrdir('rich-root')
500
def _ignore_setting_bzrdir(self, format):
503
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
505
def check_conversion_target(self, target_format):
506
if not target_format.rich_root_data:
507
raise errors.BadConversionTarget(
508
'Does not support rich root data.', target_format)
510
def get_format_string(self):
511
"""See RepositoryFormat.get_format_string()."""
512
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
514
def get_format_description(self):
515
"""See RepositoryFormat.get_format_description()."""
516
return "Knit repository format 4"