1
# Copyright (C) 2007-2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from bzrlib.lazy_import import lazy_import
18
lazy_import(globals(), """
26
revision as _mod_revision,
35
from bzrlib.decorators import needs_read_lock, needs_write_lock
36
from bzrlib.repository import (
39
InterSameDataRepository,
42
MetaDirRepositoryFormat,
46
from bzrlib import symbol_versioning
49
class _KnitParentsProvider(object):
51
def __init__(self, knit):
55
return 'KnitParentsProvider(%r)' % self._knit
57
def get_parent_map(self, keys):
58
"""See graph.StackedParentsProvider.get_parent_map"""
60
for revision_id in keys:
61
if revision_id is None:
62
raise ValueError('get_parent_map(None) is not valid')
63
if revision_id == _mod_revision.NULL_REVISION:
64
parent_map[revision_id] = ()
68
self._knit.get_parents_with_ghosts(revision_id))
69
except errors.RevisionNotPresent:
73
parents = (_mod_revision.NULL_REVISION,)
74
parent_map[revision_id] = parents
78
class _KnitsParentsProvider(object):
80
def __init__(self, knit, prefix=()):
81
"""Create a parent provider for string keys mapped to tuple keys."""
86
return 'KnitsParentsProvider(%r)' % self._knit
88
def get_parent_map(self, keys):
89
"""See graph.StackedParentsProvider.get_parent_map"""
90
parent_map = self._knit.get_parent_map(
91
[self._prefix + (key,) for key in keys])
93
for key, parents in parent_map.items():
96
parents = (_mod_revision.NULL_REVISION,)
98
parents = tuple(parent[-1] for parent in parents)
99
result[revid] = parents
100
for revision_id in keys:
101
if revision_id == _mod_revision.NULL_REVISION:
102
result[revision_id] = ()
106
class KnitRepository(MetaDirRepository):
107
"""Knit format repository."""
109
# These attributes are inherited from the Repository base class. Setting
110
# them to None ensures that if the constructor is changed to not initialize
111
# them, or a subclass fails to call the constructor, that an error will
112
# occur rather than the system working but generating incorrect data.
113
_commit_builder_class = None
116
def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
118
MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
119
self._commit_builder_class = _commit_builder_class
120
self._serializer = _serializer
121
self._reconcile_fixes_text_parents = True
124
def _all_revision_ids(self):
125
"""See Repository.all_revision_ids()."""
126
return [key[0] for key in self.revisions.keys()]
128
def _activate_new_inventory(self):
129
"""Put a replacement inventory.new into use as inventories."""
130
# Copy the content across
132
t.copy('inventory.new.kndx', 'inventory.kndx')
134
t.copy('inventory.new.knit', 'inventory.knit')
135
except errors.NoSuchFile:
136
# empty inventories knit
137
t.delete('inventory.knit')
138
# delete the temp inventory
139
t.delete('inventory.new.kndx')
141
t.delete('inventory.new.knit')
142
except errors.NoSuchFile:
143
# empty inventories knit
145
# Force index reload (sanity check)
146
self.inventories._index._reset_cache()
147
self.inventories.keys()
149
def _backup_inventory(self):
151
t.copy('inventory.kndx', 'inventory.backup.kndx')
152
t.copy('inventory.knit', 'inventory.backup.knit')
154
def _move_file_id(self, from_id, to_id):
155
t = self._transport.clone('knits')
156
from_rel_url = self.texts._index._mapper.map((from_id, None))
157
to_rel_url = self.texts._index._mapper.map((to_id, None))
158
# We expect both files to always exist in this case.
159
for suffix in ('.knit', '.kndx'):
160
t.rename(from_rel_url + suffix, to_rel_url + suffix)
162
def _remove_file_id(self, file_id):
163
t = self._transport.clone('knits')
164
rel_url = self.texts._index._mapper.map((file_id, None))
165
for suffix in ('.kndx', '.knit'):
167
t.delete(rel_url + suffix)
168
except errors.NoSuchFile:
171
def _temp_inventories(self):
172
result = self._format._get_inventories(self._transport, self,
174
# Reconciling when the output has no revisions would result in no
175
# writes - but we want to ensure there is an inventory for
176
# compatibility with older clients that don't lazy-load.
177
result.get_parent_map([('A',)])
180
def fileid_involved_between_revs(self, from_revid, to_revid):
181
"""Find file_id(s) which are involved in the changes between revisions.
183
This determines the set of revisions which are involved, and then
184
finds all file ids affected by those revisions.
186
vf = self._get_revision_vf()
187
from_set = set(vf.get_ancestry(from_revid))
188
to_set = set(vf.get_ancestry(to_revid))
189
changed = to_set.difference(from_set)
190
return self._fileid_involved_by_set(changed)
192
def fileid_involved(self, last_revid=None):
193
"""Find all file_ids modified in the ancestry of last_revid.
195
:param last_revid: If None, last_revision() will be used.
198
changed = set(self.all_revision_ids())
200
changed = set(self.get_ancestry(last_revid))
203
return self._fileid_involved_by_set(changed)
206
def get_revision(self, revision_id):
207
"""Return the Revision object for a named revision"""
208
revision_id = osutils.safe_revision_id(revision_id)
209
return self.get_revision_reconcile(revision_id)
211
def _refresh_data(self):
212
if not self.is_locked():
214
if self.is_in_write_group():
215
raise IsInWriteGroupError(self)
216
# Create a new transaction to force all knits to see the scope change.
217
# This is safe because we're outside a write group.
218
self.control_files._finish_transaction()
219
if self.is_write_locked():
220
self.control_files._set_write_transaction()
222
self.control_files._set_read_transaction()
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()
232
def _make_parents_provider(self):
233
return _KnitsParentsProvider(self.revisions)
235
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
236
"""Find revisions with different parent lists in the revision object
237
and in the index graph.
239
:param revisions_iterator: None, or an iterator of (revid,
240
Revision-or-None). This iterator controls the revisions checked.
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
if revisions_iterator is None:
248
revisions_iterator = self._iter_revisions(None)
249
for revid, revision in revisions_iterator:
252
parent_map = vf.get_parent_map([(revid,)])
253
parents_according_to_index = tuple(parent[-1] for parent in
254
parent_map[(revid,)])
255
parents_according_to_revision = tuple(revision.parent_ids)
256
if parents_according_to_index != parents_according_to_revision:
257
yield (revid, parents_according_to_index,
258
parents_according_to_revision)
260
def _check_for_inconsistent_revision_parents(self):
261
inconsistencies = list(self._find_inconsistent_revision_parents())
263
raise errors.BzrCheckError(
264
"Revision knit has inconsistent parents.")
266
def revision_graph_can_have_wrong_parents(self):
267
# The revision.kndx could potentially claim a revision has a different
268
# parent to the revision text.
272
class RepositoryFormatKnit(MetaDirRepositoryFormat):
273
"""Bzr repository knit format (generalized).
275
This repository format has:
276
- knits for file texts and inventory
277
- hash subdirectory based stores.
278
- knits for revisions and signatures
279
- TextStores for revisions and signatures.
280
- a format marker of its own
281
- an optional 'shared-storage' flag
282
- an optional 'no-working-trees' flag
286
# Set this attribute in derived classes to control the repository class
287
# created by open and initialize.
288
repository_class = None
289
# Set this attribute in derived classes to control the
290
# _commit_builder_class that the repository objects will have passed to
292
_commit_builder_class = None
293
# Set this attribute in derived clases to control the _serializer that the
294
# repository objects will have passed to their constructor.
296
def _serializer(self):
297
return xml5.serializer_v5
298
# Knit based repositories handle ghosts reasonably well.
299
supports_ghosts = True
300
# External lookups are not supported in this format.
301
supports_external_lookups = False
303
supports_chks = False
304
_fetch_order = 'topological'
305
_fetch_uses_deltas = True
307
supports_funky_characters = True
308
supports_full_versioned_files = True
310
def _get_inventories(self, repo_transport, repo, name='inventory'):
311
mapper = versionedfile.ConstantMapper(name)
312
index = _mod_knit._KndxIndex(repo_transport, mapper,
313
repo.get_transaction, repo.is_write_locked, repo.is_locked)
314
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
315
return _mod_knit.KnitVersionedFiles(index, access, annotated=False)
317
def _get_revisions(self, repo_transport, repo):
318
mapper = versionedfile.ConstantMapper('revisions')
319
index = _mod_knit._KndxIndex(repo_transport, mapper,
320
repo.get_transaction, repo.is_write_locked, repo.is_locked)
321
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
322
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
325
def _get_signatures(self, repo_transport, repo):
326
mapper = versionedfile.ConstantMapper('signatures')
327
index = _mod_knit._KndxIndex(repo_transport, mapper,
328
repo.get_transaction, repo.is_write_locked, repo.is_locked)
329
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
330
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
333
def _get_texts(self, repo_transport, repo):
334
mapper = versionedfile.HashEscapedPrefixMapper()
335
base_transport = repo_transport.clone('knits')
336
index = _mod_knit._KndxIndex(base_transport, mapper,
337
repo.get_transaction, repo.is_write_locked, repo.is_locked)
338
access = _mod_knit._KnitKeyAccess(base_transport, mapper)
339
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=200,
342
def initialize(self, a_bzrdir, shared=False):
343
"""Create a knit format 1 repository.
345
:param a_bzrdir: bzrdir to contain the new repository; must already
347
:param shared: If true the repository will be initialized as a shared
350
trace.mutter('creating repository in %s.', a_bzrdir.transport.base)
353
utf8_files = [('format', self.get_format_string())]
355
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
356
repo_transport = a_bzrdir.get_repository_transport(None)
357
control_files = lockable_files.LockableFiles(repo_transport,
358
'lock', lockdir.LockDir)
359
transaction = transactions.WriteTransaction()
360
result = self.open(a_bzrdir=a_bzrdir, _found=True)
362
# the revision id here is irrelevant: it will not be stored, and cannot
363
# already exist, we do this to create files on disk for older clients.
364
result.inventories.get_parent_map([('A',)])
365
result.revisions.get_parent_map([('A',)])
366
result.signatures.get_parent_map([('A',)])
368
self._run_post_repo_init_hooks(result, a_bzrdir, shared)
371
def open(self, a_bzrdir, _found=False, _override_transport=None):
372
"""See RepositoryFormat.open().
374
:param _override_transport: INTERNAL USE ONLY. Allows opening the
375
repository at a slightly different url
376
than normal. I.e. during 'upgrade'.
379
format = RepositoryFormat.find_format(a_bzrdir)
380
if _override_transport is not None:
381
repo_transport = _override_transport
383
repo_transport = a_bzrdir.get_repository_transport(None)
384
control_files = lockable_files.LockableFiles(repo_transport,
385
'lock', lockdir.LockDir)
386
repo = self.repository_class(_format=self,
388
control_files=control_files,
389
_commit_builder_class=self._commit_builder_class,
390
_serializer=self._serializer)
391
repo.revisions = self._get_revisions(repo_transport, repo)
392
repo.signatures = self._get_signatures(repo_transport, repo)
393
repo.inventories = self._get_inventories(repo_transport, repo)
394
repo.texts = self._get_texts(repo_transport, repo)
395
repo.chk_bytes = None
396
repo._transport = repo_transport
400
class RepositoryFormatKnit1(RepositoryFormatKnit):
401
"""Bzr repository knit format 1.
403
This repository format has:
404
- knits for file texts and inventory
405
- hash subdirectory based stores.
406
- knits for revisions and signatures
407
- TextStores for revisions and signatures.
408
- a format marker of its own
409
- an optional 'shared-storage' flag
410
- an optional 'no-working-trees' flag
413
This format was introduced in bzr 0.8.
416
repository_class = KnitRepository
417
_commit_builder_class = CommitBuilder
419
def _serializer(self):
420
return xml5.serializer_v5
422
def __ne__(self, other):
423
return self.__class__ is not other.__class__
425
def get_format_string(self):
426
"""See RepositoryFormat.get_format_string()."""
427
return "Bazaar-NG Knit Repository Format 1"
429
def get_format_description(self):
430
"""See RepositoryFormat.get_format_description()."""
431
return "Knit repository format 1"
434
class RepositoryFormatKnit3(RepositoryFormatKnit):
435
"""Bzr repository knit format 3.
437
This repository format has:
438
- knits for file texts and inventory
439
- hash subdirectory based stores.
440
- knits for revisions and signatures
441
- TextStores for revisions and signatures.
442
- a format marker of its own
443
- an optional 'shared-storage' flag
444
- an optional 'no-working-trees' flag
446
- support for recording full info about the tree root
447
- support for recording tree-references
450
repository_class = KnitRepository
451
_commit_builder_class = RootCommitBuilder
452
rich_root_data = True
454
supports_tree_reference = True
456
def _serializer(self):
457
return xml7.serializer_v7
459
def _get_matching_bzrdir(self):
460
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
462
def _ignore_setting_bzrdir(self, format):
465
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
467
def get_format_string(self):
468
"""See RepositoryFormat.get_format_string()."""
469
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
471
def get_format_description(self):
472
"""See RepositoryFormat.get_format_description()."""
473
return "Knit repository format 3"
476
class RepositoryFormatKnit4(RepositoryFormatKnit):
477
"""Bzr repository knit format 4.
479
This repository format has everything in format 3, except for
481
- knits for file texts and inventory
482
- hash subdirectory based stores.
483
- knits for revisions and signatures
484
- TextStores for revisions and signatures.
485
- a format marker of its own
486
- an optional 'shared-storage' flag
487
- an optional 'no-working-trees' flag
489
- support for recording full info about the tree root
492
repository_class = KnitRepository
493
_commit_builder_class = RootCommitBuilder
494
rich_root_data = True
495
supports_tree_reference = False
497
def _serializer(self):
498
return xml6.serializer_v6
500
def _get_matching_bzrdir(self):
501
return bzrdir.format_registry.make_bzrdir('rich-root')
503
def _ignore_setting_bzrdir(self, format):
506
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
508
def get_format_string(self):
509
"""See RepositoryFormat.get_format_string()."""
510
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
512
def get_format_description(self):
513
"""See RepositoryFormat.get_format_description()."""
514
return "Knit repository format 4"
517
class InterKnitRepo(InterSameDataRepository):
518
"""Optimised code paths between Knit based repositories."""
521
def _get_repo_format_to_test(self):
522
return RepositoryFormatKnit1()
525
def is_compatible(source, target):
526
"""Be compatible with known Knit formats.
528
We don't test for the stores being of specific types because that
529
could lead to confusing results, and there is no need to be
533
are_knits = (isinstance(source._format, RepositoryFormatKnit) and
534
isinstance(target._format, RepositoryFormatKnit))
535
except AttributeError:
537
return are_knits and InterRepository._same_model(source, target)
540
def search_missing_revision_ids(self,
541
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
542
find_ghosts=True, revision_ids=None, if_present_ids=None):
543
"""See InterRepository.search_missing_revision_ids()."""
544
if symbol_versioning.deprecated_passed(revision_id):
545
symbol_versioning.warn(
546
'search_missing_revision_ids(revision_id=...) was '
547
'deprecated in 2.4. Use revision_ids=[...] instead.',
548
DeprecationWarning, stacklevel=2)
549
if revision_ids is not None:
550
raise AssertionError(
551
'revision_ids is mutually exclusive with revision_id')
552
if revision_id is not None:
553
revision_ids = [revision_id]
555
source_ids_set = self._present_source_revisions_for(
556
revision_ids, if_present_ids)
557
# source_ids is the worst possible case we may need to pull.
558
# now we want to filter source_ids against what we actually
559
# have in target, but don't try to check for existence where we know
560
# we do not have a revision as that would be pointless.
561
target_ids = set(self.target.all_revision_ids())
562
possibly_present_revisions = target_ids.intersection(source_ids_set)
563
actually_present_revisions = set(
564
self.target._eliminate_revisions_not_present(possibly_present_revisions))
565
required_revisions = source_ids_set.difference(actually_present_revisions)
566
if revision_ids is not None:
567
# we used get_ancestry to determine source_ids then we are assured all
568
# revisions referenced are present as they are installed in topological order.
569
# and the tip revision was validated by get_ancestry.
570
result_set = required_revisions
572
# if we just grabbed the possibly available ids, then
573
# we only have an estimate of whats available and need to validate
574
# that against the revision records.
576
self.source._eliminate_revisions_not_present(required_revisions))
577
return self.source.revision_ids_to_search_result(result_set)
580
InterRepository.register_optimiser(InterKnitRepo)