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
# These attributes are inherited from the Repository base class. Setting
81
# them to None ensures that if the constructor is changed to not initialize
82
# them, or a subclass fails to call the constructor, that an error will
83
# occur rather than the system working but generating incorrect data.
84
_commit_builder_class = None
87
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
88
control_store, text_store, _commit_builder_class, _serializer):
89
MetaDirRepository.__init__(self, _format, a_bzrdir, control_files,
90
_revision_store, control_store, text_store)
91
self._commit_builder_class = _commit_builder_class
92
self._serializer = _serializer
94
def _warn_if_deprecated(self):
95
# This class isn't deprecated
98
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
99
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
100
check_content=check_content)[0]
103
def _all_revision_ids(self):
104
"""See Repository.all_revision_ids()."""
105
# Knits get the revision graph from the index of the revision knit, so
106
# it's always possible even if they're on an unlistable transport.
107
return self._revision_store.all_revision_ids(self.get_transaction())
109
def fileid_involved_between_revs(self, from_revid, to_revid):
110
"""Find file_id(s) which are involved in the changes between revisions.
112
This determines the set of revisions which are involved, and then
113
finds all file ids affected by those revisions.
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
vf = self._get_revision_vf()
145
return [None] + vf.get_ancestry(revision_id, topo_sorted)
146
except errors.RevisionNotPresent:
147
raise errors.NoSuchRevision(self, revision_id)
150
def get_revision_graph(self, revision_id=None):
151
"""Return a dictionary containing the revision graph.
153
:param revision_id: The revision_id to get a graph from. If None, then
154
the entire revision graph is returned. This is a deprecated mode of
155
operation and will be removed in the future.
156
:return: a dictionary of revision_id->revision_parents_list.
158
if 'evil' in debug.debug_flags:
160
"get_revision_graph scales with size of history.")
161
# special case NULL_REVISION
162
if revision_id == _mod_revision.NULL_REVISION:
164
a_weave = self._get_revision_vf()
165
if revision_id is None:
166
return a_weave.get_graph()
167
if revision_id not in a_weave:
168
raise errors.NoSuchRevision(self, revision_id)
170
# add what can be reached from revision_id
171
return a_weave.get_graph([revision_id])
174
def get_revision_graph_with_ghosts(self, revision_ids=None):
175
"""Return a graph of the revisions with ghosts marked as applicable.
177
:param revision_ids: an iterable of revisions to graph or None for all.
178
:return: a Graph object with the graph reachable from revision_ids.
180
if 'evil' in debug.debug_flags:
182
"get_revision_graph_with_ghosts scales with size of history.")
183
result = deprecated_graph.Graph()
184
vf = self._get_revision_vf()
185
versions = set(vf.versions())
187
pending = set(self.all_revision_ids())
190
pending = set(revision_ids)
191
# special case NULL_REVISION
192
if _mod_revision.NULL_REVISION in pending:
193
pending.remove(_mod_revision.NULL_REVISION)
194
required = set(pending)
197
revision_id = pending.pop()
198
if not revision_id in versions:
199
if revision_id in required:
200
raise errors.NoSuchRevision(self, revision_id)
202
result.add_ghost(revision_id)
203
# mark it as done so we don't try for it again.
204
done.add(revision_id)
206
parent_ids = vf.get_parents_with_ghosts(revision_id)
207
for parent_id in parent_ids:
208
# is this queued or done ?
209
if (parent_id not in pending and
210
parent_id not in done):
212
pending.add(parent_id)
213
result.add_node(revision_id, parent_ids)
214
done.add(revision_id)
217
def _get_revision_vf(self):
218
""":return: a versioned file containing the revisions."""
219
vf = self._revision_store.get_revision_file(self.get_transaction())
222
def _get_history_vf(self):
223
"""Get a versionedfile whose history graph reflects all revisions.
225
For knit repositories, this is the revision knit.
227
return self._get_revision_vf()
230
def reconcile(self, other=None, thorough=False):
231
"""Reconcile this repository."""
232
from bzrlib.reconcile import KnitReconciler
233
reconciler = KnitReconciler(self, thorough=thorough)
234
reconciler.reconcile()
237
def revision_parents(self, revision_id):
238
return self._get_revision_vf().get_parents(revision_id)
240
def _make_parents_provider(self):
241
return _KnitParentsProvider(self._get_revision_vf())
244
class RepositoryFormatKnit(MetaDirRepositoryFormat):
245
"""Bzr repository knit format (generalized).
247
This repository format has:
248
- knits for file texts and inventory
249
- hash subdirectory based stores.
250
- knits for revisions and signatures
251
- TextStores for revisions and signatures.
252
- a format marker of its own
253
- an optional 'shared-storage' flag
254
- an optional 'no-working-trees' flag
258
# Set this attribute in derived classes to control the repository class
259
# created by open and initialize.
260
repository_class = None
261
# Set this attribute in derived classes to control the
262
# _commit_builder_class that the repository objects will have passed to
264
_commit_builder_class = None
265
# Set this attribute in derived clases to control the _serializer that the
266
# repository objects will have passed to their constructor.
267
_serializer = xml5.serializer_v5
269
def _get_control_store(self, repo_transport, control_files):
270
"""Return the control store for this repository."""
271
return VersionedFileStore(
274
file_mode=control_files._file_mode,
275
versionedfile_class=knit.KnitVersionedFile,
276
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
279
def _get_revision_store(self, repo_transport, control_files):
280
"""See RepositoryFormat._get_revision_store()."""
281
versioned_file_store = VersionedFileStore(
283
file_mode=control_files._file_mode,
286
versionedfile_class=knit.KnitVersionedFile,
287
versionedfile_kwargs={'delta':False,
288
'factory':knit.KnitPlainFactory(),
292
return KnitRevisionStore(versioned_file_store)
294
def _get_text_store(self, transport, control_files):
295
"""See RepositoryFormat._get_text_store()."""
296
return self._get_versioned_file_store('knits',
299
versionedfile_class=knit.KnitVersionedFile,
300
versionedfile_kwargs={
301
'create_parent_dir':True,
303
'dir_mode':control_files._dir_mode,
307
def initialize(self, a_bzrdir, shared=False):
308
"""Create a knit format 1 repository.
310
:param a_bzrdir: bzrdir to contain the new repository; must already
312
:param shared: If true the repository will be initialized as a shared
315
mutter('creating repository in %s.', a_bzrdir.transport.base)
318
utf8_files = [('format', self.get_format_string())]
320
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
321
repo_transport = a_bzrdir.get_repository_transport(None)
322
control_files = lockable_files.LockableFiles(repo_transport,
323
'lock', lockdir.LockDir)
324
control_store = self._get_control_store(repo_transport, control_files)
325
transaction = transactions.WriteTransaction()
326
# trigger a write of the inventory store.
327
control_store.get_weave_or_empty('inventory', transaction)
328
_revision_store = self._get_revision_store(repo_transport, control_files)
329
# the revision id here is irrelevant: it will not be stored, and cannot
331
_revision_store.has_revision_id('A', transaction)
332
_revision_store.get_signature_file(transaction)
333
return self.open(a_bzrdir=a_bzrdir, _found=True)
335
def open(self, a_bzrdir, _found=False, _override_transport=None):
336
"""See RepositoryFormat.open().
338
:param _override_transport: INTERNAL USE ONLY. Allows opening the
339
repository at a slightly different url
340
than normal. I.e. during 'upgrade'.
343
format = RepositoryFormat.find_format(a_bzrdir)
344
assert format.__class__ == self.__class__
345
if _override_transport is not None:
346
repo_transport = _override_transport
348
repo_transport = a_bzrdir.get_repository_transport(None)
349
control_files = lockable_files.LockableFiles(repo_transport,
350
'lock', lockdir.LockDir)
351
text_store = self._get_text_store(repo_transport, control_files)
352
control_store = self._get_control_store(repo_transport, control_files)
353
_revision_store = self._get_revision_store(repo_transport, control_files)
354
return self.repository_class(_format=self,
356
control_files=control_files,
357
_revision_store=_revision_store,
358
control_store=control_store,
359
text_store=text_store,
360
_commit_builder_class=self._commit_builder_class,
361
_serializer=self._serializer)
364
class RepositoryFormatKnit1(RepositoryFormatKnit):
365
"""Bzr repository knit format 1.
367
This repository format has:
368
- knits for file texts and inventory
369
- hash subdirectory based stores.
370
- knits for revisions and signatures
371
- TextStores for revisions and signatures.
372
- a format marker of its own
373
- an optional 'shared-storage' flag
374
- an optional 'no-working-trees' flag
377
This format was introduced in bzr 0.8.
380
repository_class = KnitRepository
381
_commit_builder_class = CommitBuilder
382
_serializer = xml5.serializer_v5
384
def __ne__(self, other):
385
return self.__class__ is not other.__class__
387
def get_format_string(self):
388
"""See RepositoryFormat.get_format_string()."""
389
return "Bazaar-NG Knit Repository Format 1"
391
def get_format_description(self):
392
"""See RepositoryFormat.get_format_description()."""
393
return "Knit repository format 1"
395
def check_conversion_target(self, target_format):
399
class RepositoryFormatKnit3(RepositoryFormatKnit):
400
"""Bzr repository knit format 2.
402
This repository format has:
403
- knits for file texts and inventory
404
- hash subdirectory based stores.
405
- knits for revisions and signatures
406
- TextStores for revisions and signatures.
407
- a format marker of its own
408
- an optional 'shared-storage' flag
409
- an optional 'no-working-trees' flag
411
- support for recording full info about the tree root
412
- support for recording tree-references
415
repository_class = KnitRepository
416
_commit_builder_class = RootCommitBuilder
417
rich_root_data = True
418
supports_tree_reference = True
419
_serializer = xml7.serializer_v7
421
def _get_matching_bzrdir(self):
422
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
424
def _ignore_setting_bzrdir(self, format):
427
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
429
def check_conversion_target(self, target_format):
430
if not target_format.rich_root_data:
431
raise errors.BadConversionTarget(
432
'Does not support rich root data.', target_format)
433
if not getattr(target_format, 'supports_tree_reference', False):
434
raise errors.BadConversionTarget(
435
'Does not support nested trees', target_format)
437
def get_format_string(self):
438
"""See RepositoryFormat.get_format_string()."""
439
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
441
def get_format_description(self):
442
"""See RepositoryFormat.get_format_description()."""
443
return "Knit repository format 3"
446
def _get_stream_as_bytes(knit, required_versions):
447
"""Generate a serialised data stream.
449
The format is a bencoding of a list. The first element of the list is a
450
string of the format signature, then each subsequent element is a list
451
corresponding to a record. Those lists contain:
458
:returns: a bencoded list.
460
knit_stream = knit.get_data_stream(required_versions)
461
format_signature, data_list, callable = knit_stream
463
data.append(format_signature)
464
for version, options, length, parents in data_list:
465
data.append([version, options, parents, callable(length)])
466
return bencode.bencode(data)