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 (
42
MetaDirRepositoryFormat,
46
import bzrlib.revision as _mod_revision
47
from bzrlib.store.versioned import VersionedFileStore
48
from bzrlib.trace import mutter, mutter_callsite
49
from bzrlib.util import bencode
52
class _KnitParentsProvider(object):
54
def __init__(self, knit):
58
return 'KnitParentsProvider(%r)' % self._knit
60
def get_parents(self, revision_ids):
62
for revision_id in revision_ids:
63
if revision_id == _mod_revision.NULL_REVISION:
67
parents = self._knit.get_parents_with_ghosts(revision_id)
68
except errors.RevisionNotPresent:
72
parents = [_mod_revision.NULL_REVISION]
73
parents_list.append(parents)
77
class KnitRepository(MetaDirRepository):
78
"""Knit format repository."""
80
# make an manually, or incorrectly initialised KnitRepository object
82
_commit_builder_class = None
85
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
86
control_store, text_store, _commit_builder_class, _serializer):
87
MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
88
_revision_store, control_store, text_store)
89
self._commit_builder_class = _commit_builder_class
90
self._serializer = _serializer
92
def _warn_if_deprecated(self):
93
# This class isn't deprecated
96
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
97
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
98
check_content=check_content)[0]
101
def _all_revision_ids(self):
102
"""See Repository.all_revision_ids()."""
103
# Knits get the revision graph from the index of the revision knit, so
104
# it's always possible even if they're on an unlistable transport.
105
return self._revision_store.all_revision_ids(self.get_transaction())
107
def fileid_involved_between_revs(self, from_revid, to_revid):
108
"""Find file_id(s) which are involved in the changes between revisions.
110
This determines the set of revisions which are involved, and then
111
finds all file ids affected by those revisions.
113
from_revid = osutils.safe_revision_id(from_revid)
114
to_revid = osutils.safe_revision_id(to_revid)
115
vf = self._get_revision_vf()
116
from_set = set(vf.get_ancestry(from_revid))
117
to_set = set(vf.get_ancestry(to_revid))
118
changed = to_set.difference(from_set)
119
return self._fileid_involved_by_set(changed)
121
def fileid_involved(self, last_revid=None):
122
"""Find all file_ids modified in the ancestry of last_revid.
124
:param last_revid: If None, last_revision() will be used.
127
changed = set(self.all_revision_ids())
129
changed = set(self.get_ancestry(last_revid))
132
return self._fileid_involved_by_set(changed)
135
def get_ancestry(self, revision_id, topo_sorted=True):
136
"""Return a list of revision-ids integrated by a revision.
138
This is topologically sorted, unless 'topo_sorted' is specified as
141
if _mod_revision.is_null(revision_id):
143
revision_id = osutils.safe_revision_id(revision_id)
144
vf = self._get_revision_vf()
146
return [None] + vf.get_ancestry(revision_id, topo_sorted)
147
except errors.RevisionNotPresent:
148
raise errors.NoSuchRevision(self, revision_id)
151
def get_revision_graph(self, revision_id=None):
152
"""Return a dictionary containing the revision graph.
154
:param revision_id: The revision_id to get a graph from. If None, then
155
the entire revision graph is returned. This is a deprecated mode of
156
operation and will be removed in the future.
157
:return: a dictionary of revision_id->revision_parents_list.
159
if 'evil' in debug.debug_flags:
161
"get_revision_graph scales with size of history.")
162
# special case NULL_REVISION
163
if revision_id == _mod_revision.NULL_REVISION:
165
revision_id = osutils.safe_revision_id(revision_id)
166
a_weave = self._get_revision_vf()
167
if revision_id is None:
168
return a_weave.get_graph()
169
if revision_id not in a_weave:
170
raise errors.NoSuchRevision(self, revision_id)
172
# add what can be reached from revision_id
173
return a_weave.get_graph([revision_id])
176
def get_revision_graph_with_ghosts(self, revision_ids=None):
177
"""Return a graph of the revisions with ghosts marked as applicable.
179
:param revision_ids: an iterable of revisions to graph or None for all.
180
:return: a Graph object with the graph reachable from revision_ids.
182
if 'evil' in debug.debug_flags:
184
"get_revision_graph_with_ghosts scales with size of history.")
185
result = deprecated_graph.Graph()
186
vf = self._get_revision_vf()
187
versions = set(vf.versions())
189
pending = set(self.all_revision_ids())
192
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
193
# special case NULL_REVISION
194
if _mod_revision.NULL_REVISION in pending:
195
pending.remove(_mod_revision.NULL_REVISION)
196
required = set(pending)
199
revision_id = pending.pop()
200
if not revision_id in versions:
201
if revision_id in required:
202
raise errors.NoSuchRevision(self, revision_id)
204
result.add_ghost(revision_id)
205
# mark it as done so we don't try for it again.
206
done.add(revision_id)
208
parent_ids = vf.get_parents_with_ghosts(revision_id)
209
for parent_id in parent_ids:
210
# is this queued or done ?
211
if (parent_id not in pending and
212
parent_id not in done):
214
pending.add(parent_id)
215
result.add_node(revision_id, parent_ids)
216
done.add(revision_id)
219
def _get_revision_vf(self):
220
""":return: a versioned file containing the revisions."""
221
vf = self._revision_store.get_revision_file(self.get_transaction())
224
def _get_history_vf(self):
225
"""Get a versionedfile whose history graph reflects all revisions.
227
For knit repositories, this is the revision knit.
229
return self._get_revision_vf()
232
def reconcile(self, other=None, thorough=False):
233
"""Reconcile this repository."""
234
from bzrlib.reconcile import KnitReconciler
235
reconciler = KnitReconciler(self, thorough=thorough)
236
reconciler.reconcile()
239
def revision_parents(self, revision_id):
240
revision_id = osutils.safe_revision_id(revision_id)
241
return self._get_revision_vf().get_parents(revision_id)
243
def _make_parents_provider(self):
244
return _KnitParentsProvider(self._get_revision_vf())
247
class RepositoryFormatKnit(MetaDirRepositoryFormat):
248
"""Bzr repository knit format (generalized).
250
This repository format has:
251
- knits for file texts and inventory
252
- hash subdirectory based stores.
253
- knits for revisions and signatures
254
- TextStores for revisions and signatures.
255
- a format marker of its own
256
- an optional 'shared-storage' flag
257
- an optional 'no-working-trees' flag
261
# Set this attribute in derived classes to control the repository class
262
# created by open and initialize.
263
repository_class = None
264
# Set this attribute in derived classes to control the
265
# _commit_builder_class that the repository objects will have passed to
267
_commit_builder_class = None
268
# Set this attribute in derived clases to control the _serializer that the
269
# repository objects will have passed to their constructor.
272
def _get_control_store(self, repo_transport, control_files):
273
"""Return the control store for this repository."""
274
return VersionedFileStore(
277
file_mode=control_files._file_mode,
278
versionedfile_class=knit.KnitVersionedFile,
279
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
282
def _get_revision_store(self, repo_transport, control_files):
283
"""See RepositoryFormat._get_revision_store()."""
284
versioned_file_store = VersionedFileStore(
286
file_mode=control_files._file_mode,
289
versionedfile_class=knit.KnitVersionedFile,
290
versionedfile_kwargs={'delta':False,
291
'factory':knit.KnitPlainFactory(),
295
return KnitRevisionStore(versioned_file_store)
297
def _get_text_store(self, transport, control_files):
298
"""See RepositoryFormat._get_text_store()."""
299
return self._get_versioned_file_store('knits',
302
versionedfile_class=knit.KnitVersionedFile,
303
versionedfile_kwargs={
304
'create_parent_dir':True,
306
'dir_mode':control_files._dir_mode,
310
def initialize(self, a_bzrdir, shared=False):
311
"""Create a knit format 1 repository.
313
:param a_bzrdir: bzrdir to contain the new repository; must already
315
:param shared: If true the repository will be initialized as a shared
318
mutter('creating repository in %s.', a_bzrdir.transport.base)
321
utf8_files = [('format', self.get_format_string())]
323
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
324
repo_transport = a_bzrdir.get_repository_transport(None)
325
control_files = lockable_files.LockableFiles(repo_transport,
326
'lock', lockdir.LockDir)
327
control_store = self._get_control_store(repo_transport, control_files)
328
transaction = transactions.WriteTransaction()
329
# trigger a write of the inventory store.
330
control_store.get_weave_or_empty('inventory', transaction)
331
_revision_store = self._get_revision_store(repo_transport, control_files)
332
# the revision id here is irrelevant: it will not be stored, and cannot
334
_revision_store.has_revision_id('A', transaction)
335
_revision_store.get_signature_file(transaction)
336
return self.open(a_bzrdir=a_bzrdir, _found=True)
338
def open(self, a_bzrdir, _found=False, _override_transport=None):
339
"""See RepositoryFormat.open().
341
:param _override_transport: INTERNAL USE ONLY. Allows opening the
342
repository at a slightly different url
343
than normal. I.e. during 'upgrade'.
346
format = RepositoryFormat.find_format(a_bzrdir)
347
assert format.__class__ == self.__class__
348
if _override_transport is not None:
349
repo_transport = _override_transport
351
repo_transport = a_bzrdir.get_repository_transport(None)
352
control_files = lockable_files.LockableFiles(repo_transport,
353
'lock', lockdir.LockDir)
354
text_store = self._get_text_store(repo_transport, control_files)
355
control_store = self._get_control_store(repo_transport, control_files)
356
_revision_store = self._get_revision_store(repo_transport, control_files)
357
return self.repository_class(_format=self,
359
control_files=control_files,
360
_revision_store=_revision_store,
361
control_store=control_store,
362
text_store=text_store,
363
_commit_builder_class=self._commit_builder_class,
364
_serializer=self._serializer)
367
class RepositoryFormatKnit1(RepositoryFormatKnit):
368
"""Bzr repository knit format 1.
370
This repository format has:
371
- knits for file texts and inventory
372
- hash subdirectory based stores.
373
- knits for revisions and signatures
374
- TextStores for revisions and signatures.
375
- a format marker of its own
376
- an optional 'shared-storage' flag
377
- an optional 'no-working-trees' flag
380
This format was introduced in bzr 0.8.
383
repository_class = KnitRepository
384
_commit_builder_class = CommitBuilder
385
_serializer = xml5.serializer_v5
387
def __ne__(self, other):
388
return self.__class__ is not other.__class__
390
def get_format_string(self):
391
"""See RepositoryFormat.get_format_string()."""
392
return "Bazaar-NG Knit Repository Format 1"
394
def get_format_description(self):
395
"""See RepositoryFormat.get_format_description()."""
396
return "Knit repository format 1"
398
def check_conversion_target(self, target_format):
402
class RepositoryFormatKnit3(RepositoryFormatKnit):
403
"""Bzr repository knit format 2.
405
This repository format has:
406
- knits for file texts and inventory
407
- hash subdirectory based stores.
408
- knits for revisions and signatures
409
- TextStores for revisions and signatures.
410
- a format marker of its own
411
- an optional 'shared-storage' flag
412
- an optional 'no-working-trees' flag
414
- support for recording full info about the tree root
415
- support for recording tree-references
418
repository_class = KnitRepository
419
_commit_builder_class = RootCommitBuilder
420
rich_root_data = True
421
supports_tree_reference = True
422
_serializer = xml7.serializer_v7
424
def _get_matching_bzrdir(self):
425
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
427
def _ignore_setting_bzrdir(self, format):
430
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
432
def check_conversion_target(self, target_format):
433
if not target_format.rich_root_data:
434
raise errors.BadConversionTarget(
435
'Does not support rich root data.', target_format)
436
if not getattr(target_format, 'supports_tree_reference', False):
437
raise errors.BadConversionTarget(
438
'Does not support nested trees', target_format)
440
def get_format_string(self):
441
"""See RepositoryFormat.get_format_string()."""
442
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
444
def get_format_description(self):
445
"""See RepositoryFormat.get_format_description()."""
446
return "Knit repository format 3"
449
def _get_stream_as_bytes(knit, required_versions):
450
"""Generate a serialised data stream.
452
The format is a bencoding of a list. The first element of the list is a
453
string of the format signature, then each subsequent element is a list
454
corresponding to a record. Those lists contain:
461
:returns: a bencoded list.
463
knit_stream = knit.get_data_stream(required_versions)
464
format_signature, data_list, callable = knit_stream
466
data.append(format_signature)
467
for version, options, length, parents in data_list:
468
data.append([version, options, parents, callable(length)])
469
return bencode.bencode(data)