1
# Copyright (C) 2005, 2006 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 copy import deepcopy
18
from cStringIO import StringIO
19
from unittest import TestSuite
21
import bzrlib.bzrdir as bzrdir
22
from bzrlib.decorators import needs_read_lock, needs_write_lock
23
import bzrlib.errors as errors
24
from bzrlib.errors import InvalidRevisionId
25
import bzrlib.gpg as gpg
26
from bzrlib.graph import Graph
27
from bzrlib.inter import InterObject
28
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
29
from bzrlib.lockable_files import LockableFiles, TransportLock
30
from bzrlib.lockdir import LockDir
31
from bzrlib.osutils import safe_unicode
32
from bzrlib.revision import NULL_REVISION
33
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
34
from bzrlib.store.text import TextStore
35
from bzrlib.symbol_versioning import *
36
from bzrlib.trace import mutter, note
37
from bzrlib.tree import RevisionTree
38
from bzrlib.tsort import topo_sort
39
from bzrlib.testament import Testament
40
from bzrlib.tree import EmptyTree
42
from bzrlib.weave import WeaveFile
46
class Repository(object):
47
"""Repository holding history for one or more branches.
49
The repository holds and retrieves historical information including
50
revisions and file history. It's normally accessed only by the Branch,
51
which views a particular line of development through that history.
53
The Repository builds on top of Stores and a Transport, which respectively
54
describe the disk data format and the way of accessing the (possibly
59
def add_inventory(self, revid, inv, parents):
60
"""Add the inventory inv to the repository as revid.
62
:param parents: The revision ids of the parents that revid
63
is known to have and are in the repository already.
65
returns the sha1 of the serialized inventory.
67
inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
68
inv_sha1 = bzrlib.osutils.sha_string(inv_text)
69
inv_vf = self.control_weaves.get_weave('inventory',
70
self.get_transaction())
71
inv_vf.add_lines(revid, parents, bzrlib.osutils.split_lines(inv_text))
75
def add_revision(self, rev_id, rev, inv=None, config=None):
76
"""Add rev to the revision store as rev_id.
78
:param rev_id: the revision id to use.
79
:param rev: The revision object.
80
:param inv: The inventory for the revision. if None, it will be looked
81
up in the inventory storer
82
:param config: If None no digital signature will be created.
83
If supplied its signature_needed method will be used
84
to determine if a signature should be made.
86
if config is not None and config.signature_needed():
88
inv = self.get_inventory(rev_id)
89
plaintext = Testament(rev, inv).as_short_text()
90
self.store_revision_signature(
91
gpg.GPGStrategy(config), plaintext, rev_id)
92
if not rev_id in self.get_inventory_weave():
94
raise errors.WeaveRevisionNotPresent(rev_id,
95
self.get_inventory_weave())
97
# yes, this is not suitable for adding with ghosts.
98
self.add_inventory(rev_id, inv, rev.parent_ids)
99
self._revision_store.add_revision(rev, self.get_transaction())
102
def _all_possible_ids(self):
103
"""Return all the possible revisions that we could find."""
104
return self.get_inventory_weave().versions()
107
def all_revision_ids(self):
108
"""Returns a list of all the revision ids in the repository.
110
These are in as much topological order as the underlying store can
111
present: for weaves ghosts may lead to a lack of correctness until
112
the reweave updates the parents list.
114
if self._revision_store.text_store.listable():
115
return self._revision_store.all_revision_ids(self.get_transaction())
116
result = self._all_possible_ids()
117
return self._eliminate_revisions_not_present(result)
120
def _eliminate_revisions_not_present(self, revision_ids):
121
"""Check every revision id in revision_ids to see if we have it.
123
Returns a set of the present revisions.
126
for id in revision_ids:
127
if self.has_revision(id):
132
def create(a_bzrdir):
133
"""Construct the current default format repository in a_bzrdir."""
134
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
136
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
137
"""instantiate a Repository.
139
:param _format: The format of the repository on disk.
140
:param a_bzrdir: The BzrDir of the repository.
142
In the future we will have a single api for all stores for
143
getting file texts, inventories and revisions, then
144
this construct will accept instances of those things.
146
super(Repository, self).__init__()
147
self._format = _format
148
# the following are part of the public API for Repository:
149
self.bzrdir = a_bzrdir
150
self.control_files = control_files
151
self._revision_store = _revision_store
152
self.text_store = text_store
153
# backwards compatability
154
self.weave_store = text_store
155
# not right yet - should be more semantically clear ?
157
self.control_store = control_store
158
self.control_weaves = control_store
159
# TODO: make sure to construct the right store classes, etc, depending
160
# on whether escaping is required.
162
def lock_write(self):
163
self.control_files.lock_write()
166
self.control_files.lock_read()
169
return self.control_files.is_locked()
172
def missing_revision_ids(self, other, revision_id=None):
173
"""Return the revision ids that other has that this does not.
175
These are returned in topological order.
177
revision_id: only return revision ids included by revision_id.
179
return InterRepository.get(other, self).missing_revision_ids(revision_id)
183
"""Open the repository rooted at base.
185
For instance, if the repository is at URL/.bzr/repository,
186
Repository.open(URL) -> a Repository instance.
188
control = bzrlib.bzrdir.BzrDir.open(base)
189
return control.open_repository()
191
def copy_content_into(self, destination, revision_id=None, basis=None):
192
"""Make a complete copy of the content in self into destination.
194
This is a destructive operation! Do not use it on existing
197
return InterRepository.get(self, destination).copy_content(revision_id, basis)
199
def fetch(self, source, revision_id=None, pb=None):
200
"""Fetch the content required to construct revision_id from source.
202
If revision_id is None all content is copied.
204
return InterRepository.get(source, self).fetch(revision_id=revision_id,
208
self.control_files.unlock()
211
def clone(self, a_bzrdir, revision_id=None, basis=None):
212
"""Clone this repository into a_bzrdir using the current format.
214
Currently no check is made that the format of this repository and
215
the bzrdir format are compatible. FIXME RBC 20060201.
217
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
218
# use target default format.
219
result = a_bzrdir.create_repository()
220
# FIXME RBC 20060209 split out the repository type to avoid this check ?
221
elif isinstance(a_bzrdir._format,
222
(bzrlib.bzrdir.BzrDirFormat4,
223
bzrlib.bzrdir.BzrDirFormat5,
224
bzrlib.bzrdir.BzrDirFormat6)):
225
result = a_bzrdir.open_repository()
227
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
228
self.copy_content_into(result, revision_id, basis)
232
def has_revision(self, revision_id):
233
"""True if this repository has a copy of the revision."""
234
return self._revision_store.has_revision_id(revision_id,
235
self.get_transaction())
238
def get_revision_reconcile(self, revision_id):
239
"""'reconcile' helper routine that allows access to a revision always.
241
This variant of get_revision does not cross check the weave graph
242
against the revision one as get_revision does: but it should only
243
be used by reconcile, or reconcile-alike commands that are correcting
244
or testing the revision graph.
246
if not revision_id or not isinstance(revision_id, basestring):
247
raise InvalidRevisionId(revision_id=revision_id, branch=self)
248
return self._revision_store.get_revision(revision_id,
249
self.get_transaction())
252
def get_revision_xml(self, revision_id):
253
rev = self.get_revision(revision_id)
255
# the current serializer..
256
self._revision_store._serializer.write_revision(rev, rev_tmp)
258
return rev_tmp.getvalue()
261
def get_revision(self, revision_id):
262
"""Return the Revision object for a named revision"""
263
r = self.get_revision_reconcile(revision_id)
264
# weave corruption can lead to absent revision markers that should be
266
# the following test is reasonably cheap (it needs a single weave read)
267
# and the weave is cached in read transactions. In write transactions
268
# it is not cached but typically we only read a small number of
269
# revisions. For knits when they are introduced we will probably want
270
# to ensure that caching write transactions are in use.
271
inv = self.get_inventory_weave()
272
self._check_revision_parents(r, inv)
275
def _check_revision_parents(self, revision, inventory):
276
"""Private to Repository and Fetch.
278
This checks the parentage of revision in an inventory weave for
279
consistency and is only applicable to inventory-weave-for-ancestry
280
using repository formats & fetchers.
282
weave_parents = inventory.get_parents(revision.revision_id)
283
weave_names = inventory.versions()
284
for parent_id in revision.parent_ids:
285
if parent_id in weave_names:
286
# this parent must not be a ghost.
287
if not parent_id in weave_parents:
289
raise errors.CorruptRepository(self)
292
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
293
signature = gpg_strategy.sign(plaintext)
294
self._revision_store.add_revision_signature_text(revision_id,
296
self.get_transaction())
298
def fileids_altered_by_revision_ids(self, revision_ids):
299
"""Find the file ids and versions affected by revisions.
301
:param revisions: an iterable containing revision ids.
302
:return: a dictionary mapping altered file-ids to an iterable of
303
revision_ids. Each altered file-ids has the exact revision_ids that
304
altered it listed explicitly.
306
assert isinstance(self._format, (RepositoryFormat5,
309
RepositoryFormatKnit1)), \
310
"fileid_involved only supported for branches which store inventory as unnested xml"
311
selected_revision_ids = set(revision_ids)
312
w = self.get_inventory_weave()
315
# this code needs to read every new line in every inventory for the
316
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
317
# not pesent in one of those inventories is unnecessary but not
318
# harmful because we are filtering by the revision id marker in the
319
# inventory lines : we only select file ids altered in one of those
320
# revisions. We dont need to see all lines in the inventory because
321
# only those added in an inventory in rev X can contain a revision=X
323
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
324
start = line.find('file_id="')+9
325
if start < 9: continue
326
end = line.find('"', start)
328
file_id = _unescape_xml(line[start:end])
330
start = line.find('revision="')+10
331
if start < 10: continue
332
end = line.find('"', start)
334
revision_id = _unescape_xml(line[start:end])
335
if revision_id in selected_revision_ids:
336
result.setdefault(file_id, set()).add(revision_id)
340
def get_inventory_weave(self):
341
return self.control_weaves.get_weave('inventory',
342
self.get_transaction())
345
def get_inventory(self, revision_id):
346
"""Get Inventory object by hash."""
347
xml = self.get_inventory_xml(revision_id)
348
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
351
def get_inventory_xml(self, revision_id):
352
"""Get inventory XML as a file object."""
354
assert isinstance(revision_id, basestring), type(revision_id)
355
iw = self.get_inventory_weave()
356
return iw.get_text(revision_id)
358
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
361
def get_inventory_sha1(self, revision_id):
362
"""Return the sha1 hash of the inventory entry
364
return self.get_revision(revision_id).inventory_sha1
367
def get_revision_graph(self, revision_id=None):
368
"""Return a dictionary containing the revision graph.
370
:return: a dictionary of revision_id->revision_parents_list.
372
weave = self.get_inventory_weave()
373
all_revisions = self._eliminate_revisions_not_present(weave.versions())
374
entire_graph = dict([(node, weave.get_parents(node)) for
375
node in all_revisions])
376
if revision_id is None:
378
elif revision_id not in entire_graph:
379
raise errors.NoSuchRevision(self, revision_id)
381
# add what can be reached from revision_id
383
pending = set([revision_id])
384
while len(pending) > 0:
386
result[node] = entire_graph[node]
387
for revision_id in result[node]:
388
if revision_id not in result:
389
pending.add(revision_id)
393
def get_revision_graph_with_ghosts(self, revision_ids=None):
394
"""Return a graph of the revisions with ghosts marked as applicable.
396
:param revision_ids: an iterable of revisions to graph or None for all.
397
:return: a Graph object with the graph reachable from revision_ids.
401
pending = set(self.all_revision_ids())
404
pending = set(revision_ids)
405
required = set(revision_ids)
408
revision_id = pending.pop()
410
rev = self.get_revision(revision_id)
411
except errors.NoSuchRevision:
412
if revision_id in required:
415
result.add_ghost(revision_id)
417
for parent_id in rev.parent_ids:
418
# is this queued or done ?
419
if (parent_id not in pending and
420
parent_id not in done):
422
pending.add(parent_id)
423
result.add_node(revision_id, rev.parent_ids)
424
done.add(revision_id)
428
def get_revision_inventory(self, revision_id):
429
"""Return inventory of a past revision."""
430
# TODO: Unify this with get_inventory()
431
# bzr 0.0.6 and later imposes the constraint that the inventory_id
432
# must be the same as its revision, so this is trivial.
433
if revision_id is None:
434
# This does not make sense: if there is no revision,
435
# then it is the current tree inventory surely ?!
436
# and thus get_root_id() is something that looks at the last
437
# commit on the branch, and the get_root_id is an inventory check.
438
raise NotImplementedError
439
# return Inventory(self.get_root_id())
441
return self.get_inventory(revision_id)
445
"""Return True if this repository is flagged as a shared repository."""
446
raise NotImplementedError(self.is_shared)
450
"""Reconcile this repository."""
451
from bzrlib.reconcile import RepoReconciler
452
reconciler = RepoReconciler(self)
453
reconciler.reconcile()
457
def revision_tree(self, revision_id):
458
"""Return Tree for a revision on this branch.
460
`revision_id` may be None for the null revision, in which case
461
an `EmptyTree` is returned."""
462
# TODO: refactor this to use an existing revision object
463
# so we don't need to read it in twice.
464
if revision_id is None or revision_id == NULL_REVISION:
467
inv = self.get_revision_inventory(revision_id)
468
return RevisionTree(self, inv, revision_id)
471
def get_ancestry(self, revision_id):
472
"""Return a list of revision-ids integrated by a revision.
474
This is topologically sorted.
476
if revision_id is None:
478
if not self.has_revision(revision_id):
479
raise errors.NoSuchRevision(self, revision_id)
480
w = self.get_inventory_weave()
481
candidates = w.get_ancestry(revision_id)
482
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
485
def print_file(self, file, revision_id):
486
"""Print `file` to stdout.
488
FIXME RBC 20060125 as John Meinel points out this is a bad api
489
- it writes to stdout, it assumes that that is valid etc. Fix
490
by creating a new more flexible convenience function.
492
tree = self.revision_tree(revision_id)
493
# use inventory as it was in that revision
494
file_id = tree.inventory.path2id(file)
496
raise BzrError("%r is not present in revision %s" % (file, revno))
498
revno = self.revision_id_to_revno(revision_id)
499
except errors.NoSuchRevision:
500
# TODO: This should not be BzrError,
501
# but NoSuchFile doesn't fit either
502
raise BzrError('%r is not present in revision %s'
503
% (file, revision_id))
505
raise BzrError('%r is not present in revision %s'
507
tree.print_file(file_id)
509
def get_transaction(self):
510
return self.control_files.get_transaction()
512
def revision_parents(self, revid):
513
return self.get_inventory_weave().parent_names(revid)
516
def set_make_working_trees(self, new_value):
517
"""Set the policy flag for making working trees when creating branches.
519
This only applies to branches that use this repository.
521
The default is 'True'.
522
:param new_value: True to restore the default, False to disable making
525
raise NotImplementedError(self.set_make_working_trees)
527
def make_working_trees(self):
528
"""Returns the policy for making working trees on new branches."""
529
raise NotImplementedError(self.make_working_trees)
532
def sign_revision(self, revision_id, gpg_strategy):
533
plaintext = Testament.from_revision(self, revision_id).as_short_text()
534
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
537
def has_signature_for_revision_id(self, revision_id):
538
"""Query for a revision signature for revision_id in the repository."""
539
return self._revision_store.has_signature(revision_id,
540
self.get_transaction())
543
def get_signature_text(self, revision_id):
544
"""Return the text for a signature."""
545
return self._revision_store.get_signature_text(revision_id,
546
self.get_transaction())
549
class AllInOneRepository(Repository):
550
"""Legacy support - the repository behaviour for all-in-one branches."""
552
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
553
# we reuse one control files instance.
554
dir_mode = a_bzrdir._control_files._dir_mode
555
file_mode = a_bzrdir._control_files._file_mode
557
def get_store(name, compressed=True, prefixed=False):
558
# FIXME: This approach of assuming stores are all entirely compressed
559
# or entirely uncompressed is tidy, but breaks upgrade from
560
# some existing branches where there's a mixture; we probably
561
# still want the option to look for both.
562
relpath = a_bzrdir._control_files._escape(name)
563
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
564
prefixed=prefixed, compressed=compressed,
567
#if self._transport.should_cache():
568
# cache_path = os.path.join(self.cache_root, name)
569
# os.mkdir(cache_path)
570
# store = bzrlib.store.CachedStore(store, cache_path)
573
# not broken out yet because the controlweaves|inventory_store
574
# and text_store | weave_store bits are still different.
575
if isinstance(_format, RepositoryFormat4):
576
# cannot remove these - there is still no consistent api
577
# which allows access to this old info.
578
self.inventory_store = get_store('inventory-store')
579
text_store = get_store('text-store')
580
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
584
"""AllInOne repositories cannot be shared."""
588
def set_make_working_trees(self, new_value):
589
"""Set the policy flag for making working trees when creating branches.
591
This only applies to branches that use this repository.
593
The default is 'True'.
594
:param new_value: True to restore the default, False to disable making
597
raise NotImplementedError(self.set_make_working_trees)
599
def make_working_trees(self):
600
"""Returns the policy for making working trees on new branches."""
604
class MetaDirRepository(Repository):
605
"""Repositories in the new meta-dir layout."""
607
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
608
super(MetaDirRepository, self).__init__(_format,
615
dir_mode = self.control_files._dir_mode
616
file_mode = self.control_files._file_mode
620
"""Return True if this repository is flagged as a shared repository."""
621
return self.control_files._transport.has('shared-storage')
624
def set_make_working_trees(self, new_value):
625
"""Set the policy flag for making working trees when creating branches.
627
This only applies to branches that use this repository.
629
The default is 'True'.
630
:param new_value: True to restore the default, False to disable making
635
self.control_files._transport.delete('no-working-trees')
636
except errors.NoSuchFile:
639
self.control_files.put_utf8('no-working-trees', '')
641
def make_working_trees(self):
642
"""Returns the policy for making working trees on new branches."""
643
return not self.control_files._transport.has('no-working-trees')
646
class KnitRepository(MetaDirRepository):
647
"""Knit format repository."""
650
def all_revision_ids(self):
651
"""See Repository.all_revision_ids()."""
652
return self._revision_store.all_revision_ids(self.get_transaction())
654
def fileid_involved_between_revs(self, from_revid, to_revid):
655
"""Find file_id(s) which are involved in the changes between revisions.
657
This determines the set of revisions which are involved, and then
658
finds all file ids affected by those revisions.
660
vf = self._get_revision_vf()
661
from_set = set(vf.get_ancestry(from_revid))
662
to_set = set(vf.get_ancestry(to_revid))
663
changed = to_set.difference(from_set)
664
return self._fileid_involved_by_set(changed)
666
def fileid_involved(self, last_revid=None):
667
"""Find all file_ids modified in the ancestry of last_revid.
669
:param last_revid: If None, last_revision() will be used.
672
changed = set(self.all_revision_ids())
674
changed = set(self.get_ancestry(last_revid))
677
return self._fileid_involved_by_set(changed)
680
def get_ancestry(self, revision_id):
681
"""Return a list of revision-ids integrated by a revision.
683
This is topologically sorted.
685
if revision_id is None:
687
vf = self._get_revision_vf()
689
return [None] + vf.get_ancestry(revision_id)
690
except errors.RevisionNotPresent:
691
raise errors.NoSuchRevision(self, revision_id)
694
def get_revision(self, revision_id):
695
"""Return the Revision object for a named revision"""
696
return self.get_revision_reconcile(revision_id)
699
def get_revision_graph(self, revision_id=None):
700
"""Return a dictionary containing the revision graph.
702
:return: a dictionary of revision_id->revision_parents_list.
704
weave = self._get_revision_vf()
705
entire_graph = weave.get_graph()
706
if revision_id is None:
707
return weave.get_graph()
708
elif revision_id not in weave:
709
raise errors.NoSuchRevision(self, revision_id)
711
# add what can be reached from revision_id
713
pending = set([revision_id])
714
while len(pending) > 0:
716
result[node] = weave.get_parents(node)
717
for revision_id in result[node]:
718
if revision_id not in result:
719
pending.add(revision_id)
723
def get_revision_graph_with_ghosts(self, revision_ids=None):
724
"""Return a graph of the revisions with ghosts marked as applicable.
726
:param revision_ids: an iterable of revisions to graph or None for all.
727
:return: a Graph object with the graph reachable from revision_ids.
730
vf = self._get_revision_vf()
731
versions = set(vf.versions())
733
pending = set(self.all_revision_ids())
736
pending = set(revision_ids)
737
required = set(revision_ids)
740
revision_id = pending.pop()
741
if not revision_id in versions:
742
if revision_id in required:
743
raise errors.NoSuchRevision(self, revision_id)
745
result.add_ghost(revision_id)
746
# mark it as done so we dont try for it again.
747
done.add(revision_id)
749
parent_ids = vf.get_parents_with_ghosts(revision_id)
750
for parent_id in parent_ids:
751
# is this queued or done ?
752
if (parent_id not in pending and
753
parent_id not in done):
755
pending.add(parent_id)
756
result.add_node(revision_id, parent_ids)
757
done.add(revision_id)
760
def _get_revision_vf(self):
761
""":return: a versioned file containing the revisions."""
762
vf = self._revision_store.get_revision_file(self.get_transaction())
767
"""Reconcile this repository."""
768
from bzrlib.reconcile import KnitReconciler
769
reconciler = KnitReconciler(self)
770
reconciler.reconcile()
773
def revision_parents(self, revid):
774
return self._get_revision_vf().get_parents(rev_id)
776
class RepositoryFormat(object):
777
"""A repository format.
779
Formats provide three things:
780
* An initialization routine to construct repository data on disk.
781
* a format string which is used when the BzrDir supports versioned
783
* an open routine which returns a Repository instance.
785
Formats are placed in an dict by their format string for reference
786
during opening. These should be subclasses of RepositoryFormat
789
Once a format is deprecated, just deprecate the initialize and open
790
methods on the format class. Do not deprecate the object, as the
791
object will be created every system load.
793
Common instance attributes:
794
_matchingbzrdir - the bzrdir format that the repository format was
795
originally written to work with. This can be used if manually
796
constructing a bzrdir and repository, or more commonly for test suite
800
_default_format = None
801
"""The default format used for new repositories."""
804
"""The known formats."""
807
def find_format(klass, a_bzrdir):
808
"""Return the format for the repository object in a_bzrdir."""
810
transport = a_bzrdir.get_repository_transport(None)
811
format_string = transport.get("format").read()
812
return klass._formats[format_string]
813
except errors.NoSuchFile:
814
raise errors.NoRepositoryPresent(a_bzrdir)
816
raise errors.UnknownFormatError(format_string)
818
def _get_control_store(self, repo_transport, control_files):
819
"""Return the control store for this repository."""
820
raise NotImplementedError(self._get_control_store)
823
def get_default_format(klass):
824
"""Return the current default format."""
825
return klass._default_format
827
def get_format_string(self):
828
"""Return the ASCII format string that identifies this format.
830
Note that in pre format ?? repositories the format string is
831
not permitted nor written to disk.
833
raise NotImplementedError(self.get_format_string)
835
def get_format_description(self):
836
"""Return the short desciption for this format."""
837
raise NotImplementedError(self.get_format_description)
839
def _get_revision_store(self, repo_transport, control_files):
840
"""Return the revision store object for this a_bzrdir."""
841
raise NotImplementedError(self._get_revision_store)
843
def _get_text_rev_store(self,
850
"""Common logic for getting a revision store for a repository.
852
see self._get_revision_store for the subclass-overridable method to
853
get the store for a repository.
855
from bzrlib.store.revision.text import TextRevisionStore
856
dir_mode = control_files._dir_mode
857
file_mode = control_files._file_mode
858
text_store =TextStore(transport.clone(name),
860
compressed=compressed,
863
_revision_store = TextRevisionStore(text_store, serializer)
864
return _revision_store
866
def _get_versioned_file_store(self,
871
versionedfile_class=WeaveFile,
873
weave_transport = control_files._transport.clone(name)
874
dir_mode = control_files._dir_mode
875
file_mode = control_files._file_mode
876
return VersionedFileStore(weave_transport, prefixed=prefixed,
879
versionedfile_class=versionedfile_class,
882
def initialize(self, a_bzrdir, shared=False):
883
"""Initialize a repository of this format in a_bzrdir.
885
:param a_bzrdir: The bzrdir to put the new repository in it.
886
:param shared: The repository should be initialized as a sharable one.
888
This may raise UninitializableFormat if shared repository are not
889
compatible the a_bzrdir.
892
def is_supported(self):
893
"""Is this format supported?
895
Supported formats must be initializable and openable.
896
Unsupported formats may not support initialization or committing or
897
some other features depending on the reason for not being supported.
901
def open(self, a_bzrdir, _found=False):
902
"""Return an instance of this format for the bzrdir a_bzrdir.
904
_found is a private parameter, do not use it.
906
raise NotImplementedError(self.open)
909
def register_format(klass, format):
910
klass._formats[format.get_format_string()] = format
913
def set_default_format(klass, format):
914
klass._default_format = format
917
def unregister_format(klass, format):
918
assert klass._formats[format.get_format_string()] is format
919
del klass._formats[format.get_format_string()]
922
class PreSplitOutRepositoryFormat(RepositoryFormat):
923
"""Base class for the pre split out repository formats."""
925
def initialize(self, a_bzrdir, shared=False, _internal=False):
926
"""Create a weave repository.
928
TODO: when creating split out bzr branch formats, move this to a common
929
base for Format5, Format6. or something like that.
931
from bzrlib.weavefile import write_weave_v5
932
from bzrlib.weave import Weave
935
raise errors.IncompatibleFormat(self, a_bzrdir._format)
938
# always initialized when the bzrdir is.
939
return self.open(a_bzrdir, _found=True)
941
# Create an empty weave
943
bzrlib.weavefile.write_weave_v5(Weave(), sio)
944
empty_weave = sio.getvalue()
946
mutter('creating repository in %s.', a_bzrdir.transport.base)
947
dirs = ['revision-store', 'weaves']
948
files = [('inventory.weave', StringIO(empty_weave)),
951
# FIXME: RBC 20060125 dont peek under the covers
952
# NB: no need to escape relative paths that are url safe.
953
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
955
control_files.create_lock()
956
control_files.lock_write()
957
control_files._transport.mkdir_multi(dirs,
958
mode=control_files._dir_mode)
960
for file, content in files:
961
control_files.put(file, content)
963
control_files.unlock()
964
return self.open(a_bzrdir, _found=True)
966
def _get_control_store(self, repo_transport, control_files):
967
"""Return the control store for this repository."""
968
return self._get_versioned_file_store('',
973
def _get_text_store(self, transport, control_files):
974
"""Get a store for file texts for this format."""
975
raise NotImplementedError(self._get_text_store)
977
def open(self, a_bzrdir, _found=False):
978
"""See RepositoryFormat.open()."""
980
# we are being called directly and must probe.
981
raise NotImplementedError
983
repo_transport = a_bzrdir.get_repository_transport(None)
984
control_files = a_bzrdir._control_files
985
text_store = self._get_text_store(repo_transport, control_files)
986
control_store = self._get_control_store(repo_transport, control_files)
987
_revision_store = self._get_revision_store(repo_transport, control_files)
988
return AllInOneRepository(_format=self,
990
_revision_store=_revision_store,
991
control_store=control_store,
992
text_store=text_store)
995
class RepositoryFormat4(PreSplitOutRepositoryFormat):
996
"""Bzr repository format 4.
998
This repository format has:
1000
- TextStores for texts, inventories,revisions.
1002
This format is deprecated: it indexes texts using a text id which is
1003
removed in format 5; initializationa and write support for this format
1008
super(RepositoryFormat4, self).__init__()
1009
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
1011
def get_format_description(self):
1012
"""See RepositoryFormat.get_format_description()."""
1013
return "Repository format 4"
1015
def initialize(self, url, shared=False, _internal=False):
1016
"""Format 4 branches cannot be created."""
1017
raise errors.UninitializableFormat(self)
1019
def is_supported(self):
1020
"""Format 4 is not supported.
1022
It is not supported because the model changed from 4 to 5 and the
1023
conversion logic is expensive - so doing it on the fly was not
1028
def _get_control_store(self, repo_transport, control_files):
1029
"""Format 4 repositories have no formal control store at this point.
1031
This will cause any control-file-needing apis to fail - this is desired.
1035
def _get_revision_store(self, repo_transport, control_files):
1036
"""See RepositoryFormat._get_revision_store()."""
1037
from bzrlib.xml4 import serializer_v4
1038
return self._get_text_rev_store(repo_transport,
1041
serializer=serializer_v4)
1043
def _get_text_store(self, transport, control_files):
1044
"""See RepositoryFormat._get_text_store()."""
1047
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1048
"""Bzr control format 5.
1050
This repository format has:
1051
- weaves for file texts and inventory
1053
- TextStores for revisions and signatures.
1057
super(RepositoryFormat5, self).__init__()
1058
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
1060
def get_format_description(self):
1061
"""See RepositoryFormat.get_format_description()."""
1062
return "Weave repository format 5"
1064
def _get_revision_store(self, repo_transport, control_files):
1065
"""See RepositoryFormat._get_revision_store()."""
1066
"""Return the revision store object for this a_bzrdir."""
1067
return self._get_text_rev_store(repo_transport,
1072
def _get_text_store(self, transport, control_files):
1073
"""See RepositoryFormat._get_text_store()."""
1074
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1077
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1078
"""Bzr control format 6.
1080
This repository format has:
1081
- weaves for file texts and inventory
1082
- hash subdirectory based stores.
1083
- TextStores for revisions and signatures.
1087
super(RepositoryFormat6, self).__init__()
1088
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
1090
def get_format_description(self):
1091
"""See RepositoryFormat.get_format_description()."""
1092
return "Weave repository format 6"
1094
def _get_revision_store(self, repo_transport, control_files):
1095
"""See RepositoryFormat._get_revision_store()."""
1096
return self._get_text_rev_store(repo_transport,
1102
def _get_text_store(self, transport, control_files):
1103
"""See RepositoryFormat._get_text_store()."""
1104
return self._get_versioned_file_store('weaves', transport, control_files)
1107
class MetaDirRepositoryFormat(RepositoryFormat):
1108
"""Common base class for the new repositories using the metadir layour."""
1111
super(MetaDirRepositoryFormat, self).__init__()
1112
self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
1114
def _create_control_files(self, a_bzrdir):
1115
"""Create the required files and the initial control_files object."""
1116
# FIXME: RBC 20060125 dont peek under the covers
1117
# NB: no need to escape relative paths that are url safe.
1118
repository_transport = a_bzrdir.get_repository_transport(self)
1119
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1120
control_files.create_lock()
1121
return control_files
1123
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1124
"""Upload the initial blank content."""
1125
control_files = self._create_control_files(a_bzrdir)
1126
control_files.lock_write()
1128
control_files._transport.mkdir_multi(dirs,
1129
mode=control_files._dir_mode)
1130
for file, content in files:
1131
control_files.put(file, content)
1132
for file, content in utf8_files:
1133
control_files.put_utf8(file, content)
1135
control_files.put_utf8('shared-storage', '')
1137
control_files.unlock()
1140
class RepositoryFormat7(MetaDirRepositoryFormat):
1141
"""Bzr repository 7.
1143
This repository format has:
1144
- weaves for file texts and inventory
1145
- hash subdirectory based stores.
1146
- TextStores for revisions and signatures.
1147
- a format marker of its own
1148
- an optional 'shared-storage' flag
1149
- an optional 'no-working-trees' flag
1152
def _get_control_store(self, repo_transport, control_files):
1153
"""Return the control store for this repository."""
1154
return self._get_versioned_file_store('',
1159
def get_format_string(self):
1160
"""See RepositoryFormat.get_format_string()."""
1161
return "Bazaar-NG Repository format 7"
1163
def get_format_description(self):
1164
"""See RepositoryFormat.get_format_description()."""
1165
return "Weave repository format 7"
1167
def _get_revision_store(self, repo_transport, control_files):
1168
"""See RepositoryFormat._get_revision_store()."""
1169
return self._get_text_rev_store(repo_transport,
1176
def _get_text_store(self, transport, control_files):
1177
"""See RepositoryFormat._get_text_store()."""
1178
return self._get_versioned_file_store('weaves',
1182
def initialize(self, a_bzrdir, shared=False):
1183
"""Create a weave repository.
1185
:param shared: If true the repository will be initialized as a shared
1188
from bzrlib.weavefile import write_weave_v5
1189
from bzrlib.weave import Weave
1191
# Create an empty weave
1193
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1194
empty_weave = sio.getvalue()
1196
mutter('creating repository in %s.', a_bzrdir.transport.base)
1197
dirs = ['revision-store', 'weaves']
1198
files = [('inventory.weave', StringIO(empty_weave)),
1200
utf8_files = [('format', self.get_format_string())]
1202
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1203
return self.open(a_bzrdir=a_bzrdir, _found=True)
1205
def open(self, a_bzrdir, _found=False, _override_transport=None):
1206
"""See RepositoryFormat.open().
1208
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1209
repository at a slightly different url
1210
than normal. I.e. during 'upgrade'.
1213
format = RepositoryFormat.find_format(a_bzrdir)
1214
assert format.__class__ == self.__class__
1215
if _override_transport is not None:
1216
repo_transport = _override_transport
1218
repo_transport = a_bzrdir.get_repository_transport(None)
1219
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1220
text_store = self._get_text_store(repo_transport, control_files)
1221
control_store = self._get_control_store(repo_transport, control_files)
1222
_revision_store = self._get_revision_store(repo_transport, control_files)
1223
return MetaDirRepository(_format=self,
1225
control_files=control_files,
1226
_revision_store=_revision_store,
1227
control_store=control_store,
1228
text_store=text_store)
1231
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1232
"""Bzr repository knit format 1.
1234
This repository format has:
1235
- knits for file texts and inventory
1236
- hash subdirectory based stores.
1237
- knits for revisions and signatures
1238
- TextStores for revisions and signatures.
1239
- a format marker of its own
1240
- an optional 'shared-storage' flag
1241
- an optional 'no-working-trees' flag
1244
This format was introduced in bzr 0.8.
1247
def _get_control_store(self, repo_transport, control_files):
1248
"""Return the control store for this repository."""
1249
return VersionedFileStore(
1252
file_mode=control_files._file_mode,
1253
versionedfile_class=KnitVersionedFile,
1254
versionedfile_kwargs={'factory':KnitPlainFactory()},
1257
def get_format_string(self):
1258
"""See RepositoryFormat.get_format_string()."""
1259
return "Bazaar-NG Knit Repository Format 1"
1261
def get_format_description(self):
1262
"""See RepositoryFormat.get_format_description()."""
1263
return "Knit repository format 1"
1265
def _get_revision_store(self, repo_transport, control_files):
1266
"""See RepositoryFormat._get_revision_store()."""
1267
from bzrlib.store.revision.knit import KnitRevisionStore
1268
versioned_file_store = VersionedFileStore(
1270
file_mode=control_files._file_mode,
1273
versionedfile_class=KnitVersionedFile,
1274
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1277
return KnitRevisionStore(versioned_file_store)
1279
def _get_text_store(self, transport, control_files):
1280
"""See RepositoryFormat._get_text_store()."""
1281
return self._get_versioned_file_store('knits',
1284
versionedfile_class=KnitVersionedFile,
1287
def initialize(self, a_bzrdir, shared=False):
1288
"""Create a knit format 1 repository.
1290
:param a_bzrdir: bzrdir to contain the new repository; must already
1292
:param shared: If true the repository will be initialized as a shared
1295
mutter('creating repository in %s.', a_bzrdir.transport.base)
1296
dirs = ['revision-store', 'knits']
1298
utf8_files = [('format', self.get_format_string())]
1300
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1301
repo_transport = a_bzrdir.get_repository_transport(None)
1302
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1303
control_store = self._get_control_store(repo_transport, control_files)
1304
transaction = bzrlib.transactions.WriteTransaction()
1305
# trigger a write of the inventory store.
1306
control_store.get_weave_or_empty('inventory', transaction)
1307
_revision_store = self._get_revision_store(repo_transport, control_files)
1308
_revision_store.has_revision_id('A', transaction)
1309
_revision_store.get_signature_file(transaction)
1310
return self.open(a_bzrdir=a_bzrdir, _found=True)
1312
def open(self, a_bzrdir, _found=False, _override_transport=None):
1313
"""See RepositoryFormat.open().
1315
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1316
repository at a slightly different url
1317
than normal. I.e. during 'upgrade'.
1320
format = RepositoryFormat.find_format(a_bzrdir)
1321
assert format.__class__ == self.__class__
1322
if _override_transport is not None:
1323
repo_transport = _override_transport
1325
repo_transport = a_bzrdir.get_repository_transport(None)
1326
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1327
text_store = self._get_text_store(repo_transport, control_files)
1328
control_store = self._get_control_store(repo_transport, control_files)
1329
_revision_store = self._get_revision_store(repo_transport, control_files)
1330
return KnitRepository(_format=self,
1332
control_files=control_files,
1333
_revision_store=_revision_store,
1334
control_store=control_store,
1335
text_store=text_store)
1338
# formats which have no format string are not discoverable
1339
# and not independently creatable, so are not registered.
1340
RepositoryFormat.register_format(RepositoryFormat7())
1341
_default_format = RepositoryFormatKnit1()
1342
RepositoryFormat.register_format(_default_format)
1343
RepositoryFormat.set_default_format(_default_format)
1344
_legacy_formats = [RepositoryFormat4(),
1345
RepositoryFormat5(),
1346
RepositoryFormat6()]
1349
class InterRepository(InterObject):
1350
"""This class represents operations taking place between two repositories.
1352
Its instances have methods like copy_content and fetch, and contain
1353
references to the source and target repositories these operations can be
1356
Often we will provide convenience methods on 'repository' which carry out
1357
operations with another repository - they will always forward to
1358
InterRepository.get(other).method_name(parameters).
1362
"""The available optimised InterRepository types."""
1365
def copy_content(self, revision_id=None, basis=None):
1366
"""Make a complete copy of the content in self into destination.
1368
This is a destructive operation! Do not use it on existing
1371
:param revision_id: Only copy the content needed to construct
1372
revision_id and its parents.
1373
:param basis: Copy the needed data preferentially from basis.
1376
self.target.set_make_working_trees(self.source.make_working_trees())
1377
except NotImplementedError:
1379
# grab the basis available data
1380
if basis is not None:
1381
self.target.fetch(basis, revision_id=revision_id)
1382
# but dont bother fetching if we have the needed data now.
1383
if (revision_id not in (None, NULL_REVISION) and
1384
self.target.has_revision(revision_id)):
1386
self.target.fetch(self.source, revision_id=revision_id)
1388
def _double_lock(self, lock_source, lock_target):
1389
"""Take out too locks, rolling back the first if the second throws."""
1394
# we want to ensure that we don't leave source locked by mistake.
1395
# and any error on target should not confuse source.
1396
self.source.unlock()
1400
def fetch(self, revision_id=None, pb=None):
1401
"""Fetch the content required to construct revision_id.
1403
The content is copied from source to target.
1405
:param revision_id: if None all content is copied, if NULL_REVISION no
1407
:param pb: optional progress bar to use for progress reports. If not
1408
provided a default one will be created.
1410
Returns the copied revision count and the failed revisions in a tuple:
1413
from bzrlib.fetch import GenericRepoFetcher
1414
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1415
self.source, self.source._format, self.target, self.target._format)
1416
f = GenericRepoFetcher(to_repository=self.target,
1417
from_repository=self.source,
1418
last_revision=revision_id,
1420
return f.count_copied, f.failed_revisions
1422
def lock_read(self):
1423
"""Take out a logical read lock.
1425
This will lock the source branch and the target branch. The source gets
1426
a read lock and the target a read lock.
1428
self._double_lock(self.source.lock_read, self.target.lock_read)
1430
def lock_write(self):
1431
"""Take out a logical write lock.
1433
This will lock the source branch and the target branch. The source gets
1434
a read lock and the target a write lock.
1436
self._double_lock(self.source.lock_read, self.target.lock_write)
1439
def missing_revision_ids(self, revision_id=None):
1440
"""Return the revision ids that source has that target does not.
1442
These are returned in topological order.
1444
:param revision_id: only return revision ids included by this
1447
# generic, possibly worst case, slow code path.
1448
target_ids = set(self.target.all_revision_ids())
1449
if revision_id is not None:
1450
source_ids = self.source.get_ancestry(revision_id)
1451
assert source_ids.pop(0) == None
1453
source_ids = self.source.all_revision_ids()
1454
result_set = set(source_ids).difference(target_ids)
1455
# this may look like a no-op: its not. It preserves the ordering
1456
# other_ids had while only returning the members from other_ids
1457
# that we've decided we need.
1458
return [rev_id for rev_id in source_ids if rev_id in result_set]
1461
"""Release the locks on source and target."""
1463
self.target.unlock()
1465
self.source.unlock()
1468
class InterWeaveRepo(InterRepository):
1469
"""Optimised code paths between Weave based repositories."""
1471
_matching_repo_format = RepositoryFormat7()
1472
"""Repository format for testing with."""
1475
def is_compatible(source, target):
1476
"""Be compatible with known Weave formats.
1478
We dont test for the stores being of specific types becase that
1479
could lead to confusing results, and there is no need to be
1483
return (isinstance(source._format, (RepositoryFormat5,
1485
RepositoryFormat7)) and
1486
isinstance(target._format, (RepositoryFormat5,
1488
RepositoryFormat7)))
1489
except AttributeError:
1493
def copy_content(self, revision_id=None, basis=None):
1494
"""See InterRepository.copy_content()."""
1495
# weave specific optimised path:
1496
if basis is not None:
1497
# copy the basis in, then fetch remaining data.
1498
basis.copy_content_into(self.target, revision_id)
1499
# the basis copy_content_into could misset this.
1501
self.target.set_make_working_trees(self.source.make_working_trees())
1502
except NotImplementedError:
1504
self.target.fetch(self.source, revision_id=revision_id)
1507
self.target.set_make_working_trees(self.source.make_working_trees())
1508
except NotImplementedError:
1510
# FIXME do not peek!
1511
if self.source.control_files._transport.listable():
1512
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1514
self.target.weave_store.copy_all_ids(
1515
self.source.weave_store,
1517
from_transaction=self.source.get_transaction(),
1518
to_transaction=self.target.get_transaction())
1519
pb.update('copying inventory', 0, 1)
1520
self.target.control_weaves.copy_multi(
1521
self.source.control_weaves, ['inventory'],
1522
from_transaction=self.source.get_transaction(),
1523
to_transaction=self.target.get_transaction())
1524
self.target._revision_store.text_store.copy_all_ids(
1525
self.source._revision_store.text_store,
1530
self.target.fetch(self.source, revision_id=revision_id)
1533
def fetch(self, revision_id=None, pb=None):
1534
"""See InterRepository.fetch()."""
1535
from bzrlib.fetch import GenericRepoFetcher
1536
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1537
self.source, self.source._format, self.target, self.target._format)
1538
f = GenericRepoFetcher(to_repository=self.target,
1539
from_repository=self.source,
1540
last_revision=revision_id,
1542
return f.count_copied, f.failed_revisions
1545
def missing_revision_ids(self, revision_id=None):
1546
"""See InterRepository.missing_revision_ids()."""
1547
# we want all revisions to satisfy revision_id in source.
1548
# but we dont want to stat every file here and there.
1549
# we want then, all revisions other needs to satisfy revision_id
1550
# checked, but not those that we have locally.
1551
# so the first thing is to get a subset of the revisions to
1552
# satisfy revision_id in source, and then eliminate those that
1553
# we do already have.
1554
# this is slow on high latency connection to self, but as as this
1555
# disk format scales terribly for push anyway due to rewriting
1556
# inventory.weave, this is considered acceptable.
1558
if revision_id is not None:
1559
source_ids = self.source.get_ancestry(revision_id)
1560
assert source_ids.pop(0) == None
1562
source_ids = self.source._all_possible_ids()
1563
source_ids_set = set(source_ids)
1564
# source_ids is the worst possible case we may need to pull.
1565
# now we want to filter source_ids against what we actually
1566
# have in target, but dont try to check for existence where we know
1567
# we do not have a revision as that would be pointless.
1568
target_ids = set(self.target._all_possible_ids())
1569
possibly_present_revisions = target_ids.intersection(source_ids_set)
1570
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1571
required_revisions = source_ids_set.difference(actually_present_revisions)
1572
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1573
if revision_id is not None:
1574
# we used get_ancestry to determine source_ids then we are assured all
1575
# revisions referenced are present as they are installed in topological order.
1576
# and the tip revision was validated by get_ancestry.
1577
return required_topo_revisions
1579
# if we just grabbed the possibly available ids, then
1580
# we only have an estimate of whats available and need to validate
1581
# that against the revision records.
1582
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1585
class InterKnitRepo(InterRepository):
1586
"""Optimised code paths between Knit based repositories."""
1588
_matching_repo_format = RepositoryFormatKnit1()
1589
"""Repository format for testing with."""
1592
def is_compatible(source, target):
1593
"""Be compatible with known Knit formats.
1595
We dont test for the stores being of specific types becase that
1596
could lead to confusing results, and there is no need to be
1600
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1601
isinstance(target._format, (RepositoryFormatKnit1)))
1602
except AttributeError:
1606
def fetch(self, revision_id=None, pb=None):
1607
"""See InterRepository.fetch()."""
1608
from bzrlib.fetch import KnitRepoFetcher
1609
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1610
self.source, self.source._format, self.target, self.target._format)
1611
f = KnitRepoFetcher(to_repository=self.target,
1612
from_repository=self.source,
1613
last_revision=revision_id,
1615
return f.count_copied, f.failed_revisions
1618
def missing_revision_ids(self, revision_id=None):
1619
"""See InterRepository.missing_revision_ids()."""
1620
if revision_id is not None:
1621
source_ids = self.source.get_ancestry(revision_id)
1622
assert source_ids.pop(0) == None
1624
source_ids = self.source._all_possible_ids()
1625
source_ids_set = set(source_ids)
1626
# source_ids is the worst possible case we may need to pull.
1627
# now we want to filter source_ids against what we actually
1628
# have in target, but dont try to check for existence where we know
1629
# we do not have a revision as that would be pointless.
1630
target_ids = set(self.target._all_possible_ids())
1631
possibly_present_revisions = target_ids.intersection(source_ids_set)
1632
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1633
required_revisions = source_ids_set.difference(actually_present_revisions)
1634
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1635
if revision_id is not None:
1636
# we used get_ancestry to determine source_ids then we are assured all
1637
# revisions referenced are present as they are installed in topological order.
1638
# and the tip revision was validated by get_ancestry.
1639
return required_topo_revisions
1641
# if we just grabbed the possibly available ids, then
1642
# we only have an estimate of whats available and need to validate
1643
# that against the revision records.
1644
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1646
InterRepository.register_optimiser(InterWeaveRepo)
1647
InterRepository.register_optimiser(InterKnitRepo)
1650
class RepositoryTestProviderAdapter(object):
1651
"""A tool to generate a suite testing multiple repository formats at once.
1653
This is done by copying the test once for each transport and injecting
1654
the transport_server, transport_readonly_server, and bzrdir_format and
1655
repository_format classes into each copy. Each copy is also given a new id()
1656
to make it easy to identify.
1659
def __init__(self, transport_server, transport_readonly_server, formats):
1660
self._transport_server = transport_server
1661
self._transport_readonly_server = transport_readonly_server
1662
self._formats = formats
1664
def adapt(self, test):
1665
result = TestSuite()
1666
for repository_format, bzrdir_format in self._formats:
1667
new_test = deepcopy(test)
1668
new_test.transport_server = self._transport_server
1669
new_test.transport_readonly_server = self._transport_readonly_server
1670
new_test.bzrdir_format = bzrdir_format
1671
new_test.repository_format = repository_format
1672
def make_new_test_id():
1673
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1674
return lambda: new_id
1675
new_test.id = make_new_test_id()
1676
result.addTest(new_test)
1680
class InterRepositoryTestProviderAdapter(object):
1681
"""A tool to generate a suite testing multiple inter repository formats.
1683
This is done by copying the test once for each interrepo provider and injecting
1684
the transport_server, transport_readonly_server, repository_format and
1685
repository_to_format classes into each copy.
1686
Each copy is also given a new id() to make it easy to identify.
1689
def __init__(self, transport_server, transport_readonly_server, formats):
1690
self._transport_server = transport_server
1691
self._transport_readonly_server = transport_readonly_server
1692
self._formats = formats
1694
def adapt(self, test):
1695
result = TestSuite()
1696
for interrepo_class, repository_format, repository_format_to in self._formats:
1697
new_test = deepcopy(test)
1698
new_test.transport_server = self._transport_server
1699
new_test.transport_readonly_server = self._transport_readonly_server
1700
new_test.interrepo_class = interrepo_class
1701
new_test.repository_format = repository_format
1702
new_test.repository_format_to = repository_format_to
1703
def make_new_test_id():
1704
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1705
return lambda: new_id
1706
new_test.id = make_new_test_id()
1707
result.addTest(new_test)
1711
def default_test_list():
1712
"""Generate the default list of interrepo permutations to test."""
1714
# test the default InterRepository between format 6 and the current
1716
# XXX: robertc 20060220 reinstate this when there are two supported
1717
# formats which do not have an optimal code path between them.
1718
result.append((InterRepository,
1719
RepositoryFormat6(),
1720
RepositoryFormatKnit1()))
1721
for optimiser in InterRepository._optimisers:
1722
result.append((optimiser,
1723
optimiser._matching_repo_format,
1724
optimiser._matching_repo_format
1726
# if there are specific combinations we want to use, we can add them
1731
class CopyConverter(object):
1732
"""A repository conversion tool which just performs a copy of the content.
1734
This is slow but quite reliable.
1737
def __init__(self, target_format):
1738
"""Create a CopyConverter.
1740
:param target_format: The format the resulting repository should be.
1742
self.target_format = target_format
1744
def convert(self, repo, pb):
1745
"""Perform the conversion of to_convert, giving feedback via pb.
1747
:param to_convert: The disk object to convert.
1748
:param pb: a progress bar to use for progress information.
1753
# this is only useful with metadir layouts - separated repo content.
1754
# trigger an assertion if not such
1755
repo._format.get_format_string()
1756
self.repo_dir = repo.bzrdir
1757
self.step('Moving repository to repository.backup')
1758
self.repo_dir.transport.move('repository', 'repository.backup')
1759
backup_transport = self.repo_dir.transport.clone('repository.backup')
1760
self.source_repo = repo._format.open(self.repo_dir,
1762
_override_transport=backup_transport)
1763
self.step('Creating new repository')
1764
converted = self.target_format.initialize(self.repo_dir,
1765
self.source_repo.is_shared())
1766
converted.lock_write()
1768
self.step('Copying content into repository.')
1769
self.source_repo.copy_content_into(converted)
1772
self.step('Deleting old repository content.')
1773
self.repo_dir.transport.delete_tree('repository.backup')
1774
self.pb.note('repository converted')
1776
def step(self, message):
1777
"""Update the pb by a step."""
1779
self.pb.update(message, self.count, self.total)
1782
# Copied from xml.sax.saxutils
1783
def _unescape_xml(data):
1784
"""Unescape &, <, and > in a string of data.
1786
data = data.replace("<", "<")
1787
data = data.replace(">", ">")
1788
# must do ampersand last
1789
return data.replace("&", "&")