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
from_revid = osutils.safe_revision_id(from_revid)
95
to_revid = osutils.safe_revision_id(to_revid)
96
vf = self._get_revision_vf()
97
from_set = set(vf.get_ancestry(from_revid))
98
to_set = set(vf.get_ancestry(to_revid))
99
changed = to_set.difference(from_set)
100
return self._fileid_involved_by_set(changed)
102
def fileid_involved(self, last_revid=None):
103
"""Find all file_ids modified in the ancestry of last_revid.
105
:param last_revid: If None, last_revision() will be used.
108
changed = set(self.all_revision_ids())
110
changed = set(self.get_ancestry(last_revid))
113
return self._fileid_involved_by_set(changed)
116
def get_ancestry(self, revision_id, topo_sorted=True):
117
"""Return a list of revision-ids integrated by a revision.
119
This is topologically sorted, unless 'topo_sorted' is specified as
122
if _mod_revision.is_null(revision_id):
124
revision_id = osutils.safe_revision_id(revision_id)
125
vf = self._get_revision_vf()
127
return [None] + vf.get_ancestry(revision_id, topo_sorted)
128
except errors.RevisionNotPresent:
129
raise errors.NoSuchRevision(self, revision_id)
132
def get_revision(self, revision_id):
133
"""Return the Revision object for a named revision"""
134
revision_id = osutils.safe_revision_id(revision_id)
135
return self.get_revision_reconcile(revision_id)
138
def get_revision_graph(self, revision_id=None):
139
"""Return a dictionary containing the revision graph.
141
:param revision_id: The revision_id to get a graph from. If None, then
142
the entire revision graph is returned. This is a deprecated mode of
143
operation and will be removed in the future.
144
:return: a dictionary of revision_id->revision_parents_list.
146
# special case NULL_REVISION
147
if revision_id == _mod_revision.NULL_REVISION:
149
revision_id = osutils.safe_revision_id(revision_id)
150
a_weave = self._get_revision_vf()
151
if revision_id is None:
152
return a_weave.get_graph()
153
elif revision_id not in a_weave:
154
raise errors.NoSuchRevision(self, revision_id)
156
# add what can be reached from revision_id
157
return a_weave.get_graph([revision_id])
160
def get_revision_graph_with_ghosts(self, revision_ids=None):
161
"""Return a graph of the revisions with ghosts marked as applicable.
163
:param revision_ids: an iterable of revisions to graph or None for all.
164
:return: a Graph object with the graph reachable from revision_ids.
166
result = deprecated_graph.Graph()
167
vf = self._get_revision_vf()
168
versions = set(vf.versions())
170
pending = set(self.all_revision_ids())
173
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
174
# special case NULL_REVISION
175
if _mod_revision.NULL_REVISION in pending:
176
pending.remove(_mod_revision.NULL_REVISION)
177
required = set(pending)
180
revision_id = pending.pop()
181
if not revision_id in versions:
182
if revision_id in required:
183
raise errors.NoSuchRevision(self, revision_id)
185
result.add_ghost(revision_id)
186
# mark it as done so we don't try for it again.
187
done.add(revision_id)
189
parent_ids = vf.get_parents_with_ghosts(revision_id)
190
for parent_id in parent_ids:
191
# is this queued or done ?
192
if (parent_id not in pending and
193
parent_id not in done):
195
pending.add(parent_id)
196
result.add_node(revision_id, parent_ids)
197
done.add(revision_id)
200
def _get_revision_vf(self):
201
""":return: a versioned file containing the revisions."""
202
vf = self._revision_store.get_revision_file(self.get_transaction())
205
def _get_history_vf(self):
206
"""Get a versionedfile whose history graph reflects all revisions.
208
For knit repositories, this is the revision knit.
210
return self._get_revision_vf()
213
def reconcile(self, other=None, thorough=False):
214
"""Reconcile this repository."""
215
from bzrlib.reconcile import KnitReconciler
216
reconciler = KnitReconciler(self, thorough=thorough)
217
reconciler.reconcile()
220
def revision_parents(self, revision_id):
221
revision_id = osutils.safe_revision_id(revision_id)
222
return self._get_revision_vf().get_parents(revision_id)
224
def _make_parents_provider(self):
225
return _KnitParentsProvider(self._get_revision_vf())
228
class KnitRepository3(KnitRepository):
230
_commit_builder_class = RootCommitBuilder
232
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
233
control_store, text_store):
234
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
235
_revision_store, control_store, text_store)
236
self._serializer = xml7.serializer_v7
238
def deserialise_inventory(self, revision_id, xml):
239
"""Transform the xml into an inventory object.
241
:param revision_id: The expected revision id of the inventory.
242
:param xml: A serialised inventory.
244
result = self._serializer.read_inventory_from_string(xml)
245
assert result.root.revision is not None
248
def serialise_inventory(self, inv):
249
"""Transform the inventory object into XML text.
251
:param revision_id: The expected revision id of the inventory.
252
:param xml: A serialised inventory.
254
assert inv.revision_id is not None
255
assert inv.root.revision is not None
256
return KnitRepository.serialise_inventory(self, inv)
259
class RepositoryFormatKnit(MetaDirRepositoryFormat):
260
"""Bzr repository knit format (generalized).
262
This repository format has:
263
- knits for file texts and inventory
264
- hash subdirectory based stores.
265
- knits for revisions and signatures
266
- TextStores for revisions and signatures.
267
- a format marker of its own
268
- an optional 'shared-storage' flag
269
- an optional 'no-working-trees' flag
273
def _get_control_store(self, repo_transport, control_files):
274
"""Return the control store for this repository."""
275
return VersionedFileStore(
278
file_mode=control_files._file_mode,
279
versionedfile_class=knit.KnitVersionedFile,
280
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
283
def _get_revision_store(self, repo_transport, control_files):
284
"""See RepositoryFormat._get_revision_store()."""
285
from bzrlib.store.revision.knit import KnitRevisionStore
286
versioned_file_store = VersionedFileStore(
288
file_mode=control_files._file_mode,
291
versionedfile_class=knit.KnitVersionedFile,
292
versionedfile_kwargs={'delta':False,
293
'factory':knit.KnitPlainFactory(),
297
return KnitRevisionStore(versioned_file_store)
299
def _get_text_store(self, transport, control_files):
300
"""See RepositoryFormat._get_text_store()."""
301
return self._get_versioned_file_store('knits',
304
versionedfile_class=knit.KnitVersionedFile,
305
versionedfile_kwargs={
306
'create_parent_dir':True,
308
'dir_mode':control_files._dir_mode,
312
def initialize(self, a_bzrdir, shared=False):
313
"""Create a knit format 1 repository.
315
:param a_bzrdir: bzrdir to contain the new repository; must already
317
:param shared: If true the repository will be initialized as a shared
320
mutter('creating repository in %s.', a_bzrdir.transport.base)
321
dirs = ['revision-store', 'knits']
323
utf8_files = [('format', self.get_format_string())]
325
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
326
repo_transport = a_bzrdir.get_repository_transport(None)
327
control_files = lockable_files.LockableFiles(repo_transport,
328
'lock', lockdir.LockDir)
329
control_store = self._get_control_store(repo_transport, control_files)
330
transaction = transactions.WriteTransaction()
331
# trigger a write of the inventory store.
332
control_store.get_weave_or_empty('inventory', transaction)
333
_revision_store = self._get_revision_store(repo_transport, control_files)
334
# the revision id here is irrelevant: it will not be stored, and cannot
336
_revision_store.has_revision_id('A', transaction)
337
_revision_store.get_signature_file(transaction)
338
return self.open(a_bzrdir=a_bzrdir, _found=True)
340
def open(self, a_bzrdir, _found=False, _override_transport=None):
341
"""See RepositoryFormat.open().
343
:param _override_transport: INTERNAL USE ONLY. Allows opening the
344
repository at a slightly different url
345
than normal. I.e. during 'upgrade'.
348
format = RepositoryFormat.find_format(a_bzrdir)
349
assert format.__class__ == self.__class__
350
if _override_transport is not None:
351
repo_transport = _override_transport
353
repo_transport = a_bzrdir.get_repository_transport(None)
354
control_files = lockable_files.LockableFiles(repo_transport,
355
'lock', lockdir.LockDir)
356
text_store = self._get_text_store(repo_transport, control_files)
357
control_store = self._get_control_store(repo_transport, control_files)
358
_revision_store = self._get_revision_store(repo_transport, control_files)
359
return KnitRepository(_format=self,
361
control_files=control_files,
362
_revision_store=_revision_store,
363
control_store=control_store,
364
text_store=text_store)
367
class RepositoryFormatKnit1(RepositoryFormatKnit):
368
"""Bzr repository knit format 1.
370
This repository format has:
371
- knits for file texts and inventory
372
- hash subdirectory based stores.
373
- knits for revisions and signatures
374
- TextStores for revisions and signatures.
375
- a format marker of its own
376
- an optional 'shared-storage' flag
377
- an optional 'no-working-trees' flag
380
This format was introduced in bzr 0.8.
383
def __ne__(self, other):
384
return self.__class__ is not other.__class__
386
def get_format_string(self):
387
"""See RepositoryFormat.get_format_string()."""
388
return "Bazaar-NG Knit Repository Format 1"
390
def get_format_description(self):
391
"""See RepositoryFormat.get_format_description()."""
392
return "Knit repository format 1"
394
def check_conversion_target(self, target_format):
398
class RepositoryFormatKnit3(RepositoryFormatKnit):
399
"""Bzr repository knit format 2.
401
This repository format has:
402
- knits for file texts and inventory
403
- hash subdirectory based stores.
404
- knits for revisions and signatures
405
- TextStores for revisions and signatures.
406
- a format marker of its own
407
- an optional 'shared-storage' flag
408
- an optional 'no-working-trees' flag
410
- support for recording full info about the tree root
411
- support for recording tree-references
414
repository_class = KnitRepository3
415
rich_root_data = True
416
supports_tree_reference = True
418
def _get_matching_bzrdir(self):
419
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
421
def _ignore_setting_bzrdir(self, format):
424
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
426
def check_conversion_target(self, target_format):
427
if not target_format.rich_root_data:
428
raise errors.BadConversionTarget(
429
'Does not support rich root data.', target_format)
430
if not getattr(target_format, 'supports_tree_reference', False):
431
raise errors.BadConversionTarget(
432
'Does not support nested trees', target_format)
434
def get_format_string(self):
435
"""See RepositoryFormat.get_format_string()."""
436
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
438
def get_format_description(self):
439
"""See RepositoryFormat.get_format_description()."""
440
return "Knit repository format 3"
442
def open(self, a_bzrdir, _found=False, _override_transport=None):
443
"""See RepositoryFormat.open().
445
:param _override_transport: INTERNAL USE ONLY. Allows opening the
446
repository at a slightly different url
447
than normal. I.e. during 'upgrade'.
450
format = RepositoryFormat.find_format(a_bzrdir)
451
assert format.__class__ == self.__class__
452
if _override_transport is not None:
453
repo_transport = _override_transport
455
repo_transport = a_bzrdir.get_repository_transport(None)
456
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
458
text_store = self._get_text_store(repo_transport, control_files)
459
control_store = self._get_control_store(repo_transport, control_files)
460
_revision_store = self._get_revision_store(repo_transport, control_files)
461
return self.repository_class(_format=self,
463
control_files=control_files,
464
_revision_store=_revision_store,
465
control_store=control_store,
466
text_store=text_store)
469
def _get_stream_as_bytes(knit, required_versions):
470
"""Generate a serialised data stream.
472
The format is a bencoding of a list. The first element of the list is a
473
string of the format signature, then each subsequent element is a list
474
corresponding to a record. Those lists contain:
481
:returns: a bencoded list.
483
knit_stream = knit.get_data_stream(required_versions)
484
format_signature, data_list, callable = knit_stream
486
data.append(format_signature)
487
for version, options, length, parents in data_list:
488
data.append([version, options, parents, callable(length)])
489
return bencode.bencode(data)