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 __future__ import absolute_import
19
from ..lazy_import import lazy_import
20
lazy_import(globals(), """
29
revision as _mod_revision,
33
from breezy.bzr import (
41
from ..repository import (
45
from ..bzr.repository import (
46
RepositoryFormatMetaDir,
48
from ..bzr.vf_repository import (
49
InterSameDataRepository,
50
MetaDirVersionedFileRepository,
51
MetaDirVersionedFileRepositoryFormat,
52
VersionedFileCommitBuilder,
53
VersionedFileRootCommitBuilder,
57
class _KnitParentsProvider(object):
59
def __init__(self, knit):
63
return 'KnitParentsProvider(%r)' % self._knit
65
def get_parent_map(self, keys):
66
"""See graph.StackedParentsProvider.get_parent_map"""
68
for revision_id in keys:
69
if revision_id is None:
70
raise ValueError('get_parent_map(None) is not valid')
71
if revision_id == _mod_revision.NULL_REVISION:
72
parent_map[revision_id] = ()
76
self._knit.get_parents_with_ghosts(revision_id))
77
except errors.RevisionNotPresent:
81
parents = (_mod_revision.NULL_REVISION,)
82
parent_map[revision_id] = parents
86
class _KnitsParentsProvider(object):
88
def __init__(self, knit, prefix=()):
89
"""Create a parent provider for string keys mapped to tuple keys."""
94
return 'KnitsParentsProvider(%r)' % self._knit
96
def get_parent_map(self, keys):
97
"""See graph.StackedParentsProvider.get_parent_map"""
98
parent_map = self._knit.get_parent_map(
99
[self._prefix + (key,) for key in keys])
101
for key, parents in parent_map.items():
103
if len(parents) == 0:
104
parents = (_mod_revision.NULL_REVISION,)
106
parents = tuple(parent[-1] for parent in parents)
107
result[revid] = parents
108
for revision_id in keys:
109
if revision_id == _mod_revision.NULL_REVISION:
110
result[revision_id] = ()
114
class KnitRepository(MetaDirVersionedFileRepository):
115
"""Knit format repository."""
117
# These attributes are inherited from the Repository base class. Setting
118
# them to None ensures that if the constructor is changed to not initialize
119
# them, or a subclass fails to call the constructor, that an error will
120
# occur rather than the system working but generating incorrect data.
121
_commit_builder_class = None
124
def __init__(self, _format, a_controldir, control_files, _commit_builder_class,
126
super(KnitRepository, self).__init__(_format, a_controldir, control_files)
127
self._commit_builder_class = _commit_builder_class
128
self._serializer = _serializer
129
self._reconcile_fixes_text_parents = True
131
def _all_revision_ids(self):
132
"""See Repository.all_revision_ids()."""
133
with self.lock_read():
134
return [key[0] for key in self.revisions.keys()]
136
def _activate_new_inventory(self):
137
"""Put a replacement inventory.new into use as inventories."""
138
# Copy the content across
140
t.copy('inventory.new.kndx', 'inventory.kndx')
142
t.copy('inventory.new.knit', 'inventory.knit')
143
except errors.NoSuchFile:
144
# empty inventories knit
145
t.delete('inventory.knit')
146
# delete the temp inventory
147
t.delete('inventory.new.kndx')
149
t.delete('inventory.new.knit')
150
except errors.NoSuchFile:
151
# empty inventories knit
153
# Force index reload (sanity check)
154
self.inventories._index._reset_cache()
155
self.inventories.keys()
157
def _backup_inventory(self):
159
t.copy('inventory.kndx', 'inventory.backup.kndx')
160
t.copy('inventory.knit', 'inventory.backup.knit')
162
def _move_file_id(self, from_id, to_id):
163
t = self._transport.clone('knits')
164
from_rel_url = self.texts._index._mapper.map((from_id, None))
165
to_rel_url = self.texts._index._mapper.map((to_id, None))
166
# We expect both files to always exist in this case.
167
for suffix in ('.knit', '.kndx'):
168
t.rename(from_rel_url + suffix, to_rel_url + suffix)
170
def _remove_file_id(self, file_id):
171
t = self._transport.clone('knits')
172
rel_url = self.texts._index._mapper.map((file_id, None))
173
for suffix in ('.kndx', '.knit'):
175
t.delete(rel_url + suffix)
176
except errors.NoSuchFile:
179
def _temp_inventories(self):
180
result = self._format._get_inventories(self._transport, self,
182
# Reconciling when the output has no revisions would result in no
183
# writes - but we want to ensure there is an inventory for
184
# compatibility with older clients that don't lazy-load.
185
result.get_parent_map([('A',)])
188
def get_revision(self, revision_id):
189
"""Return the Revision object for a named revision"""
190
revision_id = osutils.safe_revision_id(revision_id)
191
with self.lock_read():
192
return self.get_revision_reconcile(revision_id)
194
def _refresh_data(self):
195
if not self.is_locked():
197
if self.is_in_write_group():
198
raise IsInWriteGroupError(self)
199
# Create a new transaction to force all knits to see the scope change.
200
# This is safe because we're outside a write group.
201
self.control_files._finish_transaction()
202
if self.is_write_locked():
203
self.control_files._set_write_transaction()
205
self.control_files._set_read_transaction()
207
def reconcile(self, other=None, thorough=False):
208
"""Reconcile this repository."""
209
from breezy.reconcile import KnitReconciler
210
with self.lock_write():
211
reconciler = KnitReconciler(self, thorough=thorough)
212
reconciler.reconcile()
215
def _make_parents_provider(self):
216
return _KnitsParentsProvider(self.revisions)
219
class RepositoryFormatKnit(MetaDirVersionedFileRepositoryFormat):
220
"""Bzr repository knit format (generalized).
222
This repository format has:
223
- knits for file texts and inventory
224
- hash subdirectory based stores.
225
- knits for revisions and signatures
226
- TextStores for revisions and signatures.
227
- a format marker of its own
228
- an optional 'shared-storage' flag
229
- an optional 'no-working-trees' flag
233
# Set this attribute in derived classes to control the repository class
234
# created by open and initialize.
235
repository_class = None
236
# Set this attribute in derived classes to control the
237
# _commit_builder_class that the repository objects will have passed to
239
_commit_builder_class = None
240
# Set this attribute in derived clases to control the _serializer that the
241
# repository objects will have passed to their constructor.
243
def _serializer(self):
244
return xml5.serializer_v5
245
# Knit based repositories handle ghosts reasonably well.
246
supports_ghosts = True
247
# External lookups are not supported in this format.
248
supports_external_lookups = False
250
supports_chks = False
251
_fetch_order = 'topological'
252
_fetch_uses_deltas = True
254
supports_funky_characters = True
255
# The revision.kndx could potentially claim a revision has a different
256
# parent to the revision text.
257
revision_graph_can_have_wrong_parents = True
259
def _get_inventories(self, repo_transport, repo, name='inventory'):
260
mapper = versionedfile.ConstantMapper(name)
261
index = _mod_knit._KndxIndex(repo_transport, mapper,
262
repo.get_transaction, repo.is_write_locked, repo.is_locked)
263
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
264
return _mod_knit.KnitVersionedFiles(index, access, annotated=False)
266
def _get_revisions(self, repo_transport, repo):
267
mapper = versionedfile.ConstantMapper('revisions')
268
index = _mod_knit._KndxIndex(repo_transport, mapper,
269
repo.get_transaction, repo.is_write_locked, repo.is_locked)
270
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
271
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
274
def _get_signatures(self, repo_transport, repo):
275
mapper = versionedfile.ConstantMapper('signatures')
276
index = _mod_knit._KndxIndex(repo_transport, mapper,
277
repo.get_transaction, repo.is_write_locked, repo.is_locked)
278
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
279
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
282
def _get_texts(self, repo_transport, repo):
283
mapper = versionedfile.HashEscapedPrefixMapper()
284
base_transport = repo_transport.clone('knits')
285
index = _mod_knit._KndxIndex(base_transport, mapper,
286
repo.get_transaction, repo.is_write_locked, repo.is_locked)
287
access = _mod_knit._KnitKeyAccess(base_transport, mapper)
288
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=200,
291
def initialize(self, a_controldir, shared=False):
292
"""Create a knit format 1 repository.
294
:param a_controldir: bzrdir to contain the new repository; must already
296
:param shared: If true the repository will be initialized as a shared
299
trace.mutter('creating repository in %s.', a_controldir.transport.base)
302
utf8_files = [('format', self.get_format_string())]
304
self._upload_blank_content(a_controldir, dirs, files, utf8_files, shared)
305
repo_transport = a_controldir.get_repository_transport(None)
306
control_files = lockable_files.LockableFiles(repo_transport,
307
'lock', lockdir.LockDir)
308
transaction = transactions.WriteTransaction()
309
result = self.open(a_controldir=a_controldir, _found=True)
311
# the revision id here is irrelevant: it will not be stored, and cannot
312
# already exist, we do this to create files on disk for older clients.
313
result.inventories.get_parent_map([('A',)])
314
result.revisions.get_parent_map([('A',)])
315
result.signatures.get_parent_map([('A',)])
317
self._run_post_repo_init_hooks(result, a_controldir, shared)
320
def open(self, a_controldir, _found=False, _override_transport=None):
321
"""See RepositoryFormat.open().
323
:param _override_transport: INTERNAL USE ONLY. Allows opening the
324
repository at a slightly different url
325
than normal. I.e. during 'upgrade'.
328
format = RepositoryFormatMetaDir.find_format(a_controldir)
329
if _override_transport is not None:
330
repo_transport = _override_transport
332
repo_transport = a_controldir.get_repository_transport(None)
333
control_files = lockable_files.LockableFiles(repo_transport,
334
'lock', lockdir.LockDir)
335
repo = self.repository_class(_format=self,
336
a_controldir=a_controldir,
337
control_files=control_files,
338
_commit_builder_class=self._commit_builder_class,
339
_serializer=self._serializer)
340
repo.revisions = self._get_revisions(repo_transport, repo)
341
repo.signatures = self._get_signatures(repo_transport, repo)
342
repo.inventories = self._get_inventories(repo_transport, repo)
343
repo.texts = self._get_texts(repo_transport, repo)
344
repo.chk_bytes = None
345
repo._transport = repo_transport
349
class RepositoryFormatKnit1(RepositoryFormatKnit):
350
"""Bzr repository knit format 1.
352
This repository format has:
353
- knits for file texts and inventory
354
- hash subdirectory based stores.
355
- knits for revisions and signatures
356
- TextStores for revisions and signatures.
357
- a format marker of its own
358
- an optional 'shared-storage' flag
359
- an optional 'no-working-trees' flag
362
This format was introduced in bzr 0.8.
365
repository_class = KnitRepository
366
_commit_builder_class = VersionedFileCommitBuilder
368
def _serializer(self):
369
return xml5.serializer_v5
371
def __ne__(self, other):
372
return self.__class__ is not other.__class__
375
def get_format_string(cls):
376
"""See RepositoryFormat.get_format_string()."""
377
return b"Bazaar-NG Knit Repository Format 1"
379
def get_format_description(self):
380
"""See RepositoryFormat.get_format_description()."""
381
return "Knit repository format 1"
384
class RepositoryFormatKnit3(RepositoryFormatKnit):
385
"""Bzr repository knit format 3.
387
This repository format has:
388
- knits for file texts and inventory
389
- hash subdirectory based stores.
390
- knits for revisions and signatures
391
- TextStores for revisions and signatures.
392
- a format marker of its own
393
- an optional 'shared-storage' flag
394
- an optional 'no-working-trees' flag
396
- support for recording full info about the tree root
397
- support for recording tree-references
400
repository_class = KnitRepository
401
_commit_builder_class = VersionedFileRootCommitBuilder
402
rich_root_data = True
404
supports_tree_reference = True
406
def _serializer(self):
407
return xml7.serializer_v7
409
def _get_matching_bzrdir(self):
410
return controldir.format_registry.make_controldir('dirstate-with-subtree')
412
def _ignore_setting_bzrdir(self, format):
415
_matchingcontroldir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
418
def get_format_string(cls):
419
"""See RepositoryFormat.get_format_string()."""
420
return b"Bazaar Knit Repository Format 3 (bzr 0.15)\n"
422
def get_format_description(self):
423
"""See RepositoryFormat.get_format_description()."""
424
return "Knit repository format 3"
427
class RepositoryFormatKnit4(RepositoryFormatKnit):
428
"""Bzr repository knit format 4.
430
This repository format has everything in format 3, except for
432
- knits for file texts and inventory
433
- hash subdirectory based stores.
434
- knits for revisions and signatures
435
- TextStores for revisions and signatures.
436
- a format marker of its own
437
- an optional 'shared-storage' flag
438
- an optional 'no-working-trees' flag
440
- support for recording full info about the tree root
443
repository_class = KnitRepository
444
_commit_builder_class = VersionedFileRootCommitBuilder
445
rich_root_data = True
446
supports_tree_reference = False
448
def _serializer(self):
449
return xml6.serializer_v6
451
def _get_matching_bzrdir(self):
452
return controldir.format_registry.make_controldir('rich-root')
454
def _ignore_setting_bzrdir(self, format):
457
_matchingcontroldir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
460
def get_format_string(cls):
461
"""See RepositoryFormat.get_format_string()."""
462
return b'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
464
def get_format_description(self):
465
"""See RepositoryFormat.get_format_description()."""
466
return "Knit repository format 4"
469
class InterKnitRepo(InterSameDataRepository):
470
"""Optimised code paths between Knit based repositories."""
473
def _get_repo_format_to_test(self):
474
return RepositoryFormatKnit1()
477
def is_compatible(source, target):
478
"""Be compatible with known Knit formats.
480
We don't test for the stores being of specific types because that
481
could lead to confusing results, and there is no need to be
485
are_knits = (isinstance(source._format, RepositoryFormatKnit) and
486
isinstance(target._format, RepositoryFormatKnit))
487
except AttributeError:
489
return are_knits and InterRepository._same_model(source, target)
491
def search_missing_revision_ids(self,
492
find_ghosts=True, revision_ids=None, if_present_ids=None,
494
"""See InterRepository.search_missing_revision_ids()."""
495
with self.lock_read():
496
source_ids_set = self._present_source_revisions_for(
497
revision_ids, if_present_ids)
498
# source_ids is the worst possible case we may need to pull.
499
# now we want to filter source_ids against what we actually
500
# have in target, but don't try to check for existence where we know
501
# we do not have a revision as that would be pointless.
502
target_ids = set(self.target.all_revision_ids())
503
possibly_present_revisions = target_ids.intersection(source_ids_set)
504
actually_present_revisions = set(
505
self.target._eliminate_revisions_not_present(possibly_present_revisions))
506
required_revisions = source_ids_set.difference(actually_present_revisions)
507
if revision_ids is not None:
508
# we used get_ancestry to determine source_ids then we are assured all
509
# revisions referenced are present as they are installed in topological order.
510
# and the tip revision was validated by get_ancestry.
511
result_set = required_revisions
513
# if we just grabbed the possibly available ids, then
514
# we only have an estimate of whats available and need to validate
515
# that against the revision records.
517
self.source._eliminate_revisions_not_present(required_revisions))
518
if limit is not None:
519
topo_ordered = self.source.get_graph().iter_topo_order(result_set)
520
result_set = set(itertools.islice(topo_ordered, limit))
521
return self.source.revision_ids_to_search_result(result_set)
524
InterRepository.register_optimiser(InterKnitRepo)