1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from cStringIO import StringIO
19
from bzrlib.lazy_import import lazy_import
20
lazy_import(globals(), """
39
revision as _mod_revision,
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
51
from bzrlib.decorators import needs_read_lock, needs_write_lock
52
from bzrlib.inter import InterObject
53
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
54
from bzrlib.symbol_versioning import (
58
from bzrlib.trace import mutter, note, warning
61
# Old formats display a warning, but only once
62
_deprecation_warning_done = False
65
######################################################################
68
class Repository(object):
69
"""Repository holding history for one or more branches.
71
The repository holds and retrieves historical information including
72
revisions and file history. It's normally accessed only by the Branch,
73
which views a particular line of development through that history.
75
The Repository builds on top of Stores and a Transport, which respectively
76
describe the disk data format and the way of accessing the (possibly
80
_file_ids_altered_regex = lazy_regex.lazy_compile(
81
r'file_id="(?P<file_id>[^"]+)"'
82
r'.*revision="(?P<revision_id>[^"]+)"'
86
def add_inventory(self, revision_id, inv, parents):
87
"""Add the inventory inv to the repository as revision_id.
89
:param parents: The revision ids of the parents that revision_id
90
is known to have and are in the repository already.
92
returns the sha1 of the serialized inventory.
94
revision_id = osutils.safe_revision_id(revision_id)
95
_mod_revision.check_not_reserved_id(revision_id)
96
assert inv.revision_id is None or inv.revision_id == revision_id, \
97
"Mismatch between inventory revision" \
98
" id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
99
assert inv.root is not None
100
inv_text = self.serialise_inventory(inv)
101
inv_sha1 = osutils.sha_string(inv_text)
102
inv_vf = self.control_weaves.get_weave('inventory',
103
self.get_transaction())
104
self._inventory_add_lines(inv_vf, revision_id, parents,
105
osutils.split_lines(inv_text))
108
def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
110
for parent in parents:
112
final_parents.append(parent)
114
inv_vf.add_lines(revision_id, final_parents, lines)
117
def add_revision(self, revision_id, rev, inv=None, config=None):
118
"""Add rev to the revision store as revision_id.
120
:param revision_id: the revision id to use.
121
:param rev: The revision object.
122
:param inv: The inventory for the revision. if None, it will be looked
123
up in the inventory storer
124
:param config: If None no digital signature will be created.
125
If supplied its signature_needed method will be used
126
to determine if a signature should be made.
128
revision_id = osutils.safe_revision_id(revision_id)
129
# TODO: jam 20070210 Shouldn't we check rev.revision_id and
131
_mod_revision.check_not_reserved_id(revision_id)
132
if config is not None and config.signature_needed():
134
inv = self.get_inventory(revision_id)
135
plaintext = Testament(rev, inv).as_short_text()
136
self.store_revision_signature(
137
gpg.GPGStrategy(config), plaintext, revision_id)
138
if not revision_id in self.get_inventory_weave():
140
raise errors.WeaveRevisionNotPresent(revision_id,
141
self.get_inventory_weave())
143
# yes, this is not suitable for adding with ghosts.
144
self.add_inventory(revision_id, inv, rev.parent_ids)
145
self._revision_store.add_revision(rev, self.get_transaction())
148
def _all_possible_ids(self):
149
"""Return all the possible revisions that we could find."""
150
return self.get_inventory_weave().versions()
152
def all_revision_ids(self):
153
"""Returns a list of all the revision ids in the repository.
155
This is deprecated because code should generally work on the graph
156
reachable from a particular revision, and ignore any other revisions
157
that might be present. There is no direct replacement method.
159
return self._all_revision_ids()
162
def _all_revision_ids(self):
163
"""Returns a list of all the revision ids in the repository.
165
These are in as much topological order as the underlying store can
166
present: for weaves ghosts may lead to a lack of correctness until
167
the reweave updates the parents list.
169
if self._revision_store.text_store.listable():
170
return self._revision_store.all_revision_ids(self.get_transaction())
171
result = self._all_possible_ids()
172
# TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
173
# ids. (It should, since _revision_store's API should change to
174
# return utf8 revision_ids)
175
return self._eliminate_revisions_not_present(result)
177
def break_lock(self):
178
"""Break a lock if one is present from another instance.
180
Uses the ui factory to ask for confirmation if the lock may be from
183
self.control_files.break_lock()
186
def _eliminate_revisions_not_present(self, revision_ids):
187
"""Check every revision id in revision_ids to see if we have it.
189
Returns a set of the present revisions.
192
for id in revision_ids:
193
if self.has_revision(id):
198
def create(a_bzrdir):
199
"""Construct the current default format repository in a_bzrdir."""
200
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
202
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
203
"""instantiate a Repository.
205
:param _format: The format of the repository on disk.
206
:param a_bzrdir: The BzrDir of the repository.
208
In the future we will have a single api for all stores for
209
getting file texts, inventories and revisions, then
210
this construct will accept instances of those things.
212
super(Repository, self).__init__()
213
self._format = _format
214
# the following are part of the public API for Repository:
215
self.bzrdir = a_bzrdir
216
self.control_files = control_files
217
self._revision_store = _revision_store
218
self.text_store = text_store
219
# backwards compatibility
220
self.weave_store = text_store
221
# not right yet - should be more semantically clear ?
223
self.control_store = control_store
224
self.control_weaves = control_store
225
# TODO: make sure to construct the right store classes, etc, depending
226
# on whether escaping is required.
227
self._warn_if_deprecated()
230
return '%s(%r)' % (self.__class__.__name__,
231
self.bzrdir.transport.base)
234
return self.control_files.is_locked()
236
def lock_write(self, token=None):
237
"""Lock this repository for writing.
239
:param token: if this is already locked, then lock_write will fail
240
unless the token matches the existing lock.
241
:returns: a token if this instance supports tokens, otherwise None.
242
:raises TokenLockingNotSupported: when a token is given but this
243
instance doesn't support using token locks.
244
:raises MismatchedToken: if the specified token doesn't match the token
245
of the existing lock.
247
A token should be passed in if you know that you have locked the object
248
some other way, and need to synchronise this object's state with that
251
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
253
return self.control_files.lock_write(token=token)
256
self.control_files.lock_read()
258
def get_physical_lock_status(self):
259
return self.control_files.get_physical_lock_status()
261
def leave_lock_in_place(self):
262
"""Tell this repository not to release the physical lock when this
265
If lock_write doesn't return a token, then this method is not supported.
267
self.control_files.leave_in_place()
269
def dont_leave_lock_in_place(self):
270
"""Tell this repository to release the physical lock when this
271
object is unlocked, even if it didn't originally acquire it.
273
If lock_write doesn't return a token, then this method is not supported.
275
self.control_files.dont_leave_in_place()
278
def gather_stats(self, revid=None, committers=None):
279
"""Gather statistics from a revision id.
281
:param revid: The revision id to gather statistics from, if None, then
282
no revision specific statistics are gathered.
283
:param committers: Optional parameter controlling whether to grab
284
a count of committers from the revision specific statistics.
285
:return: A dictionary of statistics. Currently this contains:
286
committers: The number of committers if requested.
287
firstrev: A tuple with timestamp, timezone for the penultimate left
288
most ancestor of revid, if revid is not the NULL_REVISION.
289
latestrev: A tuple with timestamp, timezone for revid, if revid is
290
not the NULL_REVISION.
291
revisions: The total revision count in the repository.
292
size: An estimate disk size of the repository in bytes.
295
if revid and committers:
296
result['committers'] = 0
297
if revid and revid != _mod_revision.NULL_REVISION:
299
all_committers = set()
300
revisions = self.get_ancestry(revid)
301
# pop the leading None
303
first_revision = None
305
# ignore the revisions in the middle - just grab first and last
306
revisions = revisions[0], revisions[-1]
307
for revision in self.get_revisions(revisions):
308
if not first_revision:
309
first_revision = revision
311
all_committers.add(revision.committer)
312
last_revision = revision
314
result['committers'] = len(all_committers)
315
result['firstrev'] = (first_revision.timestamp,
316
first_revision.timezone)
317
result['latestrev'] = (last_revision.timestamp,
318
last_revision.timezone)
320
# now gather global repository information
321
if self.bzrdir.root_transport.listable():
322
c, t = self._revision_store.total_size(self.get_transaction())
323
result['revisions'] = c
328
def missing_revision_ids(self, other, revision_id=None):
329
"""Return the revision ids that other has that this does not.
331
These are returned in topological order.
333
revision_id: only return revision ids included by revision_id.
335
revision_id = osutils.safe_revision_id(revision_id)
336
return InterRepository.get(other, self).missing_revision_ids(revision_id)
340
"""Open the repository rooted at base.
342
For instance, if the repository is at URL/.bzr/repository,
343
Repository.open(URL) -> a Repository instance.
345
control = bzrdir.BzrDir.open(base)
346
return control.open_repository()
348
def copy_content_into(self, destination, revision_id=None):
349
"""Make a complete copy of the content in self into destination.
351
This is a destructive operation! Do not use it on existing
354
revision_id = osutils.safe_revision_id(revision_id)
355
return InterRepository.get(self, destination).copy_content(revision_id)
357
def fetch(self, source, revision_id=None, pb=None):
358
"""Fetch the content required to construct revision_id from source.
360
If revision_id is None all content is copied.
362
revision_id = osutils.safe_revision_id(revision_id)
363
inter = InterRepository.get(source, self)
365
return inter.fetch(revision_id=revision_id, pb=pb)
366
except NotImplementedError:
367
raise errors.IncompatibleRepositories(source, self)
369
def get_commit_builder(self, branch, parents, config, timestamp=None,
370
timezone=None, committer=None, revprops=None,
372
"""Obtain a CommitBuilder for this repository.
374
:param branch: Branch to commit to.
375
:param parents: Revision ids of the parents of the new revision.
376
:param config: Configuration to use.
377
:param timestamp: Optional timestamp recorded for commit.
378
:param timezone: Optional timezone for timestamp.
379
:param committer: Optional committer to set for commit.
380
:param revprops: Optional dictionary of revision properties.
381
:param revision_id: Optional revision id.
383
revision_id = osutils.safe_revision_id(revision_id)
384
return _CommitBuilder(self, parents, config, timestamp, timezone,
385
committer, revprops, revision_id)
388
self.control_files.unlock()
391
def clone(self, a_bzrdir, revision_id=None):
392
"""Clone this repository into a_bzrdir using the current format.
394
Currently no check is made that the format of this repository and
395
the bzrdir format are compatible. FIXME RBC 20060201.
397
:return: The newly created destination repository.
399
# TODO: deprecate after 0.16; cloning this with all its settings is
400
# probably not very useful -- mbp 20070423
401
dest_repo = self._create_sprouting_repo(a_bzrdir, shared=self.is_shared())
402
self.copy_content_into(dest_repo, revision_id)
406
def sprout(self, to_bzrdir, revision_id=None):
407
"""Create a descendent repository for new development.
409
Unlike clone, this does not copy the settings of the repository.
411
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
412
dest_repo.fetch(self, revision_id=revision_id)
415
def _create_sprouting_repo(self, a_bzrdir, shared):
416
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
417
# use target default format.
418
dest_repo = a_bzrdir.create_repository()
420
# Most control formats need the repository to be specifically
421
# created, but on some old all-in-one formats it's not needed
423
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
424
except errors.UninitializableFormat:
425
dest_repo = a_bzrdir.open_repository()
429
def has_revision(self, revision_id):
430
"""True if this repository has a copy of the revision."""
431
revision_id = osutils.safe_revision_id(revision_id)
432
return self._revision_store.has_revision_id(revision_id,
433
self.get_transaction())
436
def get_revision_reconcile(self, revision_id):
437
"""'reconcile' helper routine that allows access to a revision always.
439
This variant of get_revision does not cross check the weave graph
440
against the revision one as get_revision does: but it should only
441
be used by reconcile, or reconcile-alike commands that are correcting
442
or testing the revision graph.
444
if not revision_id or not isinstance(revision_id, basestring):
445
raise errors.InvalidRevisionId(revision_id=revision_id,
447
return self.get_revisions([revision_id])[0]
450
def get_revisions(self, revision_ids):
451
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
452
revs = self._revision_store.get_revisions(revision_ids,
453
self.get_transaction())
455
assert not isinstance(rev.revision_id, unicode)
456
for parent_id in rev.parent_ids:
457
assert not isinstance(parent_id, unicode)
461
def get_revision_xml(self, revision_id):
462
# TODO: jam 20070210 This shouldn't be necessary since get_revision
463
# would have already do it.
464
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
465
revision_id = osutils.safe_revision_id(revision_id)
466
rev = self.get_revision(revision_id)
468
# the current serializer..
469
self._revision_store._serializer.write_revision(rev, rev_tmp)
471
return rev_tmp.getvalue()
474
def get_revision(self, revision_id):
475
"""Return the Revision object for a named revision"""
476
# TODO: jam 20070210 get_revision_reconcile should do this for us
477
revision_id = osutils.safe_revision_id(revision_id)
478
r = self.get_revision_reconcile(revision_id)
479
# weave corruption can lead to absent revision markers that should be
481
# the following test is reasonably cheap (it needs a single weave read)
482
# and the weave is cached in read transactions. In write transactions
483
# it is not cached but typically we only read a small number of
484
# revisions. For knits when they are introduced we will probably want
485
# to ensure that caching write transactions are in use.
486
inv = self.get_inventory_weave()
487
self._check_revision_parents(r, inv)
491
def get_deltas_for_revisions(self, revisions):
492
"""Produce a generator of revision deltas.
494
Note that the input is a sequence of REVISIONS, not revision_ids.
495
Trees will be held in memory until the generator exits.
496
Each delta is relative to the revision's lefthand predecessor.
498
required_trees = set()
499
for revision in revisions:
500
required_trees.add(revision.revision_id)
501
required_trees.update(revision.parent_ids[:1])
502
trees = dict((t.get_revision_id(), t) for
503
t in self.revision_trees(required_trees))
504
for revision in revisions:
505
if not revision.parent_ids:
506
old_tree = self.revision_tree(None)
508
old_tree = trees[revision.parent_ids[0]]
509
yield trees[revision.revision_id].changes_from(old_tree)
512
def get_revision_delta(self, revision_id):
513
"""Return the delta for one revision.
515
The delta is relative to the left-hand predecessor of the
518
r = self.get_revision(revision_id)
519
return list(self.get_deltas_for_revisions([r]))[0]
521
def _check_revision_parents(self, revision, inventory):
522
"""Private to Repository and Fetch.
524
This checks the parentage of revision in an inventory weave for
525
consistency and is only applicable to inventory-weave-for-ancestry
526
using repository formats & fetchers.
528
weave_parents = inventory.get_parents(revision.revision_id)
529
weave_names = inventory.versions()
530
for parent_id in revision.parent_ids:
531
if parent_id in weave_names:
532
# this parent must not be a ghost.
533
if not parent_id in weave_parents:
535
raise errors.CorruptRepository(self)
538
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
539
revision_id = osutils.safe_revision_id(revision_id)
540
signature = gpg_strategy.sign(plaintext)
541
self._revision_store.add_revision_signature_text(revision_id,
543
self.get_transaction())
545
def fileids_altered_by_revision_ids(self, revision_ids):
546
"""Find the file ids and versions affected by revisions.
548
:param revisions: an iterable containing revision ids.
549
:return: a dictionary mapping altered file-ids to an iterable of
550
revision_ids. Each altered file-ids has the exact revision_ids that
551
altered it listed explicitly.
553
assert self._serializer.support_altered_by_hack, \
554
("fileids_altered_by_revision_ids only supported for branches "
555
"which store inventory as unnested xml, not on %r" % self)
556
selected_revision_ids = set(osutils.safe_revision_id(r)
557
for r in revision_ids)
558
w = self.get_inventory_weave()
561
# this code needs to read every new line in every inventory for the
562
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
563
# not present in one of those inventories is unnecessary but not
564
# harmful because we are filtering by the revision id marker in the
565
# inventory lines : we only select file ids altered in one of those
566
# revisions. We don't need to see all lines in the inventory because
567
# only those added in an inventory in rev X can contain a revision=X
569
unescape_revid_cache = {}
570
unescape_fileid_cache = {}
572
# jam 20061218 In a big fetch, this handles hundreds of thousands
573
# of lines, so it has had a lot of inlining and optimizing done.
574
# Sorry that it is a little bit messy.
575
# Move several functions to be local variables, since this is a long
577
search = self._file_ids_altered_regex.search
578
unescape = _unescape_xml
579
setdefault = result.setdefault
580
pb = ui.ui_factory.nested_progress_bar()
582
for line in w.iter_lines_added_or_present_in_versions(
583
selected_revision_ids, pb=pb):
587
# One call to match.group() returning multiple items is quite a
588
# bit faster than 2 calls to match.group() each returning 1
589
file_id, revision_id = match.group('file_id', 'revision_id')
591
# Inlining the cache lookups helps a lot when you make 170,000
592
# lines and 350k ids, versus 8.4 unique ids.
593
# Using a cache helps in 2 ways:
594
# 1) Avoids unnecessary decoding calls
595
# 2) Re-uses cached strings, which helps in future set and
597
# (2) is enough that removing encoding entirely along with
598
# the cache (so we are using plain strings) results in no
599
# performance improvement.
601
revision_id = unescape_revid_cache[revision_id]
603
unescaped = unescape(revision_id)
604
unescape_revid_cache[revision_id] = unescaped
605
revision_id = unescaped
607
if revision_id in selected_revision_ids:
609
file_id = unescape_fileid_cache[file_id]
611
unescaped = unescape(file_id)
612
unescape_fileid_cache[file_id] = unescaped
614
setdefault(file_id, set()).add(revision_id)
620
def get_inventory_weave(self):
621
return self.control_weaves.get_weave('inventory',
622
self.get_transaction())
625
def get_inventory(self, revision_id):
626
"""Get Inventory object by hash."""
627
# TODO: jam 20070210 Technically we don't need to sanitize, since all
628
# called functions must sanitize.
629
revision_id = osutils.safe_revision_id(revision_id)
630
return self.deserialise_inventory(
631
revision_id, self.get_inventory_xml(revision_id))
633
def deserialise_inventory(self, revision_id, xml):
634
"""Transform the xml into an inventory object.
636
:param revision_id: The expected revision id of the inventory.
637
:param xml: A serialised inventory.
639
revision_id = osutils.safe_revision_id(revision_id)
640
result = self._serializer.read_inventory_from_string(xml)
641
result.root.revision = revision_id
644
def serialise_inventory(self, inv):
645
return self._serializer.write_inventory_to_string(inv)
648
def get_inventory_xml(self, revision_id):
649
"""Get inventory XML as a file object."""
650
revision_id = osutils.safe_revision_id(revision_id)
652
assert isinstance(revision_id, str), type(revision_id)
653
iw = self.get_inventory_weave()
654
return iw.get_text(revision_id)
656
raise errors.HistoryMissing(self, 'inventory', revision_id)
659
def get_inventory_sha1(self, revision_id):
660
"""Return the sha1 hash of the inventory entry
662
# TODO: jam 20070210 Shouldn't this be deprecated / removed?
663
revision_id = osutils.safe_revision_id(revision_id)
664
return self.get_revision(revision_id).inventory_sha1
667
def get_revision_graph(self, revision_id=None):
668
"""Return a dictionary containing the revision graph.
670
:param revision_id: The revision_id to get a graph from. If None, then
671
the entire revision graph is returned. This is a deprecated mode of
672
operation and will be removed in the future.
673
:return: a dictionary of revision_id->revision_parents_list.
675
# special case NULL_REVISION
676
if revision_id == _mod_revision.NULL_REVISION:
678
revision_id = osutils.safe_revision_id(revision_id)
679
a_weave = self.get_inventory_weave()
680
all_revisions = self._eliminate_revisions_not_present(
682
entire_graph = dict([(node, a_weave.get_parents(node)) for
683
node in all_revisions])
684
if revision_id is None:
686
elif revision_id not in entire_graph:
687
raise errors.NoSuchRevision(self, revision_id)
689
# add what can be reached from revision_id
691
pending = set([revision_id])
692
while len(pending) > 0:
694
result[node] = entire_graph[node]
695
for revision_id in result[node]:
696
if revision_id not in result:
697
pending.add(revision_id)
701
def get_revision_graph_with_ghosts(self, revision_ids=None):
702
"""Return a graph of the revisions with ghosts marked as applicable.
704
:param revision_ids: an iterable of revisions to graph or None for all.
705
:return: a Graph object with the graph reachable from revision_ids.
707
result = deprecated_graph.Graph()
709
pending = set(self.all_revision_ids())
712
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
713
# special case NULL_REVISION
714
if _mod_revision.NULL_REVISION in pending:
715
pending.remove(_mod_revision.NULL_REVISION)
716
required = set(pending)
719
revision_id = pending.pop()
721
rev = self.get_revision(revision_id)
722
except errors.NoSuchRevision:
723
if revision_id in required:
726
result.add_ghost(revision_id)
728
for parent_id in rev.parent_ids:
729
# is this queued or done ?
730
if (parent_id not in pending and
731
parent_id not in done):
733
pending.add(parent_id)
734
result.add_node(revision_id, rev.parent_ids)
735
done.add(revision_id)
738
def _get_history_vf(self):
739
"""Get a versionedfile whose history graph reflects all revisions.
741
For weave repositories, this is the inventory weave.
743
return self.get_inventory_weave()
745
def iter_reverse_revision_history(self, revision_id):
746
"""Iterate backwards through revision ids in the lefthand history
748
:param revision_id: The revision id to start with. All its lefthand
749
ancestors will be traversed.
751
revision_id = osutils.safe_revision_id(revision_id)
752
if revision_id in (None, _mod_revision.NULL_REVISION):
754
next_id = revision_id
755
versionedfile = self._get_history_vf()
758
parents = versionedfile.get_parents(next_id)
759
if len(parents) == 0:
765
def get_revision_inventory(self, revision_id):
766
"""Return inventory of a past revision."""
767
# TODO: Unify this with get_inventory()
768
# bzr 0.0.6 and later imposes the constraint that the inventory_id
769
# must be the same as its revision, so this is trivial.
770
if revision_id is None:
771
# This does not make sense: if there is no revision,
772
# then it is the current tree inventory surely ?!
773
# and thus get_root_id() is something that looks at the last
774
# commit on the branch, and the get_root_id is an inventory check.
775
raise NotImplementedError
776
# return Inventory(self.get_root_id())
778
return self.get_inventory(revision_id)
782
"""Return True if this repository is flagged as a shared repository."""
783
raise NotImplementedError(self.is_shared)
786
def reconcile(self, other=None, thorough=False):
787
"""Reconcile this repository."""
788
from bzrlib.reconcile import RepoReconciler
789
reconciler = RepoReconciler(self, thorough=thorough)
790
reconciler.reconcile()
794
def revision_tree(self, revision_id):
795
"""Return Tree for a revision on this branch.
797
`revision_id` may be None for the empty tree revision.
799
# TODO: refactor this to use an existing revision object
800
# so we don't need to read it in twice.
801
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
802
return RevisionTree(self, Inventory(root_id=None),
803
_mod_revision.NULL_REVISION)
805
revision_id = osutils.safe_revision_id(revision_id)
806
inv = self.get_revision_inventory(revision_id)
807
return RevisionTree(self, inv, revision_id)
810
def revision_trees(self, revision_ids):
811
"""Return Tree for a revision on this branch.
813
`revision_id` may not be None or 'null:'"""
814
assert None not in revision_ids
815
assert _mod_revision.NULL_REVISION not in revision_ids
816
texts = self.get_inventory_weave().get_texts(revision_ids)
817
for text, revision_id in zip(texts, revision_ids):
818
inv = self.deserialise_inventory(revision_id, text)
819
yield RevisionTree(self, inv, revision_id)
822
def get_ancestry(self, revision_id):
823
"""Return a list of revision-ids integrated by a revision.
825
The first element of the list is always None, indicating the origin
826
revision. This might change when we have history horizons, or
827
perhaps we should have a new API.
829
This is topologically sorted.
831
if revision_id is None:
833
revision_id = osutils.safe_revision_id(revision_id)
834
if not self.has_revision(revision_id):
835
raise errors.NoSuchRevision(self, revision_id)
836
w = self.get_inventory_weave()
837
candidates = w.get_ancestry(revision_id)
838
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
841
def print_file(self, file, revision_id):
842
"""Print `file` to stdout.
844
FIXME RBC 20060125 as John Meinel points out this is a bad api
845
- it writes to stdout, it assumes that that is valid etc. Fix
846
by creating a new more flexible convenience function.
848
revision_id = osutils.safe_revision_id(revision_id)
849
tree = self.revision_tree(revision_id)
850
# use inventory as it was in that revision
851
file_id = tree.inventory.path2id(file)
853
# TODO: jam 20060427 Write a test for this code path
854
# it had a bug in it, and was raising the wrong
856
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
857
tree.print_file(file_id)
859
def get_transaction(self):
860
return self.control_files.get_transaction()
862
def revision_parents(self, revision_id):
863
revision_id = osutils.safe_revision_id(revision_id)
864
return self.get_inventory_weave().parent_names(revision_id)
866
def get_parents(self, revision_ids):
867
"""See StackedParentsProvider.get_parents"""
869
for revision_id in revision_ids:
870
if revision_id == _mod_revision.NULL_REVISION:
874
parents = self.get_revision(revision_id).parent_ids
875
except errors.NoSuchRevision:
878
if len(parents) == 0:
879
parents = [_mod_revision.NULL_REVISION]
880
parents_list.append(parents)
883
def _make_parents_provider(self):
886
def get_graph(self, other_repository=None):
887
"""Return the graph walker for this repository format"""
888
parents_provider = self._make_parents_provider()
889
if (other_repository is not None and
890
other_repository.bzrdir.transport.base !=
891
self.bzrdir.transport.base):
892
parents_provider = graph._StackedParentsProvider(
893
[parents_provider, other_repository._make_parents_provider()])
894
return graph.Graph(parents_provider)
897
def set_make_working_trees(self, new_value):
898
"""Set the policy flag for making working trees when creating branches.
900
This only applies to branches that use this repository.
902
The default is 'True'.
903
:param new_value: True to restore the default, False to disable making
906
raise NotImplementedError(self.set_make_working_trees)
908
def make_working_trees(self):
909
"""Returns the policy for making working trees on new branches."""
910
raise NotImplementedError(self.make_working_trees)
913
def sign_revision(self, revision_id, gpg_strategy):
914
revision_id = osutils.safe_revision_id(revision_id)
915
plaintext = Testament.from_revision(self, revision_id).as_short_text()
916
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
919
def has_signature_for_revision_id(self, revision_id):
920
"""Query for a revision signature for revision_id in the repository."""
921
revision_id = osutils.safe_revision_id(revision_id)
922
return self._revision_store.has_signature(revision_id,
923
self.get_transaction())
926
def get_signature_text(self, revision_id):
927
"""Return the text for a signature."""
928
revision_id = osutils.safe_revision_id(revision_id)
929
return self._revision_store.get_signature_text(revision_id,
930
self.get_transaction())
933
def check(self, revision_ids):
934
"""Check consistency of all history of given revision_ids.
936
Different repository implementations should override _check().
938
:param revision_ids: A non-empty list of revision_ids whose ancestry
939
will be checked. Typically the last revision_id of a branch.
942
raise ValueError("revision_ids must be non-empty in %s.check"
944
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
945
return self._check(revision_ids)
947
def _check(self, revision_ids):
948
result = check.Check(self)
952
def _warn_if_deprecated(self):
953
global _deprecation_warning_done
954
if _deprecation_warning_done:
956
_deprecation_warning_done = True
957
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
958
% (self._format, self.bzrdir.transport.base))
960
def supports_rich_root(self):
961
return self._format.rich_root_data
963
def _check_ascii_revisionid(self, revision_id, method):
964
"""Private helper for ascii-only repositories."""
965
# weave repositories refuse to store revisionids that are non-ascii.
966
if revision_id is not None:
967
# weaves require ascii revision ids.
968
if isinstance(revision_id, unicode):
970
revision_id.encode('ascii')
971
except UnicodeEncodeError:
972
raise errors.NonAsciiRevisionId(method, self)
975
revision_id.decode('ascii')
976
except UnicodeDecodeError:
977
raise errors.NonAsciiRevisionId(method, self)
981
# remove these delegates a while after bzr 0.15
982
def __make_delegated(name, from_module):
983
def _deprecated_repository_forwarder():
984
symbol_versioning.warn('%s moved to %s in bzr 0.15'
985
% (name, from_module),
988
m = __import__(from_module, globals(), locals(), [name])
990
return getattr(m, name)
991
except AttributeError:
992
raise AttributeError('module %s has no name %s'
994
globals()[name] = _deprecated_repository_forwarder
997
'AllInOneRepository',
998
'WeaveMetaDirRepository',
999
'PreSplitOutRepositoryFormat',
1000
'RepositoryFormat4',
1001
'RepositoryFormat5',
1002
'RepositoryFormat6',
1003
'RepositoryFormat7',
1005
__make_delegated(_name, 'bzrlib.repofmt.weaverepo')
1009
'RepositoryFormatKnit',
1010
'RepositoryFormatKnit1',
1012
__make_delegated(_name, 'bzrlib.repofmt.knitrepo')
1015
def install_revision(repository, rev, revision_tree):
1016
"""Install all revision data into a repository."""
1017
present_parents = []
1019
for p_id in rev.parent_ids:
1020
if repository.has_revision(p_id):
1021
present_parents.append(p_id)
1022
parent_trees[p_id] = repository.revision_tree(p_id)
1024
parent_trees[p_id] = repository.revision_tree(None)
1026
inv = revision_tree.inventory
1027
entries = inv.iter_entries()
1028
# backwards compatability hack: skip the root id.
1029
if not repository.supports_rich_root():
1030
path, root = entries.next()
1031
if root.revision != rev.revision_id:
1032
raise errors.IncompatibleRevision(repr(repository))
1033
# Add the texts that are not already present
1034
for path, ie in entries:
1035
w = repository.weave_store.get_weave_or_empty(ie.file_id,
1036
repository.get_transaction())
1037
if ie.revision not in w:
1039
# FIXME: TODO: The following loop *may* be overlapping/duplicate
1040
# with InventoryEntry.find_previous_heads(). if it is, then there
1041
# is a latent bug here where the parents may have ancestors of each
1043
for revision, tree in parent_trees.iteritems():
1044
if ie.file_id not in tree:
1046
parent_id = tree.inventory[ie.file_id].revision
1047
if parent_id in text_parents:
1049
text_parents.append(parent_id)
1051
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
1052
repository.get_transaction())
1053
lines = revision_tree.get_file(ie.file_id).readlines()
1054
vfile.add_lines(rev.revision_id, text_parents, lines)
1056
# install the inventory
1057
repository.add_inventory(rev.revision_id, inv, present_parents)
1058
except errors.RevisionAlreadyPresent:
1060
repository.add_revision(rev.revision_id, rev, inv)
1063
class MetaDirRepository(Repository):
1064
"""Repositories in the new meta-dir layout."""
1066
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
1067
super(MetaDirRepository, self).__init__(_format,
1073
dir_mode = self.control_files._dir_mode
1074
file_mode = self.control_files._file_mode
1077
def is_shared(self):
1078
"""Return True if this repository is flagged as a shared repository."""
1079
return self.control_files._transport.has('shared-storage')
1082
def set_make_working_trees(self, new_value):
1083
"""Set the policy flag for making working trees when creating branches.
1085
This only applies to branches that use this repository.
1087
The default is 'True'.
1088
:param new_value: True to restore the default, False to disable making
1093
self.control_files._transport.delete('no-working-trees')
1094
except errors.NoSuchFile:
1097
self.control_files.put_utf8('no-working-trees', '')
1099
def make_working_trees(self):
1100
"""Returns the policy for making working trees on new branches."""
1101
return not self.control_files._transport.has('no-working-trees')
1104
class RepositoryFormatRegistry(registry.Registry):
1105
"""Registry of RepositoryFormats.
1108
def get(self, format_string):
1109
r = registry.Registry.get(self, format_string)
1115
format_registry = RepositoryFormatRegistry()
1116
"""Registry of formats, indexed by their identifying format string.
1118
This can contain either format instances themselves, or classes/factories that
1119
can be called to obtain one.
1123
#####################################################################
1124
# Repository Formats
1126
class RepositoryFormat(object):
1127
"""A repository format.
1129
Formats provide three things:
1130
* An initialization routine to construct repository data on disk.
1131
* a format string which is used when the BzrDir supports versioned
1133
* an open routine which returns a Repository instance.
1135
Formats are placed in an dict by their format string for reference
1136
during opening. These should be subclasses of RepositoryFormat
1139
Once a format is deprecated, just deprecate the initialize and open
1140
methods on the format class. Do not deprecate the object, as the
1141
object will be created every system load.
1143
Common instance attributes:
1144
_matchingbzrdir - the bzrdir format that the repository format was
1145
originally written to work with. This can be used if manually
1146
constructing a bzrdir and repository, or more commonly for test suite
1151
return "<%s>" % self.__class__.__name__
1153
def __eq__(self, other):
1154
# format objects are generally stateless
1155
return isinstance(other, self.__class__)
1157
def __ne__(self, other):
1158
return not self == other
1161
def find_format(klass, a_bzrdir):
1162
"""Return the format for the repository object in a_bzrdir.
1164
This is used by bzr native formats that have a "format" file in
1165
the repository. Other methods may be used by different types of
1169
transport = a_bzrdir.get_repository_transport(None)
1170
format_string = transport.get("format").read()
1171
return format_registry.get(format_string)
1172
except errors.NoSuchFile:
1173
raise errors.NoRepositoryPresent(a_bzrdir)
1175
raise errors.UnknownFormatError(format=format_string)
1178
def register_format(klass, format):
1179
format_registry.register(format.get_format_string(), format)
1182
def unregister_format(klass, format):
1183
format_registry.remove(format.get_format_string())
1186
def get_default_format(klass):
1187
"""Return the current default format."""
1188
from bzrlib import bzrdir
1189
return bzrdir.format_registry.make_bzrdir('default').repository_format
1191
def _get_control_store(self, repo_transport, control_files):
1192
"""Return the control store for this repository."""
1193
raise NotImplementedError(self._get_control_store)
1195
def get_format_string(self):
1196
"""Return the ASCII format string that identifies this format.
1198
Note that in pre format ?? repositories the format string is
1199
not permitted nor written to disk.
1201
raise NotImplementedError(self.get_format_string)
1203
def get_format_description(self):
1204
"""Return the short description for this format."""
1205
raise NotImplementedError(self.get_format_description)
1207
def _get_revision_store(self, repo_transport, control_files):
1208
"""Return the revision store object for this a_bzrdir."""
1209
raise NotImplementedError(self._get_revision_store)
1211
def _get_text_rev_store(self,
1218
"""Common logic for getting a revision store for a repository.
1220
see self._get_revision_store for the subclass-overridable method to
1221
get the store for a repository.
1223
from bzrlib.store.revision.text import TextRevisionStore
1224
dir_mode = control_files._dir_mode
1225
file_mode = control_files._file_mode
1226
text_store = TextStore(transport.clone(name),
1228
compressed=compressed,
1230
file_mode=file_mode)
1231
_revision_store = TextRevisionStore(text_store, serializer)
1232
return _revision_store
1234
# TODO: this shouldn't be in the base class, it's specific to things that
1235
# use weaves or knits -- mbp 20070207
1236
def _get_versioned_file_store(self,
1241
versionedfile_class=None,
1242
versionedfile_kwargs={},
1244
if versionedfile_class is None:
1245
versionedfile_class = self._versionedfile_class
1246
weave_transport = control_files._transport.clone(name)
1247
dir_mode = control_files._dir_mode
1248
file_mode = control_files._file_mode
1249
return VersionedFileStore(weave_transport, prefixed=prefixed,
1251
file_mode=file_mode,
1252
versionedfile_class=versionedfile_class,
1253
versionedfile_kwargs=versionedfile_kwargs,
1256
def initialize(self, a_bzrdir, shared=False):
1257
"""Initialize a repository of this format in a_bzrdir.
1259
:param a_bzrdir: The bzrdir to put the new repository in it.
1260
:param shared: The repository should be initialized as a sharable one.
1261
:returns: The new repository object.
1263
This may raise UninitializableFormat if shared repository are not
1264
compatible the a_bzrdir.
1266
raise NotImplementedError(self.initialize)
1268
def is_supported(self):
1269
"""Is this format supported?
1271
Supported formats must be initializable and openable.
1272
Unsupported formats may not support initialization or committing or
1273
some other features depending on the reason for not being supported.
1277
def check_conversion_target(self, target_format):
1278
raise NotImplementedError(self.check_conversion_target)
1280
def open(self, a_bzrdir, _found=False):
1281
"""Return an instance of this format for the bzrdir a_bzrdir.
1283
_found is a private parameter, do not use it.
1285
raise NotImplementedError(self.open)
1288
class MetaDirRepositoryFormat(RepositoryFormat):
1289
"""Common base class for the new repositories using the metadir layout."""
1291
rich_root_data = False
1292
supports_tree_reference = False
1293
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1296
super(MetaDirRepositoryFormat, self).__init__()
1298
def _create_control_files(self, a_bzrdir):
1299
"""Create the required files and the initial control_files object."""
1300
# FIXME: RBC 20060125 don't peek under the covers
1301
# NB: no need to escape relative paths that are url safe.
1302
repository_transport = a_bzrdir.get_repository_transport(self)
1303
control_files = lockable_files.LockableFiles(repository_transport,
1304
'lock', lockdir.LockDir)
1305
control_files.create_lock()
1306
return control_files
1308
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1309
"""Upload the initial blank content."""
1310
control_files = self._create_control_files(a_bzrdir)
1311
control_files.lock_write()
1313
control_files._transport.mkdir_multi(dirs,
1314
mode=control_files._dir_mode)
1315
for file, content in files:
1316
control_files.put(file, content)
1317
for file, content in utf8_files:
1318
control_files.put_utf8(file, content)
1320
control_files.put_utf8('shared-storage', '')
1322
control_files.unlock()
1325
# formats which have no format string are not discoverable
1326
# and not independently creatable, so are not registered. They're
1327
# all in bzrlib.repofmt.weaverepo now. When an instance of one of these is
1328
# needed, it's constructed directly by the BzrDir. Non-native formats where
1329
# the repository is not separately opened are similar.
1331
format_registry.register_lazy(
1332
'Bazaar-NG Repository format 7',
1333
'bzrlib.repofmt.weaverepo',
1336
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1337
# default control directory format
1339
format_registry.register_lazy(
1340
'Bazaar-NG Knit Repository Format 1',
1341
'bzrlib.repofmt.knitrepo',
1342
'RepositoryFormatKnit1',
1344
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
1346
format_registry.register_lazy(
1347
'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1348
'bzrlib.repofmt.knitrepo',
1349
'RepositoryFormatKnit3',
1353
class InterRepository(InterObject):
1354
"""This class represents operations taking place between two repositories.
1356
Its instances have methods like copy_content and fetch, and contain
1357
references to the source and target repositories these operations can be
1360
Often we will provide convenience methods on 'repository' which carry out
1361
operations with another repository - they will always forward to
1362
InterRepository.get(other).method_name(parameters).
1366
"""The available optimised InterRepository types."""
1368
def copy_content(self, revision_id=None):
1369
raise NotImplementedError(self.copy_content)
1371
def fetch(self, revision_id=None, pb=None):
1372
"""Fetch the content required to construct revision_id.
1374
The content is copied from self.source to self.target.
1376
:param revision_id: if None all content is copied, if NULL_REVISION no
1378
:param pb: optional progress bar to use for progress reports. If not
1379
provided a default one will be created.
1381
Returns the copied revision count and the failed revisions in a tuple:
1384
raise NotImplementedError(self.fetch)
1387
def missing_revision_ids(self, revision_id=None):
1388
"""Return the revision ids that source has that target does not.
1390
These are returned in topological order.
1392
:param revision_id: only return revision ids included by this
1395
# generic, possibly worst case, slow code path.
1396
target_ids = set(self.target.all_revision_ids())
1397
if revision_id is not None:
1398
# TODO: jam 20070210 InterRepository is internal enough that it
1399
# should assume revision_ids are already utf-8
1400
revision_id = osutils.safe_revision_id(revision_id)
1401
source_ids = self.source.get_ancestry(revision_id)
1402
assert source_ids[0] is None
1405
source_ids = self.source.all_revision_ids()
1406
result_set = set(source_ids).difference(target_ids)
1407
# this may look like a no-op: its not. It preserves the ordering
1408
# other_ids had while only returning the members from other_ids
1409
# that we've decided we need.
1410
return [rev_id for rev_id in source_ids if rev_id in result_set]
1413
class InterSameDataRepository(InterRepository):
1414
"""Code for converting between repositories that represent the same data.
1416
Data format and model must match for this to work.
1420
def _get_repo_format_to_test(self):
1421
"""Repository format for testing with."""
1422
return RepositoryFormat.get_default_format()
1425
def is_compatible(source, target):
1426
if source.supports_rich_root() != target.supports_rich_root():
1428
if source._serializer != target._serializer:
1433
def copy_content(self, revision_id=None):
1434
"""Make a complete copy of the content in self into destination.
1436
This copies both the repository's revision data, and configuration information
1437
such as the make_working_trees setting.
1439
This is a destructive operation! Do not use it on existing
1442
:param revision_id: Only copy the content needed to construct
1443
revision_id and its parents.
1446
self.target.set_make_working_trees(self.source.make_working_trees())
1447
except NotImplementedError:
1449
# TODO: jam 20070210 This is fairly internal, so we should probably
1450
# just assert that revision_id is not unicode.
1451
revision_id = osutils.safe_revision_id(revision_id)
1452
# but don't bother fetching if we have the needed data now.
1453
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1454
self.target.has_revision(revision_id)):
1456
self.target.fetch(self.source, revision_id=revision_id)
1459
def fetch(self, revision_id=None, pb=None):
1460
"""See InterRepository.fetch()."""
1461
from bzrlib.fetch import GenericRepoFetcher
1462
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1463
self.source, self.source._format, self.target,
1464
self.target._format)
1465
# TODO: jam 20070210 This should be an assert, not a translate
1466
revision_id = osutils.safe_revision_id(revision_id)
1467
f = GenericRepoFetcher(to_repository=self.target,
1468
from_repository=self.source,
1469
last_revision=revision_id,
1471
return f.count_copied, f.failed_revisions
1474
class InterWeaveRepo(InterSameDataRepository):
1475
"""Optimised code paths between Weave based repositories."""
1478
def _get_repo_format_to_test(self):
1479
from bzrlib.repofmt import weaverepo
1480
return weaverepo.RepositoryFormat7()
1483
def is_compatible(source, target):
1484
"""Be compatible with known Weave formats.
1486
We don't test for the stores being of specific types because that
1487
could lead to confusing results, and there is no need to be
1490
from bzrlib.repofmt.weaverepo import (
1496
return (isinstance(source._format, (RepositoryFormat5,
1498
RepositoryFormat7)) and
1499
isinstance(target._format, (RepositoryFormat5,
1501
RepositoryFormat7)))
1502
except AttributeError:
1506
def copy_content(self, revision_id=None):
1507
"""See InterRepository.copy_content()."""
1508
# weave specific optimised path:
1509
# TODO: jam 20070210 Internal, should be an assert, not translate
1510
revision_id = osutils.safe_revision_id(revision_id)
1512
self.target.set_make_working_trees(self.source.make_working_trees())
1513
except NotImplementedError:
1515
# FIXME do not peek!
1516
if self.source.control_files._transport.listable():
1517
pb = ui.ui_factory.nested_progress_bar()
1519
self.target.weave_store.copy_all_ids(
1520
self.source.weave_store,
1522
from_transaction=self.source.get_transaction(),
1523
to_transaction=self.target.get_transaction())
1524
pb.update('copying inventory', 0, 1)
1525
self.target.control_weaves.copy_multi(
1526
self.source.control_weaves, ['inventory'],
1527
from_transaction=self.source.get_transaction(),
1528
to_transaction=self.target.get_transaction())
1529
self.target._revision_store.text_store.copy_all_ids(
1530
self.source._revision_store.text_store,
1535
self.target.fetch(self.source, revision_id=revision_id)
1538
def fetch(self, revision_id=None, pb=None):
1539
"""See InterRepository.fetch()."""
1540
from bzrlib.fetch import GenericRepoFetcher
1541
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1542
self.source, self.source._format, self.target, self.target._format)
1543
# TODO: jam 20070210 This should be an assert, not a translate
1544
revision_id = osutils.safe_revision_id(revision_id)
1545
f = GenericRepoFetcher(to_repository=self.target,
1546
from_repository=self.source,
1547
last_revision=revision_id,
1549
return f.count_copied, f.failed_revisions
1552
def missing_revision_ids(self, revision_id=None):
1553
"""See InterRepository.missing_revision_ids()."""
1554
# we want all revisions to satisfy revision_id in source.
1555
# but we don't want to stat every file here and there.
1556
# we want then, all revisions other needs to satisfy revision_id
1557
# checked, but not those that we have locally.
1558
# so the first thing is to get a subset of the revisions to
1559
# satisfy revision_id in source, and then eliminate those that
1560
# we do already have.
1561
# this is slow on high latency connection to self, but as as this
1562
# disk format scales terribly for push anyway due to rewriting
1563
# inventory.weave, this is considered acceptable.
1565
if revision_id is not None:
1566
source_ids = self.source.get_ancestry(revision_id)
1567
assert source_ids[0] is None
1570
source_ids = self.source._all_possible_ids()
1571
source_ids_set = set(source_ids)
1572
# source_ids is the worst possible case we may need to pull.
1573
# now we want to filter source_ids against what we actually
1574
# have in target, but don't try to check for existence where we know
1575
# we do not have a revision as that would be pointless.
1576
target_ids = set(self.target._all_possible_ids())
1577
possibly_present_revisions = target_ids.intersection(source_ids_set)
1578
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1579
required_revisions = source_ids_set.difference(actually_present_revisions)
1580
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1581
if revision_id is not None:
1582
# we used get_ancestry to determine source_ids then we are assured all
1583
# revisions referenced are present as they are installed in topological order.
1584
# and the tip revision was validated by get_ancestry.
1585
return required_topo_revisions
1587
# if we just grabbed the possibly available ids, then
1588
# we only have an estimate of whats available and need to validate
1589
# that against the revision records.
1590
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1593
class InterKnitRepo(InterSameDataRepository):
1594
"""Optimised code paths between Knit based repositories."""
1597
def _get_repo_format_to_test(self):
1598
from bzrlib.repofmt import knitrepo
1599
return knitrepo.RepositoryFormatKnit1()
1602
def is_compatible(source, target):
1603
"""Be compatible with known Knit formats.
1605
We don't test for the stores being of specific types because that
1606
could lead to confusing results, and there is no need to be
1609
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
1611
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1612
isinstance(target._format, (RepositoryFormatKnit1)))
1613
except AttributeError:
1617
def fetch(self, revision_id=None, pb=None):
1618
"""See InterRepository.fetch()."""
1619
from bzrlib.fetch import KnitRepoFetcher
1620
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1621
self.source, self.source._format, self.target, self.target._format)
1622
# TODO: jam 20070210 This should be an assert, not a translate
1623
revision_id = osutils.safe_revision_id(revision_id)
1624
f = KnitRepoFetcher(to_repository=self.target,
1625
from_repository=self.source,
1626
last_revision=revision_id,
1628
return f.count_copied, f.failed_revisions
1631
def missing_revision_ids(self, revision_id=None):
1632
"""See InterRepository.missing_revision_ids()."""
1633
if revision_id is not None:
1634
source_ids = self.source.get_ancestry(revision_id)
1635
assert source_ids[0] is None
1638
source_ids = self.source._all_possible_ids()
1639
source_ids_set = set(source_ids)
1640
# source_ids is the worst possible case we may need to pull.
1641
# now we want to filter source_ids against what we actually
1642
# have in target, but don't try to check for existence where we know
1643
# we do not have a revision as that would be pointless.
1644
target_ids = set(self.target._all_possible_ids())
1645
possibly_present_revisions = target_ids.intersection(source_ids_set)
1646
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1647
required_revisions = source_ids_set.difference(actually_present_revisions)
1648
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1649
if revision_id is not None:
1650
# we used get_ancestry to determine source_ids then we are assured all
1651
# revisions referenced are present as they are installed in topological order.
1652
# and the tip revision was validated by get_ancestry.
1653
return required_topo_revisions
1655
# if we just grabbed the possibly available ids, then
1656
# we only have an estimate of whats available and need to validate
1657
# that against the revision records.
1658
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1661
class InterModel1and2(InterRepository):
1664
def _get_repo_format_to_test(self):
1668
def is_compatible(source, target):
1669
if not source.supports_rich_root() and target.supports_rich_root():
1675
def fetch(self, revision_id=None, pb=None):
1676
"""See InterRepository.fetch()."""
1677
from bzrlib.fetch import Model1toKnit2Fetcher
1678
# TODO: jam 20070210 This should be an assert, not a translate
1679
revision_id = osutils.safe_revision_id(revision_id)
1680
f = Model1toKnit2Fetcher(to_repository=self.target,
1681
from_repository=self.source,
1682
last_revision=revision_id,
1684
return f.count_copied, f.failed_revisions
1687
def copy_content(self, revision_id=None):
1688
"""Make a complete copy of the content in self into destination.
1690
This is a destructive operation! Do not use it on existing
1693
:param revision_id: Only copy the content needed to construct
1694
revision_id and its parents.
1697
self.target.set_make_working_trees(self.source.make_working_trees())
1698
except NotImplementedError:
1700
# TODO: jam 20070210 Internal, assert, don't translate
1701
revision_id = osutils.safe_revision_id(revision_id)
1702
# but don't bother fetching if we have the needed data now.
1703
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1704
self.target.has_revision(revision_id)):
1706
self.target.fetch(self.source, revision_id=revision_id)
1709
class InterKnit1and2(InterKnitRepo):
1712
def _get_repo_format_to_test(self):
1716
def is_compatible(source, target):
1717
"""Be compatible with Knit1 source and Knit3 target"""
1718
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
1720
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1721
RepositoryFormatKnit3
1722
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1723
isinstance(target._format, (RepositoryFormatKnit3)))
1724
except AttributeError:
1728
def fetch(self, revision_id=None, pb=None):
1729
"""See InterRepository.fetch()."""
1730
from bzrlib.fetch import Knit1to2Fetcher
1731
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1732
self.source, self.source._format, self.target,
1733
self.target._format)
1734
# TODO: jam 20070210 This should be an assert, not a translate
1735
revision_id = osutils.safe_revision_id(revision_id)
1736
f = Knit1to2Fetcher(to_repository=self.target,
1737
from_repository=self.source,
1738
last_revision=revision_id,
1740
return f.count_copied, f.failed_revisions
1743
class InterRemoteRepository(InterRepository):
1744
"""Code for converting between RemoteRepository objects.
1746
This just gets an non-remote repository from the RemoteRepository, and calls
1747
InterRepository.get again.
1750
def __init__(self, source, target):
1751
if isinstance(source, remote.RemoteRepository):
1752
source._ensure_real()
1753
real_source = source._real_repository
1755
real_source = source
1756
if isinstance(target, remote.RemoteRepository):
1757
target._ensure_real()
1758
real_target = target._real_repository
1760
real_target = target
1761
self.real_inter = InterRepository.get(real_source, real_target)
1764
def is_compatible(source, target):
1765
if isinstance(source, remote.RemoteRepository):
1767
if isinstance(target, remote.RemoteRepository):
1771
def copy_content(self, revision_id=None):
1772
self.real_inter.copy_content(revision_id=revision_id)
1774
def fetch(self, revision_id=None, pb=None):
1775
self.real_inter.fetch(revision_id=revision_id, pb=pb)
1778
def _get_repo_format_to_test(self):
1782
InterRepository.register_optimiser(InterSameDataRepository)
1783
InterRepository.register_optimiser(InterWeaveRepo)
1784
InterRepository.register_optimiser(InterKnitRepo)
1785
InterRepository.register_optimiser(InterModel1and2)
1786
InterRepository.register_optimiser(InterKnit1and2)
1787
InterRepository.register_optimiser(InterRemoteRepository)
1790
class RepositoryTestProviderAdapter(object):
1791
"""A tool to generate a suite testing multiple repository formats at once.
1793
This is done by copying the test once for each transport and injecting
1794
the transport_server, transport_readonly_server, and bzrdir_format and
1795
repository_format classes into each copy. Each copy is also given a new id()
1796
to make it easy to identify.
1799
def __init__(self, transport_server, transport_readonly_server, formats,
1800
vfs_transport_factory=None):
1801
self._transport_server = transport_server
1802
self._transport_readonly_server = transport_readonly_server
1803
self._vfs_transport_factory = vfs_transport_factory
1804
self._formats = formats
1806
def adapt(self, test):
1807
result = unittest.TestSuite()
1808
for repository_format, bzrdir_format in self._formats:
1809
from copy import deepcopy
1810
new_test = deepcopy(test)
1811
new_test.transport_server = self._transport_server
1812
new_test.transport_readonly_server = self._transport_readonly_server
1813
# Only override the test's vfs_transport_factory if one was
1814
# specified, otherwise just leave the default in place.
1815
if self._vfs_transport_factory:
1816
new_test.vfs_transport_factory = self._vfs_transport_factory
1817
new_test.bzrdir_format = bzrdir_format
1818
new_test.repository_format = repository_format
1819
def make_new_test_id():
1820
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1821
return lambda: new_id
1822
new_test.id = make_new_test_id()
1823
result.addTest(new_test)
1827
class InterRepositoryTestProviderAdapter(object):
1828
"""A tool to generate a suite testing multiple inter repository formats.
1830
This is done by copying the test once for each interrepo provider and injecting
1831
the transport_server, transport_readonly_server, repository_format and
1832
repository_to_format classes into each copy.
1833
Each copy is also given a new id() to make it easy to identify.
1836
def __init__(self, transport_server, transport_readonly_server, formats):
1837
self._transport_server = transport_server
1838
self._transport_readonly_server = transport_readonly_server
1839
self._formats = formats
1841
def adapt(self, test):
1842
result = unittest.TestSuite()
1843
for interrepo_class, repository_format, repository_format_to in self._formats:
1844
from copy import deepcopy
1845
new_test = deepcopy(test)
1846
new_test.transport_server = self._transport_server
1847
new_test.transport_readonly_server = self._transport_readonly_server
1848
new_test.interrepo_class = interrepo_class
1849
new_test.repository_format = repository_format
1850
new_test.repository_format_to = repository_format_to
1851
def make_new_test_id():
1852
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1853
return lambda: new_id
1854
new_test.id = make_new_test_id()
1855
result.addTest(new_test)
1859
def default_test_list():
1860
"""Generate the default list of interrepo permutations to test."""
1861
from bzrlib.repofmt import knitrepo, weaverepo
1863
# test the default InterRepository between format 6 and the current
1865
# XXX: robertc 20060220 reinstate this when there are two supported
1866
# formats which do not have an optimal code path between them.
1867
#result.append((InterRepository,
1868
# RepositoryFormat6(),
1869
# RepositoryFormatKnit1()))
1870
for optimiser_class in InterRepository._optimisers:
1871
format_to_test = optimiser_class._get_repo_format_to_test()
1872
if format_to_test is not None:
1873
result.append((optimiser_class,
1874
format_to_test, format_to_test))
1875
# if there are specific combinations we want to use, we can add them
1877
result.append((InterModel1and2,
1878
weaverepo.RepositoryFormat5(),
1879
knitrepo.RepositoryFormatKnit3()))
1880
result.append((InterKnit1and2,
1881
knitrepo.RepositoryFormatKnit1(),
1882
knitrepo.RepositoryFormatKnit3()))
1886
class CopyConverter(object):
1887
"""A repository conversion tool which just performs a copy of the content.
1889
This is slow but quite reliable.
1892
def __init__(self, target_format):
1893
"""Create a CopyConverter.
1895
:param target_format: The format the resulting repository should be.
1897
self.target_format = target_format
1899
def convert(self, repo, pb):
1900
"""Perform the conversion of to_convert, giving feedback via pb.
1902
:param to_convert: The disk object to convert.
1903
:param pb: a progress bar to use for progress information.
1908
# this is only useful with metadir layouts - separated repo content.
1909
# trigger an assertion if not such
1910
repo._format.get_format_string()
1911
self.repo_dir = repo.bzrdir
1912
self.step('Moving repository to repository.backup')
1913
self.repo_dir.transport.move('repository', 'repository.backup')
1914
backup_transport = self.repo_dir.transport.clone('repository.backup')
1915
repo._format.check_conversion_target(self.target_format)
1916
self.source_repo = repo._format.open(self.repo_dir,
1918
_override_transport=backup_transport)
1919
self.step('Creating new repository')
1920
converted = self.target_format.initialize(self.repo_dir,
1921
self.source_repo.is_shared())
1922
converted.lock_write()
1924
self.step('Copying content into repository.')
1925
self.source_repo.copy_content_into(converted)
1928
self.step('Deleting old repository content.')
1929
self.repo_dir.transport.delete_tree('repository.backup')
1930
self.pb.note('repository converted')
1932
def step(self, message):
1933
"""Update the pb by a step."""
1935
self.pb.update(message, self.count, self.total)
1938
class CommitBuilder(object):
1939
"""Provides an interface to build up a commit.
1941
This allows describing a tree to be committed without needing to
1942
know the internals of the format of the repository.
1945
record_root_entry = False
1946
def __init__(self, repository, parents, config, timestamp=None,
1947
timezone=None, committer=None, revprops=None,
1949
"""Initiate a CommitBuilder.
1951
:param repository: Repository to commit to.
1952
:param parents: Revision ids of the parents of the new revision.
1953
:param config: Configuration to use.
1954
:param timestamp: Optional timestamp recorded for commit.
1955
:param timezone: Optional timezone for timestamp.
1956
:param committer: Optional committer to set for commit.
1957
:param revprops: Optional dictionary of revision properties.
1958
:param revision_id: Optional revision id.
1960
self._config = config
1962
if committer is None:
1963
self._committer = self._config.username()
1965
assert isinstance(committer, basestring), type(committer)
1966
self._committer = committer
1968
self.new_inventory = Inventory(None)
1969
self._new_revision_id = osutils.safe_revision_id(revision_id)
1970
self.parents = parents
1971
self.repository = repository
1974
if revprops is not None:
1975
self._revprops.update(revprops)
1977
if timestamp is None:
1978
timestamp = time.time()
1979
# Restrict resolution to 1ms
1980
self._timestamp = round(timestamp, 3)
1982
if timezone is None:
1983
self._timezone = osutils.local_time_offset()
1985
self._timezone = int(timezone)
1987
self._generate_revision_if_needed()
1989
def commit(self, message):
1990
"""Make the actual commit.
1992
:return: The revision id of the recorded revision.
1994
rev = _mod_revision.Revision(
1995
timestamp=self._timestamp,
1996
timezone=self._timezone,
1997
committer=self._committer,
1999
inventory_sha1=self.inv_sha1,
2000
revision_id=self._new_revision_id,
2001
properties=self._revprops)
2002
rev.parent_ids = self.parents
2003
self.repository.add_revision(self._new_revision_id, rev,
2004
self.new_inventory, self._config)
2005
return self._new_revision_id
2007
def revision_tree(self):
2008
"""Return the tree that was just committed.
2010
After calling commit() this can be called to get a RevisionTree
2011
representing the newly committed tree. This is preferred to
2012
calling Repository.revision_tree() because that may require
2013
deserializing the inventory, while we already have a copy in
2016
return RevisionTree(self.repository, self.new_inventory,
2017
self._new_revision_id)
2019
def finish_inventory(self):
2020
"""Tell the builder that the inventory is finished."""
2021
if self.new_inventory.root is None:
2022
symbol_versioning.warn('Root entry should be supplied to'
2023
' record_entry_contents, as of bzr 0.10.',
2024
DeprecationWarning, stacklevel=2)
2025
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2026
self.new_inventory.revision_id = self._new_revision_id
2027
self.inv_sha1 = self.repository.add_inventory(
2028
self._new_revision_id,
2033
def _gen_revision_id(self):
2034
"""Return new revision-id."""
2035
return generate_ids.gen_revision_id(self._config.username(),
2038
def _generate_revision_if_needed(self):
2039
"""Create a revision id if None was supplied.
2041
If the repository can not support user-specified revision ids
2042
they should override this function and raise CannotSetRevisionId
2043
if _new_revision_id is not None.
2045
:raises: CannotSetRevisionId
2047
if self._new_revision_id is None:
2048
self._new_revision_id = self._gen_revision_id()
2050
def record_entry_contents(self, ie, parent_invs, path, tree):
2051
"""Record the content of ie from tree into the commit if needed.
2053
Side effect: sets ie.revision when unchanged
2055
:param ie: An inventory entry present in the commit.
2056
:param parent_invs: The inventories of the parent revisions of the
2058
:param path: The path the entry is at in the tree.
2059
:param tree: The tree which contains this entry and should be used to
2062
if self.new_inventory.root is None and ie.parent_id is not None:
2063
symbol_versioning.warn('Root entry should be supplied to'
2064
' record_entry_contents, as of bzr 0.10.',
2065
DeprecationWarning, stacklevel=2)
2066
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2068
self.new_inventory.add(ie)
2070
# ie.revision is always None if the InventoryEntry is considered
2071
# for committing. ie.snapshot will record the correct revision
2072
# which may be the sole parent if it is untouched.
2073
if ie.revision is not None:
2076
# In this revision format, root entries have no knit or weave
2077
if ie is self.new_inventory.root:
2078
# When serializing out to disk and back in
2079
# root.revision is always _new_revision_id
2080
ie.revision = self._new_revision_id
2082
previous_entries = ie.find_previous_heads(
2084
self.repository.weave_store,
2085
self.repository.get_transaction())
2086
# we are creating a new revision for ie in the history store
2088
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2090
def modified_directory(self, file_id, file_parents):
2091
"""Record the presence of a symbolic link.
2093
:param file_id: The file_id of the link to record.
2094
:param file_parents: The per-file parent revision ids.
2096
self._add_text_to_weave(file_id, [], file_parents.keys())
2098
def modified_reference(self, file_id, file_parents):
2099
"""Record the modification of a reference.
2101
:param file_id: The file_id of the link to record.
2102
:param file_parents: The per-file parent revision ids.
2104
self._add_text_to_weave(file_id, [], file_parents.keys())
2106
def modified_file_text(self, file_id, file_parents,
2107
get_content_byte_lines, text_sha1=None,
2109
"""Record the text of file file_id
2111
:param file_id: The file_id of the file to record the text of.
2112
:param file_parents: The per-file parent revision ids.
2113
:param get_content_byte_lines: A callable which will return the byte
2115
:param text_sha1: Optional SHA1 of the file contents.
2116
:param text_size: Optional size of the file contents.
2118
# mutter('storing text of file {%s} in revision {%s} into %r',
2119
# file_id, self._new_revision_id, self.repository.weave_store)
2120
# special case to avoid diffing on renames or
2122
if (len(file_parents) == 1
2123
and text_sha1 == file_parents.values()[0].text_sha1
2124
and text_size == file_parents.values()[0].text_size):
2125
previous_ie = file_parents.values()[0]
2126
versionedfile = self.repository.weave_store.get_weave(file_id,
2127
self.repository.get_transaction())
2128
versionedfile.clone_text(self._new_revision_id,
2129
previous_ie.revision, file_parents.keys())
2130
return text_sha1, text_size
2132
new_lines = get_content_byte_lines()
2133
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2134
# should return the SHA1 and size
2135
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2136
return osutils.sha_strings(new_lines), \
2137
sum(map(len, new_lines))
2139
def modified_link(self, file_id, file_parents, link_target):
2140
"""Record the presence of a symbolic link.
2142
:param file_id: The file_id of the link to record.
2143
:param file_parents: The per-file parent revision ids.
2144
:param link_target: Target location of this link.
2146
self._add_text_to_weave(file_id, [], file_parents.keys())
2148
def _add_text_to_weave(self, file_id, new_lines, parents):
2149
versionedfile = self.repository.weave_store.get_weave_or_empty(
2150
file_id, self.repository.get_transaction())
2151
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2152
versionedfile.clear_cache()
2155
class _CommitBuilder(CommitBuilder):
2156
"""Temporary class so old CommitBuilders are detected properly
2158
Note: CommitBuilder works whether or not root entry is recorded.
2161
record_root_entry = True
2164
class RootCommitBuilder(CommitBuilder):
2165
"""This commitbuilder actually records the root id"""
2167
record_root_entry = True
2169
def record_entry_contents(self, ie, parent_invs, path, tree):
2170
"""Record the content of ie from tree into the commit if needed.
2172
Side effect: sets ie.revision when unchanged
2174
:param ie: An inventory entry present in the commit.
2175
:param parent_invs: The inventories of the parent revisions of the
2177
:param path: The path the entry is at in the tree.
2178
:param tree: The tree which contains this entry and should be used to
2181
assert self.new_inventory.root is not None or ie.parent_id is None
2182
self.new_inventory.add(ie)
2184
# ie.revision is always None if the InventoryEntry is considered
2185
# for committing. ie.snapshot will record the correct revision
2186
# which may be the sole parent if it is untouched.
2187
if ie.revision is not None:
2190
previous_entries = ie.find_previous_heads(
2192
self.repository.weave_store,
2193
self.repository.get_transaction())
2194
# we are creating a new revision for ie in the history store
2196
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2208
def _unescaper(match, _map=_unescape_map):
2209
code = match.group(1)
2213
if not code.startswith('#'):
2215
return unichr(int(code[1:])).encode('utf8')
2221
def _unescape_xml(data):
2222
"""Unescape predefined XML entities in a string of data."""
2224
if _unescape_re is None:
2225
_unescape_re = re.compile('\&([^;]*);')
2226
return _unescape_re.sub(_unescaper, data)