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_revision_graph(self, revision_id=None):
141
"""Return a dictionary containing the revision graph.
143
:param revision_id: The revision_id to get a graph from. If None, then
144
the entire revision graph is returned. This is a deprecated mode of
145
operation and will be removed in the future.
146
:return: a dictionary of revision_id->revision_parents_list.
148
if 'evil' in debug.debug_flags:
150
"get_revision_graph scales with size of history.")
151
# special case NULL_REVISION
152
if revision_id == _mod_revision.NULL_REVISION:
154
revision_id = osutils.safe_revision_id(revision_id)
155
a_weave = self._get_revision_vf()
156
if revision_id is None:
157
return a_weave.get_graph()
158
if revision_id not in a_weave:
159
raise errors.NoSuchRevision(self, revision_id)
161
# add what can be reached from revision_id
162
return a_weave.get_graph([revision_id])
165
def get_revision_graph_with_ghosts(self, revision_ids=None):
166
"""Return a graph of the revisions with ghosts marked as applicable.
168
:param revision_ids: an iterable of revisions to graph or None for all.
169
:return: a Graph object with the graph reachable from revision_ids.
171
if 'evil' in debug.debug_flags:
173
"get_revision_graph_with_ghosts scales with size of history.")
174
result = deprecated_graph.Graph()
175
vf = self._get_revision_vf()
176
versions = set(vf.versions())
178
pending = set(self.all_revision_ids())
181
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
182
# special case NULL_REVISION
183
if _mod_revision.NULL_REVISION in pending:
184
pending.remove(_mod_revision.NULL_REVISION)
185
required = set(pending)
188
revision_id = pending.pop()
189
if not revision_id in versions:
190
if revision_id in required:
191
raise errors.NoSuchRevision(self, revision_id)
193
result.add_ghost(revision_id)
194
# mark it as done so we don't try for it again.
195
done.add(revision_id)
197
parent_ids = vf.get_parents_with_ghosts(revision_id)
198
for parent_id in parent_ids:
199
# is this queued or done ?
200
if (parent_id not in pending and
201
parent_id not in done):
203
pending.add(parent_id)
204
result.add_node(revision_id, parent_ids)
205
done.add(revision_id)
208
def _get_revision_vf(self):
209
""":return: a versioned file containing the revisions."""
210
vf = self._revision_store.get_revision_file(self.get_transaction())
213
def _get_history_vf(self):
214
"""Get a versionedfile whose history graph reflects all revisions.
216
For knit repositories, this is the revision knit.
218
return self._get_revision_vf()
221
def reconcile(self, other=None, thorough=False):
222
"""Reconcile this repository."""
223
from bzrlib.reconcile import KnitReconciler
224
reconciler = KnitReconciler(self, thorough=thorough)
225
reconciler.reconcile()
228
def revision_parents(self, revision_id):
229
revision_id = osutils.safe_revision_id(revision_id)
230
return self._get_revision_vf().get_parents(revision_id)
232
def _make_parents_provider(self):
233
return _KnitParentsProvider(self._get_revision_vf())
236
class KnitRepository3(KnitRepository):
238
# knit3 repositories need a RootCommitBuilder
239
_commit_builder_class = RootCommitBuilder
241
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
242
control_store, text_store):
243
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
244
_revision_store, control_store, text_store)
245
self._serializer = xml7.serializer_v7
247
def deserialise_inventory(self, revision_id, xml):
248
"""Transform the xml into an inventory object.
250
:param revision_id: The expected revision id of the inventory.
251
:param xml: A serialised inventory.
253
result = self._serializer.read_inventory_from_string(xml)
254
assert result.root.revision is not None
257
def serialise_inventory(self, inv):
258
"""Transform the inventory object into XML text.
260
:param revision_id: The expected revision id of the inventory.
261
:param xml: A serialised inventory.
263
assert inv.revision_id is not None
264
assert inv.root.revision is not None
265
return KnitRepository.serialise_inventory(self, inv)
268
class RepositoryFormatKnit(MetaDirRepositoryFormat):
269
"""Bzr repository knit format (generalized).
271
This repository format has:
272
- knits for file texts and inventory
273
- hash subdirectory based stores.
274
- knits for revisions and signatures
275
- TextStores for revisions and signatures.
276
- a format marker of its own
277
- an optional 'shared-storage' flag
278
- an optional 'no-working-trees' flag
282
# Set this attribute in derived classes to control the repository class
283
# created by open and initialize.
284
repository_class = None
286
def _get_control_store(self, repo_transport, control_files):
287
"""Return the control store for this repository."""
288
return VersionedFileStore(
291
file_mode=control_files._file_mode,
292
versionedfile_class=knit.KnitVersionedFile,
293
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
296
def _get_revision_store(self, repo_transport, control_files):
297
"""See RepositoryFormat._get_revision_store()."""
298
versioned_file_store = VersionedFileStore(
300
file_mode=control_files._file_mode,
303
versionedfile_class=knit.KnitVersionedFile,
304
versionedfile_kwargs={'delta':False,
305
'factory':knit.KnitPlainFactory(),
309
return KnitRevisionStore(versioned_file_store)
311
def _get_text_store(self, transport, control_files):
312
"""See RepositoryFormat._get_text_store()."""
313
return self._get_versioned_file_store('knits',
316
versionedfile_class=knit.KnitVersionedFile,
317
versionedfile_kwargs={
318
'create_parent_dir':True,
320
'dir_mode':control_files._dir_mode,
324
def initialize(self, a_bzrdir, shared=False):
325
"""Create a knit format 1 repository.
327
:param a_bzrdir: bzrdir to contain the new repository; must already
329
:param shared: If true the repository will be initialized as a shared
332
mutter('creating repository in %s.', a_bzrdir.transport.base)
335
utf8_files = [('format', self.get_format_string())]
337
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
338
repo_transport = a_bzrdir.get_repository_transport(None)
339
control_files = lockable_files.LockableFiles(repo_transport,
340
'lock', lockdir.LockDir)
341
control_store = self._get_control_store(repo_transport, control_files)
342
transaction = transactions.WriteTransaction()
343
# trigger a write of the inventory store.
344
control_store.get_weave_or_empty('inventory', transaction)
345
_revision_store = self._get_revision_store(repo_transport, control_files)
346
# the revision id here is irrelevant: it will not be stored, and cannot
348
_revision_store.has_revision_id('A', transaction)
349
_revision_store.get_signature_file(transaction)
350
return self.open(a_bzrdir=a_bzrdir, _found=True)
352
def open(self, a_bzrdir, _found=False, _override_transport=None):
353
"""See RepositoryFormat.open().
355
:param _override_transport: INTERNAL USE ONLY. Allows opening the
356
repository at a slightly different url
357
than normal. I.e. during 'upgrade'.
360
format = RepositoryFormat.find_format(a_bzrdir)
361
assert format.__class__ == self.__class__
362
if _override_transport is not None:
363
repo_transport = _override_transport
365
repo_transport = a_bzrdir.get_repository_transport(None)
366
control_files = lockable_files.LockableFiles(repo_transport,
367
'lock', lockdir.LockDir)
368
text_store = self._get_text_store(repo_transport, control_files)
369
control_store = self._get_control_store(repo_transport, control_files)
370
_revision_store = self._get_revision_store(repo_transport, control_files)
371
return self.repository_class(_format=self,
373
control_files=control_files,
374
_revision_store=_revision_store,
375
control_store=control_store,
376
text_store=text_store)
379
class RepositoryFormatKnit1(RepositoryFormatKnit):
380
"""Bzr repository knit format 1.
382
This repository format has:
383
- knits for file texts and inventory
384
- hash subdirectory based stores.
385
- knits for revisions and signatures
386
- TextStores for revisions and signatures.
387
- a format marker of its own
388
- an optional 'shared-storage' flag
389
- an optional 'no-working-trees' flag
392
This format was introduced in bzr 0.8.
395
repository_class = KnitRepository
397
def __ne__(self, other):
398
return self.__class__ is not other.__class__
400
def get_format_string(self):
401
"""See RepositoryFormat.get_format_string()."""
402
return "Bazaar-NG Knit Repository Format 1"
404
def get_format_description(self):
405
"""See RepositoryFormat.get_format_description()."""
406
return "Knit repository format 1"
408
def check_conversion_target(self, target_format):
412
class RepositoryFormatKnit3(RepositoryFormatKnit):
413
"""Bzr repository knit format 2.
415
This repository format has:
416
- knits for file texts and inventory
417
- hash subdirectory based stores.
418
- knits for revisions and signatures
419
- TextStores for revisions and signatures.
420
- a format marker of its own
421
- an optional 'shared-storage' flag
422
- an optional 'no-working-trees' flag
424
- support for recording full info about the tree root
425
- support for recording tree-references
428
repository_class = KnitRepository3
429
rich_root_data = True
430
supports_tree_reference = True
432
def _get_matching_bzrdir(self):
433
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
435
def _ignore_setting_bzrdir(self, format):
438
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
440
def check_conversion_target(self, target_format):
441
if not target_format.rich_root_data:
442
raise errors.BadConversionTarget(
443
'Does not support rich root data.', target_format)
444
if not getattr(target_format, 'supports_tree_reference', False):
445
raise errors.BadConversionTarget(
446
'Does not support nested trees', target_format)
448
def get_format_string(self):
449
"""See RepositoryFormat.get_format_string()."""
450
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
452
def get_format_description(self):
453
"""See RepositoryFormat.get_format_description()."""
454
return "Knit repository format 3"
457
def _get_stream_as_bytes(knit, required_versions):
458
"""Generate a serialised data stream.
460
The format is a bencoding of a list. The first element of the list is a
461
string of the format signature, then each subsequent element is a list
462
corresponding to a record. Those lists contain:
469
:returns: a bencoded list.
471
knit_stream = knit.get_data_stream(required_versions)
472
format_signature, data_list, callable = knit_stream
474
data.append(format_signature)
475
for version, options, length, parents in data_list:
476
data.append([version, options, parents, callable(length)])
477
return bencode.bencode(data)