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):
48
return 'KnitParentsProvider(%r)' % self._knit
50
def get_parents(self, revision_ids):
52
for revision_id in revision_ids:
53
if revision_id == _mod_revision.NULL_REVISION:
57
parents = self._knit.get_parents_with_ghosts(revision_id)
58
except errors.RevisionNotPresent:
62
parents = [_mod_revision.NULL_REVISION]
63
parents_list.append(parents)
67
class KnitRepository(MetaDirRepository):
68
"""Knit format repository."""
70
_serializer = xml5.serializer_v5
72
def _warn_if_deprecated(self):
73
# This class isn't deprecated
76
def _inventory_add_lines(self, inv_vf, revid, parents, lines):
77
inv_vf.add_lines_with_ghosts(revid, parents, lines)
80
def _all_revision_ids(self):
81
"""See Repository.all_revision_ids()."""
82
# Knits get the revision graph from the index of the revision knit, so
83
# it's always possible even if they're on an unlistable transport.
84
return self._revision_store.all_revision_ids(self.get_transaction())
86
def fileid_involved_between_revs(self, from_revid, to_revid):
87
"""Find file_id(s) which are involved in the changes between revisions.
89
This determines the set of revisions which are involved, and then
90
finds all file ids affected by those revisions.
92
from_revid = osutils.safe_revision_id(from_revid)
93
to_revid = osutils.safe_revision_id(to_revid)
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):
115
"""Return a list of revision-ids integrated by a revision.
117
This is topologically sorted.
119
if revision_id is None:
121
revision_id = osutils.safe_revision_id(revision_id)
122
vf = self._get_revision_vf()
124
return [None] + vf.get_ancestry(revision_id)
125
except errors.RevisionNotPresent:
126
raise errors.NoSuchRevision(self, revision_id)
129
def get_revision(self, revision_id):
130
"""Return the Revision object for a named revision"""
131
revision_id = osutils.safe_revision_id(revision_id)
132
return self.get_revision_reconcile(revision_id)
135
def get_revision_graph(self, revision_id=None):
136
"""Return a dictionary containing the revision graph.
138
:param revision_id: The revision_id to get a graph from. If None, then
139
the entire revision graph is returned. This is a deprecated mode of
140
operation and will be removed in the future.
141
:return: a dictionary of revision_id->revision_parents_list.
143
# special case NULL_REVISION
144
if revision_id == _mod_revision.NULL_REVISION:
146
revision_id = osutils.safe_revision_id(revision_id)
147
a_weave = self._get_revision_vf()
148
entire_graph = a_weave.get_graph()
149
if revision_id is None:
150
return a_weave.get_graph()
151
elif revision_id not in a_weave:
152
raise errors.NoSuchRevision(self, revision_id)
154
# add what can be reached from revision_id
156
pending = set([revision_id])
157
while len(pending) > 0:
159
result[node] = a_weave.get_parents(node)
160
for revision_id in result[node]:
161
if revision_id not in result:
162
pending.add(revision_id)
166
def get_revision_graph_with_ghosts(self, revision_ids=None):
167
"""Return a graph of the revisions with ghosts marked as applicable.
169
:param revision_ids: an iterable of revisions to graph or None for all.
170
:return: a Graph object with the graph reachable from revision_ids.
172
result = deprecated_graph.Graph()
173
vf = self._get_revision_vf()
174
versions = set(vf.versions())
176
pending = set(self.all_revision_ids())
179
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
180
# special case NULL_REVISION
181
if _mod_revision.NULL_REVISION in pending:
182
pending.remove(_mod_revision.NULL_REVISION)
183
required = set(pending)
186
revision_id = pending.pop()
187
if not revision_id in versions:
188
if revision_id in required:
189
raise errors.NoSuchRevision(self, revision_id)
191
result.add_ghost(revision_id)
192
# mark it as done so we don't try for it again.
193
done.add(revision_id)
195
parent_ids = vf.get_parents_with_ghosts(revision_id)
196
for parent_id in parent_ids:
197
# is this queued or done ?
198
if (parent_id not in pending and
199
parent_id not in done):
201
pending.add(parent_id)
202
result.add_node(revision_id, parent_ids)
203
done.add(revision_id)
206
def _get_revision_vf(self):
207
""":return: a versioned file containing the revisions."""
208
vf = self._revision_store.get_revision_file(self.get_transaction())
211
def _get_history_vf(self):
212
"""Get a versionedfile whose history graph reflects all revisions.
214
For knit repositories, this is the revision knit.
216
return self._get_revision_vf()
219
def reconcile(self, other=None, thorough=False):
220
"""Reconcile this repository."""
221
from bzrlib.reconcile import KnitReconciler
222
reconciler = KnitReconciler(self, thorough=thorough)
223
reconciler.reconcile()
226
def revision_parents(self, revision_id):
227
revision_id = osutils.safe_revision_id(revision_id)
228
return self._get_revision_vf().get_parents(revision_id)
230
def _make_parents_provider(self):
231
return _KnitParentsProvider(self._get_revision_vf())
234
class KnitRepository3(KnitRepository):
236
def __init__(self, _format, a_bzrdir, control_files, _revision_store,
237
control_store, text_store):
238
KnitRepository.__init__(self, _format, a_bzrdir, control_files,
239
_revision_store, control_store, text_store)
240
self._serializer = xml7.serializer_v7
242
def deserialise_inventory(self, revision_id, xml):
243
"""Transform the xml into an inventory object.
245
:param revision_id: The expected revision id of the inventory.
246
:param xml: A serialised inventory.
248
result = self._serializer.read_inventory_from_string(xml)
249
assert result.root.revision is not None
252
def serialise_inventory(self, inv):
253
"""Transform the inventory object into XML text.
255
:param revision_id: The expected revision id of the inventory.
256
:param xml: A serialised inventory.
258
assert inv.revision_id is not None
259
assert inv.root.revision is not None
260
return KnitRepository.serialise_inventory(self, inv)
262
def get_commit_builder(self, branch, parents, config, timestamp=None,
263
timezone=None, committer=None, revprops=None,
265
"""Obtain a CommitBuilder for this repository.
267
:param branch: Branch to commit to.
268
:param parents: Revision ids of the parents of the new revision.
269
:param config: Configuration to use.
270
:param timestamp: Optional timestamp recorded for commit.
271
:param timezone: Optional timezone for timestamp.
272
:param committer: Optional committer to set for commit.
273
:param revprops: Optional dictionary of revision properties.
274
:param revision_id: Optional revision id.
276
revision_id = osutils.safe_revision_id(revision_id)
277
return RootCommitBuilder(self, parents, config, timestamp, timezone,
278
committer, revprops, revision_id)
281
class RepositoryFormatKnit(MetaDirRepositoryFormat):
282
"""Bzr repository knit format (generalized).
284
This repository format has:
285
- knits for file texts and inventory
286
- hash subdirectory based stores.
287
- knits for revisions and signatures
288
- TextStores for revisions and signatures.
289
- a format marker of its own
290
- an optional 'shared-storage' flag
291
- an optional 'no-working-trees' flag
295
def _get_control_store(self, repo_transport, control_files):
296
"""Return the control store for this repository."""
297
return VersionedFileStore(
300
file_mode=control_files._file_mode,
301
versionedfile_class=knit.KnitVersionedFile,
302
versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
305
def _get_revision_store(self, repo_transport, control_files):
306
"""See RepositoryFormat._get_revision_store()."""
307
from bzrlib.store.revision.knit import KnitRevisionStore
308
versioned_file_store = VersionedFileStore(
310
file_mode=control_files._file_mode,
313
versionedfile_class=knit.KnitVersionedFile,
314
versionedfile_kwargs={'delta':False,
315
'factory':knit.KnitPlainFactory(),
319
return KnitRevisionStore(versioned_file_store)
321
def _get_text_store(self, transport, control_files):
322
"""See RepositoryFormat._get_text_store()."""
323
return self._get_versioned_file_store('knits',
326
versionedfile_class=knit.KnitVersionedFile,
327
versionedfile_kwargs={
328
'create_parent_dir':True,
330
'dir_mode':control_files._dir_mode,
334
def initialize(self, a_bzrdir, shared=False):
335
"""Create a knit format 1 repository.
337
:param a_bzrdir: bzrdir to contain the new repository; must already
339
:param shared: If true the repository will be initialized as a shared
342
mutter('creating repository in %s.', a_bzrdir.transport.base)
343
dirs = ['revision-store', 'knits']
345
utf8_files = [('format', self.get_format_string())]
347
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
348
repo_transport = a_bzrdir.get_repository_transport(None)
349
control_files = lockable_files.LockableFiles(repo_transport,
350
'lock', lockdir.LockDir)
351
control_store = self._get_control_store(repo_transport, control_files)
352
transaction = transactions.WriteTransaction()
353
# trigger a write of the inventory store.
354
control_store.get_weave_or_empty('inventory', transaction)
355
_revision_store = self._get_revision_store(repo_transport, control_files)
356
# the revision id here is irrelevant: it will not be stored, and cannot
358
_revision_store.has_revision_id('A', transaction)
359
_revision_store.get_signature_file(transaction)
360
return self.open(a_bzrdir=a_bzrdir, _found=True)
362
def open(self, a_bzrdir, _found=False, _override_transport=None):
363
"""See RepositoryFormat.open().
365
:param _override_transport: INTERNAL USE ONLY. Allows opening the
366
repository at a slightly different url
367
than normal. I.e. during 'upgrade'.
370
format = RepositoryFormat.find_format(a_bzrdir)
371
assert format.__class__ == self.__class__
372
if _override_transport is not None:
373
repo_transport = _override_transport
375
repo_transport = a_bzrdir.get_repository_transport(None)
376
control_files = lockable_files.LockableFiles(repo_transport,
377
'lock', lockdir.LockDir)
378
text_store = self._get_text_store(repo_transport, control_files)
379
control_store = self._get_control_store(repo_transport, control_files)
380
_revision_store = self._get_revision_store(repo_transport, control_files)
381
return KnitRepository(_format=self,
383
control_files=control_files,
384
_revision_store=_revision_store,
385
control_store=control_store,
386
text_store=text_store)
389
class RepositoryFormatKnit1(RepositoryFormatKnit):
390
"""Bzr repository knit format 1.
392
This repository format has:
393
- knits for file texts and inventory
394
- hash subdirectory based stores.
395
- knits for revisions and signatures
396
- TextStores for revisions and signatures.
397
- a format marker of its own
398
- an optional 'shared-storage' flag
399
- an optional 'no-working-trees' flag
402
This format was introduced in bzr 0.8.
405
def __ne__(self, other):
406
return self.__class__ is not other.__class__
408
def get_format_string(self):
409
"""See RepositoryFormat.get_format_string()."""
410
return "Bazaar-NG Knit Repository Format 1"
412
def get_format_description(self):
413
"""See RepositoryFormat.get_format_description()."""
414
return "Knit repository format 1"
416
def check_conversion_target(self, target_format):
420
class RepositoryFormatKnit3(RepositoryFormatKnit):
421
"""Bzr repository knit format 2.
423
This repository format has:
424
- knits for file texts and inventory
425
- hash subdirectory based stores.
426
- knits for revisions and signatures
427
- TextStores for revisions and signatures.
428
- a format marker of its own
429
- an optional 'shared-storage' flag
430
- an optional 'no-working-trees' flag
432
- support for recording full info about the tree root
433
- support for recording tree-references
436
repository_class = KnitRepository3
437
rich_root_data = True
438
supports_tree_reference = True
440
def _get_matching_bzrdir(self):
441
return bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
443
def _ignore_setting_bzrdir(self, format):
446
_matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
448
def check_conversion_target(self, target_format):
449
if not target_format.rich_root_data:
450
raise errors.BadConversionTarget(
451
'Does not support rich root data.', target_format)
452
if not getattr(target_format, 'supports_tree_reference', False):
453
raise errors.BadConversionTarget(
454
'Does not support nested trees', target_format)
456
def get_format_string(self):
457
"""See RepositoryFormat.get_format_string()."""
458
return "Bazaar Knit Repository Format 3 (bzr 0.15)\n"
460
def get_format_description(self):
461
"""See RepositoryFormat.get_format_description()."""
462
return "Knit repository format 3"
464
def open(self, a_bzrdir, _found=False, _override_transport=None):
465
"""See RepositoryFormat.open().
467
:param _override_transport: INTERNAL USE ONLY. Allows opening the
468
repository at a slightly different url
469
than normal. I.e. during 'upgrade'.
472
format = RepositoryFormat.find_format(a_bzrdir)
473
assert format.__class__ == self.__class__
474
if _override_transport is not None:
475
repo_transport = _override_transport
477
repo_transport = a_bzrdir.get_repository_transport(None)
478
control_files = lockable_files.LockableFiles(repo_transport, 'lock',
480
text_store = self._get_text_store(repo_transport, control_files)
481
control_store = self._get_control_store(repo_transport, control_files)
482
_revision_store = self._get_revision_store(repo_transport, control_files)
483
return self.repository_class(_format=self,
485
control_files=control_files,
486
_revision_store=_revision_store,
487
control_store=control_store,
488
text_store=text_store)