13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from __future__ import absolute_import
17
from cStringIO import StringIO
19
19
from bzrlib.lazy_import import lazy_import
20
20
lazy_import(globals(), """
24
25
from bzrlib import (
33
37
revision as _mod_revision,
34
testament as _mod_testament,
38
from bzrlib.bundle import serializer
39
from bzrlib.i18n import gettext
49
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
42
from bzrlib.revisiontree import RevisionTree
43
from bzrlib.store.versioned import VersionedFileStore
44
from bzrlib.store.text import TextStore
45
from bzrlib.testament import Testament
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
50
from bzrlib.inter import InterObject
51
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
52
from bzrlib.trace import (
53
log_exception_quietly, note, mutter, mutter_callsite, warning)
51
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
52
from bzrlib.symbol_versioning import (
56
from bzrlib.trace import mutter, note, warning
56
59
# Old formats display a warning, but only once
57
60
_deprecation_warning_done = False
60
class IsInWriteGroupError(errors.InternalBzrError):
62
_fmt = "May not refresh_data of repo %(repo)s while in a write group."
64
def __init__(self, repo):
65
errors.InternalBzrError.__init__(self, repo=repo)
68
class CommitBuilder(object):
69
"""Provides an interface to build up a commit.
71
This allows describing a tree to be committed without needing to
72
know the internals of the format of the repository.
75
# all clients should supply tree roots.
76
record_root_entry = True
77
# whether this commit builder supports the record_entry_contents interface
78
supports_record_entry_contents = False
79
# whether this commit builder will automatically update the branch that is
81
updates_branch = False
83
def __init__(self, repository, parents, config_stack, timestamp=None,
84
timezone=None, committer=None, revprops=None,
85
revision_id=None, lossy=False):
86
"""Initiate a CommitBuilder.
88
:param repository: Repository to commit to.
89
:param parents: Revision ids of the parents of the new revision.
90
:param timestamp: Optional timestamp recorded for commit.
91
:param timezone: Optional timezone for timestamp.
92
:param committer: Optional committer to set for commit.
93
:param revprops: Optional dictionary of revision properties.
94
:param revision_id: Optional revision id.
95
:param lossy: Whether to discard data that can not be natively
96
represented, when pushing to a foreign VCS
98
self._config_stack = config_stack
101
if committer is None:
102
self._committer = self._config_stack.get('email')
103
elif not isinstance(committer, unicode):
104
self._committer = committer.decode() # throw if non-ascii
106
self._committer = committer
108
self._new_revision_id = revision_id
109
self.parents = parents
110
self.repository = repository
113
if revprops is not None:
114
self._validate_revprops(revprops)
115
self._revprops.update(revprops)
117
if timestamp is None:
118
timestamp = time.time()
119
# Restrict resolution to 1ms
120
self._timestamp = round(timestamp, 3)
123
self._timezone = osutils.local_time_offset()
125
self._timezone = int(timezone)
127
self._generate_revision_if_needed()
129
def any_changes(self):
130
"""Return True if any entries were changed.
132
This includes merge-only changes. It is the core for the --unchanged
135
:return: True if any changes have occured.
137
raise NotImplementedError(self.any_changes)
139
def _validate_unicode_text(self, text, context):
140
"""Verify things like commit messages don't have bogus characters."""
142
raise ValueError('Invalid value for %s: %r' % (context, text))
144
def _validate_revprops(self, revprops):
145
for key, value in revprops.iteritems():
146
# We know that the XML serializers do not round trip '\r'
147
# correctly, so refuse to accept them
148
if not isinstance(value, basestring):
149
raise ValueError('revision property (%s) is not a valid'
150
' (unicode) string: %r' % (key, value))
151
self._validate_unicode_text(value,
152
'revision property (%s)' % (key,))
154
def commit(self, message):
155
"""Make the actual commit.
157
:return: The revision id of the recorded revision.
159
raise NotImplementedError(self.commit)
162
"""Abort the commit that is being built.
164
raise NotImplementedError(self.abort)
166
def revision_tree(self):
167
"""Return the tree that was just committed.
169
After calling commit() this can be called to get a
170
RevisionTree representing the newly committed tree. This is
171
preferred to calling Repository.revision_tree() because that may
172
require deserializing the inventory, while we already have a copy in
175
raise NotImplementedError(self.revision_tree)
177
def finish_inventory(self):
178
"""Tell the builder that the inventory is finished.
180
:return: The inventory id in the repository, which can be used with
181
repository.get_inventory.
183
raise NotImplementedError(self.finish_inventory)
185
def _gen_revision_id(self):
186
"""Return new revision-id."""
187
return generate_ids.gen_revision_id(self._committer, self._timestamp)
189
def _generate_revision_if_needed(self):
190
"""Create a revision id if None was supplied.
192
If the repository can not support user-specified revision ids
193
they should override this function and raise CannotSetRevisionId
194
if _new_revision_id is not None.
196
:raises: CannotSetRevisionId
198
if self._new_revision_id is None:
199
self._new_revision_id = self._gen_revision_id()
200
self.random_revid = True
202
self.random_revid = False
204
def will_record_deletes(self):
205
"""Tell the commit builder that deletes are being notified.
207
This enables the accumulation of an inventory delta; for the resulting
208
commit to be valid, deletes against the basis MUST be recorded via
209
builder.record_delete().
211
raise NotImplementedError(self.will_record_deletes)
213
def record_iter_changes(self, tree, basis_revision_id, iter_changes):
214
"""Record a new tree via iter_changes.
216
:param tree: The tree to obtain text contents from for changed objects.
217
:param basis_revision_id: The revision id of the tree the iter_changes
218
has been generated against. Currently assumed to be the same
219
as self.parents[0] - if it is not, errors may occur.
220
:param iter_changes: An iter_changes iterator with the changes to apply
221
to basis_revision_id. The iterator must not include any items with
222
a current kind of None - missing items must be either filtered out
223
or errored-on beefore record_iter_changes sees the item.
224
:return: A generator of (file_id, relpath, fs_hash) tuples for use with
227
raise NotImplementedError(self.record_iter_changes)
230
class RepositoryWriteLockResult(LogicalLockResult):
231
"""The result of write locking a repository.
233
:ivar repository_token: The token obtained from the underlying lock, or
235
:ivar unlock: A callable which will unlock the lock.
238
def __init__(self, unlock, repository_token):
239
LogicalLockResult.__init__(self, unlock)
240
self.repository_token = repository_token
243
return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
247
63
######################################################################
251
class Repository(_RelockDebugMixin, controldir.ControlComponent):
66
class Repository(object):
252
67
"""Repository holding history for one or more branches.
254
69
The repository holds and retrieves historical information including
255
70
revisions and file history. It's normally accessed only by the Branch,
256
71
which views a particular line of development through that history.
258
See VersionedFileRepository in bzrlib.vf_repository for the
259
base class for most Bazaar repositories.
73
The Repository builds on top of Stores and a Transport, which respectively
74
describe the disk data format and the way of accessing the (possibly
262
def abort_write_group(self, suppress_errors=False):
263
"""Commit the contents accrued within the current write group.
265
:param suppress_errors: if true, abort_write_group will catch and log
266
unexpected errors that happen during the abort, rather than
267
allowing them to propagate. Defaults to False.
269
:seealso: start_write_group.
271
if self._write_group is not self.get_transaction():
272
# has an unlock or relock occured ?
275
'(suppressed) mismatched lock context and write group. %r, %r',
276
self._write_group, self.get_transaction())
278
raise errors.BzrError(
279
'mismatched lock context and write group. %r, %r' %
280
(self._write_group, self.get_transaction()))
282
self._abort_write_group()
283
except Exception, exc:
284
self._write_group = None
285
if not suppress_errors:
287
mutter('abort_write_group failed')
288
log_exception_quietly()
289
note(gettext('bzr: ERROR (ignored): %s'), exc)
290
self._write_group = None
292
def _abort_write_group(self):
293
"""Template method for per-repository write group cleanup.
295
This is called during abort before the write group is considered to be
296
finished and should cleanup any internal state accrued during the write
297
group. There is no requirement that data handed to the repository be
298
*not* made available - this is not a rollback - but neither should any
299
attempt be made to ensure that data added is fully commited. Abort is
300
invoked when an error has occured so futher disk or network operations
301
may not be possible or may error and if possible should not be
305
def add_fallback_repository(self, repository):
306
"""Add a repository to use for looking up data not held locally.
308
:param repository: A repository.
310
raise NotImplementedError(self.add_fallback_repository)
312
def _check_fallback_repository(self, repository):
313
"""Check that this repository can fallback to repository safely.
315
Raise an error if not.
317
:param repository: A repository to fallback to.
319
return InterRepository._assert_same_model(self, repository)
78
_file_ids_altered_regex = lazy_regex.lazy_compile(
79
r'file_id="(?P<file_id>[^"]+)"'
80
r'.*revision="(?P<revision_id>[^"]+)"'
84
def add_inventory(self, revision_id, inv, parents):
85
"""Add the inventory inv to the repository as revision_id.
87
:param parents: The revision ids of the parents that revision_id
88
is known to have and are in the repository already.
90
returns the sha1 of the serialized inventory.
92
revision_id = osutils.safe_revision_id(revision_id)
93
_mod_revision.check_not_reserved_id(revision_id)
94
assert inv.revision_id is None or inv.revision_id == revision_id, \
95
"Mismatch between inventory revision" \
96
" id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
97
assert inv.root is not None
98
inv_text = self.serialise_inventory(inv)
99
inv_sha1 = osutils.sha_string(inv_text)
100
inv_vf = self.control_weaves.get_weave('inventory',
101
self.get_transaction())
102
self._inventory_add_lines(inv_vf, revision_id, parents,
103
osutils.split_lines(inv_text))
106
def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
108
for parent in parents:
110
final_parents.append(parent)
112
inv_vf.add_lines(revision_id, final_parents, lines)
115
def add_revision(self, revision_id, rev, inv=None, config=None):
116
"""Add rev to the revision store as revision_id.
118
:param revision_id: the revision id to use.
119
:param rev: The revision object.
120
:param inv: The inventory for the revision. if None, it will be looked
121
up in the inventory storer
122
:param config: If None no digital signature will be created.
123
If supplied its signature_needed method will be used
124
to determine if a signature should be made.
126
revision_id = osutils.safe_revision_id(revision_id)
127
# TODO: jam 20070210 Shouldn't we check rev.revision_id and
129
_mod_revision.check_not_reserved_id(revision_id)
130
if config is not None and config.signature_needed():
132
inv = self.get_inventory(revision_id)
133
plaintext = Testament(rev, inv).as_short_text()
134
self.store_revision_signature(
135
gpg.GPGStrategy(config), plaintext, revision_id)
136
if not revision_id in self.get_inventory_weave():
138
raise errors.WeaveRevisionNotPresent(revision_id,
139
self.get_inventory_weave())
141
# yes, this is not suitable for adding with ghosts.
142
self.add_inventory(revision_id, inv, rev.parent_ids)
143
self._revision_store.add_revision(rev, self.get_transaction())
146
def _all_possible_ids(self):
147
"""Return all the possible revisions that we could find."""
148
return self.get_inventory_weave().versions()
321
150
def all_revision_ids(self):
322
"""Returns a list of all the revision ids in the repository.
151
"""Returns a list of all the revision ids in the repository.
324
This is conceptually deprecated because code should generally work on
325
the graph reachable from a particular revision, and ignore any other
326
revisions that might be present. There is no direct replacement
153
This is deprecated because code should generally work on the graph
154
reachable from a particular revision, and ignore any other revisions
155
that might be present. There is no direct replacement method.
329
if 'evil' in debug.debug_flags:
330
mutter_callsite(2, "all_revision_ids is linear with history.")
331
157
return self._all_revision_ids()
333
160
def _all_revision_ids(self):
334
"""Returns a list of all the revision ids in the repository.
161
"""Returns a list of all the revision ids in the repository.
336
These are in as much topological order as the underlying store can
163
These are in as much topological order as the underlying store can
164
present: for weaves ghosts may lead to a lack of correctness until
165
the reweave updates the parents list.
339
raise NotImplementedError(self._all_revision_ids)
167
if self._revision_store.text_store.listable():
168
return self._revision_store.all_revision_ids(self.get_transaction())
169
result = self._all_possible_ids()
170
# TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
171
# ids. (It should, since _revision_store's API should change to
172
# return utf8 revision_ids)
173
return self._eliminate_revisions_not_present(result)
341
175
def break_lock(self):
342
176
"""Break a lock if one is present from another instance.
347
181
self.control_files.break_lock()
184
def _eliminate_revisions_not_present(self, revision_ids):
185
"""Check every revision id in revision_ids to see if we have it.
187
Returns a set of the present revisions.
190
for id in revision_ids:
191
if self.has_revision(id):
350
def create(controldir):
351
"""Construct the current default format repository in controldir."""
352
return RepositoryFormat.get_default_format().initialize(controldir)
196
def create(a_bzrdir):
197
"""Construct the current default format repository in a_bzrdir."""
198
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
354
def __init__(self, _format, controldir, control_files):
200
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
355
201
"""instantiate a Repository.
357
203
:param _format: The format of the repository on disk.
358
:param controldir: The ControlDir of the repository.
359
:param control_files: Control files to use for locking, etc.
204
:param a_bzrdir: The BzrDir of the repository.
206
In the future we will have a single api for all stores for
207
getting file texts, inventories and revisions, then
208
this construct will accept instances of those things.
361
# In the future we will have a single api for all stores for
362
# getting file texts, inventories and revisions, then
363
# this construct will accept instances of those things.
364
210
super(Repository, self).__init__()
365
211
self._format = _format
366
212
# the following are part of the public API for Repository:
367
self.bzrdir = controldir
213
self.bzrdir = a_bzrdir
368
214
self.control_files = control_files
370
self._write_group = None
371
# Additional places to query for data.
372
self._fallback_repositories = []
375
def user_transport(self):
376
return self.bzrdir.user_transport
379
def control_transport(self):
380
return self._transport
215
self._revision_store = _revision_store
216
self.text_store = text_store
217
# backwards compatibility
218
self.weave_store = text_store
219
# not right yet - should be more semantically clear ?
221
self.control_store = control_store
222
self.control_weaves = control_store
223
# TODO: make sure to construct the right store classes, etc, depending
224
# on whether escaping is required.
225
self._warn_if_deprecated()
382
227
def __repr__(self):
383
if self._fallback_repositories:
384
return '%s(%r, fallback_repositories=%r)' % (
385
self.__class__.__name__,
387
self._fallback_repositories)
389
return '%s(%r)' % (self.__class__.__name__,
392
def _has_same_fallbacks(self, other_repo):
393
"""Returns true if the repositories have the same fallbacks."""
394
my_fb = self._fallback_repositories
395
other_fb = other_repo._fallback_repositories
396
if len(my_fb) != len(other_fb):
398
for f, g in zip(my_fb, other_fb):
399
if not f.has_same_location(g):
403
def has_same_location(self, other):
404
"""Returns a boolean indicating if this repository is at the same
405
location as another repository.
407
This might return False even when two repository objects are accessing
408
the same physical repository via different URLs.
410
if self.__class__ is not other.__class__:
412
return (self.control_url == other.control_url)
414
def is_in_write_group(self):
415
"""Return True if there is an open write group.
417
:seealso: start_write_group.
419
return self._write_group is not None
228
return '%s(%r)' % (self.__class__.__name__,
229
self.bzrdir.transport.base)
421
231
def is_locked(self):
422
232
return self.control_files.is_locked()
424
def is_write_locked(self):
425
"""Return True if this object is write locked."""
426
return self.is_locked() and self.control_files._lock_mode == 'w'
428
def lock_write(self, token=None):
429
"""Lock this repository for writing.
431
This causes caching within the repository obejct to start accumlating
432
data during reads, and allows a 'write_group' to be obtained. Write
433
groups must be used for actual data insertion.
435
A token should be passed in if you know that you have locked the object
436
some other way, and need to synchronise this object's state with that
439
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
441
:param token: if this is already locked, then lock_write will fail
442
unless the token matches the existing lock.
443
:returns: a token if this instance supports tokens, otherwise None.
444
:raises TokenLockingNotSupported: when a token is given but this
445
instance doesn't support using token locks.
446
:raises MismatchedToken: if the specified token doesn't match the token
447
of the existing lock.
448
:seealso: start_write_group.
449
:return: A RepositoryWriteLockResult.
451
locked = self.is_locked()
452
token = self.control_files.lock_write(token=token)
454
self._warn_if_deprecated()
456
for repo in self._fallback_repositories:
457
# Writes don't affect fallback repos
460
return RepositoryWriteLockResult(self.unlock, token)
234
def lock_write(self):
235
self.control_files.lock_write()
462
237
def lock_read(self):
463
"""Lock the repository for read operations.
465
:return: An object with an unlock method which will release the lock
468
locked = self.is_locked()
469
238
self.control_files.lock_read()
471
self._warn_if_deprecated()
473
for repo in self._fallback_repositories:
476
return LogicalLockResult(self.unlock)
478
240
def get_physical_lock_status(self):
479
241
return self.control_files.get_physical_lock_status()
481
def leave_lock_in_place(self):
482
"""Tell this repository not to release the physical lock when this
485
If lock_write doesn't return a token, then this method is not supported.
487
self.control_files.leave_in_place()
489
def dont_leave_lock_in_place(self):
490
"""Tell this repository to release the physical lock when this
491
object is unlocked, even if it didn't originally acquire it.
493
If lock_write doesn't return a token, then this method is not supported.
495
self.control_files.dont_leave_in_place()
498
244
def gather_stats(self, revid=None, committers=None):
499
245
"""Gather statistics from a revision id.
607
308
For instance, if the repository is at URL/.bzr/repository,
608
309
Repository.open(URL) -> a Repository instance.
610
control = controldir.ControlDir.open(base)
311
control = bzrdir.BzrDir.open(base)
611
312
return control.open_repository()
613
314
def copy_content_into(self, destination, revision_id=None):
614
315
"""Make a complete copy of the content in self into destination.
616
This is a destructive operation! Do not use it on existing
317
This is a destructive operation! Do not use it on existing
320
revision_id = osutils.safe_revision_id(revision_id)
619
321
return InterRepository.get(self, destination).copy_content(revision_id)
621
def commit_write_group(self):
622
"""Commit the contents accrued within the current write group.
624
:seealso: start_write_group.
626
:return: it may return an opaque hint that can be passed to 'pack'.
628
if self._write_group is not self.get_transaction():
629
# has an unlock or relock occured ?
630
raise errors.BzrError('mismatched lock context %r and '
632
(self.get_transaction(), self._write_group))
633
result = self._commit_write_group()
634
self._write_group = None
637
def _commit_write_group(self):
638
"""Template method for per-repository write group cleanup.
640
This is called before the write group is considered to be
641
finished and should ensure that all data handed to the repository
642
for writing during the write group is safely committed (to the
643
extent possible considering file system caching etc).
646
def suspend_write_group(self):
647
"""Suspend a write group.
649
:raise UnsuspendableWriteGroup: If the write group can not be
651
:return: List of tokens
653
raise errors.UnsuspendableWriteGroup(self)
655
def refresh_data(self):
656
"""Re-read any data needed to synchronise with disk.
658
This method is intended to be called after another repository instance
659
(such as one used by a smart server) has inserted data into the
660
repository. On all repositories this will work outside of write groups.
661
Some repository formats (pack and newer for bzrlib native formats)
662
support refresh_data inside write groups. If called inside a write
663
group on a repository that does not support refreshing in a write group
664
IsInWriteGroupError will be raised.
668
def resume_write_group(self, tokens):
669
if not self.is_write_locked():
670
raise errors.NotWriteLocked(self)
671
if self._write_group:
672
raise errors.BzrError('already in a write group')
673
self._resume_write_group(tokens)
674
# so we can detect unlock/relock - the write group is now entered.
675
self._write_group = self.get_transaction()
677
def _resume_write_group(self, tokens):
678
raise errors.UnsuspendableWriteGroup(self)
680
def fetch(self, source, revision_id=None, find_ghosts=False):
323
def fetch(self, source, revision_id=None, pb=None):
681
324
"""Fetch the content required to construct revision_id from source.
683
If revision_id is None, then all content is copied.
685
fetch() may not be used when the repository is in a write group -
686
either finish the current write group before using fetch, or use
687
fetch before starting the write group.
689
:param find_ghosts: Find and copy revisions in the source that are
690
ghosts in the target (and not reachable directly by walking out to
691
the first-present revision in target from revision_id).
692
:param revision_id: If specified, all the content needed for this
693
revision ID will be copied to the target. Fetch will determine for
694
itself which content needs to be copied.
326
If revision_id is None all content is copied.
696
if self.is_in_write_group():
697
raise errors.InternalBzrError(
698
"May not fetch while in a write group.")
699
# fast path same-url fetch operations
700
# TODO: lift out to somewhere common with RemoteRepository
701
# <https://bugs.launchpad.net/bzr/+bug/401646>
702
if (self.has_same_location(source)
703
and self._has_same_fallbacks(source)):
704
# check that last_revision is in 'from' and then return a
706
if (revision_id is not None and
707
not _mod_revision.is_null(revision_id)):
708
self.get_revision(revision_id)
328
revision_id = osutils.safe_revision_id(revision_id)
710
329
inter = InterRepository.get(source, self)
711
return inter.fetch(revision_id=revision_id, find_ghosts=find_ghosts)
713
def create_bundle(self, target, base, fileobj, format=None):
714
return serializer.write_bundle(self, target, base, fileobj, format)
716
def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
717
timezone=None, committer=None, revprops=None,
718
revision_id=None, lossy=False):
331
return inter.fetch(revision_id=revision_id, pb=pb)
332
except NotImplementedError:
333
raise errors.IncompatibleRepositories(source, self)
335
def get_commit_builder(self, branch, parents, config, timestamp=None,
336
timezone=None, committer=None, revprops=None,
719
338
"""Obtain a CommitBuilder for this repository.
721
340
:param branch: Branch to commit to.
722
341
:param parents: Revision ids of the parents of the new revision.
723
:param config_stack: Configuration stack to use.
342
:param config: Configuration to use.
724
343
:param timestamp: Optional timestamp recorded for commit.
725
344
:param timezone: Optional timezone for timestamp.
726
345
:param committer: Optional committer to set for commit.
727
346
:param revprops: Optional dictionary of revision properties.
728
347
:param revision_id: Optional revision id.
729
:param lossy: Whether to discard data that can not be natively
730
represented, when pushing to a foreign VCS
732
raise NotImplementedError(self.get_commit_builder)
349
revision_id = osutils.safe_revision_id(revision_id)
350
return _CommitBuilder(self, parents, config, timestamp, timezone,
351
committer, revprops, revision_id)
734
@only_raises(errors.LockNotHeld, errors.LockBroken)
735
353
def unlock(self):
736
if (self.control_files._lock_count == 1 and
737
self.control_files._lock_mode == 'w'):
738
if self._write_group is not None:
739
self.abort_write_group()
740
self.control_files.unlock()
741
raise errors.BzrError(
742
'Must end write groups before releasing write locks.')
743
354
self.control_files.unlock()
744
if self.control_files._lock_count == 0:
745
for repo in self._fallback_repositories:
749
def clone(self, controldir, revision_id=None):
750
"""Clone this repository into controldir using the current format.
357
def clone(self, a_bzrdir, revision_id=None):
358
"""Clone this repository into a_bzrdir using the current format.
752
360
Currently no check is made that the format of this repository and
753
361
the bzrdir format are compatible. FIXME RBC 20060201.
755
363
:return: The newly created destination repository.
757
# TODO: deprecate after 0.16; cloning this with all its settings is
758
# probably not very useful -- mbp 20070423
759
dest_repo = self._create_sprouting_repo(
760
controldir, shared=self.is_shared())
761
self.copy_content_into(dest_repo, revision_id)
764
def start_write_group(self):
765
"""Start a write group in the repository.
767
Write groups are used by repositories which do not have a 1:1 mapping
768
between file ids and backend store to manage the insertion of data from
769
both fetch and commit operations.
771
A write lock is required around the start_write_group/commit_write_group
772
for the support of lock-requiring repository formats.
774
One can only insert data into a repository inside a write group.
778
if not self.is_write_locked():
779
raise errors.NotWriteLocked(self)
780
if self._write_group:
781
raise errors.BzrError('already in a write group')
782
self._start_write_group()
783
# so we can detect unlock/relock - the write group is now entered.
784
self._write_group = self.get_transaction()
786
def _start_write_group(self):
787
"""Template method for per-repository write group startup.
789
This is called before the write group is considered to be
794
def sprout(self, to_bzrdir, revision_id=None):
795
"""Create a descendent repository for new development.
797
Unlike clone, this does not copy the settings of the repository.
799
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
800
dest_repo.fetch(self, revision_id=revision_id)
803
def _create_sprouting_repo(self, a_bzrdir, shared):
804
365
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
805
366
# use target default format.
806
367
dest_repo = a_bzrdir.create_repository()
808
369
# Most control formats need the repository to be specifically
809
370
# created, but on some old all-in-one formats it's not needed
811
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
372
dest_repo = self._format.initialize(a_bzrdir, shared=self.is_shared())
812
373
except errors.UninitializableFormat:
813
374
dest_repo = a_bzrdir.open_repository()
375
self.copy_content_into(dest_repo, revision_id)
817
379
def has_revision(self, revision_id):
818
380
"""True if this repository has a copy of the revision."""
819
return revision_id in self.has_revisions((revision_id,))
822
def has_revisions(self, revision_ids):
823
"""Probe to find out the presence of multiple revisions.
825
:param revision_ids: An iterable of revision_ids.
826
:return: A set of the revision_ids that were present.
828
raise NotImplementedError(self.has_revisions)
831
def get_revision(self, revision_id):
832
"""Return the Revision object for a named revision."""
833
return self.get_revisions([revision_id])[0]
381
revision_id = osutils.safe_revision_id(revision_id)
382
return self._revision_store.has_revision_id(revision_id,
383
self.get_transaction())
835
386
def get_revision_reconcile(self, revision_id):
836
387
"""'reconcile' helper routine that allows access to a revision always.
838
389
This variant of get_revision does not cross check the weave graph
839
390
against the revision one as get_revision does: but it should only
840
391
be used by reconcile, or reconcile-alike commands that are correcting
841
392
or testing the revision graph.
843
raise NotImplementedError(self.get_revision_reconcile)
394
if not revision_id or not isinstance(revision_id, basestring):
395
raise errors.InvalidRevisionId(revision_id=revision_id,
397
return self.get_revisions([revision_id])[0]
845
400
def get_revisions(self, revision_ids):
846
"""Get many revisions at once.
848
Repositories that need to check data on every revision read should
849
subclass this method.
851
raise NotImplementedError(self.get_revisions)
853
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
401
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
402
revs = self._revision_store.get_revisions(revision_ids,
403
self.get_transaction())
405
assert not isinstance(rev.revision_id, unicode)
406
for parent_id in rev.parent_ids:
407
assert not isinstance(parent_id, unicode)
411
def get_revision_xml(self, revision_id):
412
# TODO: jam 20070210 This shouldn't be necessary since get_revision
413
# would have already do it.
414
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
415
revision_id = osutils.safe_revision_id(revision_id)
416
rev = self.get_revision(revision_id)
418
# the current serializer..
419
self._revision_store._serializer.write_revision(rev, rev_tmp)
421
return rev_tmp.getvalue()
424
def get_revision(self, revision_id):
425
"""Return the Revision object for a named revision"""
426
# TODO: jam 20070210 get_revision_reconcile should do this for us
427
revision_id = osutils.safe_revision_id(revision_id)
428
r = self.get_revision_reconcile(revision_id)
429
# weave corruption can lead to absent revision markers that should be
431
# the following test is reasonably cheap (it needs a single weave read)
432
# and the weave is cached in read transactions. In write transactions
433
# it is not cached but typically we only read a small number of
434
# revisions. For knits when they are introduced we will probably want
435
# to ensure that caching write transactions are in use.
436
inv = self.get_inventory_weave()
437
self._check_revision_parents(r, inv)
441
def get_deltas_for_revisions(self, revisions):
854
442
"""Produce a generator of revision deltas.
856
444
Note that the input is a sequence of REVISIONS, not revision_ids.
857
445
Trees will be held in memory until the generator exits.
858
446
Each delta is relative to the revision's lefthand predecessor.
860
:param specific_fileids: if not None, the result is filtered
861
so that only those file-ids, their parents and their
862
children are included.
864
# Get the revision-ids of interest
865
448
required_trees = set()
866
449
for revision in revisions:
867
450
required_trees.add(revision.revision_id)
868
451
required_trees.update(revision.parent_ids[:1])
870
# Get the matching filtered trees. Note that it's more
871
# efficient to pass filtered trees to changes_from() rather
872
# than doing the filtering afterwards. changes_from() could
873
# arguably do the filtering itself but it's path-based, not
874
# file-id based, so filtering before or afterwards is
876
if specific_fileids is None:
877
trees = dict((t.get_revision_id(), t) for
878
t in self.revision_trees(required_trees))
880
trees = dict((t.get_revision_id(), t) for
881
t in self._filtered_revision_trees(required_trees,
884
# Calculate the deltas
452
trees = dict((t.get_revision_id(), t) for
453
t in self.revision_trees(required_trees))
885
454
for revision in revisions:
886
455
if not revision.parent_ids:
887
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
456
old_tree = self.revision_tree(None)
889
458
old_tree = trees[revision.parent_ids[0]]
890
459
yield trees[revision.revision_id].changes_from(old_tree)
893
def get_revision_delta(self, revision_id, specific_fileids=None):
462
def get_revision_delta(self, revision_id):
894
463
"""Return the delta for one revision.
896
465
The delta is relative to the left-hand predecessor of the
899
:param specific_fileids: if not None, the result is filtered
900
so that only those file-ids, their parents and their
901
children are included.
903
468
r = self.get_revision(revision_id)
904
return list(self.get_deltas_for_revisions([r],
905
specific_fileids=specific_fileids))[0]
469
return list(self.get_deltas_for_revisions([r]))[0]
471
def _check_revision_parents(self, revision, inventory):
472
"""Private to Repository and Fetch.
474
This checks the parentage of revision in an inventory weave for
475
consistency and is only applicable to inventory-weave-for-ancestry
476
using repository formats & fetchers.
478
weave_parents = inventory.get_parents(revision.revision_id)
479
weave_names = inventory.versions()
480
for parent_id in revision.parent_ids:
481
if parent_id in weave_names:
482
# this parent must not be a ghost.
483
if not parent_id in weave_parents:
485
raise errors.CorruptRepository(self)
907
487
@needs_write_lock
908
488
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
489
revision_id = osutils.safe_revision_id(revision_id)
909
490
signature = gpg_strategy.sign(plaintext)
910
self.add_signature_text(revision_id, signature)
912
def add_signature_text(self, revision_id, signature):
913
"""Store a signature text for a revision.
915
:param revision_id: Revision id of the revision
916
:param signature: Signature text.
918
raise NotImplementedError(self.add_signature_text)
920
def _find_parent_ids_of_revisions(self, revision_ids):
921
"""Find all parent ids that are mentioned in the revision graph.
923
:return: set of revisions that are parents of revision_ids which are
924
not part of revision_ids themselves
926
parent_map = self.get_parent_map(revision_ids)
928
map(parent_ids.update, parent_map.itervalues())
929
parent_ids.difference_update(revision_ids)
930
parent_ids.discard(_mod_revision.NULL_REVISION)
933
def iter_files_bytes(self, desired_files):
934
"""Iterate through file versions.
936
Files will not necessarily be returned in the order they occur in
937
desired_files. No specific order is guaranteed.
939
Yields pairs of identifier, bytes_iterator. identifier is an opaque
940
value supplied by the caller as part of desired_files. It should
941
uniquely identify the file version in the caller's context. (Examples:
942
an index number or a TreeTransform trans_id.)
944
:param desired_files: a list of (file_id, revision_id, identifier)
947
raise NotImplementedError(self.iter_files_bytes)
949
def get_rev_id_for_revno(self, revno, known_pair):
950
"""Return the revision id of a revno, given a later (revno, revid)
951
pair in the same history.
953
:return: if found (True, revid). If the available history ran out
954
before reaching the revno, then this returns
955
(False, (closest_revno, closest_revid)).
957
known_revno, known_revid = known_pair
958
partial_history = [known_revid]
959
distance_from_known = known_revno - revno
960
if distance_from_known < 0:
962
'requested revno (%d) is later than given known revno (%d)'
963
% (revno, known_revno))
966
self, partial_history, stop_index=distance_from_known)
967
except errors.RevisionNotPresent, err:
968
if err.revision_id == known_revid:
969
# The start revision (known_revid) wasn't found.
971
# This is a stacked repository with no fallbacks, or a there's a
972
# left-hand ghost. Either way, even though the revision named in
973
# the error isn't in this repo, we know it's the next step in this
975
partial_history.append(err.revision_id)
976
if len(partial_history) <= distance_from_known:
977
# Didn't find enough history to get a revid for the revno.
978
earliest_revno = known_revno - len(partial_history) + 1
979
return (False, (earliest_revno, partial_history[-1]))
980
if len(partial_history) - 1 > distance_from_known:
981
raise AssertionError('_iter_for_revno returned too much history')
982
return (True, partial_history[-1])
491
self._revision_store.add_revision_signature_text(revision_id,
493
self.get_transaction())
495
def fileids_altered_by_revision_ids(self, revision_ids):
496
"""Find the file ids and versions affected by revisions.
498
:param revisions: an iterable containing revision ids.
499
:return: a dictionary mapping altered file-ids to an iterable of
500
revision_ids. Each altered file-ids has the exact revision_ids that
501
altered it listed explicitly.
503
assert self._serializer.support_altered_by_hack, \
504
("fileids_altered_by_revision_ids only supported for branches "
505
"which store inventory as unnested xml, not on %r" % self)
506
selected_revision_ids = set(osutils.safe_revision_id(r)
507
for r in revision_ids)
508
w = self.get_inventory_weave()
511
# this code needs to read every new line in every inventory for the
512
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
513
# not present in one of those inventories is unnecessary but not
514
# harmful because we are filtering by the revision id marker in the
515
# inventory lines : we only select file ids altered in one of those
516
# revisions. We don't need to see all lines in the inventory because
517
# only those added in an inventory in rev X can contain a revision=X
519
unescape_revid_cache = {}
520
unescape_fileid_cache = {}
522
# jam 20061218 In a big fetch, this handles hundreds of thousands
523
# of lines, so it has had a lot of inlining and optimizing done.
524
# Sorry that it is a little bit messy.
525
# Move several functions to be local variables, since this is a long
527
search = self._file_ids_altered_regex.search
528
unescape = _unescape_xml
529
setdefault = result.setdefault
530
pb = ui.ui_factory.nested_progress_bar()
532
for line in w.iter_lines_added_or_present_in_versions(
533
selected_revision_ids, pb=pb):
537
# One call to match.group() returning multiple items is quite a
538
# bit faster than 2 calls to match.group() each returning 1
539
file_id, revision_id = match.group('file_id', 'revision_id')
541
# Inlining the cache lookups helps a lot when you make 170,000
542
# lines and 350k ids, versus 8.4 unique ids.
543
# Using a cache helps in 2 ways:
544
# 1) Avoids unnecessary decoding calls
545
# 2) Re-uses cached strings, which helps in future set and
547
# (2) is enough that removing encoding entirely along with
548
# the cache (so we are using plain strings) results in no
549
# performance improvement.
551
revision_id = unescape_revid_cache[revision_id]
553
unescaped = unescape(revision_id)
554
unescape_revid_cache[revision_id] = unescaped
555
revision_id = unescaped
557
if revision_id in selected_revision_ids:
559
file_id = unescape_fileid_cache[file_id]
561
unescaped = unescape(file_id)
562
unescape_fileid_cache[file_id] = unescaped
564
setdefault(file_id, set()).add(revision_id)
570
def get_inventory_weave(self):
571
return self.control_weaves.get_weave('inventory',
572
self.get_transaction())
575
def get_inventory(self, revision_id):
576
"""Get Inventory object by hash."""
577
# TODO: jam 20070210 Technically we don't need to sanitize, since all
578
# called functions must sanitize.
579
revision_id = osutils.safe_revision_id(revision_id)
580
return self.deserialise_inventory(
581
revision_id, self.get_inventory_xml(revision_id))
583
def deserialise_inventory(self, revision_id, xml):
584
"""Transform the xml into an inventory object.
586
:param revision_id: The expected revision id of the inventory.
587
:param xml: A serialised inventory.
589
revision_id = osutils.safe_revision_id(revision_id)
590
result = self._serializer.read_inventory_from_string(xml)
591
result.root.revision = revision_id
594
def serialise_inventory(self, inv):
595
return self._serializer.write_inventory_to_string(inv)
598
def get_inventory_xml(self, revision_id):
599
"""Get inventory XML as a file object."""
600
revision_id = osutils.safe_revision_id(revision_id)
602
assert isinstance(revision_id, str), type(revision_id)
603
iw = self.get_inventory_weave()
604
return iw.get_text(revision_id)
606
raise errors.HistoryMissing(self, 'inventory', revision_id)
609
def get_inventory_sha1(self, revision_id):
610
"""Return the sha1 hash of the inventory entry
612
# TODO: jam 20070210 Shouldn't this be deprecated / removed?
613
revision_id = osutils.safe_revision_id(revision_id)
614
return self.get_revision(revision_id).inventory_sha1
617
def get_revision_graph(self, revision_id=None):
618
"""Return a dictionary containing the revision graph.
620
:param revision_id: The revision_id to get a graph from. If None, then
621
the entire revision graph is returned. This is a deprecated mode of
622
operation and will be removed in the future.
623
:return: a dictionary of revision_id->revision_parents_list.
625
# special case NULL_REVISION
626
if revision_id == _mod_revision.NULL_REVISION:
628
revision_id = osutils.safe_revision_id(revision_id)
629
a_weave = self.get_inventory_weave()
630
all_revisions = self._eliminate_revisions_not_present(
632
entire_graph = dict([(node, a_weave.get_parents(node)) for
633
node in all_revisions])
634
if revision_id is None:
636
elif revision_id not in entire_graph:
637
raise errors.NoSuchRevision(self, revision_id)
639
# add what can be reached from revision_id
641
pending = set([revision_id])
642
while len(pending) > 0:
644
result[node] = entire_graph[node]
645
for revision_id in result[node]:
646
if revision_id not in result:
647
pending.add(revision_id)
651
def get_revision_graph_with_ghosts(self, revision_ids=None):
652
"""Return a graph of the revisions with ghosts marked as applicable.
654
:param revision_ids: an iterable of revisions to graph or None for all.
655
:return: a Graph object with the graph reachable from revision_ids.
657
result = graph.Graph()
659
pending = set(self.all_revision_ids())
662
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
663
# special case NULL_REVISION
664
if _mod_revision.NULL_REVISION in pending:
665
pending.remove(_mod_revision.NULL_REVISION)
666
required = set(pending)
669
revision_id = pending.pop()
671
rev = self.get_revision(revision_id)
672
except errors.NoSuchRevision:
673
if revision_id in required:
676
result.add_ghost(revision_id)
678
for parent_id in rev.parent_ids:
679
# is this queued or done ?
680
if (parent_id not in pending and
681
parent_id not in done):
683
pending.add(parent_id)
684
result.add_node(revision_id, rev.parent_ids)
685
done.add(revision_id)
688
def _get_history_vf(self):
689
"""Get a versionedfile whose history graph reflects all revisions.
691
For weave repositories, this is the inventory weave.
693
return self.get_inventory_weave()
695
def iter_reverse_revision_history(self, revision_id):
696
"""Iterate backwards through revision ids in the lefthand history
698
:param revision_id: The revision id to start with. All its lefthand
699
ancestors will be traversed.
701
revision_id = osutils.safe_revision_id(revision_id)
702
if revision_id in (None, _mod_revision.NULL_REVISION):
704
next_id = revision_id
705
versionedfile = self._get_history_vf()
708
parents = versionedfile.get_parents(next_id)
709
if len(parents) == 0:
715
def get_revision_inventory(self, revision_id):
716
"""Return inventory of a past revision."""
717
# TODO: Unify this with get_inventory()
718
# bzr 0.0.6 and later imposes the constraint that the inventory_id
719
# must be the same as its revision, so this is trivial.
720
if revision_id is None:
721
# This does not make sense: if there is no revision,
722
# then it is the current tree inventory surely ?!
723
# and thus get_root_id() is something that looks at the last
724
# commit on the branch, and the get_root_id is an inventory check.
725
raise NotImplementedError
726
# return Inventory(self.get_root_id())
728
return self.get_inventory(revision_id)
984
731
def is_shared(self):
985
732
"""Return True if this repository is flagged as a shared repository."""
986
733
raise NotImplementedError(self.is_shared)
992
739
reconciler = RepoReconciler(self, thorough=thorough)
993
740
reconciler.reconcile()
994
741
return reconciler
996
def _refresh_data(self):
997
"""Helper called from lock_* to ensure coherency with disk.
999
The default implementation does nothing; it is however possible
1000
for repositories to maintain loaded indices across multiple locks
1001
by checking inside their implementation of this method to see
1002
whether their indices are still valid. This depends of course on
1003
the disk format being validatable in this manner. This method is
1004
also called by the refresh_data() public interface to cause a refresh
1005
to occur while in a write lock so that data inserted by a smart server
1006
push operation is visible on the client's instance of the physical
1010
743
@needs_read_lock
1011
744
def revision_tree(self, revision_id):
1012
745
"""Return Tree for a revision on this branch.
1014
`revision_id` may be NULL_REVISION for the empty tree revision.
747
`revision_id` may be None for the empty tree revision.
1016
raise NotImplementedError(self.revision_tree)
749
# TODO: refactor this to use an existing revision object
750
# so we don't need to read it in twice.
751
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
752
return RevisionTree(self, Inventory(root_id=None),
753
_mod_revision.NULL_REVISION)
755
revision_id = osutils.safe_revision_id(revision_id)
756
inv = self.get_revision_inventory(revision_id)
757
return RevisionTree(self, inv, revision_id)
1018
760
def revision_trees(self, revision_ids):
1019
"""Return Trees for revisions in this repository.
1021
:param revision_ids: a sequence of revision-ids;
1022
a revision-id may not be None or 'null:'
1024
raise NotImplementedError(self.revision_trees)
1026
def pack(self, hint=None, clean_obsolete_packs=False):
1027
"""Compress the data within the repository.
1029
This operation only makes sense for some repository types. For other
1030
types it should be a no-op that just returns.
1032
This stub method does not require a lock, but subclasses should use
1033
@needs_write_lock as this is a long running call it's reasonable to
1034
implicitly lock for the user.
1036
:param hint: If not supplied, the whole repository is packed.
1037
If supplied, the repository may use the hint parameter as a
1038
hint for the parts of the repository to pack. A hint can be
1039
obtained from the result of commit_write_group(). Out of
1040
date hints are simply ignored, because concurrent operations
1041
can obsolete them rapidly.
1043
:param clean_obsolete_packs: Clean obsolete packs immediately after
761
"""Return Tree for a revision on this branch.
763
`revision_id` may not be None or 'null:'"""
764
assert None not in revision_ids
765
assert _mod_revision.NULL_REVISION not in revision_ids
766
texts = self.get_inventory_weave().get_texts(revision_ids)
767
for text, revision_id in zip(texts, revision_ids):
768
inv = self.deserialise_inventory(revision_id, text)
769
yield RevisionTree(self, inv, revision_id)
772
def get_ancestry(self, revision_id):
773
"""Return a list of revision-ids integrated by a revision.
775
The first element of the list is always None, indicating the origin
776
revision. This might change when we have history horizons, or
777
perhaps we should have a new API.
779
This is topologically sorted.
781
if revision_id is None:
783
revision_id = osutils.safe_revision_id(revision_id)
784
if not self.has_revision(revision_id):
785
raise errors.NoSuchRevision(self, revision_id)
786
w = self.get_inventory_weave()
787
candidates = w.get_ancestry(revision_id)
788
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
791
def print_file(self, file, revision_id):
792
"""Print `file` to stdout.
794
FIXME RBC 20060125 as John Meinel points out this is a bad api
795
- it writes to stdout, it assumes that that is valid etc. Fix
796
by creating a new more flexible convenience function.
798
revision_id = osutils.safe_revision_id(revision_id)
799
tree = self.revision_tree(revision_id)
800
# use inventory as it was in that revision
801
file_id = tree.inventory.path2id(file)
803
# TODO: jam 20060427 Write a test for this code path
804
# it had a bug in it, and was raising the wrong
806
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
807
tree.print_file(file_id)
1047
809
def get_transaction(self):
1048
810
return self.control_files.get_transaction()
1050
def get_parent_map(self, revision_ids):
1051
"""See graph.StackedParentsProvider.get_parent_map"""
1052
raise NotImplementedError(self.get_parent_map)
1054
def _get_parent_map_no_fallbacks(self, revision_ids):
1055
"""Same as Repository.get_parent_map except doesn't query fallbacks."""
1056
# revisions index works in keys; this just works in revisions
1057
# therefore wrap and unwrap
1060
for revision_id in revision_ids:
1061
if revision_id == _mod_revision.NULL_REVISION:
1062
result[revision_id] = ()
1063
elif revision_id is None:
1064
raise ValueError('get_parent_map(None) is not valid')
1066
query_keys.append((revision_id ,))
1067
vf = self.revisions.without_fallbacks()
1068
for ((revision_id,), parent_keys) in \
1069
vf.get_parent_map(query_keys).iteritems():
1071
result[revision_id] = tuple([parent_revid
1072
for (parent_revid,) in parent_keys])
1074
result[revision_id] = (_mod_revision.NULL_REVISION,)
1077
def _make_parents_provider(self):
1078
if not self._format.supports_external_lookups:
1080
return graph.StackedParentsProvider(_LazyListJoin(
1081
[self._make_parents_provider_unstacked()],
1082
self._fallback_repositories))
1084
def _make_parents_provider_unstacked(self):
1085
return graph.CallableToParentsProviderAdapter(
1086
self._get_parent_map_no_fallbacks)
1089
def get_known_graph_ancestry(self, revision_ids):
1090
"""Return the known graph for a set of revision ids and their ancestors.
1092
raise NotImplementedError(self.get_known_graph_ancestry)
1094
def get_file_graph(self):
1095
"""Return the graph walker for files."""
1096
raise NotImplementedError(self.get_file_graph)
1098
def get_graph(self, other_repository=None):
1099
"""Return the graph walker for this repository format"""
1100
parents_provider = self._make_parents_provider()
1101
if (other_repository is not None and
1102
not self.has_same_location(other_repository)):
1103
parents_provider = graph.StackedParentsProvider(
1104
[parents_provider, other_repository._make_parents_provider()])
1105
return graph.Graph(parents_provider)
812
def revision_parents(self, revision_id):
813
revision_id = osutils.safe_revision_id(revision_id)
814
return self.get_inventory_weave().parent_names(revision_id)
1107
816
@needs_write_lock
1108
817
def set_make_working_trees(self, new_value):
1117
826
raise NotImplementedError(self.set_make_working_trees)
1119
828
def make_working_trees(self):
1120
829
"""Returns the policy for making working trees on new branches."""
1121
830
raise NotImplementedError(self.make_working_trees)
1123
832
@needs_write_lock
1124
833
def sign_revision(self, revision_id, gpg_strategy):
1125
testament = _mod_testament.Testament.from_revision(self, revision_id)
1126
plaintext = testament.as_short_text()
834
revision_id = osutils.safe_revision_id(revision_id)
835
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1127
836
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1129
838
@needs_read_lock
1130
def verify_revision_signature(self, revision_id, gpg_strategy):
1131
"""Verify the signature on a revision.
1133
:param revision_id: the revision to verify
1134
:gpg_strategy: the GPGStrategy object to used
1136
:return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value
1138
if not self.has_signature_for_revision_id(revision_id):
1139
return gpg.SIGNATURE_NOT_SIGNED, None
1140
signature = self.get_signature_text(revision_id)
1142
testament = _mod_testament.Testament.from_revision(self, revision_id)
1143
plaintext = testament.as_short_text()
1145
return gpg_strategy.verify(signature, plaintext)
1148
def verify_revision_signatures(self, revision_ids, gpg_strategy):
1149
"""Verify revision signatures for a number of revisions.
1151
:param revision_id: the revision to verify
1152
:gpg_strategy: the GPGStrategy object to used
1153
:return: Iterator over tuples with revision id, result and keys
1155
for revid in revision_ids:
1156
(result, key) = self.verify_revision_signature(revid, gpg_strategy)
1157
yield revid, result, key
1159
839
def has_signature_for_revision_id(self, revision_id):
1160
840
"""Query for a revision signature for revision_id in the repository."""
1161
raise NotImplementedError(self.has_signature_for_revision_id)
841
revision_id = osutils.safe_revision_id(revision_id)
842
return self._revision_store.has_signature(revision_id,
843
self.get_transaction())
1163
846
def get_signature_text(self, revision_id):
1164
847
"""Return the text for a signature."""
1165
raise NotImplementedError(self.get_signature_text)
848
revision_id = osutils.safe_revision_id(revision_id)
849
return self._revision_store.get_signature_text(revision_id,
850
self.get_transaction())
1167
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
853
def check(self, revision_ids):
1168
854
"""Check consistency of all history of given revision_ids.
1170
856
Different repository implementations should override _check().
1172
858
:param revision_ids: A non-empty list of revision_ids whose ancestry
1173
859
will be checked. Typically the last revision_id of a branch.
1174
:param callback_refs: A dict of check-refs to resolve and callback
1175
the check/_check method on the items listed as wanting the ref.
1177
:param check_repo: If False do not check the repository contents, just
1178
calculate the data callback_refs requires and call them back.
1180
return self._check(revision_ids=revision_ids, callback_refs=callback_refs,
1181
check_repo=check_repo)
1183
def _check(self, revision_ids=None, callback_refs=None, check_repo=True):
1184
raise NotImplementedError(self.check)
1186
def _warn_if_deprecated(self, branch=None):
1187
if not self._format.is_deprecated():
862
raise ValueError("revision_ids must be non-empty in %s.check"
864
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
865
return self._check(revision_ids)
867
def _check(self, revision_ids):
868
result = check.Check(self)
872
def _warn_if_deprecated(self):
1189
873
global _deprecation_warning_done
1190
874
if _deprecation_warning_done:
1194
conf = config.GlobalStack()
1196
conf = branch.get_config_stack()
1197
if 'format_deprecation' in conf.get('suppress_warnings'):
1199
warning("Format %s for %s is deprecated -"
1200
" please use 'bzr upgrade' to get better performance"
1201
% (self._format, self.bzrdir.transport.base))
1203
_deprecation_warning_done = True
876
_deprecation_warning_done = True
877
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
878
% (self._format, self.bzrdir.transport.base))
1205
880
def supports_rich_root(self):
1206
881
return self._format.rich_root_data
1387
1077
def __ne__(self, other):
1388
1078
return not self == other
1081
def find_format(klass, a_bzrdir):
1082
"""Return the format for the repository object in a_bzrdir.
1084
This is used by bzr native formats that have a "format" file in
1085
the repository. Other methods may be used by different types of
1089
transport = a_bzrdir.get_repository_transport(None)
1090
format_string = transport.get("format").read()
1091
return format_registry.get(format_string)
1092
except errors.NoSuchFile:
1093
raise errors.NoRepositoryPresent(a_bzrdir)
1095
raise errors.UnknownFormatError(format=format_string)
1098
def register_format(klass, format):
1099
format_registry.register(format.get_format_string(), format)
1102
def unregister_format(klass, format):
1103
format_registry.remove(format.get_format_string())
1106
def get_default_format(klass):
1107
"""Return the current default format."""
1108
from bzrlib import bzrdir
1109
return bzrdir.format_registry.make_bzrdir('default').repository_format
1111
def _get_control_store(self, repo_transport, control_files):
1112
"""Return the control store for this repository."""
1113
raise NotImplementedError(self._get_control_store)
1115
def get_format_string(self):
1116
"""Return the ASCII format string that identifies this format.
1118
Note that in pre format ?? repositories the format string is
1119
not permitted nor written to disk.
1121
raise NotImplementedError(self.get_format_string)
1390
1123
def get_format_description(self):
1391
1124
"""Return the short description for this format."""
1392
1125
raise NotImplementedError(self.get_format_description)
1394
def initialize(self, controldir, shared=False):
1395
"""Initialize a repository of this format in controldir.
1397
:param controldir: The controldir to put the new repository in it.
1127
def _get_revision_store(self, repo_transport, control_files):
1128
"""Return the revision store object for this a_bzrdir."""
1129
raise NotImplementedError(self._get_revision_store)
1131
def _get_text_rev_store(self,
1138
"""Common logic for getting a revision store for a repository.
1140
see self._get_revision_store for the subclass-overridable method to
1141
get the store for a repository.
1143
from bzrlib.store.revision.text import TextRevisionStore
1144
dir_mode = control_files._dir_mode
1145
file_mode = control_files._file_mode
1146
text_store = TextStore(transport.clone(name),
1148
compressed=compressed,
1150
file_mode=file_mode)
1151
_revision_store = TextRevisionStore(text_store, serializer)
1152
return _revision_store
1154
# TODO: this shouldn't be in the base class, it's specific to things that
1155
# use weaves or knits -- mbp 20070207
1156
def _get_versioned_file_store(self,
1161
versionedfile_class=None,
1162
versionedfile_kwargs={},
1164
if versionedfile_class is None:
1165
versionedfile_class = self._versionedfile_class
1166
weave_transport = control_files._transport.clone(name)
1167
dir_mode = control_files._dir_mode
1168
file_mode = control_files._file_mode
1169
return VersionedFileStore(weave_transport, prefixed=prefixed,
1171
file_mode=file_mode,
1172
versionedfile_class=versionedfile_class,
1173
versionedfile_kwargs=versionedfile_kwargs,
1176
def initialize(self, a_bzrdir, shared=False):
1177
"""Initialize a repository of this format in a_bzrdir.
1179
:param a_bzrdir: The bzrdir to put the new repository in it.
1398
1180
:param shared: The repository should be initialized as a sharable one.
1399
:returns: The new repository object.
1401
1182
This may raise UninitializableFormat if shared repository are not
1402
compatible the controldir.
1183
compatible the a_bzrdir.
1404
raise NotImplementedError(self.initialize)
1406
1186
def is_supported(self):
1407
1187
"""Is this format supported?
1409
1189
Supported formats must be initializable and openable.
1410
Unsupported formats may not support initialization or committing or
1190
Unsupported formats may not support initialization or committing or
1411
1191
some other features depending on the reason for not being supported.
1415
def is_deprecated(self):
1416
"""Is this format deprecated?
1418
Deprecated formats may trigger a user-visible warning recommending
1419
the user to upgrade. They are still fully supported.
1423
def network_name(self):
1424
"""A simple byte string uniquely identifying this format for RPC calls.
1426
MetaDir repository formats use their disk format string to identify the
1427
repository over the wire. All in one formats such as bzr < 0.8, and
1428
foreign formats like svn/git and hg should use some marker which is
1429
unique and immutable.
1431
raise NotImplementedError(self.network_name)
1433
1195
def check_conversion_target(self, target_format):
1434
if self.rich_root_data and not target_format.rich_root_data:
1435
raise errors.BadConversionTarget(
1436
'Does not support rich root data.', target_format,
1438
if (self.supports_tree_reference and
1439
not getattr(target_format, 'supports_tree_reference', False)):
1440
raise errors.BadConversionTarget(
1441
'Does not support nested trees', target_format,
1444
def open(self, controldir, _found=False):
1445
"""Return an instance of this format for a controldir.
1196
raise NotImplementedError(self.check_conversion_target)
1198
def open(self, a_bzrdir, _found=False):
1199
"""Return an instance of this format for the bzrdir a_bzrdir.
1447
1201
_found is a private parameter, do not use it.
1449
1203
raise NotImplementedError(self.open)
1451
def _run_post_repo_init_hooks(self, repository, controldir, shared):
1452
from bzrlib.controldir import ControlDir, RepoInitHookParams
1453
hooks = ControlDir.hooks['post_repo_init']
1456
params = RepoInitHookParams(repository, self, controldir, shared)
1461
class RepositoryFormatMetaDir(bzrdir.BzrFormat, RepositoryFormat):
1206
class MetaDirRepositoryFormat(RepositoryFormat):
1462
1207
"""Common base class for the new repositories using the metadir layout."""
1464
1209
rich_root_data = False
1465
1210
supports_tree_reference = False
1466
supports_external_lookups = False
1467
supports_leaving_lock = True
1468
supports_nesting_repositories = True
1471
def _matchingbzrdir(self):
1472
matching = bzrdir.BzrDirMetaFormat1()
1473
matching.repository_format = self
1211
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1476
1213
def __init__(self):
1477
RepositoryFormat.__init__(self)
1478
bzrdir.BzrFormat.__init__(self)
1214
super(MetaDirRepositoryFormat, self).__init__()
1480
1216
def _create_control_files(self, a_bzrdir):
1481
1217
"""Create the required files and the initial control_files object."""
1632
1283
_optimisers = []
1633
1284
"""The available optimised InterRepository types."""
1636
1286
def copy_content(self, revision_id=None):
1637
"""Make a complete copy of the content in self into destination.
1639
This is a destructive operation! Do not use it on existing
1642
:param revision_id: Only copy the content needed to construct
1643
revision_id and its parents.
1646
self.target.set_make_working_trees(self.source.make_working_trees())
1647
except NotImplementedError:
1649
self.target.fetch(self.source, revision_id=revision_id)
1652
def fetch(self, revision_id=None, find_ghosts=False):
1287
raise NotImplementedError(self.copy_content)
1289
def fetch(self, revision_id=None, pb=None):
1653
1290
"""Fetch the content required to construct revision_id.
1655
1292
The content is copied from self.source to self.target.
1657
1294
:param revision_id: if None all content is copied, if NULL_REVISION no
1658
1295
content is copied.
1296
:param pb: optional progress bar to use for progress reports. If not
1297
provided a default one will be created.
1299
Returns the copied revision count and the failed revisions in a tuple:
1661
1302
raise NotImplementedError(self.fetch)
1663
1304
@needs_read_lock
1664
def search_missing_revision_ids(self,
1665
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1666
find_ghosts=True, revision_ids=None, if_present_ids=None,
1305
def missing_revision_ids(self, revision_id=None):
1668
1306
"""Return the revision ids that source has that target does not.
1308
These are returned in topological order.
1670
1310
:param revision_id: only return revision ids included by this
1672
:param revision_ids: return revision ids included by these
1673
revision_ids. NoSuchRevision will be raised if any of these
1674
revisions are not present.
1675
:param if_present_ids: like revision_ids, but will not cause
1676
NoSuchRevision if any of these are absent, instead they will simply
1677
not be in the result. This is useful for e.g. finding revisions
1678
to fetch for tags, which may reference absent revisions.
1679
:param find_ghosts: If True find missing revisions in deep history
1680
rather than just finding the surface difference.
1681
:param limit: Maximum number of revisions to return, topologically
1683
:return: A bzrlib.graph.SearchResult.
1685
raise NotImplementedError(self.search_missing_revision_ids)
1688
def _same_model(source, target):
1689
"""True if source and target have the same data representation.
1691
Note: this is always called on the base class; overriding it in a
1692
subclass will have no effect.
1695
InterRepository._assert_same_model(source, target)
1313
# generic, possibly worst case, slow code path.
1314
target_ids = set(self.target.all_revision_ids())
1315
if revision_id is not None:
1316
# TODO: jam 20070210 InterRepository is internal enough that it
1317
# should assume revision_ids are already utf-8
1318
revision_id = osutils.safe_revision_id(revision_id)
1319
source_ids = self.source.get_ancestry(revision_id)
1320
assert source_ids[0] is None
1323
source_ids = self.source.all_revision_ids()
1324
result_set = set(source_ids).difference(target_ids)
1325
# this may look like a no-op: its not. It preserves the ordering
1326
# other_ids had while only returning the members from other_ids
1327
# that we've decided we need.
1328
return [rev_id for rev_id in source_ids if rev_id in result_set]
1331
class InterSameDataRepository(InterRepository):
1332
"""Code for converting between repositories that represent the same data.
1334
Data format and model must match for this to work.
1338
def _get_repo_format_to_test(self):
1339
"""Repository format for testing with."""
1340
return RepositoryFormat.get_default_format()
1343
def is_compatible(source, target):
1344
if not isinstance(source, Repository):
1346
if not isinstance(target, Repository):
1348
if source._format.rich_root_data != target._format.rich_root_data:
1350
if source._serializer != target._serializer:
1356
def copy_content(self, revision_id=None):
1357
"""Make a complete copy of the content in self into destination.
1359
This is a destructive operation! Do not use it on existing
1362
:param revision_id: Only copy the content needed to construct
1363
revision_id and its parents.
1366
self.target.set_make_working_trees(self.source.make_working_trees())
1367
except NotImplementedError:
1369
# TODO: jam 20070210 This is fairly internal, so we should probably
1370
# just assert that revision_id is not unicode.
1371
revision_id = osutils.safe_revision_id(revision_id)
1372
# but don't bother fetching if we have the needed data now.
1373
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1374
self.target.has_revision(revision_id)):
1376
self.target.fetch(self.source, revision_id=revision_id)
1379
def fetch(self, revision_id=None, pb=None):
1380
"""See InterRepository.fetch()."""
1381
from bzrlib.fetch import GenericRepoFetcher
1382
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1383
self.source, self.source._format, self.target,
1384
self.target._format)
1385
# TODO: jam 20070210 This should be an assert, not a translate
1386
revision_id = osutils.safe_revision_id(revision_id)
1387
f = GenericRepoFetcher(to_repository=self.target,
1388
from_repository=self.source,
1389
last_revision=revision_id,
1391
return f.count_copied, f.failed_revisions
1394
class InterWeaveRepo(InterSameDataRepository):
1395
"""Optimised code paths between Weave based repositories."""
1398
def _get_repo_format_to_test(self):
1399
from bzrlib.repofmt import weaverepo
1400
return weaverepo.RepositoryFormat7()
1403
def is_compatible(source, target):
1404
"""Be compatible with known Weave formats.
1406
We don't test for the stores being of specific types because that
1407
could lead to confusing results, and there is no need to be
1410
from bzrlib.repofmt.weaverepo import (
1416
return (isinstance(source._format, (RepositoryFormat5,
1418
RepositoryFormat7)) and
1419
isinstance(target._format, (RepositoryFormat5,
1421
RepositoryFormat7)))
1422
except AttributeError:
1426
def copy_content(self, revision_id=None):
1427
"""See InterRepository.copy_content()."""
1428
# weave specific optimised path:
1429
# TODO: jam 20070210 Internal, should be an assert, not translate
1430
revision_id = osutils.safe_revision_id(revision_id)
1432
self.target.set_make_working_trees(self.source.make_working_trees())
1433
except NotImplementedError:
1435
# FIXME do not peek!
1436
if self.source.control_files._transport.listable():
1437
pb = ui.ui_factory.nested_progress_bar()
1439
self.target.weave_store.copy_all_ids(
1440
self.source.weave_store,
1442
from_transaction=self.source.get_transaction(),
1443
to_transaction=self.target.get_transaction())
1444
pb.update('copying inventory', 0, 1)
1445
self.target.control_weaves.copy_multi(
1446
self.source.control_weaves, ['inventory'],
1447
from_transaction=self.source.get_transaction(),
1448
to_transaction=self.target.get_transaction())
1449
self.target._revision_store.text_store.copy_all_ids(
1450
self.source._revision_store.text_store,
1455
self.target.fetch(self.source, revision_id=revision_id)
1458
def fetch(self, revision_id=None, pb=None):
1459
"""See InterRepository.fetch()."""
1460
from bzrlib.fetch import GenericRepoFetcher
1461
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1462
self.source, self.source._format, self.target, self.target._format)
1463
# TODO: jam 20070210 This should be an assert, not a translate
1464
revision_id = osutils.safe_revision_id(revision_id)
1465
f = GenericRepoFetcher(to_repository=self.target,
1466
from_repository=self.source,
1467
last_revision=revision_id,
1469
return f.count_copied, f.failed_revisions
1472
def missing_revision_ids(self, revision_id=None):
1473
"""See InterRepository.missing_revision_ids()."""
1474
# we want all revisions to satisfy revision_id in source.
1475
# but we don't want to stat every file here and there.
1476
# we want then, all revisions other needs to satisfy revision_id
1477
# checked, but not those that we have locally.
1478
# so the first thing is to get a subset of the revisions to
1479
# satisfy revision_id in source, and then eliminate those that
1480
# we do already have.
1481
# this is slow on high latency connection to self, but as as this
1482
# disk format scales terribly for push anyway due to rewriting
1483
# inventory.weave, this is considered acceptable.
1485
if revision_id is not None:
1486
source_ids = self.source.get_ancestry(revision_id)
1487
assert source_ids[0] is None
1490
source_ids = self.source._all_possible_ids()
1491
source_ids_set = set(source_ids)
1492
# source_ids is the worst possible case we may need to pull.
1493
# now we want to filter source_ids against what we actually
1494
# have in target, but don't try to check for existence where we know
1495
# we do not have a revision as that would be pointless.
1496
target_ids = set(self.target._all_possible_ids())
1497
possibly_present_revisions = target_ids.intersection(source_ids_set)
1498
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1499
required_revisions = source_ids_set.difference(actually_present_revisions)
1500
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1501
if revision_id is not None:
1502
# we used get_ancestry to determine source_ids then we are assured all
1503
# revisions referenced are present as they are installed in topological order.
1504
# and the tip revision was validated by get_ancestry.
1505
return required_topo_revisions
1507
# if we just grabbed the possibly available ids, then
1508
# we only have an estimate of whats available and need to validate
1509
# that against the revision records.
1510
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1513
class InterKnitRepo(InterSameDataRepository):
1514
"""Optimised code paths between Knit based repositories."""
1517
def _get_repo_format_to_test(self):
1518
from bzrlib.repofmt import knitrepo
1519
return knitrepo.RepositoryFormatKnit1()
1522
def is_compatible(source, target):
1523
"""Be compatible with known Knit formats.
1525
We don't test for the stores being of specific types because that
1526
could lead to confusing results, and there is no need to be
1529
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
1531
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1532
isinstance(target._format, (RepositoryFormatKnit1)))
1533
except AttributeError:
1537
def fetch(self, revision_id=None, pb=None):
1538
"""See InterRepository.fetch()."""
1539
from bzrlib.fetch import KnitRepoFetcher
1540
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1541
self.source, self.source._format, self.target, self.target._format)
1542
# TODO: jam 20070210 This should be an assert, not a translate
1543
revision_id = osutils.safe_revision_id(revision_id)
1544
f = KnitRepoFetcher(to_repository=self.target,
1545
from_repository=self.source,
1546
last_revision=revision_id,
1548
return f.count_copied, f.failed_revisions
1551
def missing_revision_ids(self, revision_id=None):
1552
"""See InterRepository.missing_revision_ids()."""
1553
if revision_id is not None:
1554
source_ids = self.source.get_ancestry(revision_id)
1555
assert source_ids[0] is None
1558
source_ids = self.source._all_possible_ids()
1559
source_ids_set = set(source_ids)
1560
# source_ids is the worst possible case we may need to pull.
1561
# now we want to filter source_ids against what we actually
1562
# have in target, but don't try to check for existence where we know
1563
# we do not have a revision as that would be pointless.
1564
target_ids = set(self.target._all_possible_ids())
1565
possibly_present_revisions = target_ids.intersection(source_ids_set)
1566
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1567
required_revisions = source_ids_set.difference(actually_present_revisions)
1568
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1569
if revision_id is not None:
1570
# we used get_ancestry to determine source_ids then we are assured all
1571
# revisions referenced are present as they are installed in topological order.
1572
# and the tip revision was validated by get_ancestry.
1573
return required_topo_revisions
1575
# if we just grabbed the possibly available ids, then
1576
# we only have an estimate of whats available and need to validate
1577
# that against the revision records.
1578
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1581
class InterModel1and2(InterRepository):
1584
def _get_repo_format_to_test(self):
1588
def is_compatible(source, target):
1589
if not isinstance(source, Repository):
1591
if not isinstance(target, Repository):
1593
if not source._format.rich_root_data and target._format.rich_root_data:
1697
except errors.IncompatibleRepositories, e:
1701
def _assert_same_model(source, target):
1702
"""Raise an exception if two repositories do not use the same model.
1599
def fetch(self, revision_id=None, pb=None):
1600
"""See InterRepository.fetch()."""
1601
from bzrlib.fetch import Model1toKnit2Fetcher
1602
# TODO: jam 20070210 This should be an assert, not a translate
1603
revision_id = osutils.safe_revision_id(revision_id)
1604
f = Model1toKnit2Fetcher(to_repository=self.target,
1605
from_repository=self.source,
1606
last_revision=revision_id,
1608
return f.count_copied, f.failed_revisions
1611
def copy_content(self, revision_id=None):
1612
"""Make a complete copy of the content in self into destination.
1614
This is a destructive operation! Do not use it on existing
1617
:param revision_id: Only copy the content needed to construct
1618
revision_id and its parents.
1704
if source.supports_rich_root() != target.supports_rich_root():
1705
raise errors.IncompatibleRepositories(source, target,
1706
"different rich-root support")
1707
if source._serializer != target._serializer:
1708
raise errors.IncompatibleRepositories(source, target,
1709
"different serializers")
1621
self.target.set_make_working_trees(self.source.make_working_trees())
1622
except NotImplementedError:
1624
# TODO: jam 20070210 Internal, assert, don't translate
1625
revision_id = osutils.safe_revision_id(revision_id)
1626
# but don't bother fetching if we have the needed data now.
1627
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1628
self.target.has_revision(revision_id)):
1630
self.target.fetch(self.source, revision_id=revision_id)
1633
class InterKnit1and2(InterKnitRepo):
1636
def _get_repo_format_to_test(self):
1640
def is_compatible(source, target):
1641
"""Be compatible with Knit1 source and Knit3 target"""
1642
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
1644
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1645
RepositoryFormatKnit3
1646
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1647
isinstance(target._format, (RepositoryFormatKnit3)))
1648
except AttributeError:
1652
def fetch(self, revision_id=None, pb=None):
1653
"""See InterRepository.fetch()."""
1654
from bzrlib.fetch import Knit1to2Fetcher
1655
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1656
self.source, self.source._format, self.target,
1657
self.target._format)
1658
# TODO: jam 20070210 This should be an assert, not a translate
1659
revision_id = osutils.safe_revision_id(revision_id)
1660
f = Knit1to2Fetcher(to_repository=self.target,
1661
from_repository=self.source,
1662
last_revision=revision_id,
1664
return f.count_copied, f.failed_revisions
1667
InterRepository.register_optimiser(InterSameDataRepository)
1668
InterRepository.register_optimiser(InterWeaveRepo)
1669
InterRepository.register_optimiser(InterKnitRepo)
1670
InterRepository.register_optimiser(InterModel1and2)
1671
InterRepository.register_optimiser(InterKnit1and2)
1674
class RepositoryTestProviderAdapter(object):
1675
"""A tool to generate a suite testing multiple repository formats at once.
1677
This is done by copying the test once for each transport and injecting
1678
the transport_server, transport_readonly_server, and bzrdir_format and
1679
repository_format classes into each copy. Each copy is also given a new id()
1680
to make it easy to identify.
1683
def __init__(self, transport_server, transport_readonly_server, formats):
1684
self._transport_server = transport_server
1685
self._transport_readonly_server = transport_readonly_server
1686
self._formats = formats
1688
def adapt(self, test):
1689
result = unittest.TestSuite()
1690
for repository_format, bzrdir_format in self._formats:
1691
from copy import deepcopy
1692
new_test = deepcopy(test)
1693
new_test.transport_server = self._transport_server
1694
new_test.transport_readonly_server = self._transport_readonly_server
1695
new_test.bzrdir_format = bzrdir_format
1696
new_test.repository_format = repository_format
1697
def make_new_test_id():
1698
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1699
return lambda: new_id
1700
new_test.id = make_new_test_id()
1701
result.addTest(new_test)
1705
class InterRepositoryTestProviderAdapter(object):
1706
"""A tool to generate a suite testing multiple inter repository formats.
1708
This is done by copying the test once for each interrepo provider and injecting
1709
the transport_server, transport_readonly_server, repository_format and
1710
repository_to_format classes into each copy.
1711
Each copy is also given a new id() to make it easy to identify.
1714
def __init__(self, transport_server, transport_readonly_server, formats):
1715
self._transport_server = transport_server
1716
self._transport_readonly_server = transport_readonly_server
1717
self._formats = formats
1719
def adapt(self, test):
1720
result = unittest.TestSuite()
1721
for interrepo_class, repository_format, repository_format_to in self._formats:
1722
from copy import deepcopy
1723
new_test = deepcopy(test)
1724
new_test.transport_server = self._transport_server
1725
new_test.transport_readonly_server = self._transport_readonly_server
1726
new_test.interrepo_class = interrepo_class
1727
new_test.repository_format = repository_format
1728
new_test.repository_format_to = repository_format_to
1729
def make_new_test_id():
1730
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1731
return lambda: new_id
1732
new_test.id = make_new_test_id()
1733
result.addTest(new_test)
1737
def default_test_list():
1738
"""Generate the default list of interrepo permutations to test."""
1739
from bzrlib.repofmt import knitrepo, weaverepo
1741
# test the default InterRepository between format 6 and the current
1743
# XXX: robertc 20060220 reinstate this when there are two supported
1744
# formats which do not have an optimal code path between them.
1745
#result.append((InterRepository,
1746
# RepositoryFormat6(),
1747
# RepositoryFormatKnit1()))
1748
for optimiser_class in InterRepository._optimisers:
1749
format_to_test = optimiser_class._get_repo_format_to_test()
1750
if format_to_test is not None:
1751
result.append((optimiser_class,
1752
format_to_test, format_to_test))
1753
# if there are specific combinations we want to use, we can add them
1755
result.append((InterModel1and2,
1756
weaverepo.RepositoryFormat5(),
1757
knitrepo.RepositoryFormatKnit3()))
1758
result.append((InterKnit1and2,
1759
knitrepo.RepositoryFormatKnit1(),
1760
knitrepo.RepositoryFormatKnit3()))
1712
1764
class CopyConverter(object):
1713
1765
"""A repository conversion tool which just performs a copy of the content.
1715
1767
This is slow but quite reliable.
1721
1773
:param target_format: The format the resulting repository should be.
1723
1775
self.target_format = target_format
1725
1777
def convert(self, repo, pb):
1726
1778
"""Perform the conversion of to_convert, giving feedback via pb.
1728
1780
:param to_convert: The disk object to convert.
1729
1781
:param pb: a progress bar to use for progress information.
1731
pb = ui.ui_factory.nested_progress_bar()
1734
1786
# this is only useful with metadir layouts - separated repo content.
1735
1787
# trigger an assertion if not such
1736
1788
repo._format.get_format_string()
1737
1789
self.repo_dir = repo.bzrdir
1738
pb.update(gettext('Moving repository to repository.backup'))
1790
self.step('Moving repository to repository.backup')
1739
1791
self.repo_dir.transport.move('repository', 'repository.backup')
1740
1792
backup_transport = self.repo_dir.transport.clone('repository.backup')
1741
1793
repo._format.check_conversion_target(self.target_format)
1742
1794
self.source_repo = repo._format.open(self.repo_dir,
1744
1796
_override_transport=backup_transport)
1745
pb.update(gettext('Creating new repository'))
1797
self.step('Creating new repository')
1746
1798
converted = self.target_format.initialize(self.repo_dir,
1747
1799
self.source_repo.is_shared())
1748
1800
converted.lock_write()
1750
pb.update(gettext('Copying content'))
1802
self.step('Copying content into repository.')
1751
1803
self.source_repo.copy_content_into(converted)
1753
1805
converted.unlock()
1754
pb.update(gettext('Deleting old repository content'))
1806
self.step('Deleting old repository content.')
1755
1807
self.repo_dir.transport.delete_tree('repository.backup')
1756
ui.ui_factory.note(gettext('repository converted'))
1760
def _strip_NULL_ghosts(revision_graph):
1761
"""Also don't use this. more compatibility code for unmigrated clients."""
1762
# Filter ghosts, and null:
1763
if _mod_revision.NULL_REVISION in revision_graph:
1764
del revision_graph[_mod_revision.NULL_REVISION]
1765
for key, parents in revision_graph.items():
1766
revision_graph[key] = tuple(parent for parent in parents if parent
1768
return revision_graph
1771
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
1772
stop_revision=None):
1773
"""Extend the partial history to include a given index
1775
If a stop_index is supplied, stop when that index has been reached.
1776
If a stop_revision is supplied, stop when that revision is
1777
encountered. Otherwise, stop when the beginning of history is
1780
:param stop_index: The index which should be present. When it is
1781
present, history extension will stop.
1782
:param stop_revision: The revision id which should be present. When
1783
it is encountered, history extension will stop.
1785
start_revision = partial_history_cache[-1]
1786
graph = repo.get_graph()
1787
iterator = graph.iter_lefthand_ancestry(start_revision,
1788
(_mod_revision.NULL_REVISION,))
1808
self.pb.note('repository converted')
1810
def step(self, message):
1811
"""Update the pb by a step."""
1813
self.pb.update(message, self.count, self.total)
1816
class CommitBuilder(object):
1817
"""Provides an interface to build up a commit.
1819
This allows describing a tree to be committed without needing to
1820
know the internals of the format of the repository.
1823
record_root_entry = False
1824
def __init__(self, repository, parents, config, timestamp=None,
1825
timezone=None, committer=None, revprops=None,
1827
"""Initiate a CommitBuilder.
1829
:param repository: Repository to commit to.
1830
:param parents: Revision ids of the parents of the new revision.
1831
:param config: Configuration to use.
1832
:param timestamp: Optional timestamp recorded for commit.
1833
:param timezone: Optional timezone for timestamp.
1834
:param committer: Optional committer to set for commit.
1835
:param revprops: Optional dictionary of revision properties.
1836
:param revision_id: Optional revision id.
1838
self._config = config
1840
if committer is None:
1841
self._committer = self._config.username()
1843
assert isinstance(committer, basestring), type(committer)
1844
self._committer = committer
1846
self.new_inventory = Inventory(None)
1847
self._new_revision_id = osutils.safe_revision_id(revision_id)
1848
self.parents = parents
1849
self.repository = repository
1852
if revprops is not None:
1853
self._revprops.update(revprops)
1855
if timestamp is None:
1856
timestamp = time.time()
1857
# Restrict resolution to 1ms
1858
self._timestamp = round(timestamp, 3)
1860
if timezone is None:
1861
self._timezone = osutils.local_time_offset()
1863
self._timezone = int(timezone)
1865
self._generate_revision_if_needed()
1867
def commit(self, message):
1868
"""Make the actual commit.
1870
:return: The revision id of the recorded revision.
1872
rev = _mod_revision.Revision(
1873
timestamp=self._timestamp,
1874
timezone=self._timezone,
1875
committer=self._committer,
1877
inventory_sha1=self.inv_sha1,
1878
revision_id=self._new_revision_id,
1879
properties=self._revprops)
1880
rev.parent_ids = self.parents
1881
self.repository.add_revision(self._new_revision_id, rev,
1882
self.new_inventory, self._config)
1883
return self._new_revision_id
1885
def revision_tree(self):
1886
"""Return the tree that was just committed.
1888
After calling commit() this can be called to get a RevisionTree
1889
representing the newly committed tree. This is preferred to
1890
calling Repository.revision_tree() because that may require
1891
deserializing the inventory, while we already have a copy in
1894
return RevisionTree(self.repository, self.new_inventory,
1895
self._new_revision_id)
1897
def finish_inventory(self):
1898
"""Tell the builder that the inventory is finished."""
1899
if self.new_inventory.root is None:
1900
symbol_versioning.warn('Root entry should be supplied to'
1901
' record_entry_contents, as of bzr 0.10.',
1902
DeprecationWarning, stacklevel=2)
1903
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
1904
self.new_inventory.revision_id = self._new_revision_id
1905
self.inv_sha1 = self.repository.add_inventory(
1906
self._new_revision_id,
1911
def _gen_revision_id(self):
1912
"""Return new revision-id."""
1913
return generate_ids.gen_revision_id(self._config.username(),
1916
def _generate_revision_if_needed(self):
1917
"""Create a revision id if None was supplied.
1919
If the repository can not support user-specified revision ids
1920
they should override this function and raise CannotSetRevisionId
1921
if _new_revision_id is not None.
1923
:raises: CannotSetRevisionId
1925
if self._new_revision_id is None:
1926
self._new_revision_id = self._gen_revision_id()
1928
def record_entry_contents(self, ie, parent_invs, path, tree):
1929
"""Record the content of ie from tree into the commit if needed.
1931
Side effect: sets ie.revision when unchanged
1933
:param ie: An inventory entry present in the commit.
1934
:param parent_invs: The inventories of the parent revisions of the
1936
:param path: The path the entry is at in the tree.
1937
:param tree: The tree which contains this entry and should be used to
1940
if self.new_inventory.root is None and ie.parent_id is not None:
1941
symbol_versioning.warn('Root entry should be supplied to'
1942
' record_entry_contents, as of bzr 0.10.',
1943
DeprecationWarning, stacklevel=2)
1944
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
1946
self.new_inventory.add(ie)
1948
# ie.revision is always None if the InventoryEntry is considered
1949
# for committing. ie.snapshot will record the correct revision
1950
# which may be the sole parent if it is untouched.
1951
if ie.revision is not None:
1954
# In this revision format, root entries have no knit or weave
1955
if ie is self.new_inventory.root:
1956
# When serializing out to disk and back in
1957
# root.revision is always _new_revision_id
1958
ie.revision = self._new_revision_id
1960
previous_entries = ie.find_previous_heads(
1962
self.repository.weave_store,
1963
self.repository.get_transaction())
1964
# we are creating a new revision for ie in the history store
1966
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
1968
def modified_directory(self, file_id, file_parents):
1969
"""Record the presence of a symbolic link.
1971
:param file_id: The file_id of the link to record.
1972
:param file_parents: The per-file parent revision ids.
1974
self._add_text_to_weave(file_id, [], file_parents.keys())
1976
def modified_reference(self, file_id, file_parents):
1977
"""Record the modification of a reference.
1979
:param file_id: The file_id of the link to record.
1980
:param file_parents: The per-file parent revision ids.
1982
self._add_text_to_weave(file_id, [], file_parents.keys())
1984
def modified_file_text(self, file_id, file_parents,
1985
get_content_byte_lines, text_sha1=None,
1987
"""Record the text of file file_id
1989
:param file_id: The file_id of the file to record the text of.
1990
:param file_parents: The per-file parent revision ids.
1991
:param get_content_byte_lines: A callable which will return the byte
1993
:param text_sha1: Optional SHA1 of the file contents.
1994
:param text_size: Optional size of the file contents.
1996
# mutter('storing text of file {%s} in revision {%s} into %r',
1997
# file_id, self._new_revision_id, self.repository.weave_store)
1998
# special case to avoid diffing on renames or
2000
if (len(file_parents) == 1
2001
and text_sha1 == file_parents.values()[0].text_sha1
2002
and text_size == file_parents.values()[0].text_size):
2003
previous_ie = file_parents.values()[0]
2004
versionedfile = self.repository.weave_store.get_weave(file_id,
2005
self.repository.get_transaction())
2006
versionedfile.clone_text(self._new_revision_id,
2007
previous_ie.revision, file_parents.keys())
2008
return text_sha1, text_size
2010
new_lines = get_content_byte_lines()
2011
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2012
# should return the SHA1 and size
2013
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2014
return osutils.sha_strings(new_lines), \
2015
sum(map(len, new_lines))
2017
def modified_link(self, file_id, file_parents, link_target):
2018
"""Record the presence of a symbolic link.
2020
:param file_id: The file_id of the link to record.
2021
:param file_parents: The per-file parent revision ids.
2022
:param link_target: Target location of this link.
2024
self._add_text_to_weave(file_id, [], file_parents.keys())
2026
def _add_text_to_weave(self, file_id, new_lines, parents):
2027
versionedfile = self.repository.weave_store.get_weave_or_empty(
2028
file_id, self.repository.get_transaction())
2029
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2030
versionedfile.clear_cache()
2033
class _CommitBuilder(CommitBuilder):
2034
"""Temporary class so old CommitBuilders are detected properly
2036
Note: CommitBuilder works whether or not root entry is recorded.
2039
record_root_entry = True
2042
class RootCommitBuilder(CommitBuilder):
2043
"""This commitbuilder actually records the root id"""
2045
record_root_entry = True
2047
def record_entry_contents(self, ie, parent_invs, path, tree):
2048
"""Record the content of ie from tree into the commit if needed.
2050
Side effect: sets ie.revision when unchanged
2052
:param ie: An inventory entry present in the commit.
2053
:param parent_invs: The inventories of the parent revisions of the
2055
:param path: The path the entry is at in the tree.
2056
:param tree: The tree which contains this entry and should be used to
2059
assert self.new_inventory.root is not None or ie.parent_id is None
2060
self.new_inventory.add(ie)
2062
# ie.revision is always None if the InventoryEntry is considered
2063
# for committing. ie.snapshot will record the correct revision
2064
# which may be the sole parent if it is untouched.
2065
if ie.revision is not None:
2068
previous_entries = ie.find_previous_heads(
2070
self.repository.weave_store,
2071
self.repository.get_transaction())
2072
# we are creating a new revision for ie in the history store
2074
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2086
def _unescaper(match, _map=_unescape_map):
2087
code = match.group(1)
1790
# skip the last revision in the list
1793
if (stop_index is not None and
1794
len(partial_history_cache) > stop_index):
1796
if partial_history_cache[-1] == stop_revision:
1798
revision_id = iterator.next()
1799
partial_history_cache.append(revision_id)
1800
except StopIteration:
1805
class _LazyListJoin(object):
1806
"""An iterable yielding the contents of many lists as one list.
1808
Each iterator made from this will reflect the current contents of the lists
1809
at the time the iterator is made.
1811
This is used by Repository's _make_parents_provider implementation so that
1814
pp = repo._make_parents_provider() # uses a list of fallback repos
1815
pp.add_fallback_repository(other_repo) # appends to that list
1816
result = pp.get_parent_map(...)
1817
# The result will include revs from other_repo
1820
def __init__(self, *list_parts):
1821
self.list_parts = list_parts
1825
for list_part in self.list_parts:
1826
full_list.extend(list_part)
1827
return iter(full_list)
1830
return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
2091
if not code.startswith('#'):
2093
return unichr(int(code[1:])).encode('utf8')
2099
def _unescape_xml(data):
2100
"""Unescape predefined XML entities in a string of data."""
2102
if _unescape_re is None:
2103
_unescape_re = re.compile('\&([^;]*);')
2104
return _unescape_re.sub(_unescaper, data)