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
42
class _KnitParentsProvider(object):
44
def __init__(self, knit):
47
def get_parents(self, revision_ids):
49
for revision_id in revision_ids:
50
if revision_id == _mod_revision.NULL_REVISION:
54
parents = self._knit.get_parents_with_ghosts(revision_id)
55
except errors.RevisionNotPresent:
59
parents = [_mod_revision.NULL_REVISION]
60
parents_list.append(parents)
64
class KnitRepository(MetaDirRepository):
65
"""Knit format repository."""
67
_serializer = xml5.serializer_v5
69
def _warn_if_deprecated(self):
70
# This class isn't deprecated
73
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
74
inv_vf.add_lines_with_ghosts(revid, parents, lines)
77
def _all_revision_ids(self):
78
"""See Repository.all_revision_ids()."""
79
# Knits get the revision graph from the index of the revision knit, so
80
# it's always possible even if they're on an unlistable transport.
81
return self._revision_store.all_revision_ids(self.get_transaction())
83
def fileid_involved_between_revs(self, from_revid, to_revid):
84
"""Find file_id(s) which are involved in the changes between revisions.
86
This determines the set of revisions which are involved, and then
87
finds all file ids affected by those revisions.
89
from_revid = osutils.safe_revision_id(from_revid)
90
to_revid = osutils.safe_revision_id(to_revid)
91
vf = self._get_revision_vf()
92
from_set = set(vf.get_ancestry(from_revid))
93
to_set = set(vf.get_ancestry(to_revid))
94
changed = to_set.difference(from_set)
95
return self._fileid_involved_by_set(changed)
97
def fileid_involved(self, last_revid=None):
98
"""Find all file_ids modified in the ancestry of last_revid.
100
:param last_revid: If None, last_revision() will be used.
103
changed = set(self.all_revision_ids())
105
changed = set(self.get_ancestry(last_revid))
108
return self._fileid_involved_by_set(changed)
111
def get_ancestry(self, revision_id):
112
"""Return a list of revision-ids integrated by a revision.
114
This is topologically sorted.
116
if revision_id is None:
118
revision_id = osutils.safe_revision_id(revision_id)
119
vf = self._get_revision_vf()
121
return [None] + vf.get_ancestry(revision_id)
122
except errors.RevisionNotPresent:
123
raise errors.NoSuchRevision(self, revision_id)
126
def get_revision(self, revision_id):
127
"""Return the Revision object for a named revision"""
128
revision_id = osutils.safe_revision_id(revision_id)
129
return self.get_revision_reconcile(revision_id)
132
def get_revision_graph(self, revision_id=None):
133
"""Return a dictionary containing the revision graph.
135
:param revision_id: The revision_id to get a graph from. If None, then
136
the entire revision graph is returned. This is a deprecated mode of
137
operation and will be removed in the future.
138
:return: a dictionary of revision_id->revision_parents_list.
140
# special case NULL_REVISION
141
if revision_id == _mod_revision.NULL_REVISION:
143
revision_id = osutils.safe_revision_id(revision_id)
144
a_weave = self._get_revision_vf()
145
entire_graph = a_weave.get_graph()
146
if revision_id is None:
147
return a_weave.get_graph()
148
elif revision_id not in a_weave:
149
raise errors.NoSuchRevision(self, revision_id)
151
# add what can be reached from revision_id
153
pending = set([revision_id])
154
while len(pending) > 0:
156
result[node] = a_weave.get_parents(node)
157
for revision_id in result[node]:
158
if revision_id not in result:
159
pending.add(revision_id)
163
def get_revision_graph_with_ghosts(self, revision_ids=None):
164
"""Return a graph of the revisions with ghosts marked as applicable.
166
:param revision_ids: an iterable of revisions to graph or None for all.
167
:return: a Graph object with the graph reachable from revision_ids.
169
result = deprecated_graph.Graph()
170
vf = self._get_revision_vf()
171
versions = set(vf.versions())
173
pending = set(self.all_revision_ids())
176
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
177
# special case NULL_REVISION
178
if _mod_revision.NULL_REVISION in pending:
179
pending.remove(_mod_revision.NULL_REVISION)
180
required = set(pending)
183
revision_id = pending.pop()
184
if not revision_id in versions:
185
if revision_id in required:
186
raise errors.NoSuchRevision(self, revision_id)
188
result.add_ghost(revision_id)
189
# mark it as done so we don't try for it again.
190
done.add(revision_id)
192
parent_ids = vf.get_parents_with_ghosts(revision_id)
193
for parent_id in parent_ids:
194
# is this queued or done ?
195
if (parent_id not in pending and
196
parent_id not in done):
198
pending.add(parent_id)
199
result.add_node(revision_id, parent_ids)
200
done.add(revision_id)
203
def _get_revision_vf(self):
204
""":return: a versioned file containing the revisions."""
205
vf = self._revision_store.get_revision_file(self.get_transaction())
208
def _get_history_vf(self):
209
"""Get a versionedfile whose history graph reflects all revisions.
211
For knit repositories, this is the revision knit.
213
return self._get_revision_vf()
216
def reconcile(self, other=None, thorough=False):
217
"""Reconcile this repository."""
218
from bzrlib.reconcile import KnitReconciler
219
reconciler = KnitReconciler(self, thorough=thorough)
220
reconciler.reconcile()
223
def revision_parents(self, revision_id):
224
revision_id = osutils.safe_revision_id(revision_id)
225
return self._get_revision_vf().get_parents(revision_id)
227
def _make_parents_provider(self):
228
return _KnitParentsProvider(self._get_revision_vf())
231
class KnitRepository3(KnitRepository):
233
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
234
control_store, text_store):
235
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
236
_revision_store, control_store, text_store)
237
self._serializer = xml7.serializer_v7
239
def deserialise_inventory(self, revision_id, xml):
240
"""Transform the xml into an inventory object.
242
:param revision_id: The expected revision id of the inventory.
243
:param xml: A serialised inventory.
245
result = self._serializer.read_inventory_from_string(xml)
246
assert result.root.revision is not None
249
def serialise_inventory(self, inv):
250
"""Transform the inventory object into XML text.
252
:param revision_id: The expected revision id of the inventory.
253
:param xml: A serialised inventory.
255
assert inv.revision_id is not None
256
assert inv.root.revision is not None
257
return KnitRepository.serialise_inventory(self, inv)
259
def get_commit_builder(self, branch, parents, config, timestamp=None,
260
timezone=None, committer=None, revprops=None,
262
"""Obtain a CommitBuilder for this repository.
264
:param branch: Branch to commit to.
265
:param parents: Revision ids of the parents of the new revision.
266
:param config: Configuration to use.
267
:param timestamp: Optional timestamp recorded for commit.
268
:param timezone: Optional timezone for timestamp.
269
:param committer: Optional committer to set for commit.
270
:param revprops: Optional dictionary of revision properties.
271
:param revision_id: Optional revision id.
273
revision_id = osutils.safe_revision_id(revision_id)
274
return RootCommitBuilder(self, parents, config, timestamp, timezone,
275
committer, revprops, revision_id)
278
class RepositoryFormatKnit(MetaDirRepositoryFormat):
279
"""Bzr repository knit format (generalized).
281
This repository format has:
282
- knits for file texts and inventory
283
- hash subdirectory based stores.
284
- knits for revisions and signatures
285
- TextStores for revisions and signatures.
286
- a format marker of its own
287
- an optional 'shared-storage' flag
288
- an optional 'no-working-trees' flag
292
def _get_control_store(self, repo_transport, control_files):
293
"""Return the control store for this repository."""
294
return VersionedFileStore(
297
file_mode=control_files._file_mode,
298
versionedfile_class=knit.KnitVersionedFile,
299
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
302
def _get_revision_store(self, repo_transport, control_files):
303
"""See RepositoryFormat._get_revision_store()."""
304
from bzrlib.store.revision.knit import KnitRevisionStore
305
versioned_file_store = VersionedFileStore(
307
file_mode=control_files._file_mode,
310
versionedfile_class=knit.KnitVersionedFile,
311
versionedfile_kwargs={'delta':False,
312
'factory':knit.KnitPlainFactory(),
316
return KnitRevisionStore(versioned_file_store)
318
def _get_text_store(self, transport, control_files):
319
"""See RepositoryFormat._get_text_store()."""
320
return self._get_versioned_file_store('knits',
323
versionedfile_class=knit.KnitVersionedFile,
324
versionedfile_kwargs={
325
'create_parent_dir':True,
327
'dir_mode':control_files._dir_mode,
331
def initialize(self, a_bzrdir, shared=False):
332
"""Create a knit format 1 repository.
334
:param a_bzrdir: bzrdir to contain the new repository; must already
336
:param shared: If true the repository will be initialized as a shared
339
mutter('creating repository in %s.', a_bzrdir.transport.base)
340
dirs = ['revision-store', 'knits']
342
utf8_files = [('format', self.get_format_string())]
344
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
345
repo_transport = a_bzrdir.get_repository_transport(None)
346
control_files = lockable_files.LockableFiles(repo_transport,
347
'lock', lockdir.LockDir)
348
control_store = self._get_control_store(repo_transport, control_files)
349
transaction = transactions.WriteTransaction()
350
# trigger a write of the inventory store.
351
control_store.get_weave_or_empty('inventory', transaction)
352
_revision_store = self._get_revision_store(repo_transport, control_files)
353
# the revision id here is irrelevant: it will not be stored, and cannot
355
_revision_store.has_revision_id('A', transaction)
356
_revision_store.get_signature_file(transaction)
357
return self.open(a_bzrdir=a_bzrdir, _found=True)
359
def open(self, a_bzrdir, _found=False, _override_transport=None):
360
"""See RepositoryFormat.open().
362
:param _override_transport: INTERNAL USE ONLY. Allows opening the
363
repository at a slightly different url
364
than normal. I.e. during 'upgrade'.
367
format = RepositoryFormat.find_format(a_bzrdir)
368
assert format.__class__ == self.__class__
369
if _override_transport is not None:
370
repo_transport = _override_transport
372
repo_transport = a_bzrdir.get_repository_transport(None)
373
control_files = lockable_files.LockableFiles(repo_transport,
374
'lock', lockdir.LockDir)
375
text_store = self._get_text_store(repo_transport, control_files)
376
control_store = self._get_control_store(repo_transport, control_files)
377
_revision_store = self._get_revision_store(repo_transport, control_files)
378
return KnitRepository(_format=self,
380
control_files=control_files,
381
_revision_store=_revision_store,
382
control_store=control_store,
383
text_store=text_store)
386
class RepositoryFormatKnit1(RepositoryFormatKnit):
387
"""Bzr repository knit format 1.
389
This repository format has:
390
- knits for file texts and inventory
391
- hash subdirectory based stores.
392
- knits for revisions and signatures
393
- TextStores for revisions and signatures.
394
- a format marker of its own
395
- an optional 'shared-storage' flag
396
- an optional 'no-working-trees' flag
399
This format was introduced in bzr 0.8.
402
def __ne__(self, other):
403
return self.__class__ is not other.__class__
405
def get_format_string(self):
406
"""See RepositoryFormat.get_format_string()."""
407
return "Bazaar-NG Knit Repository Format 1"
409
def get_format_description(self):
410
"""See RepositoryFormat.get_format_description()."""
411
return "Knit repository format 1"
413
def check_conversion_target(self, target_format):
417
class RepositoryFormatKnit3(RepositoryFormatKnit):
418
"""Bzr repository knit format 2.
420
This repository format has:
421
- knits for file texts and inventory
422
- hash subdirectory based stores.
423
- knits for revisions and signatures
424
- TextStores for revisions and signatures.
425
- a format marker of its own
426
- an optional 'shared-storage' flag
427
- an optional 'no-working-trees' flag
429
- support for recording full info about the tree root
430
- support for recording tree-references
433
repository_class = KnitRepository3
434
rich_root_data = True
435
supports_tree_reference = True
437
def _get_matching_bzrdir(self):
438
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
440
def _ignore_setting_bzrdir(self, format):
443
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
445
def check_conversion_target(self, target_format):
446
if not target_format.rich_root_data:
447
raise errors.BadConversionTarget(
448
'Does not support rich root data.', target_format)
449
if not getattr(target_format, 'supports_tree_reference', False):
450
raise errors.BadConversionTarget(
451
'Does not support nested trees', target_format)
453
def get_format_string(self):
454
"""See RepositoryFormat.get_format_string()."""
455
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
457
def get_format_description(self):
458
"""See RepositoryFormat.get_format_description()."""
459
return "Knit repository format 3"
461
def open(self, a_bzrdir, _found=False, _override_transport=None):
462
"""See RepositoryFormat.open().
464
:param _override_transport: INTERNAL USE ONLY. Allows opening the
465
repository at a slightly different url
466
than normal. I.e. during 'upgrade'.
469
format = RepositoryFormat.find_format(a_bzrdir)
470
assert format.__class__ == self.__class__
471
if _override_transport is not None:
472
repo_transport = _override_transport
474
repo_transport = a_bzrdir.get_repository_transport(None)
475
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
477
text_store = self._get_text_store(repo_transport, control_files)
478
control_store = self._get_control_store(repo_transport, control_files)
479
_revision_store = self._get_revision_store(repo_transport, control_files)
480
return self.repository_class(_format=self,
482
control_files=control_files,
483
_revision_store=_revision_store,
484
control_store=control_store,
485
text_store=text_store)