13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
17
from bzrlib.lazy_import import lazy_import
20
18
lazy_import(globals(), """
22
from bzrlib.store import revision
23
from bzrlib.store.revision.knit import KnitRevisionStore
30
revision as _mod_revision,
39
39
from bzrlib.decorators import needs_read_lock, needs_write_lock
40
40
from bzrlib.repository import (
43
RepositoryFormatMetaDir,
45
from bzrlib.vf_repository import (
46
InterSameDataRepository,
47
MetaDirVersionedFileRepository,
48
MetaDirVersionedFileRepositoryFormat,
49
VersionedFileCommitBuilder,
50
VersionedFileRootCommitBuilder,
52
from bzrlib import symbol_versioning
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
55
53
class _KnitParentsProvider(object):
119
93
_commit_builder_class = None
120
94
_serializer = None
122
def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
124
super(KnitRepository, self).__init__(_format, a_bzrdir, control_files)
96
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
97
control_store, text_store, _commit_builder_class, _serializer):
98
MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
99
_revision_store, control_store, text_store)
125
100
self._commit_builder_class = _commit_builder_class
126
101
self._serializer = _serializer
127
102
self._reconcile_fixes_text_parents = True
103
control_store.get_scope = self.get_transaction
104
text_store.get_scope = self.get_transaction
105
_revision_store.get_scope = self.get_transaction
107
def _warn_if_deprecated(self):
108
# This class isn't deprecated
111
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
112
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
113
check_content=check_content)[0]
130
116
def _all_revision_ids(self):
131
117
"""See Repository.all_revision_ids()."""
132
return [key[0] for key in self.revisions.keys()]
134
def _activate_new_inventory(self):
135
"""Put a replacement inventory.new into use as inventories."""
136
# Copy the content across
138
t.copy('inventory.new.kndx', 'inventory.kndx')
140
t.copy('inventory.new.knit', 'inventory.knit')
141
except errors.NoSuchFile:
142
# empty inventories knit
143
t.delete('inventory.knit')
144
# delete the temp inventory
145
t.delete('inventory.new.kndx')
147
t.delete('inventory.new.knit')
148
except errors.NoSuchFile:
149
# empty inventories knit
151
# Force index reload (sanity check)
152
self.inventories._index._reset_cache()
153
self.inventories.keys()
155
def _backup_inventory(self):
157
t.copy('inventory.kndx', 'inventory.backup.kndx')
158
t.copy('inventory.knit', 'inventory.backup.knit')
160
def _move_file_id(self, from_id, to_id):
161
t = self._transport.clone('knits')
162
from_rel_url = self.texts._index._mapper.map((from_id, None))
163
to_rel_url = self.texts._index._mapper.map((to_id, None))
164
# We expect both files to always exist in this case.
165
for suffix in ('.knit', '.kndx'):
166
t.rename(from_rel_url + suffix, to_rel_url + suffix)
168
def _remove_file_id(self, file_id):
169
t = self._transport.clone('knits')
170
rel_url = self.texts._index._mapper.map((file_id, None))
171
for suffix in ('.kndx', '.knit'):
173
t.delete(rel_url + suffix)
174
except errors.NoSuchFile:
177
def _temp_inventories(self):
178
result = self._format._get_inventories(self._transport, self,
180
# Reconciling when the output has no revisions would result in no
181
# writes - but we want to ensure there is an inventory for
182
# compatibility with older clients that don't lazy-load.
183
result.get_parent_map([('A',)])
118
# Knits get the revision graph from the index of the revision knit, so
119
# it's always possible even if they're on an unlistable transport.
120
return self._revision_store.all_revision_ids(self.get_transaction())
122
def fileid_involved_between_revs(self, from_revid, to_revid):
123
"""Find file_id(s) which are involved in the changes between revisions.
125
This determines the set of revisions which are involved, and then
126
finds all file ids affected by those revisions.
128
vf = self._get_revision_vf()
129
from_set = set(vf.get_ancestry(from_revid))
130
to_set = set(vf.get_ancestry(to_revid))
131
changed = to_set.difference(from_set)
132
return self._fileid_involved_by_set(changed)
134
def fileid_involved(self, last_revid=None):
135
"""Find all file_ids modified in the ancestry of last_revid.
137
:param last_revid: If None, last_revision() will be used.
140
changed = set(self.all_revision_ids())
142
changed = set(self.get_ancestry(last_revid))
145
return self._fileid_involved_by_set(changed)
148
def get_ancestry(self, revision_id, topo_sorted=True):
149
"""Return a list of revision-ids integrated by a revision.
151
This is topologically sorted, unless 'topo_sorted' is specified as
154
if _mod_revision.is_null(revision_id):
156
vf = self._get_revision_vf()
158
return [None] + vf.get_ancestry(revision_id, topo_sorted)
159
except errors.RevisionNotPresent:
160
raise errors.NoSuchRevision(self, revision_id)
162
@symbol_versioning.deprecated_method(symbol_versioning.one_two)
163
def get_data_stream(self, revision_ids):
164
"""See Repository.get_data_stream.
166
Deprecated in 1.2 for get_data_stream_for_search.
168
search_result = self.revision_ids_to_search_result(set(revision_ids))
169
return self.get_data_stream_for_search(search_result)
171
def get_data_stream_for_search(self, search):
172
"""See Repository.get_data_stream_for_search."""
173
item_keys = self.item_keys_introduced_by(search.get_keys())
174
for knit_kind, file_id, versions in item_keys:
176
if knit_kind == 'file':
177
name = ('file', file_id)
178
knit = self.weave_store.get_weave_or_empty(
179
file_id, self.get_transaction())
180
elif knit_kind == 'inventory':
181
knit = self.get_inventory_weave()
182
elif knit_kind == 'revisions':
183
knit = self._revision_store.get_revision_file(
184
self.get_transaction())
185
elif knit_kind == 'signatures':
186
knit = self._revision_store.get_signature_file(
187
self.get_transaction())
189
raise AssertionError('Unknown knit kind %r' % (knit_kind,))
190
yield name, _get_stream_as_bytes(knit, versions)
187
193
def get_revision(self, revision_id):
209
216
reconciler = KnitReconciler(self, thorough=thorough)
210
217
reconciler.reconcile()
211
218
return reconciler
220
@symbol_versioning.deprecated_method(symbol_versioning.one_five)
221
def revision_parents(self, revision_id):
222
return self._get_revision_vf().get_parents(revision_id)
213
224
def _make_parents_provider(self):
214
return _KnitsParentsProvider(self.revisions)
217
class RepositoryFormatKnit(MetaDirVersionedFileRepositoryFormat):
218
"""Bzr repository knit format (generalized).
225
return _KnitParentsProvider(self._get_revision_vf())
227
def _find_inconsistent_revision_parents(self):
228
"""Find revisions with different parent lists in the revision object
229
and in the index graph.
231
:returns: an iterator yielding tuples of (revison-id, parents-in-index,
232
parents-in-revision).
234
if not self.is_locked():
235
raise AssertionError()
236
vf = self._get_revision_vf()
237
for index_version in vf.versions():
238
parents_according_to_index = tuple(vf.get_parents_with_ghosts(
240
revision = self.get_revision(index_version)
241
parents_according_to_revision = tuple(revision.parent_ids)
242
if parents_according_to_index != parents_according_to_revision:
243
yield (index_version, parents_according_to_index,
244
parents_according_to_revision)
246
def _check_for_inconsistent_revision_parents(self):
247
inconsistencies = list(self._find_inconsistent_revision_parents())
249
raise errors.BzrCheckError(
250
"Revision knit has inconsistent parents.")
252
def revision_graph_can_have_wrong_parents(self):
253
# The revision.kndx could potentially claim a revision has a different
254
# parent to the revision text.
258
class RepositoryFormatKnit(MetaDirRepositoryFormat):
259
"""Bzr repository knit format (generalized).
220
261
This repository format has:
221
262
- knits for file texts and inventory
237
278
_commit_builder_class = None
238
279
# Set this attribute in derived clases to control the _serializer that the
239
280
# repository objects will have passed to their constructor.
241
def _serializer(self):
242
return xml5.serializer_v5
281
_serializer = xml5.serializer_v5
243
282
# Knit based repositories handle ghosts reasonably well.
244
283
supports_ghosts = True
245
284
# External lookups are not supported in this format.
246
285
supports_external_lookups = False
248
supports_chks = False
249
_fetch_order = 'topological'
250
_fetch_uses_deltas = True
252
supports_funky_characters = True
253
# The revision.kndx could potentially claim a revision has a different
254
# parent to the revision text.
255
revision_graph_can_have_wrong_parents = True
257
def _get_inventories(self, repo_transport, repo, name='inventory'):
258
mapper = versionedfile.ConstantMapper(name)
259
index = _mod_knit._KndxIndex(repo_transport, mapper,
260
repo.get_transaction, repo.is_write_locked, repo.is_locked)
261
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
262
return _mod_knit.KnitVersionedFiles(index, access, annotated=False)
264
def _get_revisions(self, repo_transport, repo):
265
mapper = versionedfile.ConstantMapper('revisions')
266
index = _mod_knit._KndxIndex(repo_transport, mapper,
267
repo.get_transaction, repo.is_write_locked, repo.is_locked)
268
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
269
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
272
def _get_signatures(self, repo_transport, repo):
273
mapper = versionedfile.ConstantMapper('signatures')
274
index = _mod_knit._KndxIndex(repo_transport, mapper,
275
repo.get_transaction, repo.is_write_locked, repo.is_locked)
276
access = _mod_knit._KnitKeyAccess(repo_transport, mapper)
277
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=0,
280
def _get_texts(self, repo_transport, repo):
281
mapper = versionedfile.HashEscapedPrefixMapper()
282
base_transport = repo_transport.clone('knits')
283
index = _mod_knit._KndxIndex(base_transport, mapper,
284
repo.get_transaction, repo.is_write_locked, repo.is_locked)
285
access = _mod_knit._KnitKeyAccess(base_transport, mapper)
286
return _mod_knit.KnitVersionedFiles(index, access, max_delta_chain=200,
287
def _get_control_store(self, repo_transport, control_files):
288
"""Return the control store for this repository."""
289
return VersionedFileStore(
292
file_mode=control_files._file_mode,
293
versionedfile_class=knit.make_file_knit,
294
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
297
def _get_revision_store(self, repo_transport, control_files):
298
"""See RepositoryFormat._get_revision_store()."""
299
versioned_file_store = VersionedFileStore(
301
file_mode=control_files._file_mode,
304
versionedfile_class=knit.make_file_knit,
305
versionedfile_kwargs={'delta':False,
306
'factory':knit.KnitPlainFactory(),
310
return KnitRevisionStore(versioned_file_store)
312
def _get_text_store(self, transport, control_files):
313
"""See RepositoryFormat._get_text_store()."""
314
return self._get_versioned_file_store('knits',
317
versionedfile_class=knit.make_file_knit,
318
versionedfile_kwargs={
319
'create_parent_dir':True,
321
'dir_mode':control_files._dir_mode,
289
325
def initialize(self, a_bzrdir, shared=False):
290
326
"""Create a knit format 1 repository.
294
330
:param shared: If true the repository will be initialized as a shared
297
trace.mutter('creating repository in %s.', a_bzrdir.transport.base)
333
mutter('creating repository in %s.', a_bzrdir.transport.base)
300
336
utf8_files = [('format', self.get_format_string())]
302
338
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
303
339
repo_transport = a_bzrdir.get_repository_transport(None)
304
340
control_files = lockable_files.LockableFiles(repo_transport,
305
341
'lock', lockdir.LockDir)
342
control_store = self._get_control_store(repo_transport, control_files)
306
343
transaction = transactions.WriteTransaction()
307
result = self.open(a_bzrdir=a_bzrdir, _found=True)
344
# trigger a write of the inventory store.
345
control_store.get_weave_or_empty('inventory', transaction)
346
_revision_store = self._get_revision_store(repo_transport, control_files)
309
347
# the revision id here is irrelevant: it will not be stored, and cannot
310
# already exist, we do this to create files on disk for older clients.
311
result.inventories.get_parent_map([('A',)])
312
result.revisions.get_parent_map([('A',)])
313
result.signatures.get_parent_map([('A',)])
315
self._run_post_repo_init_hooks(result, a_bzrdir, shared)
349
_revision_store.has_revision_id('A', transaction)
350
_revision_store.get_signature_file(transaction)
351
return self.open(a_bzrdir=a_bzrdir, _found=True)
318
353
def open(self, a_bzrdir, _found=False, _override_transport=None):
319
354
"""See RepositoryFormat.open().
321
356
:param _override_transport: INTERNAL USE ONLY. Allows opening the
322
357
repository at a slightly different url
323
358
than normal. I.e. during 'upgrade'.
326
format = RepositoryFormatMetaDir.find_format(a_bzrdir)
361
format = RepositoryFormat.find_format(a_bzrdir)
327
362
if _override_transport is not None:
328
363
repo_transport = _override_transport
330
365
repo_transport = a_bzrdir.get_repository_transport(None)
331
366
control_files = lockable_files.LockableFiles(repo_transport,
332
367
'lock', lockdir.LockDir)
333
repo = self.repository_class(_format=self,
368
text_store = self._get_text_store(repo_transport, control_files)
369
control_store = self._get_control_store(repo_transport, control_files)
370
_revision_store = self._get_revision_store(repo_transport, control_files)
371
return self.repository_class(_format=self,
334
372
a_bzrdir=a_bzrdir,
335
373
control_files=control_files,
374
_revision_store=_revision_store,
375
control_store=control_store,
376
text_store=text_store,
336
377
_commit_builder_class=self._commit_builder_class,
337
378
_serializer=self._serializer)
338
repo.revisions = self._get_revisions(repo_transport, repo)
339
repo.signatures = self._get_signatures(repo_transport, repo)
340
repo.inventories = self._get_inventories(repo_transport, repo)
341
repo.texts = self._get_texts(repo_transport, repo)
342
repo.chk_bytes = None
343
repo._transport = repo_transport
347
381
class RepositoryFormatKnit1(RepositoryFormatKnit):
398
432
repository_class = KnitRepository
399
_commit_builder_class = VersionedFileRootCommitBuilder
433
_commit_builder_class = RootCommitBuilder
400
434
rich_root_data = True
402
435
supports_tree_reference = True
404
def _serializer(self):
405
return xml7.serializer_v7
436
_serializer = xml7.serializer_v7
407
438
def _get_matching_bzrdir(self):
408
return controldir.format_registry.make_bzrdir('dirstate-with-subtree')
439
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
410
441
def _ignore_setting_bzrdir(self, format):
413
444
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
416
def get_format_string(cls):
446
def check_conversion_target(self, target_format):
447
if not target_format.rich_root_data:
448
raise errors.BadConversionTarget(
449
'Does not support rich root data.', target_format)
450
if not getattr(target_format, 'supports_tree_reference', False):
451
raise errors.BadConversionTarget(
452
'Does not support nested trees', target_format)
454
def get_format_string(self):
417
455
"""See RepositoryFormat.get_format_string()."""
418
456
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
464
504
return "Knit repository format 4"
467
class InterKnitRepo(InterSameDataRepository):
468
"""Optimised code paths between Knit based repositories."""
471
def _get_repo_format_to_test(self):
472
return RepositoryFormatKnit1()
475
def is_compatible(source, target):
476
"""Be compatible with known Knit formats.
478
We don't test for the stores being of specific types because that
479
could lead to confusing results, and there is no need to be
483
are_knits = (isinstance(source._format, RepositoryFormatKnit) and
484
isinstance(target._format, RepositoryFormatKnit))
485
except AttributeError:
487
return are_knits and InterRepository._same_model(source, target)
490
def search_missing_revision_ids(self,
491
find_ghosts=True, revision_ids=None, if_present_ids=None,
493
"""See InterRepository.search_missing_revision_ids()."""
494
source_ids_set = self._present_source_revisions_for(
495
revision_ids, if_present_ids)
496
# source_ids is the worst possible case we may need to pull.
497
# now we want to filter source_ids against what we actually
498
# have in target, but don't try to check for existence where we know
499
# we do not have a revision as that would be pointless.
500
target_ids = set(self.target.all_revision_ids())
501
possibly_present_revisions = target_ids.intersection(source_ids_set)
502
actually_present_revisions = set(
503
self.target._eliminate_revisions_not_present(possibly_present_revisions))
504
required_revisions = source_ids_set.difference(actually_present_revisions)
505
if revision_ids is not None:
506
# we used get_ancestry to determine source_ids then we are assured all
507
# revisions referenced are present as they are installed in topological order.
508
# and the tip revision was validated by get_ancestry.
509
result_set = required_revisions
511
# if we just grabbed the possibly available ids, then
512
# we only have an estimate of whats available and need to validate
513
# that against the revision records.
515
self.source._eliminate_revisions_not_present(required_revisions))
516
if limit is not None:
517
topo_ordered = self.source.get_graph().iter_topo_order(result_set)
518
result_set = set(itertools.islice(topo_ordered, limit))
519
return self.source.revision_ids_to_search_result(result_set)
522
InterRepository.register_optimiser(InterKnitRepo)
507
def _get_stream_as_bytes(knit, required_versions):
508
"""Generate a serialised data stream.
510
The format is a bencoding of a list. The first element of the list is a
511
string of the format signature, then each subsequent element is a list
512
corresponding to a record. Those lists contain:
519
:returns: a bencoded list.
521
knit_stream = knit.get_data_stream(required_versions)
522
format_signature, data_list, callable = knit_stream
524
data.append(format_signature)
525
for version, options, length, parents in data_list:
526
data.append([version, options, parents, callable(length)])
527
return bencode.bencode(data)