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
vf = self._get_revision_vf()
103
from_set = set(vf.get_ancestry(from_revid))
104
to_set = set(vf.get_ancestry(to_revid))
105
changed = to_set.difference(from_set)
106
return self._fileid_involved_by_set(changed)
108
def fileid_involved(self, last_revid=None):
109
"""Find all file_ids modified in the ancestry of last_revid.
111
:param last_revid: If None, last_revision() will be used.
114
changed = set(self.all_revision_ids())
116
changed = set(self.get_ancestry(last_revid))
119
return self._fileid_involved_by_set(changed)
122
def get_ancestry(self, revision_id, topo_sorted=True):
123
"""Return a list of revision-ids integrated by a revision.
125
This is topologically sorted, unless 'topo_sorted' is specified as
128
if _mod_revision.is_null(revision_id):
130
vf = self._get_revision_vf()
132
return [None] + vf.get_ancestry(revision_id, topo_sorted)
133
except errors.RevisionNotPresent:
134
raise errors.NoSuchRevision(self, revision_id)
137
def get_revision_graph(self, revision_id=None):
138
"""Return a dictionary containing the revision graph.
140
:param revision_id: The revision_id to get a graph from. If None, then
141
the entire revision graph is returned. This is a deprecated mode of
142
operation and will be removed in the future.
143
:return: a dictionary of revision_id->revision_parents_list.
145
if 'evil' in debug.debug_flags:
147
"get_revision_graph scales with size of history.")
148
# special case NULL_REVISION
149
if revision_id == _mod_revision.NULL_REVISION:
151
a_weave = self._get_revision_vf()
152
if revision_id is None:
153
return a_weave.get_graph()
154
if revision_id not in a_weave:
155
raise errors.NoSuchRevision(self, revision_id)
157
# add what can be reached from revision_id
158
return a_weave.get_graph([revision_id])
161
def get_revision_graph_with_ghosts(self, revision_ids=None):
162
"""Return a graph of the revisions with ghosts marked as applicable.
164
:param revision_ids: an iterable of revisions to graph or None for all.
165
:return: a Graph object with the graph reachable from revision_ids.
167
if 'evil' in debug.debug_flags:
169
"get_revision_graph_with_ghosts scales with size of history.")
170
result = deprecated_graph.Graph()
171
vf = self._get_revision_vf()
172
versions = set(vf.versions())
174
pending = set(self.all_revision_ids())
177
pending = set(revision_ids)
178
# special case NULL_REVISION
179
if _mod_revision.NULL_REVISION in pending:
180
pending.remove(_mod_revision.NULL_REVISION)
181
required = set(pending)
184
revision_id = pending.pop()
185
if not revision_id in versions:
186
if revision_id in required:
187
raise errors.NoSuchRevision(self, revision_id)
189
result.add_ghost(revision_id)
190
# mark it as done so we don't try for it again.
191
done.add(revision_id)
193
parent_ids = vf.get_parents_with_ghosts(revision_id)
194
for parent_id in parent_ids:
195
# is this queued or done ?
196
if (parent_id not in pending and
197
parent_id not in done):
199
pending.add(parent_id)
200
result.add_node(revision_id, parent_ids)
201
done.add(revision_id)
204
def _get_revision_vf(self):
205
""":return: a versioned file containing the revisions."""
206
vf = self._revision_store.get_revision_file(self.get_transaction())
209
def _get_history_vf(self):
210
"""Get a versionedfile whose history graph reflects all revisions.
212
For knit repositories, this is the revision knit.
214
return self._get_revision_vf()
217
def reconcile(self, other=None, thorough=False):
218
"""Reconcile this repository."""
219
from bzrlib.reconcile import KnitReconciler
220
reconciler = KnitReconciler(self, thorough=thorough)
221
reconciler.reconcile()
224
def revision_parents(self, revision_id):
225
return self._get_revision_vf().get_parents(revision_id)
227
def _make_parents_provider(self):
228
return _KnitParentsProvider(self._get_revision_vf())
231
class KnitRepository3(KnitRepository):
233
# knit3 repositories need a RootCommitBuilder
234
_commit_builder_class = RootCommitBuilder
236
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
237
control_store, text_store):
238
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
239
_revision_store, control_store, text_store)
240
self._serializer = xml7.serializer_v7
242
def deserialise_inventory(self, revision_id, xml):
243
"""Transform the xml into an inventory object.
245
:param revision_id: The expected revision id of the inventory.
246
:param xml: A serialised inventory.
248
result = self._serializer.read_inventory_from_string(xml)
249
assert result.root.revision is not None
252
def serialise_inventory(self, inv):
253
"""Transform the inventory object into XML text.
255
:param revision_id: The expected revision id of the inventory.
256
:param xml: A serialised inventory.
258
assert inv.revision_id is not None
259
assert inv.root.revision is not None
260
return KnitRepository.serialise_inventory(self, inv)
263
class RepositoryFormatKnit(MetaDirRepositoryFormat):
264
"""Bzr repository knit format (generalized).
266
This repository format has:
267
- knits for file texts and inventory
268
- hash subdirectory based stores.
269
- knits for revisions and signatures
270
- TextStores for revisions and signatures.
271
- a format marker of its own
272
- an optional 'shared-storage' flag
273
- an optional 'no-working-trees' flag
277
# Set this attribute in derived classes to control the repository class
278
# created by open and initialize.
279
repository_class = None
281
def _get_control_store(self, repo_transport, control_files):
282
"""Return the control store for this repository."""
283
return VersionedFileStore(
286
file_mode=control_files._file_mode,
287
versionedfile_class=knit.KnitVersionedFile,
288
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
291
def _get_revision_store(self, repo_transport, control_files):
292
"""See RepositoryFormat._get_revision_store()."""
293
versioned_file_store = VersionedFileStore(
295
file_mode=control_files._file_mode,
298
versionedfile_class=knit.KnitVersionedFile,
299
versionedfile_kwargs={'delta':False,
300
'factory':knit.KnitPlainFactory(),
304
return KnitRevisionStore(versioned_file_store)
306
def _get_text_store(self, transport, control_files):
307
"""See RepositoryFormat._get_text_store()."""
308
return self._get_versioned_file_store('knits',
311
versionedfile_class=knit.KnitVersionedFile,
312
versionedfile_kwargs={
313
'create_parent_dir':True,
315
'dir_mode':control_files._dir_mode,
319
def initialize(self, a_bzrdir, shared=False):
320
"""Create a knit format 1 repository.
322
:param a_bzrdir: bzrdir to contain the new repository; must already
324
:param shared: If true the repository will be initialized as a shared
327
mutter('creating repository in %s.', a_bzrdir.transport.base)
330
utf8_files = [('format', self.get_format_string())]
332
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
333
repo_transport = a_bzrdir.get_repository_transport(None)
334
control_files = lockable_files.LockableFiles(repo_transport,
335
'lock', lockdir.LockDir)
336
control_store = self._get_control_store(repo_transport, control_files)
337
transaction = transactions.WriteTransaction()
338
# trigger a write of the inventory store.
339
control_store.get_weave_or_empty('inventory', transaction)
340
_revision_store = self._get_revision_store(repo_transport, control_files)
341
# the revision id here is irrelevant: it will not be stored, and cannot
343
_revision_store.has_revision_id('A', transaction)
344
_revision_store.get_signature_file(transaction)
345
return self.open(a_bzrdir=a_bzrdir, _found=True)
347
def open(self, a_bzrdir, _found=False, _override_transport=None):
348
"""See RepositoryFormat.open().
350
:param _override_transport: INTERNAL USE ONLY. Allows opening the
351
repository at a slightly different url
352
than normal. I.e. during 'upgrade'.
355
format = RepositoryFormat.find_format(a_bzrdir)
356
assert format.__class__ == self.__class__
357
if _override_transport is not None:
358
repo_transport = _override_transport
360
repo_transport = a_bzrdir.get_repository_transport(None)
361
control_files = lockable_files.LockableFiles(repo_transport,
362
'lock', lockdir.LockDir)
363
text_store = self._get_text_store(repo_transport, control_files)
364
control_store = self._get_control_store(repo_transport, control_files)
365
_revision_store = self._get_revision_store(repo_transport, control_files)
366
return self.repository_class(_format=self,
368
control_files=control_files,
369
_revision_store=_revision_store,
370
control_store=control_store,
371
text_store=text_store)
374
class RepositoryFormatKnit1(RepositoryFormatKnit):
375
"""Bzr repository knit format 1.
377
This repository format has:
378
- knits for file texts and inventory
379
- hash subdirectory based stores.
380
- knits for revisions and signatures
381
- TextStores for revisions and signatures.
382
- a format marker of its own
383
- an optional 'shared-storage' flag
384
- an optional 'no-working-trees' flag
387
This format was introduced in bzr 0.8.
390
repository_class = KnitRepository
392
def __ne__(self, other):
393
return self.__class__ is not other.__class__
395
def get_format_string(self):
396
"""See RepositoryFormat.get_format_string()."""
397
return "Bazaar-NG Knit Repository Format 1"
399
def get_format_description(self):
400
"""See RepositoryFormat.get_format_description()."""
401
return "Knit repository format 1"
403
def check_conversion_target(self, target_format):
407
class RepositoryFormatKnit3(RepositoryFormatKnit):
408
"""Bzr repository knit format 2.
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
419
- support for recording full info about the tree root
420
- support for recording tree-references
423
repository_class = KnitRepository3
424
rich_root_data = True
425
supports_tree_reference = True
427
def _get_matching_bzrdir(self):
428
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
430
def _ignore_setting_bzrdir(self, format):
433
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
435
def check_conversion_target(self, target_format):
436
if not target_format.rich_root_data:
437
raise errors.BadConversionTarget(
438
'Does not support rich root data.', target_format)
439
if not getattr(target_format, 'supports_tree_reference', False):
440
raise errors.BadConversionTarget(
441
'Does not support nested trees', target_format)
443
def get_format_string(self):
444
"""See RepositoryFormat.get_format_string()."""
445
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
447
def get_format_description(self):
448
"""See RepositoryFormat.get_format_description()."""
449
return "Knit repository format 3"
452
def _get_stream_as_bytes(knit, required_versions):
453
"""Generate a serialised data stream.
455
The format is a bencoding of a list. The first element of the list is a
456
string of the format signature, then each subsequent element is a list
457
corresponding to a record. Those lists contain:
464
:returns: a bencoded list.
466
knit_stream = knit.get_data_stream(required_versions)
467
format_signature, data_list, callable = knit_stream
469
data.append(format_signature)
470
for version, options, length, parents in data_list:
471
data.append([version, options, parents, callable(length)])
472
return bencode.bencode(data)