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
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.repository import (
41
MetaDirRepositoryFormat,
45
import bzrlib.revision as _mod_revision
46
from bzrlib.store.versioned import VersionedFileStore
47
from bzrlib.trace import mutter, mutter_callsite
48
from bzrlib.util import bencode
51
class _KnitParentsProvider(object):
53
def __init__(self, knit):
57
return 'KnitParentsProvider(%r)' % self._knit
59
def get_parents(self, revision_ids):
61
for revision_id in revision_ids:
62
if revision_id == _mod_revision.NULL_REVISION:
66
parents = self._knit.get_parents_with_ghosts(revision_id)
67
except errors.RevisionNotPresent:
71
parents = [_mod_revision.NULL_REVISION]
72
parents_list.append(parents)
76
class KnitRepository(MetaDirRepository):
77
"""Knit format repository."""
79
_serializer = xml5.serializer_v5
81
def _warn_if_deprecated(self):
82
# This class isn't deprecated
85
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
86
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
87
check_content=check_content)[0]
90
def _all_revision_ids(self):
91
"""See Repository.all_revision_ids()."""
92
# Knits get the revision graph from the index of the revision knit, so
93
# it's always possible even if they're on an unlistable transport.
94
return self._revision_store.all_revision_ids(self.get_transaction())
96
def fileid_involved_between_revs(self, from_revid, to_revid):
97
"""Find file_id(s) which are involved in the changes between revisions.
99
This determines the set of revisions which are involved, and then
100
finds all file ids affected by those revisions.
102
from_revid = osutils.safe_revision_id(from_revid)
103
to_revid = osutils.safe_revision_id(to_revid)
104
vf = self._get_revision_vf()
105
from_set = set(vf.get_ancestry(from_revid))
106
to_set = set(vf.get_ancestry(to_revid))
107
changed = to_set.difference(from_set)
108
return self._fileid_involved_by_set(changed)
110
def fileid_involved(self, last_revid=None):
111
"""Find all file_ids modified in the ancestry of last_revid.
113
:param last_revid: If None, last_revision() will be used.
116
changed = set(self.all_revision_ids())
118
changed = set(self.get_ancestry(last_revid))
121
return self._fileid_involved_by_set(changed)
124
def get_ancestry(self, revision_id, topo_sorted=True):
125
"""Return a list of revision-ids integrated by a revision.
127
This is topologically sorted, unless 'topo_sorted' is specified as
130
if _mod_revision.is_null(revision_id):
132
revision_id = osutils.safe_revision_id(revision_id)
133
vf = self._get_revision_vf()
135
return [None] + vf.get_ancestry(revision_id, topo_sorted)
136
except errors.RevisionNotPresent:
137
raise errors.NoSuchRevision(self, revision_id)
140
def get_data_stream(self, revision_ids):
141
"""See Repository.get_data_stream."""
142
item_keys = self.item_keys_introduced_by(revision_ids)
143
for knit_kind, file_id, versions in item_keys:
145
if knit_kind == 'file':
146
name = ('file', file_id)
147
knit = self.weave_store.get_weave_or_empty(
148
file_id, self.get_transaction())
149
elif knit_kind == 'inventory':
150
knit = self.get_inventory_weave()
151
elif knit_kind == 'revisions':
152
knit = self._revision_store.get_revision_file(
153
self.get_transaction())
154
elif knit_kind == 'signatures':
155
knit = self._revision_store.get_signature_file(
156
self.get_transaction())
158
raise AssertionError('Unknown knit kind %r' % (knit_kind,))
159
yield name, _get_stream_as_bytes(knit, versions)
162
def get_revision(self, revision_id):
163
"""Return the Revision object for a named revision"""
164
revision_id = osutils.safe_revision_id(revision_id)
165
return self.get_revision_reconcile(revision_id)
168
def get_revision_graph(self, revision_id=None):
169
"""Return a dictionary containing the revision graph.
171
:param revision_id: The revision_id to get a graph from. If None, then
172
the entire revision graph is returned. This is a deprecated mode of
173
operation and will be removed in the future.
174
:return: a dictionary of revision_id->revision_parents_list.
176
if 'evil' in debug.debug_flags:
178
"get_revision_graph scales with size of history.")
179
# special case NULL_REVISION
180
if revision_id == _mod_revision.NULL_REVISION:
182
revision_id = osutils.safe_revision_id(revision_id)
183
a_weave = self._get_revision_vf()
184
if revision_id is None:
185
return a_weave.get_graph()
186
if revision_id not in a_weave:
187
raise errors.NoSuchRevision(self, revision_id)
189
# add what can be reached from revision_id
190
return a_weave.get_graph([revision_id])
193
def get_revision_graph_with_ghosts(self, revision_ids=None):
194
"""Return a graph of the revisions with ghosts marked as applicable.
196
:param revision_ids: an iterable of revisions to graph or None for all.
197
:return: a Graph object with the graph reachable from revision_ids.
199
if 'evil' in debug.debug_flags:
201
"get_revision_graph_with_ghosts scales with size of history.")
202
result = deprecated_graph.Graph()
203
vf = self._get_revision_vf()
204
versions = set(vf.versions())
206
pending = set(self.all_revision_ids())
209
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
210
# special case NULL_REVISION
211
if _mod_revision.NULL_REVISION in pending:
212
pending.remove(_mod_revision.NULL_REVISION)
213
required = set(pending)
216
revision_id = pending.pop()
217
if not revision_id in versions:
218
if revision_id in required:
219
raise errors.NoSuchRevision(self, revision_id)
221
result.add_ghost(revision_id)
222
# mark it as done so we don't try for it again.
223
done.add(revision_id)
225
parent_ids = vf.get_parents_with_ghosts(revision_id)
226
for parent_id in parent_ids:
227
# is this queued or done ?
228
if (parent_id not in pending and
229
parent_id not in done):
231
pending.add(parent_id)
232
result.add_node(revision_id, parent_ids)
233
done.add(revision_id)
236
def _get_revision_vf(self):
237
""":return: a versioned file containing the revisions."""
238
vf = self._revision_store.get_revision_file(self.get_transaction())
241
def _get_history_vf(self):
242
"""Get a versionedfile whose history graph reflects all revisions.
244
For knit repositories, this is the revision knit.
246
return self._get_revision_vf()
249
def reconcile(self, other=None, thorough=False):
250
"""Reconcile this repository."""
251
from bzrlib.reconcile import KnitReconciler
252
reconciler = KnitReconciler(self, thorough=thorough)
253
reconciler.reconcile()
256
def revision_parents(self, revision_id):
257
revision_id = osutils.safe_revision_id(revision_id)
258
return self._get_revision_vf().get_parents(revision_id)
260
def _make_parents_provider(self):
261
return _KnitParentsProvider(self._get_revision_vf())
264
class KnitRepository3(KnitRepository):
266
# knit3 repositories need a RootCommitBuilder
267
_commit_builder_class = RootCommitBuilder
269
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
270
control_store, text_store):
271
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
272
_revision_store, control_store, text_store)
273
self._serializer = xml7.serializer_v7
275
def deserialise_inventory(self, revision_id, xml):
276
"""Transform the xml into an inventory object.
278
:param revision_id: The expected revision id of the inventory.
279
:param xml: A serialised inventory.
281
result = self._serializer.read_inventory_from_string(xml)
282
assert result.root.revision is not None
285
def serialise_inventory(self, inv):
286
"""Transform the inventory object into XML text.
288
:param revision_id: The expected revision id of the inventory.
289
:param xml: A serialised inventory.
291
assert inv.revision_id is not None
292
assert inv.root.revision is not None
293
return KnitRepository.serialise_inventory(self, inv)
296
class RepositoryFormatKnit(MetaDirRepositoryFormat):
297
"""Bzr repository knit format (generalized).
299
This repository format has:
300
- knits for file texts and inventory
301
- hash subdirectory based stores.
302
- knits for revisions and signatures
303
- TextStores for revisions and signatures.
304
- a format marker of its own
305
- an optional 'shared-storage' flag
306
- an optional 'no-working-trees' flag
310
# Set this attribute in derived classes to control the repository class
311
# created by open and initialize.
312
repository_class = None
314
def _get_control_store(self, repo_transport, control_files):
315
"""Return the control store for this repository."""
316
return VersionedFileStore(
319
file_mode=control_files._file_mode,
320
versionedfile_class=knit.KnitVersionedFile,
321
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
324
def _get_revision_store(self, repo_transport, control_files):
325
"""See RepositoryFormat._get_revision_store()."""
326
versioned_file_store = VersionedFileStore(
328
file_mode=control_files._file_mode,
331
versionedfile_class=knit.KnitVersionedFile,
332
versionedfile_kwargs={'delta':False,
333
'factory':knit.KnitPlainFactory(),
337
return KnitRevisionStore(versioned_file_store)
339
def _get_text_store(self, transport, control_files):
340
"""See RepositoryFormat._get_text_store()."""
341
return self._get_versioned_file_store('knits',
344
versionedfile_class=knit.KnitVersionedFile,
345
versionedfile_kwargs={
346
'create_parent_dir':True,
348
'dir_mode':control_files._dir_mode,
352
def initialize(self, a_bzrdir, shared=False):
353
"""Create a knit format 1 repository.
355
:param a_bzrdir: bzrdir to contain the new repository; must already
357
:param shared: If true the repository will be initialized as a shared
360
mutter('creating repository in %s.', a_bzrdir.transport.base)
363
utf8_files = [('format', self.get_format_string())]
365
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
366
repo_transport = a_bzrdir.get_repository_transport(None)
367
control_files = lockable_files.LockableFiles(repo_transport,
368
'lock', lockdir.LockDir)
369
control_store = self._get_control_store(repo_transport, control_files)
370
transaction = transactions.WriteTransaction()
371
# trigger a write of the inventory store.
372
control_store.get_weave_or_empty('inventory', transaction)
373
_revision_store = self._get_revision_store(repo_transport, control_files)
374
# the revision id here is irrelevant: it will not be stored, and cannot
376
_revision_store.has_revision_id('A', transaction)
377
_revision_store.get_signature_file(transaction)
378
return self.open(a_bzrdir=a_bzrdir, _found=True)
380
def open(self, a_bzrdir, _found=False, _override_transport=None):
381
"""See RepositoryFormat.open().
383
:param _override_transport: INTERNAL USE ONLY. Allows opening the
384
repository at a slightly different url
385
than normal. I.e. during 'upgrade'.
388
format = RepositoryFormat.find_format(a_bzrdir)
389
assert format.__class__ == self.__class__
390
if _override_transport is not None:
391
repo_transport = _override_transport
393
repo_transport = a_bzrdir.get_repository_transport(None)
394
control_files = lockable_files.LockableFiles(repo_transport,
395
'lock', lockdir.LockDir)
396
text_store = self._get_text_store(repo_transport, control_files)
397
control_store = self._get_control_store(repo_transport, control_files)
398
_revision_store = self._get_revision_store(repo_transport, control_files)
399
return self.repository_class(_format=self,
401
control_files=control_files,
402
_revision_store=_revision_store,
403
control_store=control_store,
404
text_store=text_store)
407
class RepositoryFormatKnit1(RepositoryFormatKnit):
408
"""Bzr repository knit format 1.
410
This repository format has:
411
- knits for file texts and inventory
412
- hash subdirectory based stores.
413
- knits for revisions and signatures
414
- TextStores for revisions and signatures.
415
- a format marker of its own
416
- an optional 'shared-storage' flag
417
- an optional 'no-working-trees' flag
420
This format was introduced in bzr 0.8.
423
repository_class = KnitRepository
425
def __ne__(self, other):
426
return self.__class__ is not other.__class__
428
def get_format_string(self):
429
"""See RepositoryFormat.get_format_string()."""
430
return "Bazaar-NG Knit Repository Format 1"
432
def get_format_description(self):
433
"""See RepositoryFormat.get_format_description()."""
434
return "Knit repository format 1"
436
def check_conversion_target(self, target_format):
440
class RepositoryFormatKnit3(RepositoryFormatKnit):
441
"""Bzr repository knit format 2.
443
This repository format has:
444
- knits for file texts and inventory
445
- hash subdirectory based stores.
446
- knits for revisions and signatures
447
- TextStores for revisions and signatures.
448
- a format marker of its own
449
- an optional 'shared-storage' flag
450
- an optional 'no-working-trees' flag
452
- support for recording full info about the tree root
453
- support for recording tree-references
456
repository_class = KnitRepository3
457
rich_root_data = True
458
supports_tree_reference = True
460
def _get_matching_bzrdir(self):
461
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
463
def _ignore_setting_bzrdir(self, format):
466
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
468
def check_conversion_target(self, target_format):
469
if not target_format.rich_root_data:
470
raise errors.BadConversionTarget(
471
'Does not support rich root data.', target_format)
472
if not getattr(target_format, 'supports_tree_reference', False):
473
raise errors.BadConversionTarget(
474
'Does not support nested trees', target_format)
476
def get_format_string(self):
477
"""See RepositoryFormat.get_format_string()."""
478
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
480
def get_format_description(self):
481
"""See RepositoryFormat.get_format_description()."""
482
return "Knit repository format 3"
485
def _get_stream_as_bytes(knit, required_versions):
486
"""Generate a serialised data stream.
488
The format is a bencoding of a list. The first element of the list is a
489
string of the format signature, then each subsequent element is a list
490
corresponding to a record. Those lists contain:
497
:returns: a bencoded list.
499
knit_stream = knit.get_data_stream(required_versions)
500
format_signature, data_list, callable = knit_stream
502
data.append(format_signature)
503
for version, options, length, parents in data_list:
504
data.append([version, options, parents, callable(length)])
505
return bencode.bencode(data)