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(), """
37
revision as _mod_revision,
42
from bzrlib.revisiontree import RevisionTree
43
from bzrlib.store.versioned import VersionedFileStore
44
from bzrlib.store.text import TextStore
45
from bzrlib.testament import Testament
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
from bzrlib.inter import InterObject
51
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
52
from bzrlib.symbol_versioning import (
56
from bzrlib.trace import mutter, note, warning
59
# Old formats display a warning, but only once
60
_deprecation_warning_done = False
63
######################################################################
66
class Repository(object):
67
"""Repository holding history for one or more branches.
69
The repository holds and retrieves historical information including
70
revisions and file history. It's normally accessed only by the Branch,
71
which views a particular line of development through that history.
73
The Repository builds on top of Stores and a Transport, which respectively
74
describe the disk data format and the way of accessing the (possibly
78
_file_ids_altered_regex = lazy_regex.lazy_compile(
79
r'file_id="(?P<file_id>[^"]+)"'
80
r'.*revision="(?P<revision_id>[^"]+)"'
84
def add_inventory(self, revision_id, inv, parents):
85
"""Add the inventory inv to the repository as revision_id.
87
:param parents: The revision ids of the parents that revision_id
88
is known to have and are in the repository already.
90
returns the sha1 of the serialized inventory.
92
revision_id = osutils.safe_revision_id(revision_id)
93
_mod_revision.check_not_reserved_id(revision_id)
94
assert inv.revision_id is None or inv.revision_id == revision_id, \
95
"Mismatch between inventory revision" \
96
" id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
97
assert inv.root is not None
98
inv_text = self.serialise_inventory(inv)
99
inv_sha1 = osutils.sha_string(inv_text)
100
inv_vf = self.control_weaves.get_weave('inventory',
101
self.get_transaction())
102
self._inventory_add_lines(inv_vf, revision_id, parents,
103
osutils.split_lines(inv_text))
106
def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
108
for parent in parents:
110
final_parents.append(parent)
112
inv_vf.add_lines(revision_id, final_parents, lines)
115
def add_revision(self, revision_id, rev, inv=None, config=None):
116
"""Add rev to the revision store as revision_id.
118
:param revision_id: the revision id to use.
119
:param rev: The revision object.
120
:param inv: The inventory for the revision. if None, it will be looked
121
up in the inventory storer
122
:param config: If None no digital signature will be created.
123
If supplied its signature_needed method will be used
124
to determine if a signature should be made.
126
revision_id = osutils.safe_revision_id(revision_id)
127
# TODO: jam 20070210 Shouldn't we check rev.revision_id and
129
_mod_revision.check_not_reserved_id(revision_id)
130
if config is not None and config.signature_needed():
132
inv = self.get_inventory(revision_id)
133
plaintext = Testament(rev, inv).as_short_text()
134
self.store_revision_signature(
135
gpg.GPGStrategy(config), plaintext, revision_id)
136
if not revision_id in self.get_inventory_weave():
138
raise errors.WeaveRevisionNotPresent(revision_id,
139
self.get_inventory_weave())
141
# yes, this is not suitable for adding with ghosts.
142
self.add_inventory(revision_id, inv, rev.parent_ids)
143
self._revision_store.add_revision(rev, self.get_transaction())
146
def _all_possible_ids(self):
147
"""Return all the possible revisions that we could find."""
148
return self.get_inventory_weave().versions()
150
def all_revision_ids(self):
151
"""Returns a list of all the revision ids in the repository.
153
This is deprecated because code should generally work on the graph
154
reachable from a particular revision, and ignore any other revisions
155
that might be present. There is no direct replacement method.
157
return self._all_revision_ids()
160
def _all_revision_ids(self):
161
"""Returns a list of all the revision ids in the repository.
163
These are in as much topological order as the underlying store can
164
present: for weaves ghosts may lead to a lack of correctness until
165
the reweave updates the parents list.
167
if self._revision_store.text_store.listable():
168
return self._revision_store.all_revision_ids(self.get_transaction())
169
result = self._all_possible_ids()
170
# TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
171
# ids. (It should, since _revision_store's API should change to
172
# return utf8 revision_ids)
173
return self._eliminate_revisions_not_present(result)
175
def break_lock(self):
176
"""Break a lock if one is present from another instance.
178
Uses the ui factory to ask for confirmation if the lock may be from
181
self.control_files.break_lock()
184
def _eliminate_revisions_not_present(self, revision_ids):
185
"""Check every revision id in revision_ids to see if we have it.
187
Returns a set of the present revisions.
190
for id in revision_ids:
191
if self.has_revision(id):
196
def create(a_bzrdir):
197
"""Construct the current default format repository in a_bzrdir."""
198
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
200
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
201
"""instantiate a Repository.
203
:param _format: The format of the repository on disk.
204
:param a_bzrdir: The BzrDir of the repository.
206
In the future we will have a single api for all stores for
207
getting file texts, inventories and revisions, then
208
this construct will accept instances of those things.
210
super(Repository, self).__init__()
211
self._format = _format
212
# the following are part of the public API for Repository:
213
self.bzrdir = a_bzrdir
214
self.control_files = control_files
215
self._revision_store = _revision_store
216
self.text_store = text_store
217
# backwards compatibility
218
self.weave_store = text_store
219
# not right yet - should be more semantically clear ?
221
self.control_store = control_store
222
self.control_weaves = control_store
223
# TODO: make sure to construct the right store classes, etc, depending
224
# on whether escaping is required.
225
self._warn_if_deprecated()
228
return '%s(%r)' % (self.__class__.__name__,
229
self.bzrdir.transport.base)
232
return self.control_files.is_locked()
234
def lock_write(self, token=None):
235
"""Lock this repository for writing.
237
:param token: if this is already locked, then lock_write will fail
238
unless the token matches the existing lock.
239
:returns: a token if this instance supports tokens, otherwise None.
240
:raises TokenLockingNotSupported: when a token is given but this
241
instance doesn't support using token locks.
242
:raises MismatchedToken: if the specified token doesn't match the token
243
of the existing lock.
245
XXX: this docstring is duplicated in many places, e.g. lockable_files.py
247
return self.control_files.lock_write(token=token)
250
self.control_files.lock_read()
252
def get_physical_lock_status(self):
253
return self.control_files.get_physical_lock_status()
255
def leave_lock_in_place(self):
256
"""Tell this repository not to release the physical lock when this
259
If lock_write doesn't return a token, then this method is not supported.
261
self.control_files.leave_in_place()
263
def dont_leave_lock_in_place(self):
264
"""Tell this repository to release the physical lock when this
265
object is unlocked, even if it didn't originally acquire it.
267
If lock_write doesn't return a token, then this method is not supported.
269
self.control_files.dont_leave_in_place()
272
def gather_stats(self, revid=None, committers=None):
273
"""Gather statistics from a revision id.
275
:param revid: The revision id to gather statistics from, if None, then
276
no revision specific statistics are gathered.
277
:param committers: Optional parameter controlling whether to grab
278
a count of committers from the revision specific statistics.
279
:return: A dictionary of statistics. Currently this contains:
280
committers: The number of committers if requested.
281
firstrev: A tuple with timestamp, timezone for the penultimate left
282
most ancestor of revid, if revid is not the NULL_REVISION.
283
latestrev: A tuple with timestamp, timezone for revid, if revid is
284
not the NULL_REVISION.
285
revisions: The total revision count in the repository.
286
size: An estimate disk size of the repository in bytes.
289
if revid and committers:
290
result['committers'] = 0
291
if revid and revid != _mod_revision.NULL_REVISION:
293
all_committers = set()
294
revisions = self.get_ancestry(revid)
295
# pop the leading None
297
first_revision = None
299
# ignore the revisions in the middle - just grab first and last
300
revisions = revisions[0], revisions[-1]
301
for revision in self.get_revisions(revisions):
302
if not first_revision:
303
first_revision = revision
305
all_committers.add(revision.committer)
306
last_revision = revision
308
result['committers'] = len(all_committers)
309
result['firstrev'] = (first_revision.timestamp,
310
first_revision.timezone)
311
result['latestrev'] = (last_revision.timestamp,
312
last_revision.timezone)
314
# now gather global repository information
315
if self.bzrdir.root_transport.listable():
316
c, t = self._revision_store.total_size(self.get_transaction())
317
result['revisions'] = c
322
def missing_revision_ids(self, other, revision_id=None):
323
"""Return the revision ids that other has that this does not.
325
These are returned in topological order.
327
revision_id: only return revision ids included by revision_id.
329
revision_id = osutils.safe_revision_id(revision_id)
330
return InterRepository.get(other, self).missing_revision_ids(revision_id)
334
"""Open the repository rooted at base.
336
For instance, if the repository is at URL/.bzr/repository,
337
Repository.open(URL) -> a Repository instance.
339
control = bzrdir.BzrDir.open(base)
340
return control.open_repository()
342
def copy_content_into(self, destination, revision_id=None, basis=None):
343
"""Make a complete copy of the content in self into destination.
345
This is a destructive operation! Do not use it on existing
348
revision_id = osutils.safe_revision_id(revision_id)
349
return InterRepository.get(self, destination).copy_content(revision_id, basis)
351
def fetch(self, source, revision_id=None, pb=None):
352
"""Fetch the content required to construct revision_id from source.
354
If revision_id is None all content is copied.
356
revision_id = osutils.safe_revision_id(revision_id)
357
return InterRepository.get(source, self).fetch(revision_id=revision_id,
360
def get_commit_builder(self, branch, parents, config, timestamp=None,
361
timezone=None, committer=None, revprops=None,
363
"""Obtain a CommitBuilder for this repository.
365
:param branch: Branch to commit to.
366
:param parents: Revision ids of the parents of the new revision.
367
:param config: Configuration to use.
368
:param timestamp: Optional timestamp recorded for commit.
369
:param timezone: Optional timezone for timestamp.
370
:param committer: Optional committer to set for commit.
371
:param revprops: Optional dictionary of revision properties.
372
:param revision_id: Optional revision id.
374
revision_id = osutils.safe_revision_id(revision_id)
375
return _CommitBuilder(self, parents, config, timestamp, timezone,
376
committer, revprops, revision_id)
379
self.control_files.unlock()
382
def clone(self, a_bzrdir, revision_id=None, basis=None):
383
"""Clone this repository into a_bzrdir using the current format.
385
Currently no check is made that the format of this repository and
386
the bzrdir format are compatible. FIXME RBC 20060201.
388
:return: The newly created destination repository.
390
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
391
# use target default format.
392
dest_repo = a_bzrdir.create_repository()
394
# Most control formats need the repository to be specifically
395
# created, but on some old all-in-one formats it's not needed
397
dest_repo = self._format.initialize(a_bzrdir, shared=self.is_shared())
398
except errors.UninitializableFormat:
399
dest_repo = a_bzrdir.open_repository()
400
self.copy_content_into(dest_repo, revision_id, basis)
404
def has_revision(self, revision_id):
405
"""True if this repository has a copy of the revision."""
406
revision_id = osutils.safe_revision_id(revision_id)
407
return self._revision_store.has_revision_id(revision_id,
408
self.get_transaction())
411
def get_revision_reconcile(self, revision_id):
412
"""'reconcile' helper routine that allows access to a revision always.
414
This variant of get_revision does not cross check the weave graph
415
against the revision one as get_revision does: but it should only
416
be used by reconcile, or reconcile-alike commands that are correcting
417
or testing the revision graph.
419
if not revision_id or not isinstance(revision_id, basestring):
420
raise errors.InvalidRevisionId(revision_id=revision_id,
422
return self.get_revisions([revision_id])[0]
425
def get_revisions(self, revision_ids):
426
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
427
revs = self._revision_store.get_revisions(revision_ids,
428
self.get_transaction())
430
assert not isinstance(rev.revision_id, unicode)
431
for parent_id in rev.parent_ids:
432
assert not isinstance(parent_id, unicode)
436
def get_revision_xml(self, revision_id):
437
# TODO: jam 20070210 This shouldn't be necessary since get_revision
438
# would have already do it.
439
# TODO: jam 20070210 Just use _serializer.write_revision_to_string()
440
revision_id = osutils.safe_revision_id(revision_id)
441
rev = self.get_revision(revision_id)
443
# the current serializer..
444
self._revision_store._serializer.write_revision(rev, rev_tmp)
446
return rev_tmp.getvalue()
449
def get_revision(self, revision_id):
450
"""Return the Revision object for a named revision"""
451
# TODO: jam 20070210 get_revision_reconcile should do this for us
452
revision_id = osutils.safe_revision_id(revision_id)
453
r = self.get_revision_reconcile(revision_id)
454
# weave corruption can lead to absent revision markers that should be
456
# the following test is reasonably cheap (it needs a single weave read)
457
# and the weave is cached in read transactions. In write transactions
458
# it is not cached but typically we only read a small number of
459
# revisions. For knits when they are introduced we will probably want
460
# to ensure that caching write transactions are in use.
461
inv = self.get_inventory_weave()
462
self._check_revision_parents(r, inv)
466
def get_deltas_for_revisions(self, revisions):
467
"""Produce a generator of revision deltas.
469
Note that the input is a sequence of REVISIONS, not revision_ids.
470
Trees will be held in memory until the generator exits.
471
Each delta is relative to the revision's lefthand predecessor.
473
required_trees = set()
474
for revision in revisions:
475
required_trees.add(revision.revision_id)
476
required_trees.update(revision.parent_ids[:1])
477
trees = dict((t.get_revision_id(), t) for
478
t in self.revision_trees(required_trees))
479
for revision in revisions:
480
if not revision.parent_ids:
481
old_tree = self.revision_tree(None)
483
old_tree = trees[revision.parent_ids[0]]
484
yield trees[revision.revision_id].changes_from(old_tree)
487
def get_revision_delta(self, revision_id):
488
"""Return the delta for one revision.
490
The delta is relative to the left-hand predecessor of the
493
r = self.get_revision(revision_id)
494
return list(self.get_deltas_for_revisions([r]))[0]
496
def _check_revision_parents(self, revision, inventory):
497
"""Private to Repository and Fetch.
499
This checks the parentage of revision in an inventory weave for
500
consistency and is only applicable to inventory-weave-for-ancestry
501
using repository formats & fetchers.
503
weave_parents = inventory.get_parents(revision.revision_id)
504
weave_names = inventory.versions()
505
for parent_id in revision.parent_ids:
506
if parent_id in weave_names:
507
# this parent must not be a ghost.
508
if not parent_id in weave_parents:
510
raise errors.CorruptRepository(self)
513
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
514
revision_id = osutils.safe_revision_id(revision_id)
515
signature = gpg_strategy.sign(plaintext)
516
self._revision_store.add_revision_signature_text(revision_id,
518
self.get_transaction())
520
def fileids_altered_by_revision_ids(self, revision_ids):
521
"""Find the file ids and versions affected by revisions.
523
:param revisions: an iterable containing revision ids.
524
:return: a dictionary mapping altered file-ids to an iterable of
525
revision_ids. Each altered file-ids has the exact revision_ids that
526
altered it listed explicitly.
528
assert self._serializer.support_altered_by_hack, \
529
("fileids_altered_by_revision_ids only supported for branches "
530
"which store inventory as unnested xml, not on %r" % self)
531
selected_revision_ids = set(osutils.safe_revision_id(r)
532
for r in revision_ids)
533
w = self.get_inventory_weave()
536
# this code needs to read every new line in every inventory for the
537
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
538
# not present in one of those inventories is unnecessary but not
539
# harmful because we are filtering by the revision id marker in the
540
# inventory lines : we only select file ids altered in one of those
541
# revisions. We don't need to see all lines in the inventory because
542
# only those added in an inventory in rev X can contain a revision=X
544
unescape_revid_cache = {}
545
unescape_fileid_cache = {}
547
# jam 20061218 In a big fetch, this handles hundreds of thousands
548
# of lines, so it has had a lot of inlining and optimizing done.
549
# Sorry that it is a little bit messy.
550
# Move several functions to be local variables, since this is a long
552
search = self._file_ids_altered_regex.search
553
unescape = _unescape_xml
554
setdefault = result.setdefault
555
pb = ui.ui_factory.nested_progress_bar()
557
for line in w.iter_lines_added_or_present_in_versions(
558
selected_revision_ids, pb=pb):
562
# One call to match.group() returning multiple items is quite a
563
# bit faster than 2 calls to match.group() each returning 1
564
file_id, revision_id = match.group('file_id', 'revision_id')
566
# Inlining the cache lookups helps a lot when you make 170,000
567
# lines and 350k ids, versus 8.4 unique ids.
568
# Using a cache helps in 2 ways:
569
# 1) Avoids unnecessary decoding calls
570
# 2) Re-uses cached strings, which helps in future set and
572
# (2) is enough that removing encoding entirely along with
573
# the cache (so we are using plain strings) results in no
574
# performance improvement.
576
revision_id = unescape_revid_cache[revision_id]
578
unescaped = unescape(revision_id)
579
unescape_revid_cache[revision_id] = unescaped
580
revision_id = unescaped
582
if revision_id in selected_revision_ids:
584
file_id = unescape_fileid_cache[file_id]
586
unescaped = unescape(file_id)
587
unescape_fileid_cache[file_id] = unescaped
589
setdefault(file_id, set()).add(revision_id)
595
def get_inventory_weave(self):
596
return self.control_weaves.get_weave('inventory',
597
self.get_transaction())
600
def get_inventory(self, revision_id):
601
"""Get Inventory object by hash."""
602
# TODO: jam 20070210 Technically we don't need to sanitize, since all
603
# called functions must sanitize.
604
revision_id = osutils.safe_revision_id(revision_id)
605
return self.deserialise_inventory(
606
revision_id, self.get_inventory_xml(revision_id))
608
def deserialise_inventory(self, revision_id, xml):
609
"""Transform the xml into an inventory object.
611
:param revision_id: The expected revision id of the inventory.
612
:param xml: A serialised inventory.
614
revision_id = osutils.safe_revision_id(revision_id)
615
result = self._serializer.read_inventory_from_string(xml)
616
result.root.revision = revision_id
619
def serialise_inventory(self, inv):
620
return self._serializer.write_inventory_to_string(inv)
623
def get_inventory_xml(self, revision_id):
624
"""Get inventory XML as a file object."""
625
revision_id = osutils.safe_revision_id(revision_id)
627
assert isinstance(revision_id, str), type(revision_id)
628
iw = self.get_inventory_weave()
629
return iw.get_text(revision_id)
631
raise errors.HistoryMissing(self, 'inventory', revision_id)
634
def get_inventory_sha1(self, revision_id):
635
"""Return the sha1 hash of the inventory entry
637
# TODO: jam 20070210 Shouldn't this be deprecated / removed?
638
revision_id = osutils.safe_revision_id(revision_id)
639
return self.get_revision(revision_id).inventory_sha1
642
def get_revision_graph(self, revision_id=None):
643
"""Return a dictionary containing the revision graph.
645
:param revision_id: The revision_id to get a graph from. If None, then
646
the entire revision graph is returned. This is a deprecated mode of
647
operation and will be removed in the future.
648
:return: a dictionary of revision_id->revision_parents_list.
650
# special case NULL_REVISION
651
if revision_id == _mod_revision.NULL_REVISION:
653
revision_id = osutils.safe_revision_id(revision_id)
654
a_weave = self.get_inventory_weave()
655
all_revisions = self._eliminate_revisions_not_present(
657
entire_graph = dict([(node, a_weave.get_parents(node)) for
658
node in all_revisions])
659
if revision_id is None:
661
elif revision_id not in entire_graph:
662
raise errors.NoSuchRevision(self, revision_id)
664
# add what can be reached from revision_id
666
pending = set([revision_id])
667
while len(pending) > 0:
669
result[node] = entire_graph[node]
670
for revision_id in result[node]:
671
if revision_id not in result:
672
pending.add(revision_id)
676
def get_revision_graph_with_ghosts(self, revision_ids=None):
677
"""Return a graph of the revisions with ghosts marked as applicable.
679
:param revision_ids: an iterable of revisions to graph or None for all.
680
:return: a Graph object with the graph reachable from revision_ids.
682
result = graph.Graph()
684
pending = set(self.all_revision_ids())
687
pending = set(osutils.safe_revision_id(r) for r in revision_ids)
688
# special case NULL_REVISION
689
if _mod_revision.NULL_REVISION in pending:
690
pending.remove(_mod_revision.NULL_REVISION)
691
required = set(pending)
694
revision_id = pending.pop()
696
rev = self.get_revision(revision_id)
697
except errors.NoSuchRevision:
698
if revision_id in required:
701
result.add_ghost(revision_id)
703
for parent_id in rev.parent_ids:
704
# is this queued or done ?
705
if (parent_id not in pending and
706
parent_id not in done):
708
pending.add(parent_id)
709
result.add_node(revision_id, rev.parent_ids)
710
done.add(revision_id)
713
def _get_history_vf(self):
714
"""Get a versionedfile whose history graph reflects all revisions.
716
For weave repositories, this is the inventory weave.
718
return self.get_inventory_weave()
720
def iter_reverse_revision_history(self, revision_id):
721
"""Iterate backwards through revision ids in the lefthand history
723
:param revision_id: The revision id to start with. All its lefthand
724
ancestors will be traversed.
726
revision_id = osutils.safe_revision_id(revision_id)
727
if revision_id in (None, _mod_revision.NULL_REVISION):
729
next_id = revision_id
730
versionedfile = self._get_history_vf()
733
parents = versionedfile.get_parents(next_id)
734
if len(parents) == 0:
740
def get_revision_inventory(self, revision_id):
741
"""Return inventory of a past revision."""
742
# TODO: Unify this with get_inventory()
743
# bzr 0.0.6 and later imposes the constraint that the inventory_id
744
# must be the same as its revision, so this is trivial.
745
if revision_id is None:
746
# This does not make sense: if there is no revision,
747
# then it is the current tree inventory surely ?!
748
# and thus get_root_id() is something that looks at the last
749
# commit on the branch, and the get_root_id is an inventory check.
750
raise NotImplementedError
751
# return Inventory(self.get_root_id())
753
return self.get_inventory(revision_id)
757
"""Return True if this repository is flagged as a shared repository."""
758
raise NotImplementedError(self.is_shared)
761
def reconcile(self, other=None, thorough=False):
762
"""Reconcile this repository."""
763
from bzrlib.reconcile import RepoReconciler
764
reconciler = RepoReconciler(self, thorough=thorough)
765
reconciler.reconcile()
769
def revision_tree(self, revision_id):
770
"""Return Tree for a revision on this branch.
772
`revision_id` may be None for the empty tree revision.
774
# TODO: refactor this to use an existing revision object
775
# so we don't need to read it in twice.
776
if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
777
return RevisionTree(self, Inventory(root_id=None),
778
_mod_revision.NULL_REVISION)
780
revision_id = osutils.safe_revision_id(revision_id)
781
inv = self.get_revision_inventory(revision_id)
782
return RevisionTree(self, inv, revision_id)
785
def revision_trees(self, revision_ids):
786
"""Return Tree for a revision on this branch.
788
`revision_id` may not be None or 'null:'"""
789
assert None not in revision_ids
790
assert _mod_revision.NULL_REVISION not in revision_ids
791
texts = self.get_inventory_weave().get_texts(revision_ids)
792
for text, revision_id in zip(texts, revision_ids):
793
inv = self.deserialise_inventory(revision_id, text)
794
yield RevisionTree(self, inv, revision_id)
797
def get_ancestry(self, revision_id):
798
"""Return a list of revision-ids integrated by a revision.
800
The first element of the list is always None, indicating the origin
801
revision. This might change when we have history horizons, or
802
perhaps we should have a new API.
804
This is topologically sorted.
806
if revision_id is None:
808
revision_id = osutils.safe_revision_id(revision_id)
809
if not self.has_revision(revision_id):
810
raise errors.NoSuchRevision(self, revision_id)
811
w = self.get_inventory_weave()
812
candidates = w.get_ancestry(revision_id)
813
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
816
def print_file(self, file, revision_id):
817
"""Print `file` to stdout.
819
FIXME RBC 20060125 as John Meinel points out this is a bad api
820
- it writes to stdout, it assumes that that is valid etc. Fix
821
by creating a new more flexible convenience function.
823
revision_id = osutils.safe_revision_id(revision_id)
824
tree = self.revision_tree(revision_id)
825
# use inventory as it was in that revision
826
file_id = tree.inventory.path2id(file)
828
# TODO: jam 20060427 Write a test for this code path
829
# it had a bug in it, and was raising the wrong
831
raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
832
tree.print_file(file_id)
834
def get_transaction(self):
835
return self.control_files.get_transaction()
837
def revision_parents(self, revision_id):
838
revision_id = osutils.safe_revision_id(revision_id)
839
return self.get_inventory_weave().parent_names(revision_id)
842
def set_make_working_trees(self, new_value):
843
"""Set the policy flag for making working trees when creating branches.
845
This only applies to branches that use this repository.
847
The default is 'True'.
848
:param new_value: True to restore the default, False to disable making
851
raise NotImplementedError(self.set_make_working_trees)
853
def make_working_trees(self):
854
"""Returns the policy for making working trees on new branches."""
855
raise NotImplementedError(self.make_working_trees)
858
def sign_revision(self, revision_id, gpg_strategy):
859
revision_id = osutils.safe_revision_id(revision_id)
860
plaintext = Testament.from_revision(self, revision_id).as_short_text()
861
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
864
def has_signature_for_revision_id(self, revision_id):
865
"""Query for a revision signature for revision_id in the repository."""
866
revision_id = osutils.safe_revision_id(revision_id)
867
return self._revision_store.has_signature(revision_id,
868
self.get_transaction())
871
def get_signature_text(self, revision_id):
872
"""Return the text for a signature."""
873
revision_id = osutils.safe_revision_id(revision_id)
874
return self._revision_store.get_signature_text(revision_id,
875
self.get_transaction())
878
def check(self, revision_ids):
879
"""Check consistency of all history of given revision_ids.
881
Different repository implementations should override _check().
883
:param revision_ids: A non-empty list of revision_ids whose ancestry
884
will be checked. Typically the last revision_id of a branch.
887
raise ValueError("revision_ids must be non-empty in %s.check"
889
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
890
return self._check(revision_ids)
892
def _check(self, revision_ids):
893
result = check.Check(self)
897
def _warn_if_deprecated(self):
898
global _deprecation_warning_done
899
if _deprecation_warning_done:
901
_deprecation_warning_done = True
902
warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
903
% (self._format, self.bzrdir.transport.base))
905
def supports_rich_root(self):
906
return self._format.rich_root_data
908
def _check_ascii_revisionid(self, revision_id, method):
909
"""Private helper for ascii-only repositories."""
910
# weave repositories refuse to store revisionids that are non-ascii.
911
if revision_id is not None:
912
# weaves require ascii revision ids.
913
if isinstance(revision_id, unicode):
915
revision_id.encode('ascii')
916
except UnicodeEncodeError:
917
raise errors.NonAsciiRevisionId(method, self)
920
revision_id.decode('ascii')
921
except UnicodeDecodeError:
922
raise errors.NonAsciiRevisionId(method, self)
926
# remove these delegates a while after bzr 0.15
927
def __make_delegated(name, from_module):
928
def _deprecated_repository_forwarder():
929
symbol_versioning.warn('%s moved to %s in bzr 0.15'
930
% (name, from_module),
933
m = __import__(from_module, globals(), locals(), [name])
935
return getattr(m, name)
936
except AttributeError:
937
raise AttributeError('module %s has no name %s'
939
globals()[name] = _deprecated_repository_forwarder
942
'AllInOneRepository',
943
'WeaveMetaDirRepository',
944
'PreSplitOutRepositoryFormat',
950
__make_delegated(_name, 'bzrlib.repofmt.weaverepo')
954
'RepositoryFormatKnit',
955
'RepositoryFormatKnit1',
957
__make_delegated(_name, 'bzrlib.repofmt.knitrepo')
960
def install_revision(repository, rev, revision_tree):
961
"""Install all revision data into a repository."""
964
for p_id in rev.parent_ids:
965
if repository.has_revision(p_id):
966
present_parents.append(p_id)
967
parent_trees[p_id] = repository.revision_tree(p_id)
969
parent_trees[p_id] = repository.revision_tree(None)
971
inv = revision_tree.inventory
972
entries = inv.iter_entries()
973
# backwards compatability hack: skip the root id.
974
if not repository.supports_rich_root():
975
path, root = entries.next()
976
if root.revision != rev.revision_id:
977
raise errors.IncompatibleRevision(repr(repository))
978
# Add the texts that are not already present
979
for path, ie in entries:
980
w = repository.weave_store.get_weave_or_empty(ie.file_id,
981
repository.get_transaction())
982
if ie.revision not in w:
984
# FIXME: TODO: The following loop *may* be overlapping/duplicate
985
# with InventoryEntry.find_previous_heads(). if it is, then there
986
# is a latent bug here where the parents may have ancestors of each
988
for revision, tree in parent_trees.iteritems():
989
if ie.file_id not in tree:
991
parent_id = tree.inventory[ie.file_id].revision
992
if parent_id in text_parents:
994
text_parents.append(parent_id)
996
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
997
repository.get_transaction())
998
lines = revision_tree.get_file(ie.file_id).readlines()
999
vfile.add_lines(rev.revision_id, text_parents, lines)
1001
# install the inventory
1002
repository.add_inventory(rev.revision_id, inv, present_parents)
1003
except errors.RevisionAlreadyPresent:
1005
repository.add_revision(rev.revision_id, rev, inv)
1008
class MetaDirRepository(Repository):
1009
"""Repositories in the new meta-dir layout."""
1011
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
1012
super(MetaDirRepository, self).__init__(_format,
1018
dir_mode = self.control_files._dir_mode
1019
file_mode = self.control_files._file_mode
1022
def is_shared(self):
1023
"""Return True if this repository is flagged as a shared repository."""
1024
return self.control_files._transport.has('shared-storage')
1027
def set_make_working_trees(self, new_value):
1028
"""Set the policy flag for making working trees when creating branches.
1030
This only applies to branches that use this repository.
1032
The default is 'True'.
1033
:param new_value: True to restore the default, False to disable making
1038
self.control_files._transport.delete('no-working-trees')
1039
except errors.NoSuchFile:
1042
self.control_files.put_utf8('no-working-trees', '')
1044
def make_working_trees(self):
1045
"""Returns the policy for making working trees on new branches."""
1046
return not self.control_files._transport.has('no-working-trees')
1049
class RepositoryFormatRegistry(registry.Registry):
1050
"""Registry of RepositoryFormats.
1053
def get(self, format_string):
1054
r = registry.Registry.get(self, format_string)
1060
format_registry = RepositoryFormatRegistry()
1061
"""Registry of formats, indexed by their identifying format string.
1063
This can contain either format instances themselves, or classes/factories that
1064
can be called to obtain one.
1068
#####################################################################
1069
# Repository Formats
1071
class RepositoryFormat(object):
1072
"""A repository format.
1074
Formats provide three things:
1075
* An initialization routine to construct repository data on disk.
1076
* a format string which is used when the BzrDir supports versioned
1078
* an open routine which returns a Repository instance.
1080
Formats are placed in an dict by their format string for reference
1081
during opening. These should be subclasses of RepositoryFormat
1084
Once a format is deprecated, just deprecate the initialize and open
1085
methods on the format class. Do not deprecate the object, as the
1086
object will be created every system load.
1088
Common instance attributes:
1089
_matchingbzrdir - the bzrdir format that the repository format was
1090
originally written to work with. This can be used if manually
1091
constructing a bzrdir and repository, or more commonly for test suite
1096
return "<%s>" % self.__class__.__name__
1098
def __eq__(self, other):
1099
# format objects are generally stateless
1100
return isinstance(other, self.__class__)
1102
def __ne__(self, other):
1103
return not self == other
1106
def find_format(klass, a_bzrdir):
1107
"""Return the format for the repository object in a_bzrdir.
1109
This is used by bzr native formats that have a "format" file in
1110
the repository. Other methods may be used by different types of
1114
transport = a_bzrdir.get_repository_transport(None)
1115
format_string = transport.get("format").read()
1116
return format_registry.get(format_string)
1117
except errors.NoSuchFile:
1118
raise errors.NoRepositoryPresent(a_bzrdir)
1120
raise errors.UnknownFormatError(format=format_string)
1123
def register_format(klass, format):
1124
format_registry.register(format.get_format_string(), format)
1127
def unregister_format(klass, format):
1128
format_registry.remove(format.get_format_string())
1131
def get_default_format(klass):
1132
"""Return the current default format."""
1133
from bzrlib import bzrdir
1134
return bzrdir.format_registry.make_bzrdir('default').repository_format
1136
def _get_control_store(self, repo_transport, control_files):
1137
"""Return the control store for this repository."""
1138
raise NotImplementedError(self._get_control_store)
1140
def get_format_string(self):
1141
"""Return the ASCII format string that identifies this format.
1143
Note that in pre format ?? repositories the format string is
1144
not permitted nor written to disk.
1146
raise NotImplementedError(self.get_format_string)
1148
def get_format_description(self):
1149
"""Return the short description for this format."""
1150
raise NotImplementedError(self.get_format_description)
1152
def _get_revision_store(self, repo_transport, control_files):
1153
"""Return the revision store object for this a_bzrdir."""
1154
raise NotImplementedError(self._get_revision_store)
1156
def _get_text_rev_store(self,
1163
"""Common logic for getting a revision store for a repository.
1165
see self._get_revision_store for the subclass-overridable method to
1166
get the store for a repository.
1168
from bzrlib.store.revision.text import TextRevisionStore
1169
dir_mode = control_files._dir_mode
1170
file_mode = control_files._file_mode
1171
text_store = TextStore(transport.clone(name),
1173
compressed=compressed,
1175
file_mode=file_mode)
1176
_revision_store = TextRevisionStore(text_store, serializer)
1177
return _revision_store
1179
# TODO: this shouldn't be in the base class, it's specific to things that
1180
# use weaves or knits -- mbp 20070207
1181
def _get_versioned_file_store(self,
1186
versionedfile_class=None,
1187
versionedfile_kwargs={},
1189
if versionedfile_class is None:
1190
versionedfile_class = self._versionedfile_class
1191
weave_transport = control_files._transport.clone(name)
1192
dir_mode = control_files._dir_mode
1193
file_mode = control_files._file_mode
1194
return VersionedFileStore(weave_transport, prefixed=prefixed,
1196
file_mode=file_mode,
1197
versionedfile_class=versionedfile_class,
1198
versionedfile_kwargs=versionedfile_kwargs,
1201
def initialize(self, a_bzrdir, shared=False):
1202
"""Initialize a repository of this format in a_bzrdir.
1204
:param a_bzrdir: The bzrdir to put the new repository in it.
1205
:param shared: The repository should be initialized as a sharable one.
1206
:returns: The new repository object.
1208
This may raise UninitializableFormat if shared repository are not
1209
compatible the a_bzrdir.
1211
raise NotImplementedError(self.initialize)
1213
def is_supported(self):
1214
"""Is this format supported?
1216
Supported formats must be initializable and openable.
1217
Unsupported formats may not support initialization or committing or
1218
some other features depending on the reason for not being supported.
1222
def check_conversion_target(self, target_format):
1223
raise NotImplementedError(self.check_conversion_target)
1225
def open(self, a_bzrdir, _found=False):
1226
"""Return an instance of this format for the bzrdir a_bzrdir.
1228
_found is a private parameter, do not use it.
1230
raise NotImplementedError(self.open)
1233
class MetaDirRepositoryFormat(RepositoryFormat):
1234
"""Common base class for the new repositories using the metadir layout."""
1236
rich_root_data = False
1237
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1240
super(MetaDirRepositoryFormat, self).__init__()
1242
def _create_control_files(self, a_bzrdir):
1243
"""Create the required files and the initial control_files object."""
1244
# FIXME: RBC 20060125 don't peek under the covers
1245
# NB: no need to escape relative paths that are url safe.
1246
repository_transport = a_bzrdir.get_repository_transport(self)
1247
control_files = lockable_files.LockableFiles(repository_transport,
1248
'lock', lockdir.LockDir)
1249
control_files.create_lock()
1250
return control_files
1252
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1253
"""Upload the initial blank content."""
1254
control_files = self._create_control_files(a_bzrdir)
1255
control_files.lock_write()
1257
control_files._transport.mkdir_multi(dirs,
1258
mode=control_files._dir_mode)
1259
for file, content in files:
1260
control_files.put(file, content)
1261
for file, content in utf8_files:
1262
control_files.put_utf8(file, content)
1264
control_files.put_utf8('shared-storage', '')
1266
control_files.unlock()
1269
# formats which have no format string are not discoverable
1270
# and not independently creatable, so are not registered. They're
1271
# all in bzrlib.repofmt.weaverepo now. When an instance of one of these is
1272
# needed, it's constructed directly by the BzrDir. Non-native formats where
1273
# the repository is not separately opened are similar.
1275
format_registry.register_lazy(
1276
'Bazaar-NG Repository format 7',
1277
'bzrlib.repofmt.weaverepo',
1280
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1281
# default control directory format
1283
format_registry.register_lazy(
1284
'Bazaar-NG Knit Repository Format 1',
1285
'bzrlib.repofmt.knitrepo',
1286
'RepositoryFormatKnit1',
1288
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
1290
format_registry.register_lazy(
1291
'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1292
'bzrlib.repofmt.knitrepo',
1293
'RepositoryFormatKnit3',
1297
class InterRepository(InterObject):
1298
"""This class represents operations taking place between two repositories.
1300
Its instances have methods like copy_content and fetch, and contain
1301
references to the source and target repositories these operations can be
1304
Often we will provide convenience methods on 'repository' which carry out
1305
operations with another repository - they will always forward to
1306
InterRepository.get(other).method_name(parameters).
1310
"""The available optimised InterRepository types."""
1312
def copy_content(self, revision_id=None, basis=None):
1313
raise NotImplementedError(self.copy_content)
1315
def fetch(self, revision_id=None, pb=None):
1316
"""Fetch the content required to construct revision_id.
1318
The content is copied from self.source to self.target.
1320
:param revision_id: if None all content is copied, if NULL_REVISION no
1322
:param pb: optional progress bar to use for progress reports. If not
1323
provided a default one will be created.
1325
Returns the copied revision count and the failed revisions in a tuple:
1328
raise NotImplementedError(self.fetch)
1331
def missing_revision_ids(self, revision_id=None):
1332
"""Return the revision ids that source has that target does not.
1334
These are returned in topological order.
1336
:param revision_id: only return revision ids included by this
1339
# generic, possibly worst case, slow code path.
1340
target_ids = set(self.target.all_revision_ids())
1341
if revision_id is not None:
1342
# TODO: jam 20070210 InterRepository is internal enough that it
1343
# should assume revision_ids are already utf-8
1344
revision_id = osutils.safe_revision_id(revision_id)
1345
source_ids = self.source.get_ancestry(revision_id)
1346
assert source_ids[0] is None
1349
source_ids = self.source.all_revision_ids()
1350
result_set = set(source_ids).difference(target_ids)
1351
# this may look like a no-op: its not. It preserves the ordering
1352
# other_ids had while only returning the members from other_ids
1353
# that we've decided we need.
1354
return [rev_id for rev_id in source_ids if rev_id in result_set]
1357
class InterSameDataRepository(InterRepository):
1358
"""Code for converting between repositories that represent the same data.
1360
Data format and model must match for this to work.
1364
def _get_repo_format_to_test(self):
1365
"""Repository format for testing with."""
1366
return RepositoryFormat.get_default_format()
1369
def is_compatible(source, target):
1370
if source.supports_rich_root() != target.supports_rich_root():
1372
if source._serializer != target._serializer:
1377
def copy_content(self, revision_id=None, basis=None):
1378
"""Make a complete copy of the content in self into destination.
1380
This is a destructive operation! Do not use it on existing
1383
:param revision_id: Only copy the content needed to construct
1384
revision_id and its parents.
1385
:param basis: Copy the needed data preferentially from basis.
1388
self.target.set_make_working_trees(self.source.make_working_trees())
1389
except NotImplementedError:
1391
# TODO: jam 20070210 This is fairly internal, so we should probably
1392
# just assert that revision_id is not unicode.
1393
revision_id = osutils.safe_revision_id(revision_id)
1394
# grab the basis available data
1395
if basis is not None:
1396
self.target.fetch(basis, revision_id=revision_id)
1397
# but don't bother fetching if we have the needed data now.
1398
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1399
self.target.has_revision(revision_id)):
1401
self.target.fetch(self.source, revision_id=revision_id)
1404
def fetch(self, revision_id=None, pb=None):
1405
"""See InterRepository.fetch()."""
1406
from bzrlib.fetch import GenericRepoFetcher
1407
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1408
self.source, self.source._format, self.target,
1409
self.target._format)
1410
# TODO: jam 20070210 This should be an assert, not a translate
1411
revision_id = osutils.safe_revision_id(revision_id)
1412
f = GenericRepoFetcher(to_repository=self.target,
1413
from_repository=self.source,
1414
last_revision=revision_id,
1416
return f.count_copied, f.failed_revisions
1419
class InterWeaveRepo(InterSameDataRepository):
1420
"""Optimised code paths between Weave based repositories."""
1423
def _get_repo_format_to_test(self):
1424
from bzrlib.repofmt import weaverepo
1425
return weaverepo.RepositoryFormat7()
1428
def is_compatible(source, target):
1429
"""Be compatible with known Weave formats.
1431
We don't test for the stores being of specific types because that
1432
could lead to confusing results, and there is no need to be
1435
from bzrlib.repofmt.weaverepo import (
1441
return (isinstance(source._format, (RepositoryFormat5,
1443
RepositoryFormat7)) and
1444
isinstance(target._format, (RepositoryFormat5,
1446
RepositoryFormat7)))
1447
except AttributeError:
1451
def copy_content(self, revision_id=None, basis=None):
1452
"""See InterRepository.copy_content()."""
1453
# weave specific optimised path:
1454
# TODO: jam 20070210 Internal, should be an assert, not translate
1455
revision_id = osutils.safe_revision_id(revision_id)
1456
if basis is not None:
1457
# copy the basis in, then fetch remaining data.
1458
basis.copy_content_into(self.target, revision_id)
1459
# the basis copy_content_into could miss-set this.
1461
self.target.set_make_working_trees(self.source.make_working_trees())
1462
except NotImplementedError:
1464
self.target.fetch(self.source, revision_id=revision_id)
1467
self.target.set_make_working_trees(self.source.make_working_trees())
1468
except NotImplementedError:
1470
# FIXME do not peek!
1471
if self.source.control_files._transport.listable():
1472
pb = ui.ui_factory.nested_progress_bar()
1474
self.target.weave_store.copy_all_ids(
1475
self.source.weave_store,
1477
from_transaction=self.source.get_transaction(),
1478
to_transaction=self.target.get_transaction())
1479
pb.update('copying inventory', 0, 1)
1480
self.target.control_weaves.copy_multi(
1481
self.source.control_weaves, ['inventory'],
1482
from_transaction=self.source.get_transaction(),
1483
to_transaction=self.target.get_transaction())
1484
self.target._revision_store.text_store.copy_all_ids(
1485
self.source._revision_store.text_store,
1490
self.target.fetch(self.source, revision_id=revision_id)
1493
def fetch(self, revision_id=None, pb=None):
1494
"""See InterRepository.fetch()."""
1495
from bzrlib.fetch import GenericRepoFetcher
1496
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1497
self.source, self.source._format, self.target, self.target._format)
1498
# TODO: jam 20070210 This should be an assert, not a translate
1499
revision_id = osutils.safe_revision_id(revision_id)
1500
f = GenericRepoFetcher(to_repository=self.target,
1501
from_repository=self.source,
1502
last_revision=revision_id,
1504
return f.count_copied, f.failed_revisions
1507
def missing_revision_ids(self, revision_id=None):
1508
"""See InterRepository.missing_revision_ids()."""
1509
# we want all revisions to satisfy revision_id in source.
1510
# but we don't want to stat every file here and there.
1511
# we want then, all revisions other needs to satisfy revision_id
1512
# checked, but not those that we have locally.
1513
# so the first thing is to get a subset of the revisions to
1514
# satisfy revision_id in source, and then eliminate those that
1515
# we do already have.
1516
# this is slow on high latency connection to self, but as as this
1517
# disk format scales terribly for push anyway due to rewriting
1518
# inventory.weave, this is considered acceptable.
1520
if revision_id is not None:
1521
source_ids = self.source.get_ancestry(revision_id)
1522
assert source_ids[0] is None
1525
source_ids = self.source._all_possible_ids()
1526
source_ids_set = set(source_ids)
1527
# source_ids is the worst possible case we may need to pull.
1528
# now we want to filter source_ids against what we actually
1529
# have in target, but don't try to check for existence where we know
1530
# we do not have a revision as that would be pointless.
1531
target_ids = set(self.target._all_possible_ids())
1532
possibly_present_revisions = target_ids.intersection(source_ids_set)
1533
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1534
required_revisions = source_ids_set.difference(actually_present_revisions)
1535
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1536
if revision_id is not None:
1537
# we used get_ancestry to determine source_ids then we are assured all
1538
# revisions referenced are present as they are installed in topological order.
1539
# and the tip revision was validated by get_ancestry.
1540
return required_topo_revisions
1542
# if we just grabbed the possibly available ids, then
1543
# we only have an estimate of whats available and need to validate
1544
# that against the revision records.
1545
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1548
class InterKnitRepo(InterSameDataRepository):
1549
"""Optimised code paths between Knit based repositories."""
1552
def _get_repo_format_to_test(self):
1553
from bzrlib.repofmt import knitrepo
1554
return knitrepo.RepositoryFormatKnit1()
1557
def is_compatible(source, target):
1558
"""Be compatible with known Knit formats.
1560
We don't test for the stores being of specific types because that
1561
could lead to confusing results, and there is no need to be
1564
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
1566
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1567
isinstance(target._format, (RepositoryFormatKnit1)))
1568
except AttributeError:
1572
def fetch(self, revision_id=None, pb=None):
1573
"""See InterRepository.fetch()."""
1574
from bzrlib.fetch import KnitRepoFetcher
1575
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1576
self.source, self.source._format, self.target, self.target._format)
1577
# TODO: jam 20070210 This should be an assert, not a translate
1578
revision_id = osutils.safe_revision_id(revision_id)
1579
f = KnitRepoFetcher(to_repository=self.target,
1580
from_repository=self.source,
1581
last_revision=revision_id,
1583
return f.count_copied, f.failed_revisions
1586
def missing_revision_ids(self, revision_id=None):
1587
"""See InterRepository.missing_revision_ids()."""
1588
if revision_id is not None:
1589
source_ids = self.source.get_ancestry(revision_id)
1590
assert source_ids[0] is None
1593
source_ids = self.source._all_possible_ids()
1594
source_ids_set = set(source_ids)
1595
# source_ids is the worst possible case we may need to pull.
1596
# now we want to filter source_ids against what we actually
1597
# have in target, but don't try to check for existence where we know
1598
# we do not have a revision as that would be pointless.
1599
target_ids = set(self.target._all_possible_ids())
1600
possibly_present_revisions = target_ids.intersection(source_ids_set)
1601
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1602
required_revisions = source_ids_set.difference(actually_present_revisions)
1603
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1604
if revision_id is not None:
1605
# we used get_ancestry to determine source_ids then we are assured all
1606
# revisions referenced are present as they are installed in topological order.
1607
# and the tip revision was validated by get_ancestry.
1608
return required_topo_revisions
1610
# if we just grabbed the possibly available ids, then
1611
# we only have an estimate of whats available and need to validate
1612
# that against the revision records.
1613
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1616
class InterModel1and2(InterRepository):
1619
def _get_repo_format_to_test(self):
1623
def is_compatible(source, target):
1624
if not source.supports_rich_root() and target.supports_rich_root():
1630
def fetch(self, revision_id=None, pb=None):
1631
"""See InterRepository.fetch()."""
1632
from bzrlib.fetch import Model1toKnit2Fetcher
1633
# TODO: jam 20070210 This should be an assert, not a translate
1634
revision_id = osutils.safe_revision_id(revision_id)
1635
f = Model1toKnit2Fetcher(to_repository=self.target,
1636
from_repository=self.source,
1637
last_revision=revision_id,
1639
return f.count_copied, f.failed_revisions
1642
def copy_content(self, revision_id=None, basis=None):
1643
"""Make a complete copy of the content in self into destination.
1645
This is a destructive operation! Do not use it on existing
1648
:param revision_id: Only copy the content needed to construct
1649
revision_id and its parents.
1650
:param basis: Copy the needed data preferentially from basis.
1653
self.target.set_make_working_trees(self.source.make_working_trees())
1654
except NotImplementedError:
1656
# TODO: jam 20070210 Internal, assert, don't translate
1657
revision_id = osutils.safe_revision_id(revision_id)
1658
# grab the basis available data
1659
if basis is not None:
1660
self.target.fetch(basis, revision_id=revision_id)
1661
# but don't bother fetching if we have the needed data now.
1662
if (revision_id not in (None, _mod_revision.NULL_REVISION) and
1663
self.target.has_revision(revision_id)):
1665
self.target.fetch(self.source, revision_id=revision_id)
1668
class InterKnit1and2(InterKnitRepo):
1671
def _get_repo_format_to_test(self):
1675
def is_compatible(source, target):
1676
"""Be compatible with Knit1 source and Knit3 target"""
1677
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
1679
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1680
RepositoryFormatKnit3
1681
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1682
isinstance(target._format, (RepositoryFormatKnit3)))
1683
except AttributeError:
1687
def fetch(self, revision_id=None, pb=None):
1688
"""See InterRepository.fetch()."""
1689
from bzrlib.fetch import Knit1to2Fetcher
1690
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1691
self.source, self.source._format, self.target,
1692
self.target._format)
1693
# TODO: jam 20070210 This should be an assert, not a translate
1694
revision_id = osutils.safe_revision_id(revision_id)
1695
f = Knit1to2Fetcher(to_repository=self.target,
1696
from_repository=self.source,
1697
last_revision=revision_id,
1699
return f.count_copied, f.failed_revisions
1702
InterRepository.register_optimiser(InterSameDataRepository)
1703
InterRepository.register_optimiser(InterWeaveRepo)
1704
InterRepository.register_optimiser(InterKnitRepo)
1705
InterRepository.register_optimiser(InterModel1and2)
1706
InterRepository.register_optimiser(InterKnit1and2)
1709
class RepositoryTestProviderAdapter(object):
1710
"""A tool to generate a suite testing multiple repository formats at once.
1712
This is done by copying the test once for each transport and injecting
1713
the transport_server, transport_readonly_server, and bzrdir_format and
1714
repository_format classes into each copy. Each copy is also given a new id()
1715
to make it easy to identify.
1718
def __init__(self, transport_server, transport_readonly_server, formats,
1719
vfs_transport_factory=None):
1720
self._transport_server = transport_server
1721
self._transport_readonly_server = transport_readonly_server
1722
self._vfs_transport_factory = vfs_transport_factory
1723
self._formats = formats
1725
def adapt(self, test):
1726
result = unittest.TestSuite()
1727
for repository_format, bzrdir_format in self._formats:
1728
from copy import deepcopy
1729
new_test = deepcopy(test)
1730
new_test.transport_server = self._transport_server
1731
new_test.transport_readonly_server = self._transport_readonly_server
1732
if self._vfs_transport_factory:
1733
new_test.vfs_transport_factory = self._vfs_transport_factory
1734
new_test.bzrdir_format = bzrdir_format
1735
new_test.repository_format = repository_format
1736
def make_new_test_id():
1737
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1738
return lambda: new_id
1739
new_test.id = make_new_test_id()
1740
result.addTest(new_test)
1744
class InterRepositoryTestProviderAdapter(object):
1745
"""A tool to generate a suite testing multiple inter repository formats.
1747
This is done by copying the test once for each interrepo provider and injecting
1748
the transport_server, transport_readonly_server, repository_format and
1749
repository_to_format classes into each copy.
1750
Each copy is also given a new id() to make it easy to identify.
1753
def __init__(self, transport_server, transport_readonly_server, formats):
1754
self._transport_server = transport_server
1755
self._transport_readonly_server = transport_readonly_server
1756
self._formats = formats
1758
def adapt(self, test):
1759
result = unittest.TestSuite()
1760
for interrepo_class, repository_format, repository_format_to in self._formats:
1761
from copy import deepcopy
1762
new_test = deepcopy(test)
1763
new_test.transport_server = self._transport_server
1764
new_test.transport_readonly_server = self._transport_readonly_server
1765
new_test.interrepo_class = interrepo_class
1766
new_test.repository_format = repository_format
1767
new_test.repository_format_to = repository_format_to
1768
def make_new_test_id():
1769
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1770
return lambda: new_id
1771
new_test.id = make_new_test_id()
1772
result.addTest(new_test)
1776
def default_test_list():
1777
"""Generate the default list of interrepo permutations to test."""
1778
from bzrlib.repofmt import knitrepo, weaverepo
1780
# test the default InterRepository between format 6 and the current
1782
# XXX: robertc 20060220 reinstate this when there are two supported
1783
# formats which do not have an optimal code path between them.
1784
#result.append((InterRepository,
1785
# RepositoryFormat6(),
1786
# RepositoryFormatKnit1()))
1787
for optimiser_class in InterRepository._optimisers:
1788
format_to_test = optimiser_class._get_repo_format_to_test()
1789
if format_to_test is not None:
1790
result.append((optimiser_class,
1791
format_to_test, format_to_test))
1792
# if there are specific combinations we want to use, we can add them
1794
result.append((InterModel1and2,
1795
weaverepo.RepositoryFormat5(),
1796
knitrepo.RepositoryFormatKnit3()))
1797
result.append((InterKnit1and2,
1798
knitrepo.RepositoryFormatKnit1(),
1799
knitrepo.RepositoryFormatKnit3()))
1803
class CopyConverter(object):
1804
"""A repository conversion tool which just performs a copy of the content.
1806
This is slow but quite reliable.
1809
def __init__(self, target_format):
1810
"""Create a CopyConverter.
1812
:param target_format: The format the resulting repository should be.
1814
self.target_format = target_format
1816
def convert(self, repo, pb):
1817
"""Perform the conversion of to_convert, giving feedback via pb.
1819
:param to_convert: The disk object to convert.
1820
:param pb: a progress bar to use for progress information.
1825
# this is only useful with metadir layouts - separated repo content.
1826
# trigger an assertion if not such
1827
repo._format.get_format_string()
1828
self.repo_dir = repo.bzrdir
1829
self.step('Moving repository to repository.backup')
1830
self.repo_dir.transport.move('repository', 'repository.backup')
1831
backup_transport = self.repo_dir.transport.clone('repository.backup')
1832
repo._format.check_conversion_target(self.target_format)
1833
self.source_repo = repo._format.open(self.repo_dir,
1835
_override_transport=backup_transport)
1836
self.step('Creating new repository')
1837
converted = self.target_format.initialize(self.repo_dir,
1838
self.source_repo.is_shared())
1839
converted.lock_write()
1841
self.step('Copying content into repository.')
1842
self.source_repo.copy_content_into(converted)
1845
self.step('Deleting old repository content.')
1846
self.repo_dir.transport.delete_tree('repository.backup')
1847
self.pb.note('repository converted')
1849
def step(self, message):
1850
"""Update the pb by a step."""
1852
self.pb.update(message, self.count, self.total)
1855
class CommitBuilder(object):
1856
"""Provides an interface to build up a commit.
1858
This allows describing a tree to be committed without needing to
1859
know the internals of the format of the repository.
1862
record_root_entry = False
1863
def __init__(self, repository, parents, config, timestamp=None,
1864
timezone=None, committer=None, revprops=None,
1866
"""Initiate a CommitBuilder.
1868
:param repository: Repository to commit to.
1869
:param parents: Revision ids of the parents of the new revision.
1870
:param config: Configuration to use.
1871
:param timestamp: Optional timestamp recorded for commit.
1872
:param timezone: Optional timezone for timestamp.
1873
:param committer: Optional committer to set for commit.
1874
:param revprops: Optional dictionary of revision properties.
1875
:param revision_id: Optional revision id.
1877
self._config = config
1879
if committer is None:
1880
self._committer = self._config.username()
1882
assert isinstance(committer, basestring), type(committer)
1883
self._committer = committer
1885
self.new_inventory = Inventory(None)
1886
self._new_revision_id = osutils.safe_revision_id(revision_id)
1887
self.parents = parents
1888
self.repository = repository
1891
if revprops is not None:
1892
self._revprops.update(revprops)
1894
if timestamp is None:
1895
timestamp = time.time()
1896
# Restrict resolution to 1ms
1897
self._timestamp = round(timestamp, 3)
1899
if timezone is None:
1900
self._timezone = osutils.local_time_offset()
1902
self._timezone = int(timezone)
1904
self._generate_revision_if_needed()
1906
def commit(self, message):
1907
"""Make the actual commit.
1909
:return: The revision id of the recorded revision.
1911
rev = _mod_revision.Revision(
1912
timestamp=self._timestamp,
1913
timezone=self._timezone,
1914
committer=self._committer,
1916
inventory_sha1=self.inv_sha1,
1917
revision_id=self._new_revision_id,
1918
properties=self._revprops)
1919
rev.parent_ids = self.parents
1920
self.repository.add_revision(self._new_revision_id, rev,
1921
self.new_inventory, self._config)
1922
return self._new_revision_id
1924
def revision_tree(self):
1925
"""Return the tree that was just committed.
1927
After calling commit() this can be called to get a RevisionTree
1928
representing the newly committed tree. This is preferred to
1929
calling Repository.revision_tree() because that may require
1930
deserializing the inventory, while we already have a copy in
1933
return RevisionTree(self.repository, self.new_inventory,
1934
self._new_revision_id)
1936
def finish_inventory(self):
1937
"""Tell the builder that the inventory is finished."""
1938
if self.new_inventory.root is None:
1939
symbol_versioning.warn('Root entry should be supplied to'
1940
' record_entry_contents, as of bzr 0.10.',
1941
DeprecationWarning, stacklevel=2)
1942
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
1943
self.new_inventory.revision_id = self._new_revision_id
1944
self.inv_sha1 = self.repository.add_inventory(
1945
self._new_revision_id,
1950
def _gen_revision_id(self):
1951
"""Return new revision-id."""
1952
return generate_ids.gen_revision_id(self._config.username(),
1955
def _generate_revision_if_needed(self):
1956
"""Create a revision id if None was supplied.
1958
If the repository can not support user-specified revision ids
1959
they should override this function and raise CannotSetRevisionId
1960
if _new_revision_id is not None.
1962
:raises: CannotSetRevisionId
1964
if self._new_revision_id is None:
1965
self._new_revision_id = self._gen_revision_id()
1967
def record_entry_contents(self, ie, parent_invs, path, tree):
1968
"""Record the content of ie from tree into the commit if needed.
1970
Side effect: sets ie.revision when unchanged
1972
:param ie: An inventory entry present in the commit.
1973
:param parent_invs: The inventories of the parent revisions of the
1975
:param path: The path the entry is at in the tree.
1976
:param tree: The tree which contains this entry and should be used to
1979
if self.new_inventory.root is None and ie.parent_id is not None:
1980
symbol_versioning.warn('Root entry should be supplied to'
1981
' record_entry_contents, as of bzr 0.10.',
1982
DeprecationWarning, stacklevel=2)
1983
self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
1985
self.new_inventory.add(ie)
1987
# ie.revision is always None if the InventoryEntry is considered
1988
# for committing. ie.snapshot will record the correct revision
1989
# which may be the sole parent if it is untouched.
1990
if ie.revision is not None:
1993
# In this revision format, root entries have no knit or weave
1994
if ie is self.new_inventory.root:
1995
# When serializing out to disk and back in
1996
# root.revision is always _new_revision_id
1997
ie.revision = self._new_revision_id
1999
previous_entries = ie.find_previous_heads(
2001
self.repository.weave_store,
2002
self.repository.get_transaction())
2003
# we are creating a new revision for ie in the history store
2005
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2007
def modified_directory(self, file_id, file_parents):
2008
"""Record the presence of a symbolic link.
2010
:param file_id: The file_id of the link to record.
2011
:param file_parents: The per-file parent revision ids.
2013
self._add_text_to_weave(file_id, [], file_parents.keys())
2015
def modified_reference(self, file_id, file_parents):
2016
"""Record the modification of a reference.
2018
:param file_id: The file_id of the link to record.
2019
:param file_parents: The per-file parent revision ids.
2021
self._add_text_to_weave(file_id, [], file_parents.keys())
2023
def modified_file_text(self, file_id, file_parents,
2024
get_content_byte_lines, text_sha1=None,
2026
"""Record the text of file file_id
2028
:param file_id: The file_id of the file to record the text of.
2029
:param file_parents: The per-file parent revision ids.
2030
:param get_content_byte_lines: A callable which will return the byte
2032
:param text_sha1: Optional SHA1 of the file contents.
2033
:param text_size: Optional size of the file contents.
2035
# mutter('storing text of file {%s} in revision {%s} into %r',
2036
# file_id, self._new_revision_id, self.repository.weave_store)
2037
# special case to avoid diffing on renames or
2039
if (len(file_parents) == 1
2040
and text_sha1 == file_parents.values()[0].text_sha1
2041
and text_size == file_parents.values()[0].text_size):
2042
previous_ie = file_parents.values()[0]
2043
versionedfile = self.repository.weave_store.get_weave(file_id,
2044
self.repository.get_transaction())
2045
versionedfile.clone_text(self._new_revision_id,
2046
previous_ie.revision, file_parents.keys())
2047
return text_sha1, text_size
2049
new_lines = get_content_byte_lines()
2050
# TODO: Rather than invoking sha_strings here, _add_text_to_weave
2051
# should return the SHA1 and size
2052
self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2053
return osutils.sha_strings(new_lines), \
2054
sum(map(len, new_lines))
2056
def modified_link(self, file_id, file_parents, link_target):
2057
"""Record the presence of a symbolic link.
2059
:param file_id: The file_id of the link to record.
2060
:param file_parents: The per-file parent revision ids.
2061
:param link_target: Target location of this link.
2063
self._add_text_to_weave(file_id, [], file_parents.keys())
2065
def _add_text_to_weave(self, file_id, new_lines, parents):
2066
versionedfile = self.repository.weave_store.get_weave_or_empty(
2067
file_id, self.repository.get_transaction())
2068
versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2069
versionedfile.clear_cache()
2072
class _CommitBuilder(CommitBuilder):
2073
"""Temporary class so old CommitBuilders are detected properly
2075
Note: CommitBuilder works whether or not root entry is recorded.
2078
record_root_entry = True
2081
class RootCommitBuilder(CommitBuilder):
2082
"""This commitbuilder actually records the root id"""
2084
record_root_entry = True
2086
def record_entry_contents(self, ie, parent_invs, path, tree):
2087
"""Record the content of ie from tree into the commit if needed.
2089
Side effect: sets ie.revision when unchanged
2091
:param ie: An inventory entry present in the commit.
2092
:param parent_invs: The inventories of the parent revisions of the
2094
:param path: The path the entry is at in the tree.
2095
:param tree: The tree which contains this entry and should be used to
2098
assert self.new_inventory.root is not None or ie.parent_id is None
2099
self.new_inventory.add(ie)
2101
# ie.revision is always None if the InventoryEntry is considered
2102
# for committing. ie.snapshot will record the correct revision
2103
# which may be the sole parent if it is untouched.
2104
if ie.revision is not None:
2107
previous_entries = ie.find_previous_heads(
2109
self.repository.weave_store,
2110
self.repository.get_transaction())
2111
# we are creating a new revision for ie in the history store
2113
ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2125
def _unescaper(match, _map=_unescape_map):
2126
code = match.group(1)
2130
if not code.startswith('#'):
2132
return unichr(int(code[1:])).encode('utf8')
2138
def _unescape_xml(data):
2139
"""Unescape predefined XML entities in a string of data."""
2141
if _unescape_re is None:
2142
_unescape_re = re.compile('\&([^;]*);')
2143
return _unescape_re.sub(_unescaper, data)