1
# Copyright (C) 2005 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
20
import xml.sax.saxutils
23
from bzrlib.decorators import needs_read_lock, needs_write_lock
24
import bzrlib.errors as errors
25
from bzrlib.errors import InvalidRevisionId
26
import bzrlib.gpg as gpg
27
from bzrlib.inter import InterObject
28
from bzrlib.knit import KnitVersionedFile
29
from bzrlib.lockable_files import LockableFiles
30
from bzrlib.osutils import safe_unicode
31
from bzrlib.revision import NULL_REVISION
32
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
33
from bzrlib.store.text import TextStore
34
from bzrlib.symbol_versioning import *
35
from bzrlib.trace import mutter
36
from bzrlib.tree import RevisionTree
37
from bzrlib.tsort import topo_sort
38
from bzrlib.testament import Testament
39
from bzrlib.tree import EmptyTree
41
from bzrlib.weave import WeaveFile
45
class Repository(object):
46
"""Repository holding history for one or more branches.
48
The repository holds and retrieves historical information including
49
revisions and file history. It's normally accessed only by the Branch,
50
which views a particular line of development through that history.
52
The Repository builds on top of Stores and a Transport, which respectively
53
describe the disk data format and the way of accessing the (possibly
58
def add_inventory(self, revid, inv, parents):
59
"""Add the inventory inv to the repository as revid.
61
:param parents: The revision ids of the parents that revid
62
is known to have and are in the repository already.
64
returns the sha1 of the serialized inventory.
66
inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
67
inv_sha1 = bzrlib.osutils.sha_string(inv_text)
68
inv_vf = self.control_weaves.get_weave('inventory',
69
self.get_transaction())
70
inv_vf.add_lines(revid, parents, bzrlib.osutils.split_lines(inv_text))
74
def add_revision(self, rev_id, rev, inv=None, config=None):
75
"""Add rev to the revision store as rev_id.
77
:param rev_id: the revision id to use.
78
:param rev: The revision object.
79
:param inv: The inventory for the revision. if None, it will be looked
80
up in the inventory storer
81
:param config: If None no digital signature will be created.
82
If supplied its signature_needed method will be used
83
to determine if a signature should be made.
85
if config is not None and config.signature_needed():
87
inv = self.get_inventory(rev_id)
88
plaintext = Testament(rev, inv).as_short_text()
89
self.store_revision_signature(
90
gpg.GPGStrategy(config), plaintext, rev_id)
91
if not rev_id in self.get_inventory_weave():
93
raise errors.WeaveRevisionNotPresent(rev_id,
94
self.get_inventory_weave())
96
# yes, this is not suitable for adding with ghosts.
97
self.add_inventory(rev_id, inv, rev.parent_ids)
98
self._revision_store.add_revision(rev, self.get_transaction())
101
def _all_possible_ids(self):
102
"""Return all the possible revisions that we could find."""
103
return self.get_inventory_weave().versions()
106
def all_revision_ids(self):
107
"""Returns a list of all the revision ids in the repository.
109
These are in as much topological order as the underlying store can
110
present: for weaves ghosts may lead to a lack of correctness until
111
the reweave updates the parents list.
113
if self._revision_store.text_store.listable():
114
return self._revision_store.all_revision_ids(self.get_transaction())
115
result = self._all_possible_ids()
116
return self._eliminate_revisions_not_present(result)
119
def _eliminate_revisions_not_present(self, revision_ids):
120
"""Check every revision id in revision_ids to see if we have it.
122
Returns a set of the present revisions.
125
for id in revision_ids:
126
if self.has_revision(id):
131
def create(a_bzrdir):
132
"""Construct the current default format repository in a_bzrdir."""
133
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
135
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
136
"""instantiate a Repository.
138
:param _format: The format of the repository on disk.
139
:param a_bzrdir: The BzrDir of the repository.
141
In the future we will have a single api for all stores for
142
getting file texts, inventories and revisions, then
143
this construct will accept instances of those things.
145
object.__init__(self)
146
self._format = _format
147
# the following are part of the public API for Repository:
148
self.bzrdir = a_bzrdir
149
self.control_files = control_files
150
self._revision_store = _revision_store
151
self.text_store = text_store
152
# backwards compatability
153
self.weave_store = text_store
154
# not right yet - should be more semantically clear ?
156
self.control_store = control_store
157
self.control_weaves = control_store
159
def lock_write(self):
160
self.control_files.lock_write()
163
self.control_files.lock_read()
166
def missing_revision_ids(self, other, revision_id=None):
167
"""Return the revision ids that other has that this does not.
169
These are returned in topological order.
171
revision_id: only return revision ids included by revision_id.
173
return InterRepository.get(other, self).missing_revision_ids(revision_id)
177
"""Open the repository rooted at base.
179
For instance, if the repository is at URL/.bzr/repository,
180
Repository.open(URL) -> a Repository instance.
182
control = bzrlib.bzrdir.BzrDir.open(base)
183
return control.open_repository()
185
def copy_content_into(self, destination, revision_id=None, basis=None):
186
"""Make a complete copy of the content in self into destination.
188
This is a destructive operation! Do not use it on existing
191
return InterRepository.get(self, destination).copy_content(revision_id, basis)
193
def fetch(self, source, revision_id=None, pb=None):
194
"""Fetch the content required to construct revision_id from source.
196
If revision_id is None all content is copied.
198
return InterRepository.get(source, self).fetch(revision_id=revision_id,
202
self.control_files.unlock()
205
def clone(self, a_bzrdir, revision_id=None, basis=None):
206
"""Clone this repository into a_bzrdir using the current format.
208
Currently no check is made that the format of this repository and
209
the bzrdir format are compatible. FIXME RBC 20060201.
211
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
212
# use target default format.
213
result = a_bzrdir.create_repository()
214
# FIXME RBC 20060209 split out the repository type to avoid this check ?
215
elif isinstance(a_bzrdir._format,
216
(bzrlib.bzrdir.BzrDirFormat4,
217
bzrlib.bzrdir.BzrDirFormat5,
218
bzrlib.bzrdir.BzrDirFormat6)):
219
result = a_bzrdir.open_repository()
221
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
222
self.copy_content_into(result, revision_id, basis)
226
def has_revision(self, revision_id):
227
"""True if this repository has a copy of the revision."""
228
return self._revision_store.has_revision_id(revision_id,
229
self.get_transaction())
232
def get_revision_reconcile(self, revision_id):
233
"""'reconcile' helper routine that allows access to a revision always.
235
This variant of get_revision does not cross check the weave graph
236
against the revision one as get_revision does: but it should only
237
be used by reconcile, or reconcile-alike commands that are correcting
238
or testing the revision graph.
240
if not revision_id or not isinstance(revision_id, basestring):
241
raise InvalidRevisionId(revision_id=revision_id, branch=self)
242
return self._revision_store.get_revision(revision_id,
243
self.get_transaction())
246
def get_revision_xml(self, revision_id):
247
rev = self.get_revision(revision_id)
249
# the current serializer..
250
self._revision_store._serializer.write_revision(rev, rev_tmp)
252
return rev_tmp.getvalue()
255
def get_revision(self, revision_id):
256
"""Return the Revision object for a named revision"""
257
r = self.get_revision_reconcile(revision_id)
258
# weave corruption can lead to absent revision markers that should be
260
# the following test is reasonably cheap (it needs a single weave read)
261
# and the weave is cached in read transactions. In write transactions
262
# it is not cached but typically we only read a small number of
263
# revisions. For knits when they are introduced we will probably want
264
# to ensure that caching write transactions are in use.
265
inv = self.get_inventory_weave()
266
self._check_revision_parents(r, inv)
269
def _check_revision_parents(self, revision, inventory):
270
"""Private to Repository and Fetch.
272
This checks the parentage of revision in an inventory weave for
273
consistency and is only applicable to inventory-weave-for-ancestry
274
using repository formats & fetchers.
276
weave_parents = inventory.get_parents(revision.revision_id)
277
weave_names = inventory.versions()
278
for parent_id in revision.parent_ids:
279
if parent_id in weave_names:
280
# this parent must not be a ghost.
281
if not parent_id in weave_parents:
283
raise errors.CorruptRepository(self)
286
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
287
signature = gpg_strategy.sign(plaintext)
288
self._revision_store.add_revision_signature_text(revision_id,
290
self.get_transaction())
292
def fileid_involved_between_revs(self, from_revid, to_revid):
293
"""Find file_id(s) which are involved in the changes between revisions.
295
This determines the set of revisions which are involved, and then
296
finds all file ids affected by those revisions.
298
# TODO: jam 20060119 This code assumes that w.inclusions will
299
# always be correct. But because of the presence of ghosts
300
# it is possible to be wrong.
301
# One specific example from Robert Collins:
302
# Two branches, with revisions ABC, and AD
303
# C is a ghost merge of D.
304
# Inclusions doesn't recognize D as an ancestor.
305
# If D is ever merged in the future, the weave
306
# won't be fixed, because AD never saw revision C
307
# to cause a conflict which would force a reweave.
308
w = self.get_inventory_weave()
309
from_set = set(w.get_ancestry(from_revid))
310
to_set = set(w.get_ancestry(to_revid))
311
changed = to_set.difference(from_set)
312
return self._fileid_involved_by_set(changed)
314
def fileid_involved(self, last_revid=None):
315
"""Find all file_ids modified in the ancestry of last_revid.
317
:param last_revid: If None, last_revision() will be used.
319
w = self.get_inventory_weave()
321
changed = set(w.versions())
323
changed = set(w.get_ancestry(last_revid))
324
return self._fileid_involved_by_set(changed)
326
def fileid_involved_by_set(self, changes):
327
"""Find all file_ids modified by the set of revisions passed in.
329
:param changes: A set() of revision ids
331
# TODO: jam 20060119 This line does *nothing*, remove it.
332
# or better yet, change _fileid_involved_by_set so
333
# that it takes the inventory weave, rather than
334
# pulling it out by itself.
335
return self._fileid_involved_by_set(changes)
337
def _fileid_involved_by_set(self, changes):
338
"""Find the set of file-ids affected by the set of revisions.
340
:param changes: A set() of revision ids.
341
:return: A set() of file ids.
343
This peaks at the Weave, interpreting each line, looking to
344
see if it mentions one of the revisions. And if so, includes
345
the file id mentioned.
346
This expects both the Weave format, and the serialization
347
to have a single line per file/directory, and to have
348
fileid="" and revision="" on that line.
350
assert isinstance(self._format, (RepositoryFormat5,
353
RepositoryFormatKnit1)), \
354
"fileid_involved only supported for branches which store inventory as unnested xml"
356
w = self.get_inventory_weave()
359
for lineno, insert, deletes, line in w.walk(changes):
360
start = line.find('file_id="')+9
361
if start < 9: continue
362
end = line.find('"', start)
364
file_id = xml.sax.saxutils.unescape(line[start:end])
366
# check if file_id is already present
367
if file_id in file_ids: continue
369
start = line.find('revision="')+10
370
if start < 10: continue
371
end = line.find('"', start)
373
revision_id = xml.sax.saxutils.unescape(line[start:end])
375
if revision_id in changes:
376
file_ids.add(file_id)
380
def get_inventory_weave(self):
381
return self.control_weaves.get_weave('inventory',
382
self.get_transaction())
385
def get_inventory(self, revision_id):
386
"""Get Inventory object by hash."""
387
xml = self.get_inventory_xml(revision_id)
388
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
391
def get_inventory_xml(self, revision_id):
392
"""Get inventory XML as a file object."""
394
assert isinstance(revision_id, basestring), type(revision_id)
395
iw = self.get_inventory_weave()
396
return iw.get_text(revision_id)
398
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
401
def get_inventory_sha1(self, revision_id):
402
"""Return the sha1 hash of the inventory entry
404
return self.get_revision(revision_id).inventory_sha1
407
def get_revision_graph(self, revision_id=None):
408
"""Return a dictionary containing the revision graph.
410
:return: a dictionary of revision_id->revision_parents_list.
412
weave = self.get_inventory_weave()
413
all_revisions = self._eliminate_revisions_not_present(weave.versions())
414
entire_graph = dict([(node, weave.get_parents(node)) for
415
node in all_revisions])
416
if revision_id is None:
418
elif revision_id not in entire_graph:
419
raise errors.NoSuchRevision(self, revision_id)
421
# add what can be reached from revision_id
423
pending = set([revision_id])
424
while len(pending) > 0:
426
result[node] = entire_graph[node]
427
for revision_id in result[node]:
428
if revision_id not in result:
429
pending.add(revision_id)
433
def get_revision_inventory(self, revision_id):
434
"""Return inventory of a past revision."""
435
# TODO: Unify this with get_inventory()
436
# bzr 0.0.6 and later imposes the constraint that the inventory_id
437
# must be the same as its revision, so this is trivial.
438
if revision_id is None:
439
# This does not make sense: if there is no revision,
440
# then it is the current tree inventory surely ?!
441
# and thus get_root_id() is something that looks at the last
442
# commit on the branch, and the get_root_id is an inventory check.
443
raise NotImplementedError
444
# return Inventory(self.get_root_id())
446
return self.get_inventory(revision_id)
450
"""Return True if this repository is flagged as a shared repository."""
451
# FIXME format 4-6 cannot be shared, this is technically faulty.
452
return self.control_files._transport.has('shared-storage')
455
def revision_tree(self, revision_id):
456
"""Return Tree for a revision on this branch.
458
`revision_id` may be None for the null revision, in which case
459
an `EmptyTree` is returned."""
460
# TODO: refactor this to use an existing revision object
461
# so we don't need to read it in twice.
462
if revision_id is None or revision_id == NULL_REVISION:
465
inv = self.get_revision_inventory(revision_id)
466
return RevisionTree(self, inv, revision_id)
469
def get_ancestry(self, revision_id):
470
"""Return a list of revision-ids integrated by a revision.
472
This is topologically sorted.
474
if revision_id is None:
476
if not self.has_revision(revision_id):
477
raise errors.NoSuchRevision(self, revision_id)
478
w = self.get_inventory_weave()
479
return [None] + w.get_ancestry(revision_id)
482
def print_file(self, file, revision_id):
483
"""Print `file` to stdout.
485
FIXME RBC 20060125 as John Meinel points out this is a bad api
486
- it writes to stdout, it assumes that that is valid etc. Fix
487
by creating a new more flexible convenience function.
489
tree = self.revision_tree(revision_id)
490
# use inventory as it was in that revision
491
file_id = tree.inventory.path2id(file)
493
raise BzrError("%r is not present in revision %s" % (file, revno))
495
revno = self.revision_id_to_revno(revision_id)
496
except errors.NoSuchRevision:
497
# TODO: This should not be BzrError,
498
# but NoSuchFile doesn't fit either
499
raise BzrError('%r is not present in revision %s'
500
% (file, revision_id))
502
raise BzrError('%r is not present in revision %s'
504
tree.print_file(file_id)
506
def get_transaction(self):
507
return self.control_files.get_transaction()
509
def revision_parents(self, revid):
510
return self.get_inventory_weave().parent_names(revid)
513
def set_make_working_trees(self, new_value):
514
"""Set the policy flag for making working trees when creating branches.
516
This only applies to branches that use this repository.
518
The default is 'True'.
519
:param new_value: True to restore the default, False to disable making
522
# FIXME: split out into a new class/strategy ?
523
if isinstance(self._format, (RepositoryFormat4,
526
raise NotImplementedError(self.set_make_working_trees)
529
self.control_files._transport.delete('no-working-trees')
530
except errors.NoSuchFile:
533
self.control_files.put_utf8('no-working-trees', '')
535
def make_working_trees(self):
536
"""Returns the policy for making working trees on new branches."""
537
# FIXME: split out into a new class/strategy ?
538
if isinstance(self._format, (RepositoryFormat4,
542
return not self.control_files._transport.has('no-working-trees')
545
def sign_revision(self, revision_id, gpg_strategy):
546
plaintext = Testament.from_revision(self, revision_id).as_short_text()
547
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
550
def has_signature_for_revision_id(self, revision_id):
551
"""Query for a revision signature for revision_id in the repository."""
552
return self._revision_store.has_signature(revision_id,
553
self.get_transaction())
556
def get_signature_text(self, revision_id):
557
"""Return the text for a signature."""
558
return self._revision_store.get_signature_text(revision_id,
559
self.get_transaction())
562
class AllInOneRepository(Repository):
563
"""Legacy support - the repository behaviour for all-in-one branches."""
565
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
566
# we reuse one control files instance.
567
dir_mode = a_bzrdir._control_files._dir_mode
568
file_mode = a_bzrdir._control_files._file_mode
570
def get_weave(name, prefixed=False):
572
name = safe_unicode(name)
575
relpath = a_bzrdir._control_files._escape(name)
576
weave_transport = a_bzrdir._control_files._transport.clone(relpath)
577
ws = WeaveStore(weave_transport, prefixed=prefixed,
580
if a_bzrdir._control_files._transport.should_cache():
581
ws.enable_cache = True
584
def get_store(name, compressed=True, prefixed=False):
585
# FIXME: This approach of assuming stores are all entirely compressed
586
# or entirely uncompressed is tidy, but breaks upgrade from
587
# some existing branches where there's a mixture; we probably
588
# still want the option to look for both.
589
relpath = a_bzrdir._control_files._escape(name)
590
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
591
prefixed=prefixed, compressed=compressed,
594
#if self._transport.should_cache():
595
# cache_path = os.path.join(self.cache_root, name)
596
# os.mkdir(cache_path)
597
# store = bzrlib.store.CachedStore(store, cache_path)
600
# not broken out yet because the controlweaves|inventory_store
601
# and text_store | weave_store bits are still different.
602
if isinstance(_format, RepositoryFormat4):
603
# cannot remove these - there is still no consistent api
604
# which allows access to this old info.
605
self.inventory_store = get_store('inventory-store')
606
text_store = get_store('text-store')
607
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
610
class MetaDirRepository(Repository):
611
"""Repositories in the new meta-dir layout."""
613
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
614
super(MetaDirRepository, self).__init__(_format,
621
dir_mode = self.control_files._dir_mode
622
file_mode = self.control_files._file_mode
624
def get_weave(name, prefixed=False):
626
name = safe_unicode(name)
629
relpath = self.control_files._escape(name)
630
weave_transport = self.control_files._transport.clone(relpath)
631
ws = WeaveStore(weave_transport, prefixed=prefixed,
634
if self.control_files._transport.should_cache():
635
ws.enable_cache = True
639
class KnitRepository(MetaDirRepository):
640
"""Knit format repository."""
643
def all_revision_ids(self):
644
"""See Repository.all_revision_ids()."""
645
return self._revision_store.all_revision_ids(self.get_transaction())
648
class RepositoryFormat(object):
649
"""A repository format.
651
Formats provide three things:
652
* An initialization routine to construct repository data on disk.
653
* a format string which is used when the BzrDir supports versioned
655
* an open routine which returns a Repository instance.
657
Formats are placed in an dict by their format string for reference
658
during opening. These should be subclasses of RepositoryFormat
661
Once a format is deprecated, just deprecate the initialize and open
662
methods on the format class. Do not deprecate the object, as the
663
object will be created every system load.
665
Common instance attributes:
666
_matchingbzrdir - the bzrdir format that the repository format was
667
originally written to work with. This can be used if manually
668
constructing a bzrdir and repository, or more commonly for test suite
672
_default_format = None
673
"""The default format used for new repositories."""
676
"""The known formats."""
679
def find_format(klass, a_bzrdir):
680
"""Return the format for the repository object in a_bzrdir."""
682
transport = a_bzrdir.get_repository_transport(None)
683
format_string = transport.get("format").read()
684
return klass._formats[format_string]
685
except errors.NoSuchFile:
686
raise errors.NoRepositoryPresent(a_bzrdir)
688
raise errors.UnknownFormatError(format_string)
690
def _get_control_store(self, repo_transport, control_files):
691
"""Return the control store for this repository."""
692
raise NotImplementedError(self._get_control_store)
695
def get_default_format(klass):
696
"""Return the current default format."""
697
return klass._default_format
699
def get_format_string(self):
700
"""Return the ASCII format string that identifies this format.
702
Note that in pre format ?? repositories the format string is
703
not permitted nor written to disk.
705
raise NotImplementedError(self.get_format_string)
707
def _get_revision_store(self, repo_transport, control_files):
708
"""Return the revision store object for this a_bzrdir."""
709
raise NotImplementedError(self._get_revision_store)
711
def _get_text_rev_store(self,
718
"""Common logic for getting a revision store for a repository.
720
see self._get_revision_store for the subclass-overridable method to
721
get the store for a repository.
723
from bzrlib.store.revision.text import TextRevisionStore
724
dir_mode = control_files._dir_mode
725
file_mode = control_files._file_mode
726
text_store =TextStore(transport.clone(name),
728
compressed=compressed,
731
_revision_store = TextRevisionStore(text_store, serializer)
732
return _revision_store
734
def _get_versioned_file_store(self,
739
versionedfile_class=WeaveFile):
740
weave_transport = control_files._transport.clone(name)
741
dir_mode = control_files._dir_mode
742
file_mode = control_files._file_mode
743
return VersionedFileStore(weave_transport, prefixed=prefixed,
746
versionedfile_class=versionedfile_class)
748
def initialize(self, a_bzrdir, shared=False):
749
"""Initialize a repository of this format in a_bzrdir.
751
:param a_bzrdir: The bzrdir to put the new repository in it.
752
:param shared: The repository should be initialized as a sharable one.
754
This may raise UninitializableFormat if shared repository are not
755
compatible the a_bzrdir.
758
def is_supported(self):
759
"""Is this format supported?
761
Supported formats must be initializable and openable.
762
Unsupported formats may not support initialization or committing or
763
some other features depending on the reason for not being supported.
767
def open(self, a_bzrdir, _found=False):
768
"""Return an instance of this format for the bzrdir a_bzrdir.
770
_found is a private parameter, do not use it.
772
raise NotImplementedError(self.open)
775
def register_format(klass, format):
776
klass._formats[format.get_format_string()] = format
779
def set_default_format(klass, format):
780
klass._default_format = format
783
def unregister_format(klass, format):
784
assert klass._formats[format.get_format_string()] is format
785
del klass._formats[format.get_format_string()]
788
class PreSplitOutRepositoryFormat(RepositoryFormat):
789
"""Base class for the pre split out repository formats."""
791
def initialize(self, a_bzrdir, shared=False, _internal=False):
792
"""Create a weave repository.
794
TODO: when creating split out bzr branch formats, move this to a common
795
base for Format5, Format6. or something like that.
797
from bzrlib.weavefile import write_weave_v5
798
from bzrlib.weave import Weave
801
raise errors.IncompatibleFormat(self, a_bzrdir._format)
804
# always initialized when the bzrdir is.
805
return self.open(a_bzrdir, _found=True)
807
# Create an empty weave
809
bzrlib.weavefile.write_weave_v5(Weave(), sio)
810
empty_weave = sio.getvalue()
812
mutter('creating repository in %s.', a_bzrdir.transport.base)
813
dirs = ['revision-store', 'weaves']
814
lock_file = 'branch-lock'
815
files = [('inventory.weave', StringIO(empty_weave)),
818
# FIXME: RBC 20060125 dont peek under the covers
819
# NB: no need to escape relative paths that are url safe.
820
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
821
control_files.lock_write()
822
control_files._transport.mkdir_multi(dirs,
823
mode=control_files._dir_mode)
825
for file, content in files:
826
control_files.put(file, content)
828
control_files.unlock()
829
return self.open(a_bzrdir, _found=True)
831
def _get_control_store(self, repo_transport, control_files):
832
"""Return the control store for this repository."""
833
return self._get_versioned_file_store('',
838
def _get_text_store(self, transport, control_files):
839
"""Get a store for file texts for this format."""
840
raise NotImplementedError(self._get_text_store)
842
def open(self, a_bzrdir, _found=False):
843
"""See RepositoryFormat.open()."""
845
# we are being called directly and must probe.
846
raise NotImplementedError
848
repo_transport = a_bzrdir.get_repository_transport(None)
849
control_files = a_bzrdir._control_files
850
text_store = self._get_text_store(repo_transport, control_files)
851
control_store = self._get_control_store(repo_transport, control_files)
852
_revision_store = self._get_revision_store(repo_transport, control_files)
853
return AllInOneRepository(_format=self,
855
_revision_store=_revision_store,
856
control_store=control_store,
857
text_store=text_store)
860
class RepositoryFormat4(PreSplitOutRepositoryFormat):
861
"""Bzr repository format 4.
863
This repository format has:
865
- TextStores for texts, inventories,revisions.
867
This format is deprecated: it indexes texts using a text id which is
868
removed in format 5; initializationa and write support for this format
873
super(RepositoryFormat4, self).__init__()
874
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
876
def initialize(self, url, shared=False, _internal=False):
877
"""Format 4 branches cannot be created."""
878
raise errors.UninitializableFormat(self)
880
def is_supported(self):
881
"""Format 4 is not supported.
883
It is not supported because the model changed from 4 to 5 and the
884
conversion logic is expensive - so doing it on the fly was not
889
def _get_control_store(self, repo_transport, control_files):
890
"""Format 4 repositories have no formal control store at this point.
892
This will cause any control-file-needing apis to fail - this is desired.
896
def _get_revision_store(self, repo_transport, control_files):
897
"""See RepositoryFormat._get_revision_store()."""
898
from bzrlib.xml4 import serializer_v4
899
return self._get_text_rev_store(repo_transport,
902
serializer=serializer_v4)
904
def _get_text_store(self, transport, control_files):
905
"""See RepositoryFormat._get_text_store()."""
908
class RepositoryFormat5(PreSplitOutRepositoryFormat):
909
"""Bzr control format 5.
911
This repository format has:
912
- weaves for file texts and inventory
914
- TextStores for revisions and signatures.
918
super(RepositoryFormat5, self).__init__()
919
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
921
def _get_revision_store(self, repo_transport, control_files):
922
"""See RepositoryFormat._get_revision_store()."""
923
"""Return the revision store object for this a_bzrdir."""
924
return self._get_text_rev_store(repo_transport,
929
def _get_text_store(self, transport, control_files):
930
"""See RepositoryFormat._get_text_store()."""
931
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
934
class RepositoryFormat6(PreSplitOutRepositoryFormat):
935
"""Bzr control format 6.
937
This repository format has:
938
- weaves for file texts and inventory
939
- hash subdirectory based stores.
940
- TextStores for revisions and signatures.
944
super(RepositoryFormat6, self).__init__()
945
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
947
def _get_revision_store(self, repo_transport, control_files):
948
"""See RepositoryFormat._get_revision_store()."""
949
return self._get_text_rev_store(repo_transport,
955
def _get_text_store(self, transport, control_files):
956
"""See RepositoryFormat._get_text_store()."""
957
return self._get_versioned_file_store('weaves', transport, control_files)
960
class MetaDirRepositoryFormat(RepositoryFormat):
961
"""Common base class for the new repositories using the metadir layour."""
964
super(MetaDirRepositoryFormat, self).__init__()
965
self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
967
def _create_control_files(self, a_bzrdir):
968
"""Create the required files and the initial control_files object."""
969
# FIXME: RBC 20060125 dont peek under the covers
970
# NB: no need to escape relative paths that are url safe.
972
repository_transport = a_bzrdir.get_repository_transport(self)
973
repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
974
control_files = LockableFiles(repository_transport, 'lock')
977
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
978
"""Upload the initial blank content."""
979
control_files = self._create_control_files(a_bzrdir)
980
control_files.lock_write()
981
control_files._transport.mkdir_multi(dirs,
982
mode=control_files._dir_mode)
984
for file, content in files:
985
control_files.put(file, content)
986
for file, content in utf8_files:
987
control_files.put_utf8(file, content)
989
control_files.put_utf8('shared-storage', '')
991
control_files.unlock()
994
class RepositoryFormat7(MetaDirRepositoryFormat):
997
This repository format has:
998
- weaves for file texts and inventory
999
- hash subdirectory based stores.
1000
- TextStores for revisions and signatures.
1001
- a format marker of its own
1002
- an optional 'shared-storage' flag
1003
- an optional 'no-working-trees' flag
1006
def _get_control_store(self, repo_transport, control_files):
1007
"""Return the control store for this repository."""
1008
return self._get_versioned_file_store('',
1013
def get_format_string(self):
1014
"""See RepositoryFormat.get_format_string()."""
1015
return "Bazaar-NG Repository format 7"
1017
def _get_revision_store(self, repo_transport, control_files):
1018
"""See RepositoryFormat._get_revision_store()."""
1019
return self._get_text_rev_store(repo_transport,
1026
def _get_text_store(self, transport, control_files):
1027
"""See RepositoryFormat._get_text_store()."""
1028
return self._get_versioned_file_store('weaves',
1032
def initialize(self, a_bzrdir, shared=False):
1033
"""Create a weave repository.
1035
:param shared: If true the repository will be initialized as a shared
1038
from bzrlib.weavefile import write_weave_v5
1039
from bzrlib.weave import Weave
1041
# Create an empty weave
1043
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1044
empty_weave = sio.getvalue()
1046
mutter('creating repository in %s.', a_bzrdir.transport.base)
1047
dirs = ['revision-store', 'weaves']
1048
files = [('inventory.weave', StringIO(empty_weave)),
1050
utf8_files = [('format', self.get_format_string())]
1052
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1053
return self.open(a_bzrdir=a_bzrdir, _found=True)
1055
def open(self, a_bzrdir, _found=False, _override_transport=None):
1056
"""See RepositoryFormat.open().
1058
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1059
repository at a slightly different url
1060
than normal. I.e. during 'upgrade'.
1063
format = RepositoryFormat.find_format(a_bzrdir)
1064
assert format.__class__ == self.__class__
1065
if _override_transport is not None:
1066
repo_transport = _override_transport
1068
repo_transport = a_bzrdir.get_repository_transport(None)
1069
control_files = LockableFiles(repo_transport, 'lock')
1070
text_store = self._get_text_store(repo_transport, control_files)
1071
control_store = self._get_control_store(repo_transport, control_files)
1072
_revision_store = self._get_revision_store(repo_transport, control_files)
1073
return MetaDirRepository(_format=self,
1075
control_files=control_files,
1076
_revision_store=_revision_store,
1077
control_store=control_store,
1078
text_store=text_store)
1081
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1082
"""Bzr repository knit format 1.
1084
This repository format has:
1085
- knits for file texts and inventory
1086
- hash subdirectory based stores.
1087
- knits for revisions and signatures
1088
- TextStores for revisions and signatures.
1089
- a format marker of its own
1090
- an optional 'shared-storage' flag
1091
- an optional 'no-working-trees' flag
1094
def _get_control_store(self, repo_transport, control_files):
1095
"""Return the control store for this repository."""
1096
return self._get_versioned_file_store('',
1100
versionedfile_class=KnitVersionedFile)
1102
def get_format_string(self):
1103
"""See RepositoryFormat.get_format_string()."""
1104
return "Bazaar-NG Knit Repository Format 1"
1106
def _get_revision_store(self, repo_transport, control_files):
1107
"""See RepositoryFormat._get_revision_store()."""
1108
from bzrlib.store.revision.knit import KnitRevisionStore
1109
versioned_file_store = VersionedFileStore(
1111
file_mode = control_files._file_mode,
1114
versionedfile_class=KnitVersionedFile)
1115
return KnitRevisionStore(versioned_file_store)
1117
def _get_text_store(self, transport, control_files):
1118
"""See RepositoryFormat._get_text_store()."""
1119
return self._get_versioned_file_store('knits',
1122
versionedfile_class=KnitVersionedFile)
1124
def initialize(self, a_bzrdir, shared=False):
1125
"""Create a knit format 1 repository.
1127
:param shared: If true the repository will be initialized as a shared
1129
XXX NOTE that this current uses a Weave for testing and will become
1130
A Knit in due course.
1132
from bzrlib.weavefile import write_weave_v5
1133
from bzrlib.weave import Weave
1135
# Create an empty weave
1137
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1138
empty_weave = sio.getvalue()
1140
mutter('creating repository in %s.', a_bzrdir.transport.base)
1141
dirs = ['revision-store', 'knits', 'control']
1142
files = [('control/inventory.weave', StringIO(empty_weave)),
1144
utf8_files = [('format', self.get_format_string())]
1146
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1147
repo_transport = a_bzrdir.get_repository_transport(None)
1148
control_files = LockableFiles(repo_transport, 'lock')
1149
control_store = self._get_control_store(repo_transport, control_files)
1150
transaction = bzrlib.transactions.PassThroughTransaction()
1151
# trigger a write of the inventory store.
1152
control_store.get_weave_or_empty('inventory', transaction)
1153
_revision_store = self._get_revision_store(repo_transport, control_files)
1154
_revision_store.has_revision_id('A', transaction)
1155
_revision_store.get_signature_file(transaction)
1156
return self.open(a_bzrdir=a_bzrdir, _found=True)
1158
def open(self, a_bzrdir, _found=False, _override_transport=None):
1159
"""See RepositoryFormat.open().
1161
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1162
repository at a slightly different url
1163
than normal. I.e. during 'upgrade'.
1166
format = RepositoryFormat.find_format(a_bzrdir)
1167
assert format.__class__ == self.__class__
1168
if _override_transport is not None:
1169
repo_transport = _override_transport
1171
repo_transport = a_bzrdir.get_repository_transport(None)
1172
control_files = LockableFiles(repo_transport, 'lock')
1173
text_store = self._get_text_store(repo_transport, control_files)
1174
control_store = self._get_control_store(repo_transport, control_files)
1175
_revision_store = self._get_revision_store(repo_transport, control_files)
1176
return KnitRepository(_format=self,
1178
control_files=control_files,
1179
_revision_store=_revision_store,
1180
control_store=control_store,
1181
text_store=text_store)
1184
# formats which have no format string are not discoverable
1185
# and not independently creatable, so are not registered.
1186
_default_format = RepositoryFormat7()
1187
RepositoryFormat.register_format(_default_format)
1188
RepositoryFormat.register_format(RepositoryFormatKnit1())
1189
RepositoryFormat.set_default_format(_default_format)
1190
_legacy_formats = [RepositoryFormat4(),
1191
RepositoryFormat5(),
1192
RepositoryFormat6()]
1195
class InterRepository(InterObject):
1196
"""This class represents operations taking place between two repositories.
1198
Its instances have methods like copy_content and fetch, and contain
1199
references to the source and target repositories these operations can be
1202
Often we will provide convenience methods on 'repository' which carry out
1203
operations with another repository - they will always forward to
1204
InterRepository.get(other).method_name(parameters).
1208
"""The available optimised InterRepository types."""
1211
def copy_content(self, revision_id=None, basis=None):
1212
"""Make a complete copy of the content in self into destination.
1214
This is a destructive operation! Do not use it on existing
1217
:param revision_id: Only copy the content needed to construct
1218
revision_id and its parents.
1219
:param basis: Copy the needed data preferentially from basis.
1222
self.target.set_make_working_trees(self.source.make_working_trees())
1223
except NotImplementedError:
1225
# grab the basis available data
1226
if basis is not None:
1227
self.target.fetch(basis, revision_id=revision_id)
1228
# but dont bother fetching if we have the needed data now.
1229
if (revision_id not in (None, NULL_REVISION) and
1230
self.target.has_revision(revision_id)):
1232
self.target.fetch(self.source, revision_id=revision_id)
1234
def _double_lock(self, lock_source, lock_target):
1235
"""Take out too locks, rolling back the first if the second throws."""
1240
# we want to ensure that we don't leave source locked by mistake.
1241
# and any error on target should not confuse source.
1242
self.source.unlock()
1246
def fetch(self, revision_id=None, pb=None):
1247
"""Fetch the content required to construct revision_id.
1249
The content is copied from source to target.
1251
:param revision_id: if None all content is copied, if NULL_REVISION no
1253
:param pb: optional progress bar to use for progress reports. If not
1254
provided a default one will be created.
1256
Returns the copied revision count and the failed revisions in a tuple:
1259
from bzrlib.fetch import GenericRepoFetcher
1260
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1261
self.source, self.source._format, self.target, self.target._format)
1262
f = GenericRepoFetcher(to_repository=self.target,
1263
from_repository=self.source,
1264
last_revision=revision_id,
1266
return f.count_copied, f.failed_revisions
1268
def lock_read(self):
1269
"""Take out a logical read lock.
1271
This will lock the source branch and the target branch. The source gets
1272
a read lock and the target a read lock.
1274
self._double_lock(self.source.lock_read, self.target.lock_read)
1276
def lock_write(self):
1277
"""Take out a logical write lock.
1279
This will lock the source branch and the target branch. The source gets
1280
a read lock and the target a write lock.
1282
self._double_lock(self.source.lock_read, self.target.lock_write)
1285
def missing_revision_ids(self, revision_id=None):
1286
"""Return the revision ids that source has that target does not.
1288
These are returned in topological order.
1290
:param revision_id: only return revision ids included by this
1293
# generic, possibly worst case, slow code path.
1294
target_ids = set(self.target.all_revision_ids())
1295
if revision_id is not None:
1296
source_ids = self.source.get_ancestry(revision_id)
1297
assert source_ids.pop(0) == None
1299
source_ids = self.source.all_revision_ids()
1300
result_set = set(source_ids).difference(target_ids)
1301
# this may look like a no-op: its not. It preserves the ordering
1302
# other_ids had while only returning the members from other_ids
1303
# that we've decided we need.
1304
return [rev_id for rev_id in source_ids if rev_id in result_set]
1307
"""Release the locks on source and target."""
1309
self.target.unlock()
1311
self.source.unlock()
1314
class InterWeaveRepo(InterRepository):
1315
"""Optimised code paths between Weave based repositories."""
1317
_matching_repo_format = _default_format
1318
"""Repository format for testing with."""
1321
def is_compatible(source, target):
1322
"""Be compatible with known Weave formats.
1324
We dont test for the stores being of specific types becase that
1325
could lead to confusing results, and there is no need to be
1329
return (isinstance(source._format, (RepositoryFormat5,
1331
RepositoryFormat7)) and
1332
isinstance(target._format, (RepositoryFormat5,
1334
RepositoryFormat7)))
1335
except AttributeError:
1339
def copy_content(self, revision_id=None, basis=None):
1340
"""See InterRepository.copy_content()."""
1341
# weave specific optimised path:
1342
if basis is not None:
1343
# copy the basis in, then fetch remaining data.
1344
basis.copy_content_into(self.target, revision_id)
1345
# the basis copy_content_into could misset this.
1347
self.target.set_make_working_trees(self.source.make_working_trees())
1348
except NotImplementedError:
1350
self.target.fetch(self.source, revision_id=revision_id)
1353
self.target.set_make_working_trees(self.source.make_working_trees())
1354
except NotImplementedError:
1356
# FIXME do not peek!
1357
if self.source.control_files._transport.listable():
1358
pb = bzrlib.ui.ui_factory.progress_bar()
1359
self.target.weave_store.copy_all_ids(
1360
self.source.weave_store,
1362
from_transaction=self.source.get_transaction(),
1363
to_transaction=self.target.get_transaction())
1364
pb.update('copying inventory', 0, 1)
1365
self.target.control_weaves.copy_multi(
1366
self.source.control_weaves, ['inventory'],
1367
from_transaction=self.source.get_transaction(),
1368
to_transaction=self.target.get_transaction())
1369
self.target._revision_store.text_store.copy_all_ids(
1370
self.source._revision_store.text_store,
1373
self.target.fetch(self.source, revision_id=revision_id)
1376
def fetch(self, revision_id=None, pb=None):
1377
"""See InterRepository.fetch()."""
1378
from bzrlib.fetch import GenericRepoFetcher
1379
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1380
self.source, self.source._format, self.target, self.target._format)
1381
f = GenericRepoFetcher(to_repository=self.target,
1382
from_repository=self.source,
1383
last_revision=revision_id,
1385
return f.count_copied, f.failed_revisions
1388
def missing_revision_ids(self, revision_id=None):
1389
"""See InterRepository.missing_revision_ids()."""
1390
# we want all revisions to satisfy revision_id in source.
1391
# but we dont want to stat every file here and there.
1392
# we want then, all revisions other needs to satisfy revision_id
1393
# checked, but not those that we have locally.
1394
# so the first thing is to get a subset of the revisions to
1395
# satisfy revision_id in source, and then eliminate those that
1396
# we do already have.
1397
# this is slow on high latency connection to self, but as as this
1398
# disk format scales terribly for push anyway due to rewriting
1399
# inventory.weave, this is considered acceptable.
1401
if revision_id is not None:
1402
source_ids = self.source.get_ancestry(revision_id)
1403
assert source_ids.pop(0) == None
1405
source_ids = self.source._all_possible_ids()
1406
source_ids_set = set(source_ids)
1407
# source_ids is the worst possible case we may need to pull.
1408
# now we want to filter source_ids against what we actually
1409
# have in target, but dont try to check for existence where we know
1410
# we do not have a revision as that would be pointless.
1411
target_ids = set(self.target._all_possible_ids())
1412
possibly_present_revisions = target_ids.intersection(source_ids_set)
1413
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1414
required_revisions = source_ids_set.difference(actually_present_revisions)
1415
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1416
if revision_id is not None:
1417
# we used get_ancestry to determine source_ids then we are assured all
1418
# revisions referenced are present as they are installed in topological order.
1419
# and the tip revision was validated by get_ancestry.
1420
return required_topo_revisions
1422
# if we just grabbed the possibly available ids, then
1423
# we only have an estimate of whats available and need to validate
1424
# that against the revision records.
1425
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1428
class InterKnitRepo(InterRepository):
1429
"""Optimised code paths between Knit based repositories."""
1431
_matching_repo_format = RepositoryFormatKnit1()
1432
"""Repository format for testing with."""
1435
def is_compatible(source, target):
1436
"""Be compatible with known Knit formats.
1438
We dont test for the stores being of specific types becase that
1439
could lead to confusing results, and there is no need to be
1443
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1444
isinstance(target._format, (RepositoryFormatKnit1)))
1445
except AttributeError:
1449
def fetch(self, revision_id=None, pb=None):
1450
"""See InterRepository.fetch()."""
1451
from bzrlib.fetch import KnitRepoFetcher
1452
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1453
self.source, self.source._format, self.target, self.target._format)
1454
f = KnitRepoFetcher(to_repository=self.target,
1455
from_repository=self.source,
1456
last_revision=revision_id,
1458
return f.count_copied, f.failed_revisions
1461
def missing_revision_ids(self, revision_id=None):
1462
"""See InterRepository.missing_revision_ids()."""
1463
if revision_id is not None:
1464
source_ids = self.source.get_ancestry(revision_id)
1465
assert source_ids.pop(0) == None
1467
source_ids = self.source._all_possible_ids()
1468
source_ids_set = set(source_ids)
1469
# source_ids is the worst possible case we may need to pull.
1470
# now we want to filter source_ids against what we actually
1471
# have in target, but dont try to check for existence where we know
1472
# we do not have a revision as that would be pointless.
1473
target_ids = set(self.target._all_possible_ids())
1474
possibly_present_revisions = target_ids.intersection(source_ids_set)
1475
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1476
required_revisions = source_ids_set.difference(actually_present_revisions)
1477
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1478
if revision_id is not None:
1479
# we used get_ancestry to determine source_ids then we are assured all
1480
# revisions referenced are present as they are installed in topological order.
1481
# and the tip revision was validated by get_ancestry.
1482
return required_topo_revisions
1484
# if we just grabbed the possibly available ids, then
1485
# we only have an estimate of whats available and need to validate
1486
# that against the revision records.
1487
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1489
InterRepository.register_optimiser(InterWeaveRepo)
1490
InterRepository.register_optimiser(InterKnitRepo)
1493
class RepositoryTestProviderAdapter(object):
1494
"""A tool to generate a suite testing multiple repository formats at once.
1496
This is done by copying the test once for each transport and injecting
1497
the transport_server, transport_readonly_server, and bzrdir_format and
1498
repository_format classes into each copy. Each copy is also given a new id()
1499
to make it easy to identify.
1502
def __init__(self, transport_server, transport_readonly_server, formats):
1503
self._transport_server = transport_server
1504
self._transport_readonly_server = transport_readonly_server
1505
self._formats = formats
1507
def adapt(self, test):
1508
result = TestSuite()
1509
for repository_format, bzrdir_format in self._formats:
1510
new_test = deepcopy(test)
1511
new_test.transport_server = self._transport_server
1512
new_test.transport_readonly_server = self._transport_readonly_server
1513
new_test.bzrdir_format = bzrdir_format
1514
new_test.repository_format = repository_format
1515
def make_new_test_id():
1516
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1517
return lambda: new_id
1518
new_test.id = make_new_test_id()
1519
result.addTest(new_test)
1523
class InterRepositoryTestProviderAdapter(object):
1524
"""A tool to generate a suite testing multiple inter repository formats.
1526
This is done by copying the test once for each interrepo provider and injecting
1527
the transport_server, transport_readonly_server, repository_format and
1528
repository_to_format classes into each copy.
1529
Each copy is also given a new id() to make it easy to identify.
1532
def __init__(self, transport_server, transport_readonly_server, formats):
1533
self._transport_server = transport_server
1534
self._transport_readonly_server = transport_readonly_server
1535
self._formats = formats
1537
def adapt(self, test):
1538
result = TestSuite()
1539
for interrepo_class, repository_format, repository_format_to in self._formats:
1540
new_test = deepcopy(test)
1541
new_test.transport_server = self._transport_server
1542
new_test.transport_readonly_server = self._transport_readonly_server
1543
new_test.interrepo_class = interrepo_class
1544
new_test.repository_format = repository_format
1545
new_test.repository_format_to = repository_format_to
1546
def make_new_test_id():
1547
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1548
return lambda: new_id
1549
new_test.id = make_new_test_id()
1550
result.addTest(new_test)
1554
def default_test_list():
1555
"""Generate the default list of interrepo permutations to test."""
1557
# test the default InterRepository between format 6 and the current
1559
# XXX: robertc 20060220 reinstate this when there are two supported
1560
# formats which do not have an optimal code path between them.
1561
result.append((InterRepository,
1562
RepositoryFormat6(),
1563
RepositoryFormatKnit1()))
1564
for optimiser in InterRepository._optimisers:
1565
result.append((optimiser,
1566
optimiser._matching_repo_format,
1567
optimiser._matching_repo_format
1569
# if there are specific combinations we want to use, we can add them
1574
class CopyConverter(object):
1575
"""A repository conversion tool which just performs a copy of the content.
1577
This is slow but quite reliable.
1580
def __init__(self, target_format):
1581
"""Create a CopyConverter.
1583
:param target_format: The format the resulting repository should be.
1585
self.target_format = target_format
1587
def convert(self, repo, pb):
1588
"""Perform the conversion of to_convert, giving feedback via pb.
1590
:param to_convert: The disk object to convert.
1591
:param pb: a progress bar to use for progress information.
1596
# this is only useful with metadir layouts - separated repo content.
1597
# trigger an assertion if not such
1598
repo._format.get_format_string()
1599
self.repo_dir = repo.bzrdir
1600
self.step('Moving repository to repository.backup')
1601
self.repo_dir.transport.move('repository', 'repository.backup')
1602
backup_transport = self.repo_dir.transport.clone('repository.backup')
1603
self.source_repo = repo._format.open(self.repo_dir,
1605
_override_transport=backup_transport)
1606
self.step('Creating new repository')
1607
converted = self.target_format.initialize(self.repo_dir,
1608
self.source_repo.is_shared())
1609
converted.lock_write()
1611
self.step('Copying content into repository.')
1612
self.source_repo.copy_content_into(converted)
1615
self.step('Deleting old repository content.')
1616
self.repo_dir.transport.delete_tree('repository.backup')
1617
self.pb.note('repository converted')
1619
def step(self, message):
1620
"""Update the pb by a step."""
1622
self.pb.update(message, self.count, self.total)