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(), """
22
from bzrlib.store import revision
23
from bzrlib.store.revision.knit import KnitRevisionStore
39
from bzrlib.decorators import needs_read_lock, needs_write_lock
40
from bzrlib.repository import (
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
53
class _KnitParentsProvider(object):
55
def __init__(self, knit):
59
return 'KnitParentsProvider(%r)' % self._knit
61
@symbol_versioning.deprecated_method(symbol_versioning.one_one)
62
def get_parents(self, revision_ids):
63
"""See graph._StackedParentsProvider.get_parents"""
64
parent_map = self.get_parent_map(revision_ids)
65
return [parent_map.get(r, None) for r in revision_ids]
67
def get_parent_map(self, keys):
68
"""See graph._StackedParentsProvider.get_parent_map"""
70
for revision_id in keys:
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 KnitRepository(MetaDirRepository):
87
"""Knit format repository."""
89
# These attributes are inherited from the Repository base class. Setting
90
# them to None ensures that if the constructor is changed to not initialize
91
# them, or a subclass fails to call the constructor, that an error will
92
# occur rather than the system working but generating incorrect data.
93
_commit_builder_class = None
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)
100
self._commit_builder_class = _commit_builder_class
101
self._serializer = _serializer
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]
116
def _all_revision_ids(self):
117
"""See Repository.all_revision_ids()."""
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)
193
def get_revision(self, revision_id):
194
"""Return the Revision object for a named revision"""
195
revision_id = osutils.safe_revision_id(revision_id)
196
return self.get_revision_reconcile(revision_id)
198
def _get_revision_vf(self):
199
""":return: a versioned file containing the revisions."""
200
vf = self._revision_store.get_revision_file(self.get_transaction())
203
def has_revisions(self, revision_ids):
204
"""See Repository.has_revisions()."""
206
transaction = self.get_transaction()
207
for revision_id in revision_ids:
208
if self._revision_store.has_revision_id(revision_id, transaction):
209
result.add(revision_id)
213
def reconcile(self, other=None, thorough=False):
214
"""Reconcile this repository."""
215
from bzrlib.reconcile import KnitReconciler
216
reconciler = KnitReconciler(self, thorough=thorough)
217
reconciler.reconcile()
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)
224
def _make_parents_provider(self):
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).
261
This repository format has:
262
- knits for file texts and inventory
263
- hash subdirectory based stores.
264
- knits for revisions and signatures
265
- TextStores for revisions and signatures.
266
- a format marker of its own
267
- an optional 'shared-storage' flag
268
- an optional 'no-working-trees' flag
272
# Set this attribute in derived classes to control the repository class
273
# created by open and initialize.
274
repository_class = None
275
# Set this attribute in derived classes to control the
276
# _commit_builder_class that the repository objects will have passed to
278
_commit_builder_class = None
279
# Set this attribute in derived clases to control the _serializer that the
280
# repository objects will have passed to their constructor.
281
_serializer = xml5.serializer_v5
282
# Knit based repositories handle ghosts reasonably well.
283
supports_ghosts = True
284
# External lookups are not supported in this format.
285
supports_external_lookups = False
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,
325
def initialize(self, a_bzrdir, shared=False):
326
"""Create a knit format 1 repository.
328
:param a_bzrdir: bzrdir to contain the new repository; must already
330
:param shared: If true the repository will be initialized as a shared
333
mutter('creating repository in %s.', a_bzrdir.transport.base)
336
utf8_files = [('format', self.get_format_string())]
338
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
339
repo_transport = a_bzrdir.get_repository_transport(None)
340
control_files = lockable_files.LockableFiles(repo_transport,
341
'lock', lockdir.LockDir)
342
control_store = self._get_control_store(repo_transport, control_files)
343
transaction = transactions.WriteTransaction()
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)
347
# the revision id here is irrelevant: it will not be stored, and cannot
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)
353
def open(self, a_bzrdir, _found=False, _override_transport=None):
354
"""See RepositoryFormat.open().
356
:param _override_transport: INTERNAL USE ONLY. Allows opening the
357
repository at a slightly different url
358
than normal. I.e. during 'upgrade'.
361
format = RepositoryFormat.find_format(a_bzrdir)
362
if _override_transport is not None:
363
repo_transport = _override_transport
365
repo_transport = a_bzrdir.get_repository_transport(None)
366
control_files = lockable_files.LockableFiles(repo_transport,
367
'lock', lockdir.LockDir)
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,
373
control_files=control_files,
374
_revision_store=_revision_store,
375
control_store=control_store,
376
text_store=text_store,
377
_commit_builder_class=self._commit_builder_class,
378
_serializer=self._serializer)
381
class RepositoryFormatKnit1(RepositoryFormatKnit):
382
"""Bzr repository knit format 1.
384
This repository format has:
385
- knits for file texts and inventory
386
- hash subdirectory based stores.
387
- knits for revisions and signatures
388
- TextStores for revisions and signatures.
389
- a format marker of its own
390
- an optional 'shared-storage' flag
391
- an optional 'no-working-trees' flag
394
This format was introduced in bzr 0.8.
397
repository_class = KnitRepository
398
_commit_builder_class = CommitBuilder
399
_serializer = xml5.serializer_v5
401
def __ne__(self, other):
402
return self.__class__ is not other.__class__
404
def get_format_string(self):
405
"""See RepositoryFormat.get_format_string()."""
406
return "Bazaar-NG Knit Repository Format 1"
408
def get_format_description(self):
409
"""See RepositoryFormat.get_format_description()."""
410
return "Knit repository format 1"
412
def check_conversion_target(self, target_format):
416
class RepositoryFormatKnit3(RepositoryFormatKnit):
417
"""Bzr repository knit format 3.
419
This repository format has:
420
- knits for file texts and inventory
421
- hash subdirectory based stores.
422
- knits for revisions and signatures
423
- TextStores for revisions and signatures.
424
- a format marker of its own
425
- an optional 'shared-storage' flag
426
- an optional 'no-working-trees' flag
428
- support for recording full info about the tree root
429
- support for recording tree-references
432
repository_class = KnitRepository
433
_commit_builder_class = RootCommitBuilder
434
rich_root_data = True
435
supports_tree_reference = True
436
_serializer = xml7.serializer_v7
438
def _get_matching_bzrdir(self):
439
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
441
def _ignore_setting_bzrdir(self, format):
444
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
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):
455
"""See RepositoryFormat.get_format_string()."""
456
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
458
def get_format_description(self):
459
"""See RepositoryFormat.get_format_description()."""
460
return "Knit repository format 3"
463
class RepositoryFormatKnit4(RepositoryFormatKnit):
464
"""Bzr repository knit format 4.
466
This repository format has everything in format 3, except for
468
- knits for file texts and inventory
469
- hash subdirectory based stores.
470
- knits for revisions and signatures
471
- TextStores for revisions and signatures.
472
- a format marker of its own
473
- an optional 'shared-storage' flag
474
- an optional 'no-working-trees' flag
476
- support for recording full info about the tree root
479
repository_class = KnitRepository
480
_commit_builder_class = RootCommitBuilder
481
rich_root_data = True
482
supports_tree_reference = False
483
_serializer = xml6.serializer_v6
485
def _get_matching_bzrdir(self):
486
return bzrdir.format_registry.make_bzrdir('rich-root')
488
def _ignore_setting_bzrdir(self, format):
491
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
493
def check_conversion_target(self, target_format):
494
if not target_format.rich_root_data:
495
raise errors.BadConversionTarget(
496
'Does not support rich root data.', target_format)
498
def get_format_string(self):
499
"""See RepositoryFormat.get_format_string()."""
500
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
502
def get_format_description(self):
503
"""See RepositoryFormat.get_format_description()."""
504
return "Knit repository format 4"
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)