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
308
def _get_inventories(self, repo_transport, repo, name='inventory'):
309
mapper = versionedfile.ConstantMapper(name)
310
index = _mod_knit._KndxIndex(repo_transport, mapper,
311
repo.get_transaction, repo.is_write_locked, repo.is_locked)
312
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
313
return _mod_knit.KnitVersionedFiles(index, access, annotated=False)
315
def _get_revisions(self, repo_transport, repo):
316
mapper = versionedfile.ConstantMapper('revisions')
317
index = _mod_knit._KndxIndex(repo_transport, mapper,
318
repo.get_transaction, repo.is_write_locked, repo.is_locked)
319
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
320
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
323
def _get_signatures(self, repo_transport, repo):
324
mapper = versionedfile.ConstantMapper('signatures')
325
index = _mod_knit._KndxIndex(repo_transport, mapper,
326
repo.get_transaction, repo.is_write_locked, repo.is_locked)
327
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
328
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
331
def _get_texts(self, repo_transport, repo):
332
mapper = versionedfile.HashEscapedPrefixMapper()
333
base_transport = repo_transport.clone('knits')
334
index = _mod_knit._KndxIndex(base_transport, mapper,
335
repo.get_transaction, repo.is_write_locked, repo.is_locked)
336
access = _mod_knit._KnitKeyAccess(base_transport, mapper)
337
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=200,
340
def initialize(self, a_bzrdir, shared=False):
341
"""Create a knit format 1 repository.
343
:param a_bzrdir: bzrdir to contain the new repository; must already
345
:param shared: If true the repository will be initialized as a shared
348
trace.mutter('creating repository in %s.', a_bzrdir.transport.base)
351
utf8_files = [('format', self.get_format_string())]
353
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
354
repo_transport = a_bzrdir.get_repository_transport(None)
355
control_files = lockable_files.LockableFiles(repo_transport,
356
'lock', lockdir.LockDir)
357
transaction = transactions.WriteTransaction()
358
result = self.open(a_bzrdir=a_bzrdir, _found=True)
360
# the revision id here is irrelevant: it will not be stored, and cannot
361
# already exist, we do this to create files on disk for older clients.
362
result.inventories.get_parent_map([('A',)])
363
result.revisions.get_parent_map([('A',)])
364
result.signatures.get_parent_map([('A',)])
366
self._run_post_repo_init_hooks(result, a_bzrdir, shared)
369
def open(self, a_bzrdir, _found=False, _override_transport=None):
370
"""See RepositoryFormat.open().
372
:param _override_transport: INTERNAL USE ONLY. Allows opening the
373
repository at a slightly different url
374
than normal. I.e. during 'upgrade'.
377
format = RepositoryFormat.find_format(a_bzrdir)
378
if _override_transport is not None:
379
repo_transport = _override_transport
381
repo_transport = a_bzrdir.get_repository_transport(None)
382
control_files = lockable_files.LockableFiles(repo_transport,
383
'lock', lockdir.LockDir)
384
repo = self.repository_class(_format=self,
386
control_files=control_files,
387
_commit_builder_class=self._commit_builder_class,
388
_serializer=self._serializer)
389
repo.revisions = self._get_revisions(repo_transport, repo)
390
repo.signatures = self._get_signatures(repo_transport, repo)
391
repo.inventories = self._get_inventories(repo_transport, repo)
392
repo.texts = self._get_texts(repo_transport, repo)
393
repo.chk_bytes = None
394
repo._transport = repo_transport
398
class RepositoryFormatKnit1(RepositoryFormatKnit):
399
"""Bzr repository knit format 1.
401
This repository format has:
402
- knits for file texts and inventory
403
- hash subdirectory based stores.
404
- knits for revisions and signatures
405
- TextStores for revisions and signatures.
406
- a format marker of its own
407
- an optional 'shared-storage' flag
408
- an optional 'no-working-trees' flag
411
This format was introduced in bzr 0.8.
414
repository_class = KnitRepository
415
_commit_builder_class = CommitBuilder
417
def _serializer(self):
418
return xml5.serializer_v5
420
def __ne__(self, other):
421
return self.__class__ is not other.__class__
423
def get_format_string(self):
424
"""See RepositoryFormat.get_format_string()."""
425
return "Bazaar-NG Knit Repository Format 1"
427
def get_format_description(self):
428
"""See RepositoryFormat.get_format_description()."""
429
return "Knit repository format 1"
432
class RepositoryFormatKnit3(RepositoryFormatKnit):
433
"""Bzr repository knit format 3.
435
This repository format has:
436
- knits for file texts and inventory
437
- hash subdirectory based stores.
438
- knits for revisions and signatures
439
- TextStores for revisions and signatures.
440
- a format marker of its own
441
- an optional 'shared-storage' flag
442
- an optional 'no-working-trees' flag
444
- support for recording full info about the tree root
445
- support for recording tree-references
448
repository_class = KnitRepository
449
_commit_builder_class = RootCommitBuilder
450
rich_root_data = True
452
supports_tree_reference = True
454
def _serializer(self):
455
return xml7.serializer_v7
457
def _get_matching_bzrdir(self):
458
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
460
def _ignore_setting_bzrdir(self, format):
463
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
465
def get_format_string(self):
466
"""See RepositoryFormat.get_format_string()."""
467
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
469
def get_format_description(self):
470
"""See RepositoryFormat.get_format_description()."""
471
return "Knit repository format 3"
474
class RepositoryFormatKnit4(RepositoryFormatKnit):
475
"""Bzr repository knit format 4.
477
This repository format has everything in format 3, except for
479
- knits for file texts and inventory
480
- hash subdirectory based stores.
481
- knits for revisions and signatures
482
- TextStores for revisions and signatures.
483
- a format marker of its own
484
- an optional 'shared-storage' flag
485
- an optional 'no-working-trees' flag
487
- support for recording full info about the tree root
490
repository_class = KnitRepository
491
_commit_builder_class = RootCommitBuilder
492
rich_root_data = True
493
supports_tree_reference = False
495
def _serializer(self):
496
return xml6.serializer_v6
498
def _get_matching_bzrdir(self):
499
return bzrdir.format_registry.make_bzrdir('rich-root')
501
def _ignore_setting_bzrdir(self, format):
504
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
506
def get_format_string(self):
507
"""See RepositoryFormat.get_format_string()."""
508
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
510
def get_format_description(self):
511
"""See RepositoryFormat.get_format_description()."""
512
return "Knit repository format 4"
515
class InterKnitRepo(InterSameDataRepository):
516
"""Optimised code paths between Knit based repositories."""
519
def _get_repo_format_to_test(self):
520
return RepositoryFormatKnit1()
523
def is_compatible(source, target):
524
"""Be compatible with known Knit formats.
526
We don't test for the stores being of specific types because that
527
could lead to confusing results, and there is no need to be
531
are_knits = (isinstance(source._format, RepositoryFormatKnit) and
532
isinstance(target._format, RepositoryFormatKnit))
533
except AttributeError:
535
return are_knits and InterRepository._same_model(source, target)
538
def search_missing_revision_ids(self,
539
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
540
find_ghosts=True, revision_ids=None, if_present_ids=None):
541
"""See InterRepository.search_missing_revision_ids()."""
542
if symbol_versioning.deprecated_passed(revision_id):
543
symbol_versioning.warn(
544
'search_missing_revision_ids(revision_id=...) was '
545
'deprecated in 2.4. Use revision_ids=[...] instead.',
546
DeprecationWarning, stacklevel=2)
547
if revision_ids is not None:
548
raise AssertionError(
549
'revision_ids is mutually exclusive with revision_id')
550
if revision_id is not None:
551
revision_ids = [revision_id]
553
source_ids_set = self._present_source_revisions_for(
554
revision_ids, if_present_ids)
555
# source_ids is the worst possible case we may need to pull.
556
# now we want to filter source_ids against what we actually
557
# have in target, but don't try to check for existence where we know
558
# we do not have a revision as that would be pointless.
559
target_ids = set(self.target.all_revision_ids())
560
possibly_present_revisions = target_ids.intersection(source_ids_set)
561
actually_present_revisions = set(
562
self.target._eliminate_revisions_not_present(possibly_present_revisions))
563
required_revisions = source_ids_set.difference(actually_present_revisions)
564
if revision_ids is not None:
565
# we used get_ancestry to determine source_ids then we are assured all
566
# revisions referenced are present as they are installed in topological order.
567
# and the tip revision was validated by get_ancestry.
568
result_set = required_revisions
570
# if we just grabbed the possibly available ids, then
571
# we only have an estimate of whats available and need to validate
572
# that against the revision records.
574
self.source._eliminate_revisions_not_present(required_revisions))
575
return self.source.revision_ids_to_search_result(result_set)
578
InterRepository.register_optimiser(InterKnitRepo)