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(), """
19
from itertools import izip
27
from bzrlib.index import (
32
GraphIndexPrefixAdapter,
34
from bzrlib.knit import KnitGraphIndex, _PackAccess
35
from bzrlib.pack import ContainerWriter
36
from bzrlib.store import revision
51
from bzrlib.decorators import needs_read_lock, needs_write_lock
52
from bzrlib.repository import (
54
MetaDirRepositoryFormat,
58
import bzrlib.revision as _mod_revision
59
from bzrlib.store.revision.knit import KnitRevisionStore
60
from bzrlib.store.versioned import VersionedFileStore
61
from bzrlib.trace import mutter, mutter_callsite, note, warning
62
from bzrlib.trace import mutter, note, warning
63
from bzrlib.util import bencode
66
class _KnitParentsProvider(object):
68
def __init__(self, knit):
72
return 'KnitParentsProvider(%r)' % self._knit
74
def get_parents(self, revision_ids):
76
for revision_id in revision_ids:
77
if revision_id == _mod_revision.NULL_REVISION:
81
parents = self._knit.get_parents_with_ghosts(revision_id)
82
except errors.RevisionNotPresent:
86
parents = [_mod_revision.NULL_REVISION]
87
parents_list.append(parents)
91
class KnitRepository(MetaDirRepository):
92
"""Knit format repository."""
94
_serializer = xml5.serializer_v5
96
def _warn_if_deprecated(self):
97
# This class isn't deprecated
100
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
101
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
102
check_content=check_content)[0]
105
def _all_revision_ids(self):
106
"""See Repository.all_revision_ids()."""
107
# Knits get the revision graph from the index of the revision knit, so
108
# it's always possible even if they're on an unlistable transport.
109
return self._revision_store.all_revision_ids(self.get_transaction())
111
def fileid_involved_between_revs(self, from_revid, to_revid):
112
"""Find file_id(s) which are involved in the changes between revisions.
114
This determines the set of revisions which are involved, and then
115
finds all file ids affected by those revisions.
117
from_revid = osutils.safe_revision_id(from_revid)
118
to_revid = osutils.safe_revision_id(to_revid)
119
vf = self._get_revision_vf()
120
from_set = set(vf.get_ancestry(from_revid))
121
to_set = set(vf.get_ancestry(to_revid))
122
changed = to_set.difference(from_set)
123
return self._fileid_involved_by_set(changed)
125
def fileid_involved(self, last_revid=None):
126
"""Find all file_ids modified in the ancestry of last_revid.
128
:param last_revid: If None, last_revision() will be used.
131
changed = set(self.all_revision_ids())
133
changed = set(self.get_ancestry(last_revid))
136
return self._fileid_involved_by_set(changed)
139
def get_ancestry(self, revision_id, topo_sorted=True):
140
"""Return a list of revision-ids integrated by a revision.
142
This is topologically sorted, unless 'topo_sorted' is specified as
145
if _mod_revision.is_null(revision_id):
147
revision_id = osutils.safe_revision_id(revision_id)
148
vf = self._get_revision_vf()
150
return [None] + vf.get_ancestry(revision_id, topo_sorted)
151
except errors.RevisionNotPresent:
152
raise errors.NoSuchRevision(self, revision_id)
155
def get_revision(self, revision_id):
156
"""Return the Revision object for a named revision"""
157
revision_id = osutils.safe_revision_id(revision_id)
158
return self.get_revision_reconcile(revision_id)
161
def get_revision_graph(self, revision_id=None):
162
"""Return a dictionary containing the revision graph.
164
:param revision_id: The revision_id to get a graph from. If None, then
165
the entire revision graph is returned. This is a deprecated mode of
166
operation and will be removed in the future.
167
:return: a dictionary of revision_id->revision_parents_list.
169
if 'evil' in debug.debug_flags:
171
"get_revision_graph scales with size of history.")
172
# special case NULL_REVISION
173
if revision_id == _mod_revision.NULL_REVISION:
175
revision_id = osutils.safe_revision_id(revision_id)
176
a_weave = self._get_revision_vf()
177
if revision_id is None:
178
return a_weave.get_graph()
179
if revision_id not in a_weave:
180
raise errors.NoSuchRevision(self, revision_id)
182
# add what can be reached from revision_id
183
return a_weave.get_graph([revision_id])
186
def get_revision_graph_with_ghosts(self, revision_ids=None):
187
"""Return a graph of the revisions with ghosts marked as applicable.
189
:param revision_ids: an iterable of revisions to graph or None for all.
190
:return: a Graph object with the graph reachable from revision_ids.
192
if 'evil' in debug.debug_flags:
194
"get_revision_graph_with_ghosts scales with size of history.")
195
result = deprecated_graph.Graph()
196
vf = self._get_revision_vf()
197
versions = set(vf.versions())
199
pending = set(self.all_revision_ids())
202
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
203
# special case NULL_REVISION
204
if _mod_revision.NULL_REVISION in pending:
205
pending.remove(_mod_revision.NULL_REVISION)
206
required = set(pending)
209
revision_id = pending.pop()
210
if not revision_id in versions:
211
if revision_id in required:
212
raise errors.NoSuchRevision(self, revision_id)
214
result.add_ghost(revision_id)
215
# mark it as done so we don't try for it again.
216
done.add(revision_id)
218
parent_ids = vf.get_parents_with_ghosts(revision_id)
219
for parent_id in parent_ids:
220
# is this queued or done ?
221
if (parent_id not in pending and
222
parent_id not in done):
224
pending.add(parent_id)
225
result.add_node(revision_id, parent_ids)
226
done.add(revision_id)
229
def _get_revision_vf(self):
230
""":return: a versioned file containing the revisions."""
231
vf = self._revision_store.get_revision_file(self.get_transaction())
234
def _get_history_vf(self):
235
"""Get a versionedfile whose history graph reflects all revisions.
237
For knit repositories, this is the revision knit.
239
return self._get_revision_vf()
242
def reconcile(self, other=None, thorough=False):
243
"""Reconcile this repository."""
244
from bzrlib.reconcile import KnitReconciler
245
reconciler = KnitReconciler(self, thorough=thorough)
246
reconciler.reconcile()
249
def revision_parents(self, revision_id):
250
revision_id = osutils.safe_revision_id(revision_id)
251
return self._get_revision_vf().get_parents(revision_id)
253
def _make_parents_provider(self):
254
return _KnitParentsProvider(self._get_revision_vf())
257
class KnitRepository3(KnitRepository):
259
# knit3 repositories need a RootCommitBuilder
260
_commit_builder_class = RootCommitBuilder
262
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
263
control_store, text_store):
264
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
265
_revision_store, control_store, text_store)
266
self._serializer = xml7.serializer_v7
268
def deserialise_inventory(self, revision_id, xml):
269
"""Transform the xml into an inventory object.
271
:param revision_id: The expected revision id of the inventory.
272
:param xml: A serialised inventory.
274
result = self._serializer.read_inventory_from_string(xml)
275
assert result.root.revision is not None
278
def serialise_inventory(self, inv):
279
"""Transform the inventory object into XML text.
281
:param revision_id: The expected revision id of the inventory.
282
:param xml: A serialised inventory.
284
assert inv.revision_id is not None
285
assert inv.root.revision is not None
286
return KnitRepository.serialise_inventory(self, inv)
289
class RepositoryFormatKnit(MetaDirRepositoryFormat):
290
"""Bzr repository knit format (generalized).
292
This repository format has:
293
- knits for file texts and inventory
294
- hash subdirectory based stores.
295
- knits for revisions and signatures
296
- TextStores for revisions and signatures.
297
- a format marker of its own
298
- an optional 'shared-storage' flag
299
- an optional 'no-working-trees' flag
303
def _get_control_store(self, repo_transport, control_files):
304
"""Return the control store for this repository."""
305
return VersionedFileStore(
308
file_mode=control_files._file_mode,
309
versionedfile_class=knit.KnitVersionedFile,
310
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
313
def _get_revision_store(self, repo_transport, control_files):
314
"""See RepositoryFormat._get_revision_store()."""
315
versioned_file_store = VersionedFileStore(
317
file_mode=control_files._file_mode,
320
versionedfile_class=knit.KnitVersionedFile,
321
versionedfile_kwargs={'delta':False,
322
'factory':knit.KnitPlainFactory(),
326
return KnitRevisionStore(versioned_file_store)
328
def _get_text_store(self, transport, control_files):
329
"""See RepositoryFormat._get_text_store()."""
330
return self._get_versioned_file_store('knits',
333
versionedfile_class=knit.KnitVersionedFile,
334
versionedfile_kwargs={
335
'create_parent_dir':True,
337
'dir_mode':control_files._dir_mode,
341
def initialize(self, a_bzrdir, shared=False):
342
"""Create a knit format 1 repository.
344
:param a_bzrdir: bzrdir to contain the new repository; must already
346
:param shared: If true the repository will be initialized as a shared
349
mutter('creating repository in %s.', a_bzrdir.transport.base)
352
utf8_files = [('format', self.get_format_string())]
354
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
355
repo_transport = a_bzrdir.get_repository_transport(None)
356
control_files = lockable_files.LockableFiles(repo_transport,
357
'lock', lockdir.LockDir)
358
control_store = self._get_control_store(repo_transport, control_files)
359
transaction = transactions.WriteTransaction()
360
# trigger a write of the inventory store.
361
control_store.get_weave_or_empty('inventory', transaction)
362
_revision_store = self._get_revision_store(repo_transport, control_files)
363
# the revision id here is irrelevant: it will not be stored, and cannot
365
_revision_store.has_revision_id('A', transaction)
366
_revision_store.get_signature_file(transaction)
367
return self.open(a_bzrdir=a_bzrdir, _found=True)
369
def open(self, a_bzrdir, _found=False, _override_transport=None):
370
"""See RepositoryFormat.open().
372
:param _override_transport: INTERNAL USE ONLY. Allows opening the
373
repository at a slightly different url
374
than normal. I.e. during 'upgrade'.
377
format = RepositoryFormat.find_format(a_bzrdir)
378
assert format.__class__ == self.__class__
379
if _override_transport is not None:
380
repo_transport = _override_transport
382
repo_transport = a_bzrdir.get_repository_transport(None)
383
control_files = lockable_files.LockableFiles(repo_transport,
384
'lock', lockdir.LockDir)
385
text_store = self._get_text_store(repo_transport, control_files)
386
control_store = self._get_control_store(repo_transport, control_files)
387
_revision_store = self._get_revision_store(repo_transport, control_files)
388
return self.repository_class(_format=self,
390
control_files=control_files,
391
_revision_store=_revision_store,
392
control_store=control_store,
393
text_store=text_store)
396
class RepositoryFormatKnit1(RepositoryFormatKnit):
397
"""Bzr repository knit format 1.
399
This repository format has:
400
- knits for file texts and inventory
401
- hash subdirectory based stores.
402
- knits for revisions and signatures
403
- TextStores for revisions and signatures.
404
- a format marker of its own
405
- an optional 'shared-storage' flag
406
- an optional 'no-working-trees' flag
409
This format was introduced in bzr 0.8.
412
repository_class = KnitRepository
414
def __ne__(self, other):
415
return self.__class__ is not other.__class__
417
def get_format_string(self):
418
"""See RepositoryFormat.get_format_string()."""
419
return "Bazaar-NG Knit Repository Format 1"
421
def get_format_description(self):
422
"""See RepositoryFormat.get_format_description()."""
423
return "Knit repository format 1"
425
def check_conversion_target(self, target_format):
429
class RepositoryFormatKnit3(RepositoryFormatKnit):
430
"""Bzr repository knit format 2.
432
This repository format has:
433
- knits for file texts and inventory
434
- hash subdirectory based stores.
435
- knits for revisions and signatures
436
- TextStores for revisions and signatures.
437
- a format marker of its own
438
- an optional 'shared-storage' flag
439
- an optional 'no-working-trees' flag
441
- support for recording full info about the tree root
442
- support for recording tree-references
445
repository_class = KnitRepository3
446
rich_root_data = True
447
supports_tree_reference = True
449
def _get_matching_bzrdir(self):
450
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
452
def _ignore_setting_bzrdir(self, format):
455
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
457
def check_conversion_target(self, target_format):
458
if not target_format.rich_root_data:
459
raise errors.BadConversionTarget(
460
'Does not support rich root data.', target_format)
461
if not getattr(target_format, 'supports_tree_reference', False):
462
raise errors.BadConversionTarget(
463
'Does not support nested trees', target_format)
465
def get_format_string(self):
466
"""See RepositoryFormat.get_format_string()."""
467
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
469
def get_format_description(self):
470
"""See RepositoryFormat.get_format_description()."""
471
return "Knit repository format 3"
474
def _get_stream_as_bytes(knit, required_versions):
475
"""Generate a serialised data stream.
477
The format is a bencoding of a list. The first element of the list is a
478
string of the format signature, then each subsequent element is a list
479
corresponding to a record. Those lists contain:
486
:returns: a bencoded list.
488
knit_stream = knit.get_data_stream(required_versions)
489
format_signature, data_list, callable = knit_stream
491
data.append(format_signature)
492
for version, options, length, parents in data_list:
493
data.append([version, options, parents, callable(length)])
494
return bencode.bencode(data)