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
inv_vf.add_lines_with_ghosts(revid, parents, lines,
79
check_content=check_content)
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
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
231
control_store, text_store):
232
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
233
_revision_store, control_store, text_store)
234
self._serializer = xml7.serializer_v7
236
def deserialise_inventory(self, revision_id, xml):
237
"""Transform the xml into an inventory object.
239
:param revision_id: The expected revision id of the inventory.
240
:param xml: A serialised inventory.
242
result = self._serializer.read_inventory_from_string(xml)
243
assert result.root.revision is not None
246
def serialise_inventory(self, inv):
247
"""Transform the inventory object into XML text.
249
:param revision_id: The expected revision id of the inventory.
250
:param xml: A serialised inventory.
252
assert inv.revision_id is not None
253
assert inv.root.revision is not None
254
return KnitRepository.serialise_inventory(self, inv)
256
def get_commit_builder(self, branch, parents, config, timestamp=None,
257
timezone=None, committer=None, revprops=None,
259
"""Obtain a CommitBuilder for this repository.
261
:param branch: Branch to commit to.
262
:param parents: Revision ids of the parents of the new revision.
263
:param config: Configuration to use.
264
:param timestamp: Optional timestamp recorded for commit.
265
:param timezone: Optional timezone for timestamp.
266
:param committer: Optional committer to set for commit.
267
:param revprops: Optional dictionary of revision properties.
268
:param revision_id: Optional revision id.
270
revision_id = osutils.safe_revision_id(revision_id)
271
result = RootCommitBuilder(self, parents, config, timestamp, timezone,
272
committer, revprops, revision_id)
273
self.start_write_group()
277
class RepositoryFormatKnit(MetaDirRepositoryFormat):
278
"""Bzr repository knit format (generalized).
280
This repository format has:
281
- knits for file texts and inventory
282
- hash subdirectory based stores.
283
- knits for revisions and signatures
284
- TextStores for revisions and signatures.
285
- a format marker of its own
286
- an optional 'shared-storage' flag
287
- an optional 'no-working-trees' flag
291
def _get_control_store(self, repo_transport, control_files):
292
"""Return the control store for this repository."""
293
return VersionedFileStore(
296
file_mode=control_files._file_mode,
297
versionedfile_class=knit.KnitVersionedFile,
298
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
301
def _get_revision_store(self, repo_transport, control_files):
302
"""See RepositoryFormat._get_revision_store()."""
303
from bzrlib.store.revision.knit import KnitRevisionStore
304
versioned_file_store = VersionedFileStore(
306
file_mode=control_files._file_mode,
309
versionedfile_class=knit.KnitVersionedFile,
310
versionedfile_kwargs={'delta':False,
311
'factory':knit.KnitPlainFactory(),
315
return KnitRevisionStore(versioned_file_store)
317
def _get_text_store(self, transport, control_files):
318
"""See RepositoryFormat._get_text_store()."""
319
return self._get_versioned_file_store('knits',
322
versionedfile_class=knit.KnitVersionedFile,
323
versionedfile_kwargs={
324
'create_parent_dir':True,
326
'dir_mode':control_files._dir_mode,
330
def initialize(self, a_bzrdir, shared=False):
331
"""Create a knit format 1 repository.
333
:param a_bzrdir: bzrdir to contain the new repository; must already
335
:param shared: If true the repository will be initialized as a shared
338
mutter('creating repository in %s.', a_bzrdir.transport.base)
339
dirs = ['revision-store', 'knits']
341
utf8_files = [('format', self.get_format_string())]
343
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
344
repo_transport = a_bzrdir.get_repository_transport(None)
345
control_files = lockable_files.LockableFiles(repo_transport,
346
'lock', lockdir.LockDir)
347
control_store = self._get_control_store(repo_transport, control_files)
348
transaction = transactions.WriteTransaction()
349
# trigger a write of the inventory store.
350
control_store.get_weave_or_empty('inventory', transaction)
351
_revision_store = self._get_revision_store(repo_transport, control_files)
352
# the revision id here is irrelevant: it will not be stored, and cannot
354
_revision_store.has_revision_id('A', transaction)
355
_revision_store.get_signature_file(transaction)
356
return self.open(a_bzrdir=a_bzrdir, _found=True)
358
def open(self, a_bzrdir, _found=False, _override_transport=None):
359
"""See RepositoryFormat.open().
361
:param _override_transport: INTERNAL USE ONLY. Allows opening the
362
repository at a slightly different url
363
than normal. I.e. during 'upgrade'.
366
format = RepositoryFormat.find_format(a_bzrdir)
367
assert format.__class__ == self.__class__
368
if _override_transport is not None:
369
repo_transport = _override_transport
371
repo_transport = a_bzrdir.get_repository_transport(None)
372
control_files = lockable_files.LockableFiles(repo_transport,
373
'lock', lockdir.LockDir)
374
text_store = self._get_text_store(repo_transport, control_files)
375
control_store = self._get_control_store(repo_transport, control_files)
376
_revision_store = self._get_revision_store(repo_transport, control_files)
377
return KnitRepository(_format=self,
379
control_files=control_files,
380
_revision_store=_revision_store,
381
control_store=control_store,
382
text_store=text_store)
385
class RepositoryFormatKnit1(RepositoryFormatKnit):
386
"""Bzr repository knit format 1.
388
This repository format has:
389
- knits for file texts and inventory
390
- hash subdirectory based stores.
391
- knits for revisions and signatures
392
- TextStores for revisions and signatures.
393
- a format marker of its own
394
- an optional 'shared-storage' flag
395
- an optional 'no-working-trees' flag
398
This format was introduced in bzr 0.8.
401
def __ne__(self, other):
402
return self.__class__ is not other.__class__
404
def get_format_string(self):
405
"""See RepositoryFormat.get_format_string()."""
406
return "Bazaar-NG Knit Repository Format 1"
408
def get_format_description(self):
409
"""See RepositoryFormat.get_format_description()."""
410
return "Knit repository format 1"
412
def check_conversion_target(self, target_format):
416
class RepositoryFormatKnit3(RepositoryFormatKnit):
417
"""Bzr repository knit format 2.
419
This repository format has:
420
- knits for file texts and inventory
421
- hash subdirectory based stores.
422
- knits for revisions and signatures
423
- TextStores for revisions and signatures.
424
- a format marker of its own
425
- an optional 'shared-storage' flag
426
- an optional 'no-working-trees' flag
428
- support for recording full info about the tree root
429
- support for recording tree-references
432
repository_class = KnitRepository3
433
rich_root_data = True
434
supports_tree_reference = True
436
def _get_matching_bzrdir(self):
437
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
439
def _ignore_setting_bzrdir(self, format):
442
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
444
def check_conversion_target(self, target_format):
445
if not target_format.rich_root_data:
446
raise errors.BadConversionTarget(
447
'Does not support rich root data.', target_format)
448
if not getattr(target_format, 'supports_tree_reference', False):
449
raise errors.BadConversionTarget(
450
'Does not support nested trees', target_format)
452
def get_format_string(self):
453
"""See RepositoryFormat.get_format_string()."""
454
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
456
def get_format_description(self):
457
"""See RepositoryFormat.get_format_description()."""
458
return "Knit repository format 3"
460
def open(self, a_bzrdir, _found=False, _override_transport=None):
461
"""See RepositoryFormat.open().
463
:param _override_transport: INTERNAL USE ONLY. Allows opening the
464
repository at a slightly different url
465
than normal. I.e. during 'upgrade'.
468
format = RepositoryFormat.find_format(a_bzrdir)
469
assert format.__class__ == self.__class__
470
if _override_transport is not None:
471
repo_transport = _override_transport
473
repo_transport = a_bzrdir.get_repository_transport(None)
474
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
476
text_store = self._get_text_store(repo_transport, control_files)
477
control_store = self._get_control_store(repo_transport, control_files)
478
_revision_store = self._get_revision_store(repo_transport, control_files)
479
return self.repository_class(_format=self,
481
control_files=control_files,
482
_revision_store=_revision_store,
483
control_store=control_store,
484
text_store=text_store)
487
def _get_stream_as_bytes(knit, required_versions):
488
"""Generate a serialised data stream.
490
The format is a bencoding of a list. The first element of the list is a
491
string of the format signature, then each subsequent element is a list
492
corresponding to a record. Those lists contain:
499
:returns: a bencoded list.
501
knit_stream = knit.get_data_stream(required_versions)
502
format_signature, data_list, callable = knit_stream
504
data.append(format_signature)
505
for version, options, length, parents in data_list:
506
data.append([version, options, parents, callable(length)])
507
return bencode.bencode(data)