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
24
from bzrlib import (
33
38
revision as _mod_revision,
34
testament as _mod_testament,
38
43
from bzrlib.bundle import serializer
39
from bzrlib.i18n import gettext
44
from bzrlib.revisiontree import RevisionTree
45
from bzrlib.store.versioned import VersionedFileStore
46
from bzrlib.store.text import TextStore
47
from bzrlib.testament import Testament
49
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
51
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
52
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)
53
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
54
from bzrlib.symbol_versioning import (
57
from bzrlib.trace import mutter, note, warning
56
60
# Old formats display a warning, but only once
57
61
_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
64
######################################################################
251
class Repository(_RelockDebugMixin, controldir.ControlComponent):
67
class Repository(object):
252
68
"""Repository holding history for one or more branches.
254
70
The repository holds and retrieves historical information including
255
71
revisions and file history. It's normally accessed only by the Branch,
256
72
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.
74
The Repository builds on top of Stores and a Transport, which respectively
75
describe the disk data format and the way of accessing the (possibly
262
def abort_write_group(self, suppress_errors=False):
79
_file_ids_altered_regex = lazy_regex.lazy_compile(
80
r'file_id="(?P<file_id>[^"]+)"'
81
r'.*revision="(?P<revision_id>[^"]+)"'
84
def abort_write_group(self):
263
85
"""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
87
:seealso: start_write_group.
271
89
if self._write_group is not self.get_transaction():
272
90
# 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)
91
raise errors.BzrError('mismatched lock context and write group.')
92
self._abort_write_group()
290
93
self._write_group = None
292
95
def _abort_write_group(self):
293
96
"""Template method for per-repository write group cleanup.
295
This is called during abort before the write group is considered to be
98
This is called during abort before the write group is considered to be
296
99
finished and should cleanup any internal state accrued during the write
297
100
group. There is no requirement that data handed to the repository be
298
101
*not* made available - this is not a rollback - but neither should any
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)
109
def add_inventory(self, revision_id, inv, parents):
110
"""Add the inventory inv to the repository as revision_id.
112
:param parents: The revision ids of the parents that revision_id
113
is known to have and are in the repository already.
115
returns the sha1 of the serialized inventory.
117
revision_id = osutils.safe_revision_id(revision_id)
118
_mod_revision.check_not_reserved_id(revision_id)
119
assert inv.revision_id is None or inv.revision_id == revision_id, \
120
"Mismatch between inventory revision" \
121
" id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
122
assert inv.root is not None
123
inv_text = self.serialise_inventory(inv)
124
inv_sha1 = osutils.sha_string(inv_text)
125
inv_vf = self.control_weaves.get_weave('inventory',
126
self.get_transaction())
127
self._inventory_add_lines(inv_vf, revision_id, parents,
128
osutils.split_lines(inv_text))
131
def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
133
for parent in parents:
135
final_parents.append(parent)
137
inv_vf.add_lines(revision_id, final_parents, lines)
140
def add_revision(self, revision_id, rev, inv=None, config=None):
141
"""Add rev to the revision store as revision_id.
143
:param revision_id: the revision id to use.
144
:param rev: The revision object.
145
:param inv: The inventory for the revision. if None, it will be looked
146
up in the inventory storer
147
:param config: If None no digital signature will be created.
148
If supplied its signature_needed method will be used
149
to determine if a signature should be made.
151
revision_id = osutils.safe_revision_id(revision_id)
152
# TODO: jam 20070210 Shouldn't we check rev.revision_id and
154
_mod_revision.check_not_reserved_id(revision_id)
155
if config is not None and config.signature_needed():
157
inv = self.get_inventory(revision_id)
158
plaintext = Testament(rev, inv).as_short_text()
159
self.store_revision_signature(
160
gpg.GPGStrategy(config), plaintext, revision_id)
161
if not revision_id in self.get_inventory_weave():
163
raise errors.WeaveRevisionNotPresent(revision_id,
164
self.get_inventory_weave())
166
# yes, this is not suitable for adding with ghosts.
167
self.add_inventory(revision_id, inv, rev.parent_ids)
168
self._revision_store.add_revision(rev, self.get_transaction())
170
def _add_revision_text(self, revision_id, text):
171
revision = self._revision_store._serializer.read_revision_from_string(
173
self._revision_store._add_revision(revision, StringIO(text),
174
self.get_transaction())
177
def _all_possible_ids(self):
178
"""Return all the possible revisions that we could find."""
179
return self.get_inventory_weave().versions()
321
181
def all_revision_ids(self):
322
"""Returns a list of all the revision ids in the repository.
182
"""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
184
This is deprecated because code should generally work on the graph
185
reachable from a particular revision, and ignore any other revisions
186
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
188
return self._all_revision_ids()
333
191
def _all_revision_ids(self):
334
"""Returns a list of all the revision ids in the repository.
192
"""Returns a list of all the revision ids in the repository.
336
These are in as much topological order as the underlying store can
194
These are in as much topological order as the underlying store can
195
present: for weaves ghosts may lead to a lack of correctness until
196
the reweave updates the parents list.
339
raise NotImplementedError(self._all_revision_ids)
198
if self._revision_store.text_store.listable():
199
return self._revision_store.all_revision_ids(self.get_transaction())
200
result = self._all_possible_ids()
201
# TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
202
# ids. (It should, since _revision_store's API should change to
203
# return utf8 revision_ids)
204
return self._eliminate_revisions_not_present(result)
341
206
def break_lock(self):
342
207
"""Break a lock if one is present from another instance.
607
398
For instance, if the repository is at URL/.bzr/repository,
608
399
Repository.open(URL) -> a Repository instance.
610
control = controldir.ControlDir.open(base)
401
control = bzrdir.BzrDir.open(base)
611
402
return control.open_repository()
613
404
def copy_content_into(self, destination, revision_id=None):
614
405
"""Make a complete copy of the content in self into destination.
616
This is a destructive operation! Do not use it on existing
407
This is a destructive operation! Do not use it on existing
410
revision_id = osutils.safe_revision_id(revision_id)
619
411
return InterRepository.get(self, destination).copy_content(revision_id)
621
413
def commit_write_group(self):
622
414
"""Commit the contents accrued within the current write group.
624
416
:seealso: start_write_group.
626
:return: it may return an opaque hint that can be passed to 'pack'.
628
418
if self._write_group is not self.get_transaction():
629
419
# 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()
420
raise errors.BzrError('mismatched lock context and write group.')
421
self._commit_write_group()
634
422
self._write_group = None
637
424
def _commit_write_group(self):
638
425
"""Template method for per-repository write group cleanup.
640
This is called before the write group is considered to be
427
This is called before the write group is considered to be
641
428
finished and should ensure that all data handed to the repository
642
for writing during the write group is safely committed (to the
429
for writing during the write group is safely committed (to the
643
430
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):
433
def fetch(self, source, revision_id=None, pb=None):
681
434
"""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.
436
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)
438
revision_id = osutils.safe_revision_id(revision_id)
710
439
inter = InterRepository.get(source, self)
711
return inter.fetch(revision_id=revision_id, find_ghosts=find_ghosts)
441
return inter.fetch(revision_id=revision_id, pb=pb)
442
except NotImplementedError:
443
raise errors.IncompatibleRepositories(source, self)
713
445
def create_bundle(self, target, base, fileobj, format=None):
714
446
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):
448
def get_commit_builder(self, branch, parents, config, timestamp=None,
449
timezone=None, committer=None, revprops=None,
719
451
"""Obtain a CommitBuilder for this repository.
721
453
:param branch: Branch to commit to.
722
454
:param parents: Revision ids of the parents of the new revision.
723
:param config_stack: Configuration stack to use.
455
:param config: Configuration to use.
724
456
:param timestamp: Optional timestamp recorded for commit.
725
457
:param timezone: Optional timezone for timestamp.
726
458
:param committer: Optional committer to set for commit.
727
459
:param revprops: Optional dictionary of revision properties.
728
460
: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)
462
revision_id = osutils.safe_revision_id(revision_id)
463
result =_CommitBuilder(self, parents, config, timestamp, timezone,
464
committer, revprops, revision_id)
465
self.start_write_group()
734
@only_raises(errors.LockNotHeld, errors.LockBroken)
735
468
def unlock(self):
736
469
if (self.control_files._lock_count == 1 and
737
470
self.control_files._lock_mode == 'w'):
738
471
if self._write_group is not None:
739
self.abort_write_group()
740
self.control_files.unlock()
741
472
raise errors.BzrError(
742
473
'Must end write groups before releasing write locks.')
743
474
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.
477
def clone(self, a_bzrdir, revision_id=None):
478
"""Clone this repository into a_bzrdir using the current format.
752
480
Currently no check is made that the format of this repository and
753
481
the bzrdir format are compatible. FIXME RBC 20060201.
817
544
def has_revision(self, revision_id):
818
545
"""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]
546
revision_id = osutils.safe_revision_id(revision_id)
547
return self._revision_store.has_revision_id(revision_id,
548
self.get_transaction())
835
551
def get_revision_reconcile(self, revision_id):
836
552
"""'reconcile' helper routine that allows access to a revision always.
838
554
This variant of get_revision does not cross check the weave graph
839
555
against the revision one as get_revision does: but it should only
840
556
be used by reconcile, or reconcile-alike commands that are correcting
841
557
or testing the revision graph.
843
raise NotImplementedError(self.get_revision_reconcile)
559
if not revision_id or not isinstance(revision_id, basestring):
560
raise errors.InvalidRevisionId(revision_id=revision_id,
562
return self.get_revisions([revision_id])[0]
845
565
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):
566
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
567
revs = self._revision_store.get_revisions(revision_ids,
568
self.get_transaction())
570
assert not isinstance(rev.revision_id, unicode)
571
for parent_id in rev.parent_ids:
572
assert not isinstance(parent_id, unicode)
576
def get_revision_xml(self, revision_id):
577
# TODO: jam 20070210 This shouldn't be necessary since get_revision
578
# would have already do it.
579
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
580
revision_id = osutils.safe_revision_id(revision_id)
581
rev = self.get_revision(revision_id)
583
# the current serializer..
584
self._revision_store._serializer.write_revision(rev, rev_tmp)
586
return rev_tmp.getvalue()
589
def get_revision(self, revision_id):
590
"""Return the Revision object for a named revision"""
591
# TODO: jam 20070210 get_revision_reconcile should do this for us
592
revision_id = osutils.safe_revision_id(revision_id)
593
r = self.get_revision_reconcile(revision_id)
594
# weave corruption can lead to absent revision markers that should be
596
# the following test is reasonably cheap (it needs a single weave read)
597
# and the weave is cached in read transactions. In write transactions
598
# it is not cached but typically we only read a small number of
599
# revisions. For knits when they are introduced we will probably want
600
# to ensure that caching write transactions are in use.
601
inv = self.get_inventory_weave()
602
self._check_revision_parents(r, inv)
606
def get_deltas_for_revisions(self, revisions):
854
607
"""Produce a generator of revision deltas.
856
609
Note that the input is a sequence of REVISIONS, not revision_ids.
857
610
Trees will be held in memory until the generator exits.
858
611
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
613
required_trees = set()
866
614
for revision in revisions:
867
615
required_trees.add(revision.revision_id)
868
616
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
617
trees = dict((t.get_revision_id(), t) for
618
t in self.revision_trees(required_trees))
885
619
for revision in revisions:
886
620
if not revision.parent_ids:
887
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
621
old_tree = self.revision_tree(None)
889
623
old_tree = trees[revision.parent_ids[0]]
890
624
yield trees[revision.revision_id].changes_from(old_tree)
893
def get_revision_delta(self, revision_id, specific_fileids=None):
627
def get_revision_delta(self, revision_id):
894
628
"""Return the delta for one revision.
896
630
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
633
r = self.get_revision(revision_id)
904
return list(self.get_deltas_for_revisions([r],
905
specific_fileids=specific_fileids))[0]
634
return list(self.get_deltas_for_revisions([r]))[0]
636
def _check_revision_parents(self, revision, inventory):
637
"""Private to Repository and Fetch.
639
This checks the parentage of revision in an inventory weave for
640
consistency and is only applicable to inventory-weave-for-ancestry
641
using repository formats & fetchers.
643
weave_parents = inventory.get_parents(revision.revision_id)
644
weave_names = inventory.versions()
645
for parent_id in revision.parent_ids:
646
if parent_id in weave_names:
647
# this parent must not be a ghost.
648
if not parent_id in weave_parents:
650
raise errors.CorruptRepository(self)
907
652
@needs_write_lock
908
653
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
654
revision_id = osutils.safe_revision_id(revision_id)
909
655
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])
656
self._revision_store.add_revision_signature_text(revision_id,
658
self.get_transaction())
660
def fileids_altered_by_revision_ids(self, revision_ids):
661
"""Find the file ids and versions affected by revisions.
663
:param revisions: an iterable containing revision ids.
664
:return: a dictionary mapping altered file-ids to an iterable of
665
revision_ids. Each altered file-ids has the exact revision_ids that
666
altered it listed explicitly.
668
assert self._serializer.support_altered_by_hack, \
669
("fileids_altered_by_revision_ids only supported for branches "
670
"which store inventory as unnested xml, not on %r" % self)
671
selected_revision_ids = set(osutils.safe_revision_id(r)
672
for r in revision_ids)
673
w = self.get_inventory_weave()
676
# this code needs to read every new line in every inventory for the
677
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
678
# not present in one of those inventories is unnecessary but not
679
# harmful because we are filtering by the revision id marker in the
680
# inventory lines : we only select file ids altered in one of those
681
# revisions. We don't need to see all lines in the inventory because
682
# only those added in an inventory in rev X can contain a revision=X
684
unescape_revid_cache = {}
685
unescape_fileid_cache = {}
687
# jam 20061218 In a big fetch, this handles hundreds of thousands
688
# of lines, so it has had a lot of inlining and optimizing done.
689
# Sorry that it is a little bit messy.
690
# Move several functions to be local variables, since this is a long
692
search = self._file_ids_altered_regex.search
693
unescape = _unescape_xml
694
setdefault = result.setdefault
695
pb = ui.ui_factory.nested_progress_bar()
697
for line in w.iter_lines_added_or_present_in_versions(
698
selected_revision_ids, pb=pb):
702
# One call to match.group() returning multiple items is quite a
703
# bit faster than 2 calls to match.group() each returning 1
704
file_id, revision_id = match.group('file_id', 'revision_id')
706
# Inlining the cache lookups helps a lot when you make 170,000
707
# lines and 350k ids, versus 8.4 unique ids.
708
# Using a cache helps in 2 ways:
709
# 1) Avoids unnecessary decoding calls
710
# 2) Re-uses cached strings, which helps in future set and
712
# (2) is enough that removing encoding entirely along with
713
# the cache (so we are using plain strings) results in no
714
# performance improvement.
716
revision_id = unescape_revid_cache[revision_id]
718
unescaped = unescape(revision_id)
719
unescape_revid_cache[revision_id] = unescaped
720
revision_id = unescaped
722
if revision_id in selected_revision_ids:
724
file_id = unescape_fileid_cache[file_id]
726
unescaped = unescape(file_id)
727
unescape_fileid_cache[file_id] = unescaped
729
setdefault(file_id, set()).add(revision_id)
734
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
735
"""Get an iterable listing the keys of all the data introduced by a set
738
The keys will be ordered so that the corresponding items can be safely
739
fetched and inserted in that order.
741
:returns: An iterable producing tuples of (knit-kind, file-id,
742
versions). knit-kind is one of 'file', 'inventory', 'signatures',
743
'revisions'. file-id is None unless knit-kind is 'file'.
745
# XXX: it's a bit weird to control the inventory weave caching in this
746
# generator. Ideally the caching would be done in fetch.py I think. Or
747
# maybe this generator should explicitly have the contract that it
748
# should not be iterated until the previously yielded item has been
750
inv_w = self.get_inventory_weave()
753
# file ids that changed
754
file_ids = self.fileids_altered_by_revision_ids(revision_ids)
756
num_file_ids = len(file_ids)
757
for file_id, altered_versions in file_ids.iteritems():
758
if _files_pb is not None:
759
_files_pb.update("fetch texts", count, num_file_ids)
761
yield ("file", file_id, altered_versions)
762
# We're done with the files_pb. Note that it finished by the caller,
763
# just as it was created by the caller.
767
yield ("inventory", None, revision_ids)
771
revisions_with_signatures = set()
772
for rev_id in revision_ids:
774
self.get_signature_text(rev_id)
775
except errors.NoSuchRevision:
779
revisions_with_signatures.add(rev_id)
780
yield ("signatures", None, revisions_with_signatures)
783
yield ("revisions", None, revision_ids)
786
def get_inventory_weave(self):
787
return self.control_weaves.get_weave('inventory',
788
self.get_transaction())
791
def get_inventory(self, revision_id):
792
"""Get Inventory object by hash."""
793
# TODO: jam 20070210 Technically we don't need to sanitize, since all
794
# called functions must sanitize.
795
revision_id = osutils.safe_revision_id(revision_id)
796
return self.deserialise_inventory(
797
revision_id, self.get_inventory_xml(revision_id))
799
def deserialise_inventory(self, revision_id, xml):
800
"""Transform the xml into an inventory object.
802
:param revision_id: The expected revision id of the inventory.
803
:param xml: A serialised inventory.
805
revision_id = osutils.safe_revision_id(revision_id)
806
result = self._serializer.read_inventory_from_string(xml)
807
result.root.revision = revision_id
810
def serialise_inventory(self, inv):
811
return self._serializer.write_inventory_to_string(inv)
813
def get_serializer_format(self):
814
return self._serializer.format_num
817
def get_inventory_xml(self, revision_id):
818
"""Get inventory XML as a file object."""
819
revision_id = osutils.safe_revision_id(revision_id)
821
assert isinstance(revision_id, str), type(revision_id)
822
iw = self.get_inventory_weave()
823
return iw.get_text(revision_id)
825
raise errors.HistoryMissing(self, 'inventory', revision_id)
828
def get_inventory_sha1(self, revision_id):
829
"""Return the sha1 hash of the inventory entry
831
# TODO: jam 20070210 Shouldn't this be deprecated / removed?
832
revision_id = osutils.safe_revision_id(revision_id)
833
return self.get_revision(revision_id).inventory_sha1
836
def get_revision_graph(self, revision_id=None):
837
"""Return a dictionary containing the revision graph.
839
:param revision_id: The revision_id to get a graph from. If None, then
840
the entire revision graph is returned. This is a deprecated mode of
841
operation and will be removed in the future.
842
:return: a dictionary of revision_id->revision_parents_list.
844
# special case NULL_REVISION
845
if revision_id == _mod_revision.NULL_REVISION:
847
revision_id = osutils.safe_revision_id(revision_id)
848
a_weave = self.get_inventory_weave()
849
all_revisions = self._eliminate_revisions_not_present(
851
entire_graph = dict([(node, tuple(a_weave.get_parents(node))) for
852
node in all_revisions])
853
if revision_id is None:
855
elif revision_id not in entire_graph:
856
raise errors.NoSuchRevision(self, revision_id)
858
# add what can be reached from revision_id
860
pending = set([revision_id])
861
while len(pending) > 0:
863
result[node] = entire_graph[node]
864
for revision_id in result[node]:
865
if revision_id not in result:
866
pending.add(revision_id)
870
def get_revision_graph_with_ghosts(self, revision_ids=None):
871
"""Return a graph of the revisions with ghosts marked as applicable.
873
:param revision_ids: an iterable of revisions to graph or None for all.
874
:return: a Graph object with the graph reachable from revision_ids.
876
result = deprecated_graph.Graph()
878
pending = set(self.all_revision_ids())
881
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
882
# special case NULL_REVISION
883
if _mod_revision.NULL_REVISION in pending:
884
pending.remove(_mod_revision.NULL_REVISION)
885
required = set(pending)
888
revision_id = pending.pop()
890
rev = self.get_revision(revision_id)
891
except errors.NoSuchRevision:
892
if revision_id in required:
895
result.add_ghost(revision_id)
897
for parent_id in rev.parent_ids:
898
# is this queued or done ?
899
if (parent_id not in pending and
900
parent_id not in done):
902
pending.add(parent_id)
903
result.add_node(revision_id, rev.parent_ids)
904
done.add(revision_id)
907
def _get_history_vf(self):
908
"""Get a versionedfile whose history graph reflects all revisions.
910
For weave repositories, this is the inventory weave.
912
return self.get_inventory_weave()
914
def iter_reverse_revision_history(self, revision_id):
915
"""Iterate backwards through revision ids in the lefthand history
917
:param revision_id: The revision id to start with. All its lefthand
918
ancestors will be traversed.
920
revision_id = osutils.safe_revision_id(revision_id)
921
if revision_id in (None, _mod_revision.NULL_REVISION):
923
next_id = revision_id
924
versionedfile = self._get_history_vf()
927
parents = versionedfile.get_parents(next_id)
928
if len(parents) == 0:
934
def get_revision_inventory(self, revision_id):
935
"""Return inventory of a past revision."""
936
# TODO: Unify this with get_inventory()
937
# bzr 0.0.6 and later imposes the constraint that the inventory_id
938
# must be the same as its revision, so this is trivial.
939
if revision_id is None:
940
# This does not make sense: if there is no revision,
941
# then it is the current tree inventory surely ?!
942
# and thus get_root_id() is something that looks at the last
943
# commit on the branch, and the get_root_id is an inventory check.
944
raise NotImplementedError
945
# return Inventory(self.get_root_id())
947
return self.get_inventory(revision_id)
984
950
def is_shared(self):
985
951
"""Return True if this repository is flagged as a shared repository."""
986
952
raise NotImplementedError(self.is_shared)
1000
966
for repositories to maintain loaded indices across multiple locks
1001
967
by checking inside their implementation of this method to see
1002
968
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
969
the disk format being validatable in this manner.
1010
972
@needs_read_lock
1011
973
def revision_tree(self, revision_id):
1012
974
"""Return Tree for a revision on this branch.
1014
`revision_id` may be NULL_REVISION for the empty tree revision.
976
`revision_id` may be None for the empty tree revision.
1016
raise NotImplementedError(self.revision_tree)
978
# TODO: refactor this to use an existing revision object
979
# so we don't need to read it in twice.
980
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
981
return RevisionTree(self, Inventory(root_id=None),
982
_mod_revision.NULL_REVISION)
984
revision_id = osutils.safe_revision_id(revision_id)
985
inv = self.get_revision_inventory(revision_id)
986
return RevisionTree(self, inv, revision_id)
1018
989
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:'
990
"""Return Tree for a revision on this branch.
992
`revision_id` may not be None or 'null:'"""
993
assert None not in revision_ids
994
assert _mod_revision.NULL_REVISION not in revision_ids
995
texts = self.get_inventory_weave().get_texts(revision_ids)
996
for text, revision_id in zip(texts, revision_ids):
997
inv = self.deserialise_inventory(revision_id, text)
998
yield RevisionTree(self, inv, revision_id)
1001
def get_ancestry(self, revision_id, topo_sorted=True):
1002
"""Return a list of revision-ids integrated by a revision.
1004
The first element of the list is always None, indicating the origin
1005
revision. This might change when we have history horizons, or
1006
perhaps we should have a new API.
1008
This is topologically sorted.
1024
raise NotImplementedError(self.revision_trees)
1010
if _mod_revision.is_null(revision_id):
1012
revision_id = osutils.safe_revision_id(revision_id)
1013
if not self.has_revision(revision_id):
1014
raise errors.NoSuchRevision(self, revision_id)
1015
w = self.get_inventory_weave()
1016
candidates = w.get_ancestry(revision_id, topo_sorted)
1017
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
1026
def pack(self, hint=None, clean_obsolete_packs=False):
1027
1020
"""Compress the data within the repository.
1029
1022
This operation only makes sense for some repository types. For other
1030
1023
types it should be a no-op that just returns.
1032
1025
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
1026
@needs_write_lock as this is a long running call its reasonable to
1034
1027
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
1031
def print_file(self, file, revision_id):
1032
"""Print `file` to stdout.
1034
FIXME RBC 20060125 as John Meinel points out this is a bad api
1035
- it writes to stdout, it assumes that that is valid etc. Fix
1036
by creating a new more flexible convenience function.
1038
revision_id = osutils.safe_revision_id(revision_id)
1039
tree = self.revision_tree(revision_id)
1040
# use inventory as it was in that revision
1041
file_id = tree.inventory.path2id(file)
1043
# TODO: jam 20060427 Write a test for this code path
1044
# it had a bug in it, and was raising the wrong
1046
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
1047
tree.print_file(file_id)
1047
1049
def get_transaction(self):
1048
1050
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)
1052
def revision_parents(self, revision_id):
1053
revision_id = osutils.safe_revision_id(revision_id)
1054
return self.get_inventory_weave().parent_names(revision_id)
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
1056
def get_parents(self, revision_ids):
1057
"""See StackedParentsProvider.get_parents"""
1060
1059
for revision_id in revision_ids:
1061
1060
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,)
1064
parents = self.get_revision(revision_id).parent_ids
1065
except errors.NoSuchRevision:
1068
if len(parents) == 0:
1069
parents = [_mod_revision.NULL_REVISION]
1070
parents_list.append(parents)
1077
1073
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
1076
def get_graph(self, other_repository=None):
1099
1077
"""Return the graph walker for this repository format"""
1100
1078
parents_provider = self._make_parents_provider()
1101
1079
if (other_repository is not None and
1102
not self.has_same_location(other_repository)):
1103
parents_provider = graph.StackedParentsProvider(
1080
other_repository.bzrdir.transport.base !=
1081
self.bzrdir.transport.base):
1082
parents_provider = graph._StackedParentsProvider(
1104
1083
[parents_provider, other_repository._make_parents_provider()])
1105
1084
return graph.Graph(parents_provider)
1117
1096
raise NotImplementedError(self.set_make_working_trees)
1119
1098
def make_working_trees(self):
1120
1099
"""Returns the policy for making working trees on new branches."""
1121
1100
raise NotImplementedError(self.make_working_trees)
1123
1102
@needs_write_lock
1124
1103
def sign_revision(self, revision_id, gpg_strategy):
1125
testament = _mod_testament.Testament.from_revision(self, revision_id)
1126
plaintext = testament.as_short_text()
1104
revision_id = osutils.safe_revision_id(revision_id)
1105
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1127
1106
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1129
1108
@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
1109
def has_signature_for_revision_id(self, revision_id):
1160
1110
"""Query for a revision signature for revision_id in the repository."""
1161
raise NotImplementedError(self.has_signature_for_revision_id)
1111
revision_id = osutils.safe_revision_id(revision_id)
1112
return self._revision_store.has_signature(revision_id,
1113
self.get_transaction())
1163
1116
def get_signature_text(self, revision_id):
1164
1117
"""Return the text for a signature."""
1165
raise NotImplementedError(self.get_signature_text)
1118
revision_id = osutils.safe_revision_id(revision_id)
1119
return self._revision_store.get_signature_text(revision_id,
1120
self.get_transaction())
1167
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1123
def check(self, revision_ids):
1168
1124
"""Check consistency of all history of given revision_ids.
1170
1126
Different repository implementations should override _check().
1172
1128
:param revision_ids: A non-empty list of revision_ids whose ancestry
1173
1129
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():
1131
if not revision_ids:
1132
raise ValueError("revision_ids must be non-empty in %s.check"
1134
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1135
return self._check(revision_ids)
1137
def _check(self, revision_ids):
1138
result = check.Check(self)
1142
def _warn_if_deprecated(self):
1189
1143
global _deprecation_warning_done
1190
1144
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
1146
_deprecation_warning_done = True
1147
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
1148
% (self._format, self.bzrdir.transport.base))
1205
1150
def supports_rich_root(self):
1206
1151
return self._format.rich_root_data
1222
1167
raise errors.NonAsciiRevisionId(method, self)
1171
# remove these delegates a while after bzr 0.15
1172
def __make_delegated(name, from_module):
1173
def _deprecated_repository_forwarder():
1174
symbol_versioning.warn('%s moved to %s in bzr 0.15'
1175
% (name, from_module),
1178
m = __import__(from_module, globals(), locals(), [name])
1180
return getattr(m, name)
1181
except AttributeError:
1182
raise AttributeError('module %s has no name %s'
1184
globals()[name] = _deprecated_repository_forwarder
1187
'AllInOneRepository',
1188
'WeaveMetaDirRepository',
1189
'PreSplitOutRepositoryFormat',
1190
'RepositoryFormat4',
1191
'RepositoryFormat5',
1192
'RepositoryFormat6',
1193
'RepositoryFormat7',
1195
__make_delegated(_name, 'bzrlib.repofmt.weaverepo')
1199
'RepositoryFormatKnit',
1200
'RepositoryFormatKnit1',
1202
__make_delegated(_name, 'bzrlib.repofmt.knitrepo')
1205
def install_revision(repository, rev, revision_tree):
1206
"""Install all revision data into a repository."""
1207
present_parents = []
1209
for p_id in rev.parent_ids:
1210
if repository.has_revision(p_id):
1211
present_parents.append(p_id)
1212
parent_trees[p_id] = repository.revision_tree(p_id)
1214
parent_trees[p_id] = repository.revision_tree(None)
1216
inv = revision_tree.inventory
1217
entries = inv.iter_entries()
1218
# backwards compatibility hack: skip the root id.
1219
if not repository.supports_rich_root():
1220
path, root = entries.next()
1221
if root.revision != rev.revision_id:
1222
raise errors.IncompatibleRevision(repr(repository))
1223
# Add the texts that are not already present
1224
for path, ie in entries:
1225
w = repository.weave_store.get_weave_or_empty(ie.file_id,
1226
repository.get_transaction())
1227
if ie.revision not in w:
1229
# FIXME: TODO: The following loop *may* be overlapping/duplicate
1230
# with InventoryEntry.find_previous_heads(). if it is, then there
1231
# is a latent bug here where the parents may have ancestors of each
1233
for revision, tree in parent_trees.iteritems():
1234
if ie.file_id not in tree:
1236
parent_id = tree.inventory[ie.file_id].revision
1237
if parent_id in text_parents:
1239
text_parents.append(parent_id)
1241
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
1242
repository.get_transaction())
1243
lines = revision_tree.get_file(ie.file_id).readlines()
1244
vfile.add_lines(rev.revision_id, text_parents, lines)
1246
# install the inventory
1247
repository.add_inventory(rev.revision_id, inv, present_parents)
1248
except errors.RevisionAlreadyPresent:
1250
repository.add_revision(rev.revision_id, rev, inv)
1225
1253
class MetaDirRepository(Repository):
1226
"""Repositories in the new meta-dir layout.
1228
:ivar _transport: Transport for access to repository control files,
1229
typically pointing to .bzr/repository.
1232
def __init__(self, _format, a_bzrdir, control_files):
1233
super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
1234
self._transport = control_files._transport
1254
"""Repositories in the new meta-dir layout."""
1256
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
1257
super(MetaDirRepository, self).__init__(_format,
1263
dir_mode = self.control_files._dir_mode
1264
file_mode = self.control_files._file_mode
1236
1267
def is_shared(self):
1237
1268
"""Return True if this repository is flagged as a shared repository."""
1238
return self._transport.has('shared-storage')
1269
return self.control_files._transport.has('shared-storage')
1240
1271
@needs_write_lock
1241
1272
def set_make_working_trees(self, new_value):
1632
1555
_optimisers = []
1633
1556
"""The available optimised InterRepository types."""
1636
1558
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):
1559
raise NotImplementedError(self.copy_content)
1561
def fetch(self, revision_id=None, pb=None):
1653
1562
"""Fetch the content required to construct revision_id.
1655
1564
The content is copied from self.source to self.target.
1657
1566
:param revision_id: if None all content is copied, if NULL_REVISION no
1658
1567
content is copied.
1568
:param pb: optional progress bar to use for progress reports. If not
1569
provided a default one will be created.
1571
Returns the copied revision count and the failed revisions in a tuple:
1661
1574
raise NotImplementedError(self.fetch)
1663
1576
@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,
1577
def missing_revision_ids(self, revision_id=None):
1668
1578
"""Return the revision ids that source has that target does not.
1580
These are returned in topological order.
1670
1582
: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)
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.
1585
# generic, possibly worst case, slow code path.
1586
target_ids = set(self.target.all_revision_ids())
1587
if revision_id is not None:
1588
# TODO: jam 20070210 InterRepository is internal enough that it
1589
# should assume revision_ids are already utf-8
1590
revision_id = osutils.safe_revision_id(revision_id)
1591
source_ids = self.source.get_ancestry(revision_id)
1592
assert source_ids[0] is None
1595
source_ids = self.source.all_revision_ids()
1596
result_set = set(source_ids).difference(target_ids)
1597
# this may look like a no-op: its not. It preserves the ordering
1598
# other_ids had while only returning the members from other_ids
1599
# that we've decided we need.
1600
return [rev_id for rev_id in source_ids if rev_id in result_set]
1603
class InterSameDataRepository(InterRepository):
1604
"""Code for converting between repositories that represent the same data.
1606
Data format and model must match for this to work.
1610
def _get_repo_format_to_test(self):
1611
"""Repository format for testing with."""
1612
return RepositoryFormat.get_default_format()
1615
def is_compatible(source, target):
1704
1616
if source.supports_rich_root() != target.supports_rich_root():
1705
raise errors.IncompatibleRepositories(source, target,
1706
"different rich-root support")
1707
1618
if source._serializer != target._serializer:
1708
raise errors.IncompatibleRepositories(source, target,
1709
"different serializers")
1623
def copy_content(self, revision_id=None):
1624
"""Make a complete copy of the content in self into destination.
1626
This copies both the repository's revision data, and configuration information
1627
such as the make_working_trees setting.
1629
This is a destructive operation! Do not use it on existing
1632
:param revision_id: Only copy the content needed to construct
1633
revision_id and its parents.
1636
self.target.set_make_working_trees(self.source.make_working_trees())
1637
except NotImplementedError:
1639
# TODO: jam 20070210 This is fairly internal, so we should probably
1640
# just assert that revision_id is not unicode.
1641
revision_id = osutils.safe_revision_id(revision_id)
1642
# but don't bother fetching if we have the needed data now.
1643
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1644
self.target.has_revision(revision_id)):
1646
self.target.fetch(self.source, revision_id=revision_id)
1649
def fetch(self, revision_id=None, pb=None):
1650
"""See InterRepository.fetch()."""
1651
from bzrlib.fetch import GenericRepoFetcher
1652
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1653
self.source, self.source._format, self.target,
1654
self.target._format)
1655
# TODO: jam 20070210 This should be an assert, not a translate
1656
revision_id = osutils.safe_revision_id(revision_id)
1657
f = GenericRepoFetcher(to_repository=self.target,
1658
from_repository=self.source,
1659
last_revision=revision_id,
1661
return f.count_copied, f.failed_revisions
1664
class InterWeaveRepo(InterSameDataRepository):
1665
"""Optimised code paths between Weave based repositories."""
1668
def _get_repo_format_to_test(self):
1669
from bzrlib.repofmt import weaverepo
1670
return weaverepo.RepositoryFormat7()
1673
def is_compatible(source, target):
1674
"""Be compatible with known Weave formats.
1676
We don't test for the stores being of specific types because that
1677
could lead to confusing results, and there is no need to be
1680
from bzrlib.repofmt.weaverepo import (
1686
return (isinstance(source._format, (RepositoryFormat5,
1688
RepositoryFormat7)) and
1689
isinstance(target._format, (RepositoryFormat5,
1691
RepositoryFormat7)))
1692
except AttributeError:
1696
def copy_content(self, revision_id=None):
1697
"""See InterRepository.copy_content()."""
1698
# weave specific optimised path:
1699
# TODO: jam 20070210 Internal, should be an assert, not translate
1700
revision_id = osutils.safe_revision_id(revision_id)
1702
self.target.set_make_working_trees(self.source.make_working_trees())
1703
except NotImplementedError:
1705
# FIXME do not peek!
1706
if self.source.control_files._transport.listable():
1707
pb = ui.ui_factory.nested_progress_bar()
1709
self.target.weave_store.copy_all_ids(
1710
self.source.weave_store,
1712
from_transaction=self.source.get_transaction(),
1713
to_transaction=self.target.get_transaction())
1714
pb.update('copying inventory', 0, 1)
1715
self.target.control_weaves.copy_multi(
1716
self.source.control_weaves, ['inventory'],
1717
from_transaction=self.source.get_transaction(),
1718
to_transaction=self.target.get_transaction())
1719
self.target._revision_store.text_store.copy_all_ids(
1720
self.source._revision_store.text_store,
1725
self.target.fetch(self.source, revision_id=revision_id)
1728
def fetch(self, revision_id=None, pb=None):
1729
"""See InterRepository.fetch()."""
1730
from bzrlib.fetch import GenericRepoFetcher
1731
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1732
self.source, self.source._format, self.target, self.target._format)
1733
# TODO: jam 20070210 This should be an assert, not a translate
1734
revision_id = osutils.safe_revision_id(revision_id)
1735
f = GenericRepoFetcher(to_repository=self.target,
1736
from_repository=self.source,
1737
last_revision=revision_id,
1739
return f.count_copied, f.failed_revisions
1742
def missing_revision_ids(self, revision_id=None):
1743
"""See InterRepository.missing_revision_ids()."""
1744
# we want all revisions to satisfy revision_id in source.
1745
# but we don't want to stat every file here and there.
1746
# we want then, all revisions other needs to satisfy revision_id
1747
# checked, but not those that we have locally.
1748
# so the first thing is to get a subset of the revisions to
1749
# satisfy revision_id in source, and then eliminate those that
1750
# we do already have.
1751
# this is slow on high latency connection to self, but as as this
1752
# disk format scales terribly for push anyway due to rewriting
1753
# inventory.weave, this is considered acceptable.
1755
if revision_id is not None:
1756
source_ids = self.source.get_ancestry(revision_id)
1757
assert source_ids[0] is None
1760
source_ids = self.source._all_possible_ids()
1761
source_ids_set = set(source_ids)
1762
# source_ids is the worst possible case we may need to pull.
1763
# now we want to filter source_ids against what we actually
1764
# have in target, but don't try to check for existence where we know
1765
# we do not have a revision as that would be pointless.
1766
target_ids = set(self.target._all_possible_ids())
1767
possibly_present_revisions = target_ids.intersection(source_ids_set)
1768
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1769
required_revisions = source_ids_set.difference(actually_present_revisions)
1770
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1771
if revision_id is not None:
1772
# we used get_ancestry to determine source_ids then we are assured all
1773
# revisions referenced are present as they are installed in topological order.
1774
# and the tip revision was validated by get_ancestry.
1775
return required_topo_revisions
1777
# if we just grabbed the possibly available ids, then
1778
# we only have an estimate of whats available and need to validate
1779
# that against the revision records.
1780
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1783
class InterKnitRepo(InterSameDataRepository):
1784
"""Optimised code paths between Knit based repositories."""
1787
def _get_repo_format_to_test(self):
1788
from bzrlib.repofmt import knitrepo
1789
return knitrepo.RepositoryFormatKnit1()
1792
def is_compatible(source, target):
1793
"""Be compatible with known Knit formats.
1795
We don't test for the stores being of specific types because that
1796
could lead to confusing results, and there is no need to be
1799
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
1801
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1802
isinstance(target._format, (RepositoryFormatKnit1)))
1803
except AttributeError:
1807
def fetch(self, revision_id=None, pb=None):
1808
"""See InterRepository.fetch()."""
1809
from bzrlib.fetch import KnitRepoFetcher
1810
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1811
self.source, self.source._format, self.target, self.target._format)
1812
# TODO: jam 20070210 This should be an assert, not a translate
1813
revision_id = osutils.safe_revision_id(revision_id)
1814
f = KnitRepoFetcher(to_repository=self.target,
1815
from_repository=self.source,
1816
last_revision=revision_id,
1818
return f.count_copied, f.failed_revisions
1821
def missing_revision_ids(self, revision_id=None):
1822
"""See InterRepository.missing_revision_ids()."""
1823
if revision_id is not None:
1824
source_ids = self.source.get_ancestry(revision_id)
1825
assert source_ids[0] is None
1828
source_ids = self.source._all_possible_ids()
1829
source_ids_set = set(source_ids)
1830
# source_ids is the worst possible case we may need to pull.
1831
# now we want to filter source_ids against what we actually
1832
# have in target, but don't try to check for existence where we know
1833
# we do not have a revision as that would be pointless.
1834
target_ids = set(self.target._all_possible_ids())
1835
possibly_present_revisions = target_ids.intersection(source_ids_set)
1836
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1837
required_revisions = source_ids_set.difference(actually_present_revisions)
1838
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1839
if revision_id is not None:
1840
# we used get_ancestry to determine source_ids then we are assured all
1841
# revisions referenced are present as they are installed in topological order.
1842
# and the tip revision was validated by get_ancestry.
1843
return required_topo_revisions
1845
# if we just grabbed the possibly available ids, then
1846
# we only have an estimate of whats available and need to validate
1847
# that against the revision records.
1848
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1851
class InterModel1and2(InterRepository):
1854
def _get_repo_format_to_test(self):
1858
def is_compatible(source, target):
1859
if not source.supports_rich_root() and target.supports_rich_root():
1865
def fetch(self, revision_id=None, pb=None):
1866
"""See InterRepository.fetch()."""
1867
from bzrlib.fetch import Model1toKnit2Fetcher
1868
# TODO: jam 20070210 This should be an assert, not a translate
1869
revision_id = osutils.safe_revision_id(revision_id)
1870
f = Model1toKnit2Fetcher(to_repository=self.target,
1871
from_repository=self.source,
1872
last_revision=revision_id,
1874
return f.count_copied, f.failed_revisions
1877
def copy_content(self, revision_id=None):
1878
"""Make a complete copy of the content in self into destination.
1880
This is a destructive operation! Do not use it on existing
1883
:param revision_id: Only copy the content needed to construct
1884
revision_id and its parents.
1887
self.target.set_make_working_trees(self.source.make_working_trees())
1888
except NotImplementedError:
1890
# TODO: jam 20070210 Internal, assert, don't translate
1891
revision_id = osutils.safe_revision_id(revision_id)
1892
# but don't bother fetching if we have the needed data now.
1893
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1894
self.target.has_revision(revision_id)):
1896
self.target.fetch(self.source, revision_id=revision_id)
1899
class InterKnit1and2(InterKnitRepo):
1902
def _get_repo_format_to_test(self):
1906
def is_compatible(source, target):
1907
"""Be compatible with Knit1 source and Knit3 target"""
1908
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
1910
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1911
RepositoryFormatKnit3
1912
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1913
isinstance(target._format, (RepositoryFormatKnit3)))
1914
except AttributeError:
1918
def fetch(self, revision_id=None, pb=None):
1919
"""See InterRepository.fetch()."""
1920
from bzrlib.fetch import Knit1to2Fetcher
1921
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1922
self.source, self.source._format, self.target,
1923
self.target._format)
1924
# TODO: jam 20070210 This should be an assert, not a translate
1925
revision_id = osutils.safe_revision_id(revision_id)
1926
f = Knit1to2Fetcher(to_repository=self.target,
1927
from_repository=self.source,
1928
last_revision=revision_id,
1930
return f.count_copied, f.failed_revisions
1933
class InterRemoteRepository(InterRepository):
1934
"""Code for converting between RemoteRepository objects.
1936
This just gets an non-remote repository from the RemoteRepository, and calls
1937
InterRepository.get again.
1940
def __init__(self, source, target):
1941
if isinstance(source, remote.RemoteRepository):
1942
source._ensure_real()
1943
real_source = source._real_repository
1945
real_source = source
1946
if isinstance(target, remote.RemoteRepository):
1947
target._ensure_real()
1948
real_target = target._real_repository
1950
real_target = target
1951
self.real_inter = InterRepository.get(real_source, real_target)
1954
def is_compatible(source, target):
1955
if isinstance(source, remote.RemoteRepository):
1957
if isinstance(target, remote.RemoteRepository):
1961
def copy_content(self, revision_id=None):
1962
self.real_inter.copy_content(revision_id=revision_id)
1964
def fetch(self, revision_id=None, pb=None):
1965
self.real_inter.fetch(revision_id=revision_id, pb=pb)
1968
def _get_repo_format_to_test(self):
1972
InterRepository.register_optimiser(InterSameDataRepository)
1973
InterRepository.register_optimiser(InterWeaveRepo)
1974
InterRepository.register_optimiser(InterKnitRepo)
1975
InterRepository.register_optimiser(InterModel1and2)
1976
InterRepository.register_optimiser(InterKnit1and2)
1977
InterRepository.register_optimiser(InterRemoteRepository)
1712
1980
class CopyConverter(object):
1713
1981
"""A repository conversion tool which just performs a copy of the content.
1715
1983
This is slow but quite reliable.
1721
1989
:param target_format: The format the resulting repository should be.
1723
1991
self.target_format = target_format
1725
1993
def convert(self, repo, pb):
1726
1994
"""Perform the conversion of to_convert, giving feedback via pb.
1728
1996
:param to_convert: The disk object to convert.
1729
1997
:param pb: a progress bar to use for progress information.
1731
pb = ui.ui_factory.nested_progress_bar()
1734
2002
# this is only useful with metadir layouts - separated repo content.
1735
2003
# trigger an assertion if not such
1736
2004
repo._format.get_format_string()
1737
2005
self.repo_dir = repo.bzrdir
1738
pb.update(gettext('Moving repository to repository.backup'))
2006
self.step('Moving repository to repository.backup')
1739
2007
self.repo_dir.transport.move('repository', 'repository.backup')
1740
2008
backup_transport = self.repo_dir.transport.clone('repository.backup')
1741
2009
repo._format.check_conversion_target(self.target_format)
1742
2010
self.source_repo = repo._format.open(self.repo_dir,
1744
2012
_override_transport=backup_transport)
1745
pb.update(gettext('Creating new repository'))
2013
self.step('Creating new repository')
1746
2014
converted = self.target_format.initialize(self.repo_dir,
1747
2015
self.source_repo.is_shared())
1748
2016
converted.lock_write()
1750
pb.update(gettext('Copying content'))
2018
self.step('Copying content into repository.')
1751
2019
self.source_repo.copy_content_into(converted)
1753
2021
converted.unlock()
1754
pb.update(gettext('Deleting old repository content'))
2022
self.step('Deleting old repository content.')
1755
2023
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,))
2024
self.pb.note('repository converted')
2026
def step(self, message):
2027
"""Update the pb by a step."""
2029
self.pb.update(message, self.count, self.total)
2032
class CommitBuilder(object):
2033
"""Provides an interface to build up a commit.
2035
This allows describing a tree to be committed without needing to
2036
know the internals of the format of the repository.
2039
record_root_entry = False
2040
def __init__(self, repository, parents, config, timestamp=None,
2041
timezone=None, committer=None, revprops=None,
2043
"""Initiate a CommitBuilder.
2045
:param repository: Repository to commit to.
2046
:param parents: Revision ids of the parents of the new revision.
2047
:param config: Configuration to use.
2048
:param timestamp: Optional timestamp recorded for commit.
2049
:param timezone: Optional timezone for timestamp.
2050
:param committer: Optional committer to set for commit.
2051
:param revprops: Optional dictionary of revision properties.
2052
:param revision_id: Optional revision id.
2054
self._config = config
2056
if committer is None:
2057
self._committer = self._config.username()
2059
assert isinstance(committer, basestring), type(committer)
2060
self._committer = committer
2062
self.new_inventory = Inventory(None)
2063
self._new_revision_id = osutils.safe_revision_id(revision_id)
2064
self.parents = parents
2065
self.repository = repository
2068
if revprops is not None:
2069
self._revprops.update(revprops)
2071
if timestamp is None:
2072
timestamp = time.time()
2073
# Restrict resolution to 1ms
2074
self._timestamp = round(timestamp, 3)
2076
if timezone is None:
2077
self._timezone = osutils.local_time_offset()
2079
self._timezone = int(timezone)
2081
self._generate_revision_if_needed()
2083
def commit(self, message):
2084
"""Make the actual commit.
2086
:return: The revision id of the recorded revision.
2088
rev = _mod_revision.Revision(
2089
timestamp=self._timestamp,
2090
timezone=self._timezone,
2091
committer=self._committer,
2093
inventory_sha1=self.inv_sha1,
2094
revision_id=self._new_revision_id,
2095
properties=self._revprops)
2096
rev.parent_ids = self.parents
2097
self.repository.add_revision(self._new_revision_id, rev,
2098
self.new_inventory, self._config)
2099
self.repository.commit_write_group()
2100
return self._new_revision_id
2102
def revision_tree(self):
2103
"""Return the tree that was just committed.
2105
After calling commit() this can be called to get a RevisionTree
2106
representing the newly committed tree. This is preferred to
2107
calling Repository.revision_tree() because that may require
2108
deserializing the inventory, while we already have a copy in
2111
return RevisionTree(self.repository, self.new_inventory,
2112
self._new_revision_id)
2114
def finish_inventory(self):
2115
"""Tell the builder that the inventory is finished."""
2116
if self.new_inventory.root is None:
2117
symbol_versioning.warn('Root entry should be supplied to'
2118
' record_entry_contents, as of bzr 0.10.',
2119
DeprecationWarning, stacklevel=2)
2120
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2121
self.new_inventory.revision_id = self._new_revision_id
2122
self.inv_sha1 = self.repository.add_inventory(
2123
self._new_revision_id,
2128
def _gen_revision_id(self):
2129
"""Return new revision-id."""
2130
return generate_ids.gen_revision_id(self._config.username(),
2133
def _generate_revision_if_needed(self):
2134
"""Create a revision id if None was supplied.
2136
If the repository can not support user-specified revision ids
2137
they should override this function and raise CannotSetRevisionId
2138
if _new_revision_id is not None.
2140
:raises: CannotSetRevisionId
2142
if self._new_revision_id is None:
2143
self._new_revision_id = self._gen_revision_id()
2145
def record_entry_contents(self, ie, parent_invs, path, tree):
2146
"""Record the content of ie from tree into the commit if needed.
2148
Side effect: sets ie.revision when unchanged
2150
:param ie: An inventory entry present in the commit.
2151
:param parent_invs: The inventories of the parent revisions of the
2153
:param path: The path the entry is at in the tree.
2154
:param tree: The tree which contains this entry and should be used to
2157
if self.new_inventory.root is None and ie.parent_id is not None:
2158
symbol_versioning.warn('Root entry should be supplied to'
2159
' record_entry_contents, as of bzr 0.10.',
2160
DeprecationWarning, stacklevel=2)
2161
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2163
self.new_inventory.add(ie)
2165
# ie.revision is always None if the InventoryEntry is considered
2166
# for committing. ie.snapshot will record the correct revision
2167
# which may be the sole parent if it is untouched.
2168
if ie.revision is not None:
2171
# In this revision format, root entries have no knit or weave
2172
if ie is self.new_inventory.root:
2173
# When serializing out to disk and back in
2174
# root.revision is always _new_revision_id
2175
ie.revision = self._new_revision_id
2177
previous_entries = ie.find_previous_heads(
2179
self.repository.weave_store,
2180
self.repository.get_transaction())
2181
# we are creating a new revision for ie in the history store
2183
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2185
def modified_directory(self, file_id, file_parents):
2186
"""Record the presence of a symbolic link.
2188
:param file_id: The file_id of the link to record.
2189
:param file_parents: The per-file parent revision ids.
2191
self._add_text_to_weave(file_id, [], file_parents.keys())
2193
def modified_reference(self, file_id, file_parents):
2194
"""Record the modification of a reference.
2196
:param file_id: The file_id of the link to record.
2197
:param file_parents: The per-file parent revision ids.
2199
self._add_text_to_weave(file_id, [], file_parents.keys())
2201
def modified_file_text(self, file_id, file_parents,
2202
get_content_byte_lines, text_sha1=None,
2204
"""Record the text of file file_id
2206
:param file_id: The file_id of the file to record the text of.
2207
:param file_parents: The per-file parent revision ids.
2208
:param get_content_byte_lines: A callable which will return the byte
2210
:param text_sha1: Optional SHA1 of the file contents.
2211
:param text_size: Optional size of the file contents.
2213
# mutter('storing text of file {%s} in revision {%s} into %r',
2214
# file_id, self._new_revision_id, self.repository.weave_store)
2215
# special case to avoid diffing on renames or
2217
if (len(file_parents) == 1
2218
and text_sha1 == file_parents.values()[0].text_sha1
2219
and text_size == file_parents.values()[0].text_size):
2220
previous_ie = file_parents.values()[0]
2221
versionedfile = self.repository.weave_store.get_weave(file_id,
2222
self.repository.get_transaction())
2223
versionedfile.clone_text(self._new_revision_id,
2224
previous_ie.revision, file_parents.keys())
2225
return text_sha1, text_size
2227
new_lines = get_content_byte_lines()
2228
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2229
# should return the SHA1 and size
2230
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2231
return osutils.sha_strings(new_lines), \
2232
sum(map(len, new_lines))
2234
def modified_link(self, file_id, file_parents, link_target):
2235
"""Record the presence of a symbolic link.
2237
:param file_id: The file_id of the link to record.
2238
:param file_parents: The per-file parent revision ids.
2239
:param link_target: Target location of this link.
2241
self._add_text_to_weave(file_id, [], file_parents.keys())
2243
def _add_text_to_weave(self, file_id, new_lines, parents):
2244
versionedfile = self.repository.weave_store.get_weave_or_empty(
2245
file_id, self.repository.get_transaction())
2246
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2247
versionedfile.clear_cache()
2250
class _CommitBuilder(CommitBuilder):
2251
"""Temporary class so old CommitBuilders are detected properly
2253
Note: CommitBuilder works whether or not root entry is recorded.
2256
record_root_entry = True
2259
class RootCommitBuilder(CommitBuilder):
2260
"""This commitbuilder actually records the root id"""
2262
record_root_entry = True
2264
def record_entry_contents(self, ie, parent_invs, path, tree):
2265
"""Record the content of ie from tree into the commit if needed.
2267
Side effect: sets ie.revision when unchanged
2269
:param ie: An inventory entry present in the commit.
2270
:param parent_invs: The inventories of the parent revisions of the
2272
:param path: The path the entry is at in the tree.
2273
:param tree: The tree which contains this entry and should be used to
2276
assert self.new_inventory.root is not None or ie.parent_id is None
2277
self.new_inventory.add(ie)
2279
# ie.revision is always None if the InventoryEntry is considered
2280
# for committing. ie.snapshot will record the correct revision
2281
# which may be the sole parent if it is untouched.
2282
if ie.revision is not None:
2285
previous_entries = ie.find_previous_heads(
2287
self.repository.weave_store,
2288
self.repository.get_transaction())
2289
# we are creating a new revision for ie in the history store
2291
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2303
def _unescaper(match, _map=_unescape_map):
2304
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__,
2308
if not code.startswith('#'):
2310
return unichr(int(code[1:])).encode('utf8')
2316
def _unescape_xml(data):
2317
"""Unescape predefined XML entities in a string of data."""
2319
if _unescape_re is None:
2320
_unescape_re = re.compile('\&([^;]*);')
2321
return _unescape_re.sub(_unescaper, data)