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
30
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
from bzrlib.repository import (
33
MetaDirRepositoryFormat,
37
import bzrlib.revision as _mod_revision
38
from bzrlib.store.versioned import VersionedFileStore
39
from bzrlib.trace import mutter, note, warning
40
from bzrlib.util import bencode
43
class _KnitParentsProvider(object):
45
def __init__(self, knit):
49
return 'KnitParentsProvider(%r)' % self._knit
51
def get_parents(self, revision_ids):
53
for revision_id in revision_ids:
54
if revision_id == _mod_revision.NULL_REVISION:
58
parents = self._knit.get_parents_with_ghosts(revision_id)
59
except errors.RevisionNotPresent:
63
parents = [_mod_revision.NULL_REVISION]
64
parents_list.append(parents)
68
class KnitRepository(MetaDirRepository):
69
"""Knit format repository."""
71
_serializer = xml5.serializer_v5
73
def _warn_if_deprecated(self):
74
# This class isn't deprecated
77
def _inventory_add_lines(self, inv_vf, revid, parents, lines, check_content):
78
return inv_vf.add_lines_with_ghosts(revid, parents, lines,
79
check_content=check_content)[0]
82
def _all_revision_ids(self):
83
"""See Repository.all_revision_ids()."""
84
# Knits get the revision graph from the index of the revision knit, so
85
# it's always possible even if they're on an unlistable transport.
86
return self._revision_store.all_revision_ids(self.get_transaction())
88
def fileid_involved_between_revs(self, from_revid, to_revid):
89
"""Find file_id(s) which are involved in the changes between revisions.
91
This determines the set of revisions which are involved, and then
92
finds all file ids affected by those revisions.
94
vf = self._get_revision_vf()
95
from_set = set(vf.get_ancestry(from_revid))
96
to_set = set(vf.get_ancestry(to_revid))
97
changed = to_set.difference(from_set)
98
return self._fileid_involved_by_set(changed)
100
def fileid_involved(self, last_revid=None):
101
"""Find all file_ids modified in the ancestry of last_revid.
103
:param last_revid: If None, last_revision() will be used.
106
changed = set(self.all_revision_ids())
108
changed = set(self.get_ancestry(last_revid))
111
return self._fileid_involved_by_set(changed)
114
def get_ancestry(self, revision_id, topo_sorted=True):
115
"""Return a list of revision-ids integrated by a revision.
117
This is topologically sorted, unless 'topo_sorted' is specified as
120
if _mod_revision.is_null(revision_id):
122
vf = self._get_revision_vf()
124
return [None] + vf.get_ancestry(revision_id, topo_sorted)
125
except errors.RevisionNotPresent:
126
raise errors.NoSuchRevision(self, revision_id)
129
def get_revision_graph(self, revision_id=None):
130
"""Return a dictionary containing the revision graph.
132
:param revision_id: The revision_id to get a graph from. If None, then
133
the entire revision graph is returned. This is a deprecated mode of
134
operation and will be removed in the future.
135
:return: a dictionary of revision_id->revision_parents_list.
137
# special case NULL_REVISION
138
if revision_id == _mod_revision.NULL_REVISION:
140
a_weave = self._get_revision_vf()
141
if revision_id is None:
142
return a_weave.get_graph()
143
elif revision_id not in a_weave:
144
raise errors.NoSuchRevision(self, revision_id)
146
# add what can be reached from revision_id
147
return a_weave.get_graph([revision_id])
150
def get_revision_graph_with_ghosts(self, revision_ids=None):
151
"""Return a graph of the revisions with ghosts marked as applicable.
153
:param revision_ids: an iterable of revisions to graph or None for all.
154
:return: a Graph object with the graph reachable from revision_ids.
156
result = deprecated_graph.Graph()
157
vf = self._get_revision_vf()
158
versions = set(vf.versions())
160
pending = set(self.all_revision_ids())
163
pending = set(revision_ids)
164
# special case NULL_REVISION
165
if _mod_revision.NULL_REVISION in pending:
166
pending.remove(_mod_revision.NULL_REVISION)
167
required = set(pending)
170
revision_id = pending.pop()
171
if not revision_id in versions:
172
if revision_id in required:
173
raise errors.NoSuchRevision(self, revision_id)
175
result.add_ghost(revision_id)
176
# mark it as done so we don't try for it again.
177
done.add(revision_id)
179
parent_ids = vf.get_parents_with_ghosts(revision_id)
180
for parent_id in parent_ids:
181
# is this queued or done ?
182
if (parent_id not in pending and
183
parent_id not in done):
185
pending.add(parent_id)
186
result.add_node(revision_id, parent_ids)
187
done.add(revision_id)
190
def _get_revision_vf(self):
191
""":return: a versioned file containing the revisions."""
192
vf = self._revision_store.get_revision_file(self.get_transaction())
195
def _get_history_vf(self):
196
"""Get a versionedfile whose history graph reflects all revisions.
198
For knit repositories, this is the revision knit.
200
return self._get_revision_vf()
203
def reconcile(self, other=None, thorough=False):
204
"""Reconcile this repository."""
205
from bzrlib.reconcile import KnitReconciler
206
reconciler = KnitReconciler(self, thorough=thorough)
207
reconciler.reconcile()
210
def revision_parents(self, revision_id):
211
return self._get_revision_vf().get_parents(revision_id)
213
def _make_parents_provider(self):
214
return _KnitParentsProvider(self._get_revision_vf())
217
class KnitRepository3(KnitRepository):
219
_commit_builder_class = RootCommitBuilder
221
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
222
control_store, text_store):
223
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
224
_revision_store, control_store, text_store)
225
self._serializer = xml7.serializer_v7
227
def deserialise_inventory(self, revision_id, xml):
228
"""Transform the xml into an inventory object.
230
:param revision_id: The expected revision id of the inventory.
231
:param xml: A serialised inventory.
233
result = self._serializer.read_inventory_from_string(xml)
234
assert result.root.revision is not None
237
def serialise_inventory(self, inv):
238
"""Transform the inventory object into XML text.
240
:param revision_id: The expected revision id of the inventory.
241
:param xml: A serialised inventory.
243
assert inv.revision_id is not None
244
assert inv.root.revision is not None
245
return KnitRepository.serialise_inventory(self, inv)
248
class RepositoryFormatKnit(MetaDirRepositoryFormat):
249
"""Bzr repository knit format (generalized).
251
This repository format has:
252
- knits for file texts and inventory
253
- hash subdirectory based stores.
254
- knits for revisions and signatures
255
- TextStores for revisions and signatures.
256
- a format marker of its own
257
- an optional 'shared-storage' flag
258
- an optional 'no-working-trees' flag
262
def _get_control_store(self, repo_transport, control_files):
263
"""Return the control store for this repository."""
264
return VersionedFileStore(
267
file_mode=control_files._file_mode,
268
versionedfile_class=knit.KnitVersionedFile,
269
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
272
def _get_revision_store(self, repo_transport, control_files):
273
"""See RepositoryFormat._get_revision_store()."""
274
from bzrlib.store.revision.knit import KnitRevisionStore
275
versioned_file_store = VersionedFileStore(
277
file_mode=control_files._file_mode,
280
versionedfile_class=knit.KnitVersionedFile,
281
versionedfile_kwargs={'delta':False,
282
'factory':knit.KnitPlainFactory(),
286
return KnitRevisionStore(versioned_file_store)
288
def _get_text_store(self, transport, control_files):
289
"""See RepositoryFormat._get_text_store()."""
290
return self._get_versioned_file_store('knits',
293
versionedfile_class=knit.KnitVersionedFile,
294
versionedfile_kwargs={
295
'create_parent_dir':True,
297
'dir_mode':control_files._dir_mode,
301
def initialize(self, a_bzrdir, shared=False):
302
"""Create a knit format 1 repository.
304
:param a_bzrdir: bzrdir to contain the new repository; must already
306
:param shared: If true the repository will be initialized as a shared
309
mutter('creating repository in %s.', a_bzrdir.transport.base)
310
dirs = ['revision-store', 'knits']
312
utf8_files = [('format', self.get_format_string())]
314
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
315
repo_transport = a_bzrdir.get_repository_transport(None)
316
control_files = lockable_files.LockableFiles(repo_transport,
317
'lock', lockdir.LockDir)
318
control_store = self._get_control_store(repo_transport, control_files)
319
transaction = transactions.WriteTransaction()
320
# trigger a write of the inventory store.
321
control_store.get_weave_or_empty('inventory', transaction)
322
_revision_store = self._get_revision_store(repo_transport, control_files)
323
# the revision id here is irrelevant: it will not be stored, and cannot
325
_revision_store.has_revision_id('A', transaction)
326
_revision_store.get_signature_file(transaction)
327
return self.open(a_bzrdir=a_bzrdir, _found=True)
329
def open(self, a_bzrdir, _found=False, _override_transport=None):
330
"""See RepositoryFormat.open().
332
:param _override_transport: INTERNAL USE ONLY. Allows opening the
333
repository at a slightly different url
334
than normal. I.e. during 'upgrade'.
337
format = RepositoryFormat.find_format(a_bzrdir)
338
assert format.__class__ == self.__class__
339
if _override_transport is not None:
340
repo_transport = _override_transport
342
repo_transport = a_bzrdir.get_repository_transport(None)
343
control_files = lockable_files.LockableFiles(repo_transport,
344
'lock', lockdir.LockDir)
345
text_store = self._get_text_store(repo_transport, control_files)
346
control_store = self._get_control_store(repo_transport, control_files)
347
_revision_store = self._get_revision_store(repo_transport, control_files)
348
return KnitRepository(_format=self,
350
control_files=control_files,
351
_revision_store=_revision_store,
352
control_store=control_store,
353
text_store=text_store)
356
class RepositoryFormatKnit1(RepositoryFormatKnit):
357
"""Bzr repository knit format 1.
359
This repository format has:
360
- knits for file texts and inventory
361
- hash subdirectory based stores.
362
- knits for revisions and signatures
363
- TextStores for revisions and signatures.
364
- a format marker of its own
365
- an optional 'shared-storage' flag
366
- an optional 'no-working-trees' flag
369
This format was introduced in bzr 0.8.
372
def __ne__(self, other):
373
return self.__class__ is not other.__class__
375
def get_format_string(self):
376
"""See RepositoryFormat.get_format_string()."""
377
return "Bazaar-NG Knit Repository Format 1"
379
def get_format_description(self):
380
"""See RepositoryFormat.get_format_description()."""
381
return "Knit repository format 1"
383
def check_conversion_target(self, target_format):
387
class RepositoryFormatKnit3(RepositoryFormatKnit):
388
"""Bzr repository knit format 2.
390
This repository format has:
391
- knits for file texts and inventory
392
- hash subdirectory based stores.
393
- knits for revisions and signatures
394
- TextStores for revisions and signatures.
395
- a format marker of its own
396
- an optional 'shared-storage' flag
397
- an optional 'no-working-trees' flag
399
- support for recording full info about the tree root
400
- support for recording tree-references
403
repository_class = KnitRepository3
404
rich_root_data = True
405
supports_tree_reference = True
407
def _get_matching_bzrdir(self):
408
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
410
def _ignore_setting_bzrdir(self, format):
413
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
415
def check_conversion_target(self, target_format):
416
if not target_format.rich_root_data:
417
raise errors.BadConversionTarget(
418
'Does not support rich root data.', target_format)
419
if not getattr(target_format, 'supports_tree_reference', False):
420
raise errors.BadConversionTarget(
421
'Does not support nested trees', target_format)
423
def get_format_string(self):
424
"""See RepositoryFormat.get_format_string()."""
425
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
427
def get_format_description(self):
428
"""See RepositoryFormat.get_format_description()."""
429
return "Knit repository format 3"
431
def open(self, a_bzrdir, _found=False, _override_transport=None):
432
"""See RepositoryFormat.open().
434
:param _override_transport: INTERNAL USE ONLY. Allows opening the
435
repository at a slightly different url
436
than normal. I.e. during 'upgrade'.
439
format = RepositoryFormat.find_format(a_bzrdir)
440
assert format.__class__ == self.__class__
441
if _override_transport is not None:
442
repo_transport = _override_transport
444
repo_transport = a_bzrdir.get_repository_transport(None)
445
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
447
text_store = self._get_text_store(repo_transport, control_files)
448
control_store = self._get_control_store(repo_transport, control_files)
449
_revision_store = self._get_revision_store(repo_transport, control_files)
450
return self.repository_class(_format=self,
452
control_files=control_files,
453
_revision_store=_revision_store,
454
control_store=control_store,
455
text_store=text_store)
458
def _get_stream_as_bytes(knit, required_versions):
459
"""Generate a serialised data stream.
461
The format is a bencoding of a list. The first element of the list is a
462
string of the format signature, then each subsequent element is a list
463
corresponding to a record. Those lists contain:
470
:returns: a bencoded list.
472
knit_stream = knit.get_data_stream(required_versions)
473
format_signature, data_list, callable = knit_stream
475
data.append(format_signature)
476
for version, options, length, parents in data_list:
477
data.append([version, options, parents, callable(length)])
478
return bencode.bencode(data)