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 is None:
72
raise ValueError('get_parent_map(None) is not valid')
73
if revision_id == _mod_revision.NULL_REVISION:
74
parent_map[revision_id] = ()
78
self._knit.get_parents_with_ghosts(revision_id))
79
except errors.RevisionNotPresent:
83
parents = (_mod_revision.NULL_REVISION,)
84
parent_map[revision_id] = parents
88
class KnitRepository(MetaDirRepository):
89
"""Knit format repository."""
91
# These attributes are inherited from the Repository base class. Setting
92
# them to None ensures that if the constructor is changed to not initialize
93
# them, or a subclass fails to call the constructor, that an error will
94
# occur rather than the system working but generating incorrect data.
95
_commit_builder_class = None
98
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
99
control_store, text_store, _commit_builder_class, _serializer):
100
MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
101
_revision_store, control_store, text_store)
102
self._commit_builder_class = _commit_builder_class
103
self._serializer = _serializer
104
self._reconcile_fixes_text_parents = True
105
control_store.get_scope = self.get_transaction
106
text_store.get_scope = self.get_transaction
107
_revision_store.get_scope = self.get_transaction
109
def _warn_if_deprecated(self):
110
# This class isn't deprecated
113
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
114
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
115
check_content=check_content)[0]
118
def _all_revision_ids(self):
119
"""See Repository.all_revision_ids()."""
120
# Knits get the revision graph from the index of the revision knit, so
121
# it's always possible even if they're on an unlistable transport.
122
return self._revision_store.all_revision_ids(self.get_transaction())
124
def fileid_involved_between_revs(self, from_revid, to_revid):
125
"""Find file_id(s) which are involved in the changes between revisions.
127
This determines the set of revisions which are involved, and then
128
finds all file ids affected by those revisions.
130
vf = self._get_revision_vf()
131
from_set = set(vf.get_ancestry(from_revid))
132
to_set = set(vf.get_ancestry(to_revid))
133
changed = to_set.difference(from_set)
134
return self._fileid_involved_by_set(changed)
136
def fileid_involved(self, last_revid=None):
137
"""Find all file_ids modified in the ancestry of last_revid.
139
:param last_revid: If None, last_revision() will be used.
142
changed = set(self.all_revision_ids())
144
changed = set(self.get_ancestry(last_revid))
147
return self._fileid_involved_by_set(changed)
150
def get_ancestry(self, revision_id, topo_sorted=True):
151
"""Return a list of revision-ids integrated by a revision.
153
This is topologically sorted, unless 'topo_sorted' is specified as
156
if _mod_revision.is_null(revision_id):
158
vf = self._get_revision_vf()
160
return [None] + vf.get_ancestry(revision_id, topo_sorted)
161
except errors.RevisionNotPresent:
162
raise errors.NoSuchRevision(self, revision_id)
164
@symbol_versioning.deprecated_method(symbol_versioning.one_two)
165
def get_data_stream(self, revision_ids):
166
"""See Repository.get_data_stream.
168
Deprecated in 1.2 for get_data_stream_for_search.
170
search_result = self.revision_ids_to_search_result(set(revision_ids))
171
return self.get_data_stream_for_search(search_result)
173
def get_data_stream_for_search(self, search):
174
"""See Repository.get_data_stream_for_search."""
175
item_keys = self.item_keys_introduced_by(search.get_keys())
176
for knit_kind, file_id, versions in item_keys:
178
if knit_kind == 'file':
179
name = ('file', file_id)
180
knit = self.weave_store.get_weave_or_empty(
181
file_id, self.get_transaction())
182
elif knit_kind == 'inventory':
183
knit = self.get_inventory_weave()
184
elif knit_kind == 'revisions':
185
knit = self._revision_store.get_revision_file(
186
self.get_transaction())
187
elif knit_kind == 'signatures':
188
knit = self._revision_store.get_signature_file(
189
self.get_transaction())
191
raise AssertionError('Unknown knit kind %r' % (knit_kind,))
192
yield name, _get_stream_as_bytes(knit, versions)
195
def get_revision(self, revision_id):
196
"""Return the Revision object for a named revision"""
197
revision_id = osutils.safe_revision_id(revision_id)
198
return self.get_revision_reconcile(revision_id)
200
def _get_revision_vf(self):
201
""":return: a versioned file containing the revisions."""
202
vf = self._revision_store.get_revision_file(self.get_transaction())
205
def has_revisions(self, revision_ids):
206
"""See Repository.has_revisions()."""
208
transaction = self.get_transaction()
209
for revision_id in revision_ids:
210
if self._revision_store.has_revision_id(revision_id, transaction):
211
result.add(revision_id)
215
def reconcile(self, other=None, thorough=False):
216
"""Reconcile this repository."""
217
from bzrlib.reconcile import KnitReconciler
218
reconciler = KnitReconciler(self, thorough=thorough)
219
reconciler.reconcile()
222
@symbol_versioning.deprecated_method(symbol_versioning.one_five)
223
def revision_parents(self, revision_id):
224
return self._get_revision_vf().get_parents(revision_id)
226
def _make_parents_provider(self):
227
return _KnitParentsProvider(self._get_revision_vf())
229
def _find_inconsistent_revision_parents(self):
230
"""Find revisions with different parent lists in the revision object
231
and in the index graph.
233
:returns: an iterator yielding tuples of (revison-id, parents-in-index,
234
parents-in-revision).
236
if not self.is_locked():
237
raise AssertionError()
238
vf = self._get_revision_vf()
239
for index_version in vf.versions():
240
parents_according_to_index = tuple(vf.get_parents_with_ghosts(
242
revision = self.get_revision(index_version)
243
parents_according_to_revision = tuple(revision.parent_ids)
244
if parents_according_to_index != parents_according_to_revision:
245
yield (index_version, parents_according_to_index,
246
parents_according_to_revision)
248
def _check_for_inconsistent_revision_parents(self):
249
inconsistencies = list(self._find_inconsistent_revision_parents())
251
raise errors.BzrCheckError(
252
"Revision knit has inconsistent parents.")
254
def revision_graph_can_have_wrong_parents(self):
255
# The revision.kndx could potentially claim a revision has a different
256
# parent to the revision text.
260
class RepositoryFormatKnit(MetaDirRepositoryFormat):
261
"""Bzr repository knit format (generalized).
263
This repository format has:
264
- knits for file texts and inventory
265
- hash subdirectory based stores.
266
- knits for revisions and signatures
267
- TextStores for revisions and signatures.
268
- a format marker of its own
269
- an optional 'shared-storage' flag
270
- an optional 'no-working-trees' flag
274
# Set this attribute in derived classes to control the repository class
275
# created by open and initialize.
276
repository_class = None
277
# Set this attribute in derived classes to control the
278
# _commit_builder_class that the repository objects will have passed to
280
_commit_builder_class = None
281
# Set this attribute in derived clases to control the _serializer that the
282
# repository objects will have passed to their constructor.
283
_serializer = xml5.serializer_v5
284
# Knit based repositories handle ghosts reasonably well.
285
supports_ghosts = True
286
# External lookups are not supported in this format.
287
supports_external_lookups = False
289
def _get_control_store(self, repo_transport, control_files):
290
"""Return the control store for this repository."""
291
return VersionedFileStore(
294
file_mode=control_files._file_mode,
295
versionedfile_class=knit.make_file_knit,
296
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
299
def _get_revision_store(self, repo_transport, control_files):
300
"""See RepositoryFormat._get_revision_store()."""
301
versioned_file_store = VersionedFileStore(
303
file_mode=control_files._file_mode,
306
versionedfile_class=knit.make_file_knit,
307
versionedfile_kwargs={'delta':False,
308
'factory':knit.KnitPlainFactory(),
312
return KnitRevisionStore(versioned_file_store)
314
def _get_text_store(self, transport, control_files):
315
"""See RepositoryFormat._get_text_store()."""
316
return self._get_versioned_file_store('knits',
319
versionedfile_class=knit.make_file_knit,
320
versionedfile_kwargs={
321
'create_parent_dir':True,
323
'dir_mode':control_files._dir_mode,
327
def initialize(self, a_bzrdir, shared=False):
328
"""Create a knit format 1 repository.
330
:param a_bzrdir: bzrdir to contain the new repository; must already
332
:param shared: If true the repository will be initialized as a shared
335
mutter('creating repository in %s.', a_bzrdir.transport.base)
338
utf8_files = [('format', self.get_format_string())]
340
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
341
repo_transport = a_bzrdir.get_repository_transport(None)
342
control_files = lockable_files.LockableFiles(repo_transport,
343
'lock', lockdir.LockDir)
344
control_store = self._get_control_store(repo_transport, control_files)
345
transaction = transactions.WriteTransaction()
346
# trigger a write of the inventory store.
347
control_store.get_weave_or_empty('inventory', transaction)
348
_revision_store = self._get_revision_store(repo_transport, control_files)
349
# the revision id here is irrelevant: it will not be stored, and cannot
351
_revision_store.has_revision_id('A', transaction)
352
_revision_store.get_signature_file(transaction)
353
return self.open(a_bzrdir=a_bzrdir, _found=True)
355
def open(self, a_bzrdir, _found=False, _override_transport=None):
356
"""See RepositoryFormat.open().
358
:param _override_transport: INTERNAL USE ONLY. Allows opening the
359
repository at a slightly different url
360
than normal. I.e. during 'upgrade'.
363
format = RepositoryFormat.find_format(a_bzrdir)
364
if _override_transport is not None:
365
repo_transport = _override_transport
367
repo_transport = a_bzrdir.get_repository_transport(None)
368
control_files = lockable_files.LockableFiles(repo_transport,
369
'lock', lockdir.LockDir)
370
text_store = self._get_text_store(repo_transport, control_files)
371
control_store = self._get_control_store(repo_transport, control_files)
372
_revision_store = self._get_revision_store(repo_transport, control_files)
373
return self.repository_class(_format=self,
375
control_files=control_files,
376
_revision_store=_revision_store,
377
control_store=control_store,
378
text_store=text_store,
379
_commit_builder_class=self._commit_builder_class,
380
_serializer=self._serializer)
383
class RepositoryFormatKnit1(RepositoryFormatKnit):
384
"""Bzr repository knit format 1.
386
This repository format has:
387
- knits for file texts and inventory
388
- hash subdirectory based stores.
389
- knits for revisions and signatures
390
- TextStores for revisions and signatures.
391
- a format marker of its own
392
- an optional 'shared-storage' flag
393
- an optional 'no-working-trees' flag
396
This format was introduced in bzr 0.8.
399
repository_class = KnitRepository
400
_commit_builder_class = CommitBuilder
401
_serializer = xml5.serializer_v5
403
def __ne__(self, other):
404
return self.__class__ is not other.__class__
406
def get_format_string(self):
407
"""See RepositoryFormat.get_format_string()."""
408
return "Bazaar-NG Knit Repository Format 1"
410
def get_format_description(self):
411
"""See RepositoryFormat.get_format_description()."""
412
return "Knit repository format 1"
414
def check_conversion_target(self, target_format):
418
class RepositoryFormatKnit3(RepositoryFormatKnit):
419
"""Bzr repository knit format 3.
421
This repository format has:
422
- knits for file texts and inventory
423
- hash subdirectory based stores.
424
- knits for revisions and signatures
425
- TextStores for revisions and signatures.
426
- a format marker of its own
427
- an optional 'shared-storage' flag
428
- an optional 'no-working-trees' flag
430
- support for recording full info about the tree root
431
- support for recording tree-references
434
repository_class = KnitRepository
435
_commit_builder_class = RootCommitBuilder
436
rich_root_data = True
437
supports_tree_reference = True
438
_serializer = xml7.serializer_v7
440
def _get_matching_bzrdir(self):
441
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
443
def _ignore_setting_bzrdir(self, format):
446
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
448
def check_conversion_target(self, target_format):
449
if not target_format.rich_root_data:
450
raise errors.BadConversionTarget(
451
'Does not support rich root data.', target_format)
452
if not getattr(target_format, 'supports_tree_reference', False):
453
raise errors.BadConversionTarget(
454
'Does not support nested trees', target_format)
456
def get_format_string(self):
457
"""See RepositoryFormat.get_format_string()."""
458
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
460
def get_format_description(self):
461
"""See RepositoryFormat.get_format_description()."""
462
return "Knit repository format 3"
465
class RepositoryFormatKnit4(RepositoryFormatKnit):
466
"""Bzr repository knit format 4.
468
This repository format has everything in format 3, except for
470
- knits for file texts and inventory
471
- hash subdirectory based stores.
472
- knits for revisions and signatures
473
- TextStores for revisions and signatures.
474
- a format marker of its own
475
- an optional 'shared-storage' flag
476
- an optional 'no-working-trees' flag
478
- support for recording full info about the tree root
481
repository_class = KnitRepository
482
_commit_builder_class = RootCommitBuilder
483
rich_root_data = True
484
supports_tree_reference = False
485
_serializer = xml6.serializer_v6
487
def _get_matching_bzrdir(self):
488
return bzrdir.format_registry.make_bzrdir('rich-root')
490
def _ignore_setting_bzrdir(self, format):
493
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
495
def check_conversion_target(self, target_format):
496
if not target_format.rich_root_data:
497
raise errors.BadConversionTarget(
498
'Does not support rich root data.', target_format)
500
def get_format_string(self):
501
"""See RepositoryFormat.get_format_string()."""
502
return 'Bazaar Knit Repository Format 4 (bzr 1.0)\n'
504
def get_format_description(self):
505
"""See RepositoryFormat.get_format_description()."""
506
return "Knit repository format 4"
509
def _get_stream_as_bytes(knit, required_versions):
510
"""Generate a serialised data stream.
512
The format is a bencoding of a list. The first element of the list is a
513
string of the format signature, then each subsequent element is a list
514
corresponding to a record. Those lists contain:
521
:returns: a bencoded list.
523
knit_stream = knit.get_data_stream(required_versions)
524
format_signature, data_list, callable = knit_stream
526
data.append(format_signature)
527
for version, options, length, parents in data_list:
528
data.append([version, options, parents, callable(length)])
529
return bencode.bencode(data)