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)
119
def break_lock(self):
120
"""Break a lock if one is present from another instance.
122
Uses the ui factory to ask for confirmation if the lock may be from
125
self.control_files.break_lock()
128
def _eliminate_revisions_not_present(self, revision_ids):
129
"""Check every revision id in revision_ids to see if we have it.
131
Returns a set of the present revisions.
134
for id in revision_ids:
135
if self.has_revision(id):
140
def create(a_bzrdir):
141
"""Construct the current default format repository in a_bzrdir."""
142
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
144
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
145
"""instantiate a Repository.
147
:param _format: The format of the repository on disk.
148
:param a_bzrdir: The BzrDir of the repository.
150
In the future we will have a single api for all stores for
151
getting file texts, inventories and revisions, then
152
this construct will accept instances of those things.
154
super(Repository, self).__init__()
155
self._format = _format
156
# the following are part of the public API for Repository:
157
self.bzrdir = a_bzrdir
158
self.control_files = control_files
159
self._revision_store = _revision_store
160
self.text_store = text_store
161
# backwards compatability
162
self.weave_store = text_store
163
# not right yet - should be more semantically clear ?
165
self.control_store = control_store
166
self.control_weaves = control_store
167
# TODO: make sure to construct the right store classes, etc, depending
168
# on whether escaping is required.
171
return self.control_files.is_locked()
173
def lock_write(self):
174
self.control_files.lock_write()
177
self.control_files.lock_read()
179
def get_physical_lock_status(self):
180
return self.control_files.get_physical_lock_status()
183
def missing_revision_ids(self, other, revision_id=None):
184
"""Return the revision ids that other has that this does not.
186
These are returned in topological order.
188
revision_id: only return revision ids included by revision_id.
190
return InterRepository.get(other, self).missing_revision_ids(revision_id)
194
"""Open the repository rooted at base.
196
For instance, if the repository is at URL/.bzr/repository,
197
Repository.open(URL) -> a Repository instance.
199
control = bzrlib.bzrdir.BzrDir.open(base)
200
return control.open_repository()
202
def copy_content_into(self, destination, revision_id=None, basis=None):
203
"""Make a complete copy of the content in self into destination.
205
This is a destructive operation! Do not use it on existing
208
return InterRepository.get(self, destination).copy_content(revision_id, basis)
210
def fetch(self, source, revision_id=None, pb=None):
211
"""Fetch the content required to construct revision_id from source.
213
If revision_id is None all content is copied.
215
return InterRepository.get(source, self).fetch(revision_id=revision_id,
219
self.control_files.unlock()
222
def clone(self, a_bzrdir, revision_id=None, basis=None):
223
"""Clone this repository into a_bzrdir using the current format.
225
Currently no check is made that the format of this repository and
226
the bzrdir format are compatible. FIXME RBC 20060201.
228
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
229
# use target default format.
230
result = a_bzrdir.create_repository()
231
# FIXME RBC 20060209 split out the repository type to avoid this check ?
232
elif isinstance(a_bzrdir._format,
233
(bzrlib.bzrdir.BzrDirFormat4,
234
bzrlib.bzrdir.BzrDirFormat5,
235
bzrlib.bzrdir.BzrDirFormat6)):
236
result = a_bzrdir.open_repository()
238
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
239
self.copy_content_into(result, revision_id, basis)
243
def has_revision(self, revision_id):
244
"""True if this repository has a copy of the revision."""
245
return self._revision_store.has_revision_id(revision_id,
246
self.get_transaction())
249
def get_revision_reconcile(self, revision_id):
250
"""'reconcile' helper routine that allows access to a revision always.
252
This variant of get_revision does not cross check the weave graph
253
against the revision one as get_revision does: but it should only
254
be used by reconcile, or reconcile-alike commands that are correcting
255
or testing the revision graph.
257
if not revision_id or not isinstance(revision_id, basestring):
258
raise InvalidRevisionId(revision_id=revision_id, branch=self)
259
return self._revision_store.get_revision(revision_id,
260
self.get_transaction())
263
def get_revision_xml(self, revision_id):
264
rev = self.get_revision(revision_id)
266
# the current serializer..
267
self._revision_store._serializer.write_revision(rev, rev_tmp)
269
return rev_tmp.getvalue()
272
def get_revision(self, revision_id):
273
"""Return the Revision object for a named revision"""
274
r = self.get_revision_reconcile(revision_id)
275
# weave corruption can lead to absent revision markers that should be
277
# the following test is reasonably cheap (it needs a single weave read)
278
# and the weave is cached in read transactions. In write transactions
279
# it is not cached but typically we only read a small number of
280
# revisions. For knits when they are introduced we will probably want
281
# to ensure that caching write transactions are in use.
282
inv = self.get_inventory_weave()
283
self._check_revision_parents(r, inv)
286
def _check_revision_parents(self, revision, inventory):
287
"""Private to Repository and Fetch.
289
This checks the parentage of revision in an inventory weave for
290
consistency and is only applicable to inventory-weave-for-ancestry
291
using repository formats & fetchers.
293
weave_parents = inventory.get_parents(revision.revision_id)
294
weave_names = inventory.versions()
295
for parent_id in revision.parent_ids:
296
if parent_id in weave_names:
297
# this parent must not be a ghost.
298
if not parent_id in weave_parents:
300
raise errors.CorruptRepository(self)
303
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
304
signature = gpg_strategy.sign(plaintext)
305
self._revision_store.add_revision_signature_text(revision_id,
307
self.get_transaction())
309
def fileids_altered_by_revision_ids(self, revision_ids):
310
"""Find the file ids and versions affected by revisions.
312
:param revisions: an iterable containing revision ids.
313
:return: a dictionary mapping altered file-ids to an iterable of
314
revision_ids. Each altered file-ids has the exact revision_ids that
315
altered it listed explicitly.
317
assert isinstance(self._format, (RepositoryFormat5,
320
RepositoryFormatKnit1)), \
321
"fileid_involved only supported for branches which store inventory as unnested xml"
322
selected_revision_ids = set(revision_ids)
323
w = self.get_inventory_weave()
326
# this code needs to read every new line in every inventory for the
327
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
328
# not pesent in one of those inventories is unnecessary but not
329
# harmful because we are filtering by the revision id marker in the
330
# inventory lines : we only select file ids altered in one of those
331
# revisions. We dont need to see all lines in the inventory because
332
# only those added in an inventory in rev X can contain a revision=X
334
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
335
start = line.find('file_id="')+9
336
if start < 9: continue
337
end = line.find('"', start)
339
file_id = _unescape_xml(line[start:end])
341
start = line.find('revision="')+10
342
if start < 10: continue
343
end = line.find('"', start)
345
revision_id = _unescape_xml(line[start:end])
346
if revision_id in selected_revision_ids:
347
result.setdefault(file_id, set()).add(revision_id)
351
def get_inventory_weave(self):
352
return self.control_weaves.get_weave('inventory',
353
self.get_transaction())
356
def get_inventory(self, revision_id):
357
"""Get Inventory object by hash."""
358
xml = self.get_inventory_xml(revision_id)
359
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
362
def get_inventory_xml(self, revision_id):
363
"""Get inventory XML as a file object."""
365
assert isinstance(revision_id, basestring), type(revision_id)
366
iw = self.get_inventory_weave()
367
return iw.get_text(revision_id)
369
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
372
def get_inventory_sha1(self, revision_id):
373
"""Return the sha1 hash of the inventory entry
375
return self.get_revision(revision_id).inventory_sha1
378
def get_revision_graph(self, revision_id=None):
379
"""Return a dictionary containing the revision graph.
381
:return: a dictionary of revision_id->revision_parents_list.
383
weave = self.get_inventory_weave()
384
all_revisions = self._eliminate_revisions_not_present(weave.versions())
385
entire_graph = dict([(node, weave.get_parents(node)) for
386
node in all_revisions])
387
if revision_id is None:
389
elif revision_id not in entire_graph:
390
raise errors.NoSuchRevision(self, revision_id)
392
# add what can be reached from revision_id
394
pending = set([revision_id])
395
while len(pending) > 0:
397
result[node] = entire_graph[node]
398
for revision_id in result[node]:
399
if revision_id not in result:
400
pending.add(revision_id)
404
def get_revision_graph_with_ghosts(self, revision_ids=None):
405
"""Return a graph of the revisions with ghosts marked as applicable.
407
:param revision_ids: an iterable of revisions to graph or None for all.
408
:return: a Graph object with the graph reachable from revision_ids.
412
pending = set(self.all_revision_ids())
415
pending = set(revision_ids)
416
required = set(revision_ids)
419
revision_id = pending.pop()
421
rev = self.get_revision(revision_id)
422
except errors.NoSuchRevision:
423
if revision_id in required:
426
result.add_ghost(revision_id)
428
for parent_id in rev.parent_ids:
429
# is this queued or done ?
430
if (parent_id not in pending and
431
parent_id not in done):
433
pending.add(parent_id)
434
result.add_node(revision_id, rev.parent_ids)
435
done.add(revision_id)
439
def get_revision_inventory(self, revision_id):
440
"""Return inventory of a past revision."""
441
# TODO: Unify this with get_inventory()
442
# bzr 0.0.6 and later imposes the constraint that the inventory_id
443
# must be the same as its revision, so this is trivial.
444
if revision_id is None:
445
# This does not make sense: if there is no revision,
446
# then it is the current tree inventory surely ?!
447
# and thus get_root_id() is something that looks at the last
448
# commit on the branch, and the get_root_id is an inventory check.
449
raise NotImplementedError
450
# return Inventory(self.get_root_id())
452
return self.get_inventory(revision_id)
456
"""Return True if this repository is flagged as a shared repository."""
457
raise NotImplementedError(self.is_shared)
460
def reconcile(self, other=None, thorough=False):
461
"""Reconcile this repository."""
462
from bzrlib.reconcile import RepoReconciler
463
reconciler = RepoReconciler(self, thorough=thorough)
464
reconciler.reconcile()
468
def revision_tree(self, revision_id):
469
"""Return Tree for a revision on this branch.
471
`revision_id` may be None for the null revision, in which case
472
an `EmptyTree` is returned."""
473
# TODO: refactor this to use an existing revision object
474
# so we don't need to read it in twice.
475
if revision_id is None or revision_id == NULL_REVISION:
478
inv = self.get_revision_inventory(revision_id)
479
return RevisionTree(self, inv, revision_id)
482
def get_ancestry(self, revision_id):
483
"""Return a list of revision-ids integrated by a revision.
485
This is topologically sorted.
487
if revision_id is None:
489
if not self.has_revision(revision_id):
490
raise errors.NoSuchRevision(self, revision_id)
491
w = self.get_inventory_weave()
492
candidates = w.get_ancestry(revision_id)
493
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
496
def print_file(self, file, revision_id):
497
"""Print `file` to stdout.
499
FIXME RBC 20060125 as John Meinel points out this is a bad api
500
- it writes to stdout, it assumes that that is valid etc. Fix
501
by creating a new more flexible convenience function.
503
tree = self.revision_tree(revision_id)
504
# use inventory as it was in that revision
505
file_id = tree.inventory.path2id(file)
507
raise BzrError("%r is not present in revision %s" % (file, revno))
509
revno = self.revision_id_to_revno(revision_id)
510
except errors.NoSuchRevision:
511
# TODO: This should not be BzrError,
512
# but NoSuchFile doesn't fit either
513
raise BzrError('%r is not present in revision %s'
514
% (file, revision_id))
516
raise BzrError('%r is not present in revision %s'
518
tree.print_file(file_id)
520
def get_transaction(self):
521
return self.control_files.get_transaction()
523
def revision_parents(self, revid):
524
return self.get_inventory_weave().parent_names(revid)
527
def set_make_working_trees(self, new_value):
528
"""Set the policy flag for making working trees when creating branches.
530
This only applies to branches that use this repository.
532
The default is 'True'.
533
:param new_value: True to restore the default, False to disable making
536
raise NotImplementedError(self.set_make_working_trees)
538
def make_working_trees(self):
539
"""Returns the policy for making working trees on new branches."""
540
raise NotImplementedError(self.make_working_trees)
543
def sign_revision(self, revision_id, gpg_strategy):
544
plaintext = Testament.from_revision(self, revision_id).as_short_text()
545
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
548
def has_signature_for_revision_id(self, revision_id):
549
"""Query for a revision signature for revision_id in the repository."""
550
return self._revision_store.has_signature(revision_id,
551
self.get_transaction())
554
def get_signature_text(self, revision_id):
555
"""Return the text for a signature."""
556
return self._revision_store.get_signature_text(revision_id,
557
self.get_transaction())
560
class AllInOneRepository(Repository):
561
"""Legacy support - the repository behaviour for all-in-one branches."""
563
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
564
# we reuse one control files instance.
565
dir_mode = a_bzrdir._control_files._dir_mode
566
file_mode = a_bzrdir._control_files._file_mode
568
def get_store(name, compressed=True, prefixed=False):
569
# FIXME: This approach of assuming stores are all entirely compressed
570
# or entirely uncompressed is tidy, but breaks upgrade from
571
# some existing branches where there's a mixture; we probably
572
# still want the option to look for both.
573
relpath = a_bzrdir._control_files._escape(name)
574
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
575
prefixed=prefixed, compressed=compressed,
578
#if self._transport.should_cache():
579
# cache_path = os.path.join(self.cache_root, name)
580
# os.mkdir(cache_path)
581
# store = bzrlib.store.CachedStore(store, cache_path)
584
# not broken out yet because the controlweaves|inventory_store
585
# and text_store | weave_store bits are still different.
586
if isinstance(_format, RepositoryFormat4):
587
# cannot remove these - there is still no consistent api
588
# which allows access to this old info.
589
self.inventory_store = get_store('inventory-store')
590
text_store = get_store('text-store')
591
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
595
"""AllInOne repositories cannot be shared."""
599
def set_make_working_trees(self, new_value):
600
"""Set the policy flag for making working trees when creating branches.
602
This only applies to branches that use this repository.
604
The default is 'True'.
605
:param new_value: True to restore the default, False to disable making
608
raise NotImplementedError(self.set_make_working_trees)
610
def make_working_trees(self):
611
"""Returns the policy for making working trees on new branches."""
615
class MetaDirRepository(Repository):
616
"""Repositories in the new meta-dir layout."""
618
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
619
super(MetaDirRepository, self).__init__(_format,
626
dir_mode = self.control_files._dir_mode
627
file_mode = self.control_files._file_mode
631
"""Return True if this repository is flagged as a shared repository."""
632
return self.control_files._transport.has('shared-storage')
635
def set_make_working_trees(self, new_value):
636
"""Set the policy flag for making working trees when creating branches.
638
This only applies to branches that use this repository.
640
The default is 'True'.
641
:param new_value: True to restore the default, False to disable making
646
self.control_files._transport.delete('no-working-trees')
647
except errors.NoSuchFile:
650
self.control_files.put_utf8('no-working-trees', '')
652
def make_working_trees(self):
653
"""Returns the policy for making working trees on new branches."""
654
return not self.control_files._transport.has('no-working-trees')
657
class KnitRepository(MetaDirRepository):
658
"""Knit format repository."""
661
def all_revision_ids(self):
662
"""See Repository.all_revision_ids()."""
663
return self._revision_store.all_revision_ids(self.get_transaction())
665
def fileid_involved_between_revs(self, from_revid, to_revid):
666
"""Find file_id(s) which are involved in the changes between revisions.
668
This determines the set of revisions which are involved, and then
669
finds all file ids affected by those revisions.
671
vf = self._get_revision_vf()
672
from_set = set(vf.get_ancestry(from_revid))
673
to_set = set(vf.get_ancestry(to_revid))
674
changed = to_set.difference(from_set)
675
return self._fileid_involved_by_set(changed)
677
def fileid_involved(self, last_revid=None):
678
"""Find all file_ids modified in the ancestry of last_revid.
680
:param last_revid: If None, last_revision() will be used.
683
changed = set(self.all_revision_ids())
685
changed = set(self.get_ancestry(last_revid))
688
return self._fileid_involved_by_set(changed)
691
def get_ancestry(self, revision_id):
692
"""Return a list of revision-ids integrated by a revision.
694
This is topologically sorted.
696
if revision_id is None:
698
vf = self._get_revision_vf()
700
return [None] + vf.get_ancestry(revision_id)
701
except errors.RevisionNotPresent:
702
raise errors.NoSuchRevision(self, revision_id)
705
def get_revision(self, revision_id):
706
"""Return the Revision object for a named revision"""
707
return self.get_revision_reconcile(revision_id)
710
def get_revision_graph(self, revision_id=None):
711
"""Return a dictionary containing the revision graph.
713
:return: a dictionary of revision_id->revision_parents_list.
715
weave = self._get_revision_vf()
716
entire_graph = weave.get_graph()
717
if revision_id is None:
718
return weave.get_graph()
719
elif revision_id not in weave:
720
raise errors.NoSuchRevision(self, revision_id)
722
# add what can be reached from revision_id
724
pending = set([revision_id])
725
while len(pending) > 0:
727
result[node] = weave.get_parents(node)
728
for revision_id in result[node]:
729
if revision_id not in result:
730
pending.add(revision_id)
734
def get_revision_graph_with_ghosts(self, revision_ids=None):
735
"""Return a graph of the revisions with ghosts marked as applicable.
737
:param revision_ids: an iterable of revisions to graph or None for all.
738
:return: a Graph object with the graph reachable from revision_ids.
741
vf = self._get_revision_vf()
742
versions = set(vf.versions())
744
pending = set(self.all_revision_ids())
747
pending = set(revision_ids)
748
required = set(revision_ids)
751
revision_id = pending.pop()
752
if not revision_id in versions:
753
if revision_id in required:
754
raise errors.NoSuchRevision(self, revision_id)
756
result.add_ghost(revision_id)
757
# mark it as done so we dont try for it again.
758
done.add(revision_id)
760
parent_ids = vf.get_parents_with_ghosts(revision_id)
761
for parent_id in parent_ids:
762
# is this queued or done ?
763
if (parent_id not in pending and
764
parent_id not in done):
766
pending.add(parent_id)
767
result.add_node(revision_id, parent_ids)
768
done.add(revision_id)
771
def _get_revision_vf(self):
772
""":return: a versioned file containing the revisions."""
773
vf = self._revision_store.get_revision_file(self.get_transaction())
777
def reconcile(self, other=None, thorough=False):
778
"""Reconcile this repository."""
779
from bzrlib.reconcile import KnitReconciler
780
reconciler = KnitReconciler(self, thorough=thorough)
781
reconciler.reconcile()
784
def revision_parents(self, revid):
785
return self._get_revision_vf().get_parents(rev_id)
787
class RepositoryFormat(object):
788
"""A repository format.
790
Formats provide three things:
791
* An initialization routine to construct repository data on disk.
792
* a format string which is used when the BzrDir supports versioned
794
* an open routine which returns a Repository instance.
796
Formats are placed in an dict by their format string for reference
797
during opening. These should be subclasses of RepositoryFormat
800
Once a format is deprecated, just deprecate the initialize and open
801
methods on the format class. Do not deprecate the object, as the
802
object will be created every system load.
804
Common instance attributes:
805
_matchingbzrdir - the bzrdir format that the repository format was
806
originally written to work with. This can be used if manually
807
constructing a bzrdir and repository, or more commonly for test suite
811
_default_format = None
812
"""The default format used for new repositories."""
815
"""The known formats."""
818
def find_format(klass, a_bzrdir):
819
"""Return the format for the repository object in a_bzrdir."""
821
transport = a_bzrdir.get_repository_transport(None)
822
format_string = transport.get("format").read()
823
return klass._formats[format_string]
824
except errors.NoSuchFile:
825
raise errors.NoRepositoryPresent(a_bzrdir)
827
raise errors.UnknownFormatError(format_string)
829
def _get_control_store(self, repo_transport, control_files):
830
"""Return the control store for this repository."""
831
raise NotImplementedError(self._get_control_store)
834
def get_default_format(klass):
835
"""Return the current default format."""
836
return klass._default_format
838
def get_format_string(self):
839
"""Return the ASCII format string that identifies this format.
841
Note that in pre format ?? repositories the format string is
842
not permitted nor written to disk.
844
raise NotImplementedError(self.get_format_string)
846
def get_format_description(self):
847
"""Return the short desciption for this format."""
848
raise NotImplementedError(self.get_format_description)
850
def _get_revision_store(self, repo_transport, control_files):
851
"""Return the revision store object for this a_bzrdir."""
852
raise NotImplementedError(self._get_revision_store)
854
def _get_text_rev_store(self,
861
"""Common logic for getting a revision store for a repository.
863
see self._get_revision_store for the subclass-overridable method to
864
get the store for a repository.
866
from bzrlib.store.revision.text import TextRevisionStore
867
dir_mode = control_files._dir_mode
868
file_mode = control_files._file_mode
869
text_store =TextStore(transport.clone(name),
871
compressed=compressed,
874
_revision_store = TextRevisionStore(text_store, serializer)
875
return _revision_store
877
def _get_versioned_file_store(self,
882
versionedfile_class=WeaveFile,
884
weave_transport = control_files._transport.clone(name)
885
dir_mode = control_files._dir_mode
886
file_mode = control_files._file_mode
887
return VersionedFileStore(weave_transport, prefixed=prefixed,
890
versionedfile_class=versionedfile_class,
893
def initialize(self, a_bzrdir, shared=False):
894
"""Initialize a repository of this format in a_bzrdir.
896
:param a_bzrdir: The bzrdir to put the new repository in it.
897
:param shared: The repository should be initialized as a sharable one.
899
This may raise UninitializableFormat if shared repository are not
900
compatible the a_bzrdir.
903
def is_supported(self):
904
"""Is this format supported?
906
Supported formats must be initializable and openable.
907
Unsupported formats may not support initialization or committing or
908
some other features depending on the reason for not being supported.
912
def open(self, a_bzrdir, _found=False):
913
"""Return an instance of this format for the bzrdir a_bzrdir.
915
_found is a private parameter, do not use it.
917
raise NotImplementedError(self.open)
920
def register_format(klass, format):
921
klass._formats[format.get_format_string()] = format
924
def set_default_format(klass, format):
925
klass._default_format = format
928
def unregister_format(klass, format):
929
assert klass._formats[format.get_format_string()] is format
930
del klass._formats[format.get_format_string()]
933
class PreSplitOutRepositoryFormat(RepositoryFormat):
934
"""Base class for the pre split out repository formats."""
936
def initialize(self, a_bzrdir, shared=False, _internal=False):
937
"""Create a weave repository.
939
TODO: when creating split out bzr branch formats, move this to a common
940
base for Format5, Format6. or something like that.
942
from bzrlib.weavefile import write_weave_v5
943
from bzrlib.weave import Weave
946
raise errors.IncompatibleFormat(self, a_bzrdir._format)
949
# always initialized when the bzrdir is.
950
return self.open(a_bzrdir, _found=True)
952
# Create an empty weave
954
bzrlib.weavefile.write_weave_v5(Weave(), sio)
955
empty_weave = sio.getvalue()
957
mutter('creating repository in %s.', a_bzrdir.transport.base)
958
dirs = ['revision-store', 'weaves']
959
files = [('inventory.weave', StringIO(empty_weave)),
962
# FIXME: RBC 20060125 dont peek under the covers
963
# NB: no need to escape relative paths that are url safe.
964
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
966
control_files.create_lock()
967
control_files.lock_write()
968
control_files._transport.mkdir_multi(dirs,
969
mode=control_files._dir_mode)
971
for file, content in files:
972
control_files.put(file, content)
974
control_files.unlock()
975
return self.open(a_bzrdir, _found=True)
977
def _get_control_store(self, repo_transport, control_files):
978
"""Return the control store for this repository."""
979
return self._get_versioned_file_store('',
984
def _get_text_store(self, transport, control_files):
985
"""Get a store for file texts for this format."""
986
raise NotImplementedError(self._get_text_store)
988
def open(self, a_bzrdir, _found=False):
989
"""See RepositoryFormat.open()."""
991
# we are being called directly and must probe.
992
raise NotImplementedError
994
repo_transport = a_bzrdir.get_repository_transport(None)
995
control_files = a_bzrdir._control_files
996
text_store = self._get_text_store(repo_transport, control_files)
997
control_store = self._get_control_store(repo_transport, control_files)
998
_revision_store = self._get_revision_store(repo_transport, control_files)
999
return AllInOneRepository(_format=self,
1001
_revision_store=_revision_store,
1002
control_store=control_store,
1003
text_store=text_store)
1006
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1007
"""Bzr repository format 4.
1009
This repository format has:
1011
- TextStores for texts, inventories,revisions.
1013
This format is deprecated: it indexes texts using a text id which is
1014
removed in format 5; initializationa and write support for this format
1019
super(RepositoryFormat4, self).__init__()
1020
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
1022
def get_format_description(self):
1023
"""See RepositoryFormat.get_format_description()."""
1024
return "Repository format 4"
1026
def initialize(self, url, shared=False, _internal=False):
1027
"""Format 4 branches cannot be created."""
1028
raise errors.UninitializableFormat(self)
1030
def is_supported(self):
1031
"""Format 4 is not supported.
1033
It is not supported because the model changed from 4 to 5 and the
1034
conversion logic is expensive - so doing it on the fly was not
1039
def _get_control_store(self, repo_transport, control_files):
1040
"""Format 4 repositories have no formal control store at this point.
1042
This will cause any control-file-needing apis to fail - this is desired.
1046
def _get_revision_store(self, repo_transport, control_files):
1047
"""See RepositoryFormat._get_revision_store()."""
1048
from bzrlib.xml4 import serializer_v4
1049
return self._get_text_rev_store(repo_transport,
1052
serializer=serializer_v4)
1054
def _get_text_store(self, transport, control_files):
1055
"""See RepositoryFormat._get_text_store()."""
1058
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1059
"""Bzr control format 5.
1061
This repository format has:
1062
- weaves for file texts and inventory
1064
- TextStores for revisions and signatures.
1068
super(RepositoryFormat5, self).__init__()
1069
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
1071
def get_format_description(self):
1072
"""See RepositoryFormat.get_format_description()."""
1073
return "Weave repository format 5"
1075
def _get_revision_store(self, repo_transport, control_files):
1076
"""See RepositoryFormat._get_revision_store()."""
1077
"""Return the revision store object for this a_bzrdir."""
1078
return self._get_text_rev_store(repo_transport,
1083
def _get_text_store(self, transport, control_files):
1084
"""See RepositoryFormat._get_text_store()."""
1085
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1088
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1089
"""Bzr control format 6.
1091
This repository format has:
1092
- weaves for file texts and inventory
1093
- hash subdirectory based stores.
1094
- TextStores for revisions and signatures.
1098
super(RepositoryFormat6, self).__init__()
1099
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
1101
def get_format_description(self):
1102
"""See RepositoryFormat.get_format_description()."""
1103
return "Weave repository format 6"
1105
def _get_revision_store(self, repo_transport, control_files):
1106
"""See RepositoryFormat._get_revision_store()."""
1107
return self._get_text_rev_store(repo_transport,
1113
def _get_text_store(self, transport, control_files):
1114
"""See RepositoryFormat._get_text_store()."""
1115
return self._get_versioned_file_store('weaves', transport, control_files)
1118
class MetaDirRepositoryFormat(RepositoryFormat):
1119
"""Common base class for the new repositories using the metadir layour."""
1122
super(MetaDirRepositoryFormat, self).__init__()
1123
self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
1125
def _create_control_files(self, a_bzrdir):
1126
"""Create the required files and the initial control_files object."""
1127
# FIXME: RBC 20060125 dont peek under the covers
1128
# NB: no need to escape relative paths that are url safe.
1129
repository_transport = a_bzrdir.get_repository_transport(self)
1130
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1131
control_files.create_lock()
1132
return control_files
1134
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1135
"""Upload the initial blank content."""
1136
control_files = self._create_control_files(a_bzrdir)
1137
control_files.lock_write()
1139
control_files._transport.mkdir_multi(dirs,
1140
mode=control_files._dir_mode)
1141
for file, content in files:
1142
control_files.put(file, content)
1143
for file, content in utf8_files:
1144
control_files.put_utf8(file, content)
1146
control_files.put_utf8('shared-storage', '')
1148
control_files.unlock()
1151
class RepositoryFormat7(MetaDirRepositoryFormat):
1152
"""Bzr repository 7.
1154
This repository format has:
1155
- weaves for file texts and inventory
1156
- hash subdirectory based stores.
1157
- TextStores for revisions and signatures.
1158
- a format marker of its own
1159
- an optional 'shared-storage' flag
1160
- an optional 'no-working-trees' flag
1163
def _get_control_store(self, repo_transport, control_files):
1164
"""Return the control store for this repository."""
1165
return self._get_versioned_file_store('',
1170
def get_format_string(self):
1171
"""See RepositoryFormat.get_format_string()."""
1172
return "Bazaar-NG Repository format 7"
1174
def get_format_description(self):
1175
"""See RepositoryFormat.get_format_description()."""
1176
return "Weave repository format 7"
1178
def _get_revision_store(self, repo_transport, control_files):
1179
"""See RepositoryFormat._get_revision_store()."""
1180
return self._get_text_rev_store(repo_transport,
1187
def _get_text_store(self, transport, control_files):
1188
"""See RepositoryFormat._get_text_store()."""
1189
return self._get_versioned_file_store('weaves',
1193
def initialize(self, a_bzrdir, shared=False):
1194
"""Create a weave repository.
1196
:param shared: If true the repository will be initialized as a shared
1199
from bzrlib.weavefile import write_weave_v5
1200
from bzrlib.weave import Weave
1202
# Create an empty weave
1204
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1205
empty_weave = sio.getvalue()
1207
mutter('creating repository in %s.', a_bzrdir.transport.base)
1208
dirs = ['revision-store', 'weaves']
1209
files = [('inventory.weave', StringIO(empty_weave)),
1211
utf8_files = [('format', self.get_format_string())]
1213
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1214
return self.open(a_bzrdir=a_bzrdir, _found=True)
1216
def open(self, a_bzrdir, _found=False, _override_transport=None):
1217
"""See RepositoryFormat.open().
1219
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1220
repository at a slightly different url
1221
than normal. I.e. during 'upgrade'.
1224
format = RepositoryFormat.find_format(a_bzrdir)
1225
assert format.__class__ == self.__class__
1226
if _override_transport is not None:
1227
repo_transport = _override_transport
1229
repo_transport = a_bzrdir.get_repository_transport(None)
1230
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1231
text_store = self._get_text_store(repo_transport, control_files)
1232
control_store = self._get_control_store(repo_transport, control_files)
1233
_revision_store = self._get_revision_store(repo_transport, control_files)
1234
return MetaDirRepository(_format=self,
1236
control_files=control_files,
1237
_revision_store=_revision_store,
1238
control_store=control_store,
1239
text_store=text_store)
1242
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1243
"""Bzr repository knit format 1.
1245
This repository format has:
1246
- knits for file texts and inventory
1247
- hash subdirectory based stores.
1248
- knits for revisions and signatures
1249
- TextStores for revisions and signatures.
1250
- a format marker of its own
1251
- an optional 'shared-storage' flag
1252
- an optional 'no-working-trees' flag
1255
This format was introduced in bzr 0.8.
1258
def _get_control_store(self, repo_transport, control_files):
1259
"""Return the control store for this repository."""
1260
return VersionedFileStore(
1263
file_mode=control_files._file_mode,
1264
versionedfile_class=KnitVersionedFile,
1265
versionedfile_kwargs={'factory':KnitPlainFactory()},
1268
def get_format_string(self):
1269
"""See RepositoryFormat.get_format_string()."""
1270
return "Bazaar-NG Knit Repository Format 1"
1272
def get_format_description(self):
1273
"""See RepositoryFormat.get_format_description()."""
1274
return "Knit repository format 1"
1276
def _get_revision_store(self, repo_transport, control_files):
1277
"""See RepositoryFormat._get_revision_store()."""
1278
from bzrlib.store.revision.knit import KnitRevisionStore
1279
versioned_file_store = VersionedFileStore(
1281
file_mode=control_files._file_mode,
1284
versionedfile_class=KnitVersionedFile,
1285
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1288
return KnitRevisionStore(versioned_file_store)
1290
def _get_text_store(self, transport, control_files):
1291
"""See RepositoryFormat._get_text_store()."""
1292
return self._get_versioned_file_store('knits',
1295
versionedfile_class=KnitVersionedFile,
1298
def initialize(self, a_bzrdir, shared=False):
1299
"""Create a knit format 1 repository.
1301
:param a_bzrdir: bzrdir to contain the new repository; must already
1303
:param shared: If true the repository will be initialized as a shared
1306
mutter('creating repository in %s.', a_bzrdir.transport.base)
1307
dirs = ['revision-store', 'knits']
1309
utf8_files = [('format', self.get_format_string())]
1311
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1312
repo_transport = a_bzrdir.get_repository_transport(None)
1313
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1314
control_store = self._get_control_store(repo_transport, control_files)
1315
transaction = bzrlib.transactions.WriteTransaction()
1316
# trigger a write of the inventory store.
1317
control_store.get_weave_or_empty('inventory', transaction)
1318
_revision_store = self._get_revision_store(repo_transport, control_files)
1319
_revision_store.has_revision_id('A', transaction)
1320
_revision_store.get_signature_file(transaction)
1321
return self.open(a_bzrdir=a_bzrdir, _found=True)
1323
def open(self, a_bzrdir, _found=False, _override_transport=None):
1324
"""See RepositoryFormat.open().
1326
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1327
repository at a slightly different url
1328
than normal. I.e. during 'upgrade'.
1331
format = RepositoryFormat.find_format(a_bzrdir)
1332
assert format.__class__ == self.__class__
1333
if _override_transport is not None:
1334
repo_transport = _override_transport
1336
repo_transport = a_bzrdir.get_repository_transport(None)
1337
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1338
text_store = self._get_text_store(repo_transport, control_files)
1339
control_store = self._get_control_store(repo_transport, control_files)
1340
_revision_store = self._get_revision_store(repo_transport, control_files)
1341
return KnitRepository(_format=self,
1343
control_files=control_files,
1344
_revision_store=_revision_store,
1345
control_store=control_store,
1346
text_store=text_store)
1349
# formats which have no format string are not discoverable
1350
# and not independently creatable, so are not registered.
1351
RepositoryFormat.register_format(RepositoryFormat7())
1352
_default_format = RepositoryFormatKnit1()
1353
RepositoryFormat.register_format(_default_format)
1354
RepositoryFormat.set_default_format(_default_format)
1355
_legacy_formats = [RepositoryFormat4(),
1356
RepositoryFormat5(),
1357
RepositoryFormat6()]
1360
class InterRepository(InterObject):
1361
"""This class represents operations taking place between two repositories.
1363
Its instances have methods like copy_content and fetch, and contain
1364
references to the source and target repositories these operations can be
1367
Often we will provide convenience methods on 'repository' which carry out
1368
operations with another repository - they will always forward to
1369
InterRepository.get(other).method_name(parameters).
1373
"""The available optimised InterRepository types."""
1376
def copy_content(self, revision_id=None, basis=None):
1377
"""Make a complete copy of the content in self into destination.
1379
This is a destructive operation! Do not use it on existing
1382
:param revision_id: Only copy the content needed to construct
1383
revision_id and its parents.
1384
:param basis: Copy the needed data preferentially from basis.
1387
self.target.set_make_working_trees(self.source.make_working_trees())
1388
except NotImplementedError:
1390
# grab the basis available data
1391
if basis is not None:
1392
self.target.fetch(basis, revision_id=revision_id)
1393
# but dont bother fetching if we have the needed data now.
1394
if (revision_id not in (None, NULL_REVISION) and
1395
self.target.has_revision(revision_id)):
1397
self.target.fetch(self.source, revision_id=revision_id)
1399
def _double_lock(self, lock_source, lock_target):
1400
"""Take out too locks, rolling back the first if the second throws."""
1405
# we want to ensure that we don't leave source locked by mistake.
1406
# and any error on target should not confuse source.
1407
self.source.unlock()
1411
def fetch(self, revision_id=None, pb=None):
1412
"""Fetch the content required to construct revision_id.
1414
The content is copied from source to target.
1416
:param revision_id: if None all content is copied, if NULL_REVISION no
1418
:param pb: optional progress bar to use for progress reports. If not
1419
provided a default one will be created.
1421
Returns the copied revision count and the failed revisions in a tuple:
1424
from bzrlib.fetch import GenericRepoFetcher
1425
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1426
self.source, self.source._format, self.target, self.target._format)
1427
f = GenericRepoFetcher(to_repository=self.target,
1428
from_repository=self.source,
1429
last_revision=revision_id,
1431
return f.count_copied, f.failed_revisions
1433
def lock_read(self):
1434
"""Take out a logical read lock.
1436
This will lock the source branch and the target branch. The source gets
1437
a read lock and the target a read lock.
1439
self._double_lock(self.source.lock_read, self.target.lock_read)
1441
def lock_write(self):
1442
"""Take out a logical write lock.
1444
This will lock the source branch and the target branch. The source gets
1445
a read lock and the target a write lock.
1447
self._double_lock(self.source.lock_read, self.target.lock_write)
1450
def missing_revision_ids(self, revision_id=None):
1451
"""Return the revision ids that source has that target does not.
1453
These are returned in topological order.
1455
:param revision_id: only return revision ids included by this
1458
# generic, possibly worst case, slow code path.
1459
target_ids = set(self.target.all_revision_ids())
1460
if revision_id is not None:
1461
source_ids = self.source.get_ancestry(revision_id)
1462
assert source_ids.pop(0) == None
1464
source_ids = self.source.all_revision_ids()
1465
result_set = set(source_ids).difference(target_ids)
1466
# this may look like a no-op: its not. It preserves the ordering
1467
# other_ids had while only returning the members from other_ids
1468
# that we've decided we need.
1469
return [rev_id for rev_id in source_ids if rev_id in result_set]
1472
"""Release the locks on source and target."""
1474
self.target.unlock()
1476
self.source.unlock()
1479
class InterWeaveRepo(InterRepository):
1480
"""Optimised code paths between Weave based repositories."""
1482
_matching_repo_format = RepositoryFormat7()
1483
"""Repository format for testing with."""
1486
def is_compatible(source, target):
1487
"""Be compatible with known Weave formats.
1489
We dont test for the stores being of specific types becase that
1490
could lead to confusing results, and there is no need to be
1494
return (isinstance(source._format, (RepositoryFormat5,
1496
RepositoryFormat7)) and
1497
isinstance(target._format, (RepositoryFormat5,
1499
RepositoryFormat7)))
1500
except AttributeError:
1504
def copy_content(self, revision_id=None, basis=None):
1505
"""See InterRepository.copy_content()."""
1506
# weave specific optimised path:
1507
if basis is not None:
1508
# copy the basis in, then fetch remaining data.
1509
basis.copy_content_into(self.target, revision_id)
1510
# the basis copy_content_into could misset this.
1512
self.target.set_make_working_trees(self.source.make_working_trees())
1513
except NotImplementedError:
1515
self.target.fetch(self.source, revision_id=revision_id)
1518
self.target.set_make_working_trees(self.source.make_working_trees())
1519
except NotImplementedError:
1521
# FIXME do not peek!
1522
if self.source.control_files._transport.listable():
1523
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1525
self.target.weave_store.copy_all_ids(
1526
self.source.weave_store,
1528
from_transaction=self.source.get_transaction(),
1529
to_transaction=self.target.get_transaction())
1530
pb.update('copying inventory', 0, 1)
1531
self.target.control_weaves.copy_multi(
1532
self.source.control_weaves, ['inventory'],
1533
from_transaction=self.source.get_transaction(),
1534
to_transaction=self.target.get_transaction())
1535
self.target._revision_store.text_store.copy_all_ids(
1536
self.source._revision_store.text_store,
1541
self.target.fetch(self.source, revision_id=revision_id)
1544
def fetch(self, revision_id=None, pb=None):
1545
"""See InterRepository.fetch()."""
1546
from bzrlib.fetch import GenericRepoFetcher
1547
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1548
self.source, self.source._format, self.target, self.target._format)
1549
f = GenericRepoFetcher(to_repository=self.target,
1550
from_repository=self.source,
1551
last_revision=revision_id,
1553
return f.count_copied, f.failed_revisions
1556
def missing_revision_ids(self, revision_id=None):
1557
"""See InterRepository.missing_revision_ids()."""
1558
# we want all revisions to satisfy revision_id in source.
1559
# but we dont want to stat every file here and there.
1560
# we want then, all revisions other needs to satisfy revision_id
1561
# checked, but not those that we have locally.
1562
# so the first thing is to get a subset of the revisions to
1563
# satisfy revision_id in source, and then eliminate those that
1564
# we do already have.
1565
# this is slow on high latency connection to self, but as as this
1566
# disk format scales terribly for push anyway due to rewriting
1567
# inventory.weave, this is considered acceptable.
1569
if revision_id is not None:
1570
source_ids = self.source.get_ancestry(revision_id)
1571
assert source_ids.pop(0) == None
1573
source_ids = self.source._all_possible_ids()
1574
source_ids_set = set(source_ids)
1575
# source_ids is the worst possible case we may need to pull.
1576
# now we want to filter source_ids against what we actually
1577
# have in target, but dont try to check for existence where we know
1578
# we do not have a revision as that would be pointless.
1579
target_ids = set(self.target._all_possible_ids())
1580
possibly_present_revisions = target_ids.intersection(source_ids_set)
1581
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1582
required_revisions = source_ids_set.difference(actually_present_revisions)
1583
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1584
if revision_id is not None:
1585
# we used get_ancestry to determine source_ids then we are assured all
1586
# revisions referenced are present as they are installed in topological order.
1587
# and the tip revision was validated by get_ancestry.
1588
return required_topo_revisions
1590
# if we just grabbed the possibly available ids, then
1591
# we only have an estimate of whats available and need to validate
1592
# that against the revision records.
1593
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1596
class InterKnitRepo(InterRepository):
1597
"""Optimised code paths between Knit based repositories."""
1599
_matching_repo_format = RepositoryFormatKnit1()
1600
"""Repository format for testing with."""
1603
def is_compatible(source, target):
1604
"""Be compatible with known Knit formats.
1606
We dont test for the stores being of specific types becase that
1607
could lead to confusing results, and there is no need to be
1611
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1612
isinstance(target._format, (RepositoryFormatKnit1)))
1613
except AttributeError:
1617
def fetch(self, revision_id=None, pb=None):
1618
"""See InterRepository.fetch()."""
1619
from bzrlib.fetch import KnitRepoFetcher
1620
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1621
self.source, self.source._format, self.target, self.target._format)
1622
f = KnitRepoFetcher(to_repository=self.target,
1623
from_repository=self.source,
1624
last_revision=revision_id,
1626
return f.count_copied, f.failed_revisions
1629
def missing_revision_ids(self, revision_id=None):
1630
"""See InterRepository.missing_revision_ids()."""
1631
if revision_id is not None:
1632
source_ids = self.source.get_ancestry(revision_id)
1633
assert source_ids.pop(0) == None
1635
source_ids = self.source._all_possible_ids()
1636
source_ids_set = set(source_ids)
1637
# source_ids is the worst possible case we may need to pull.
1638
# now we want to filter source_ids against what we actually
1639
# have in target, but dont try to check for existence where we know
1640
# we do not have a revision as that would be pointless.
1641
target_ids = set(self.target._all_possible_ids())
1642
possibly_present_revisions = target_ids.intersection(source_ids_set)
1643
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1644
required_revisions = source_ids_set.difference(actually_present_revisions)
1645
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1646
if revision_id is not None:
1647
# we used get_ancestry to determine source_ids then we are assured all
1648
# revisions referenced are present as they are installed in topological order.
1649
# and the tip revision was validated by get_ancestry.
1650
return required_topo_revisions
1652
# if we just grabbed the possibly available ids, then
1653
# we only have an estimate of whats available and need to validate
1654
# that against the revision records.
1655
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1657
InterRepository.register_optimiser(InterWeaveRepo)
1658
InterRepository.register_optimiser(InterKnitRepo)
1661
class RepositoryTestProviderAdapter(object):
1662
"""A tool to generate a suite testing multiple repository formats at once.
1664
This is done by copying the test once for each transport and injecting
1665
the transport_server, transport_readonly_server, and bzrdir_format and
1666
repository_format classes into each copy. Each copy is also given a new id()
1667
to make it easy to identify.
1670
def __init__(self, transport_server, transport_readonly_server, formats):
1671
self._transport_server = transport_server
1672
self._transport_readonly_server = transport_readonly_server
1673
self._formats = formats
1675
def adapt(self, test):
1676
result = TestSuite()
1677
for repository_format, bzrdir_format in self._formats:
1678
new_test = deepcopy(test)
1679
new_test.transport_server = self._transport_server
1680
new_test.transport_readonly_server = self._transport_readonly_server
1681
new_test.bzrdir_format = bzrdir_format
1682
new_test.repository_format = repository_format
1683
def make_new_test_id():
1684
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1685
return lambda: new_id
1686
new_test.id = make_new_test_id()
1687
result.addTest(new_test)
1691
class InterRepositoryTestProviderAdapter(object):
1692
"""A tool to generate a suite testing multiple inter repository formats.
1694
This is done by copying the test once for each interrepo provider and injecting
1695
the transport_server, transport_readonly_server, repository_format and
1696
repository_to_format classes into each copy.
1697
Each copy is also given a new id() to make it easy to identify.
1700
def __init__(self, transport_server, transport_readonly_server, formats):
1701
self._transport_server = transport_server
1702
self._transport_readonly_server = transport_readonly_server
1703
self._formats = formats
1705
def adapt(self, test):
1706
result = TestSuite()
1707
for interrepo_class, repository_format, repository_format_to in self._formats:
1708
new_test = deepcopy(test)
1709
new_test.transport_server = self._transport_server
1710
new_test.transport_readonly_server = self._transport_readonly_server
1711
new_test.interrepo_class = interrepo_class
1712
new_test.repository_format = repository_format
1713
new_test.repository_format_to = repository_format_to
1714
def make_new_test_id():
1715
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1716
return lambda: new_id
1717
new_test.id = make_new_test_id()
1718
result.addTest(new_test)
1722
def default_test_list():
1723
"""Generate the default list of interrepo permutations to test."""
1725
# test the default InterRepository between format 6 and the current
1727
# XXX: robertc 20060220 reinstate this when there are two supported
1728
# formats which do not have an optimal code path between them.
1729
result.append((InterRepository,
1730
RepositoryFormat6(),
1731
RepositoryFormatKnit1()))
1732
for optimiser in InterRepository._optimisers:
1733
result.append((optimiser,
1734
optimiser._matching_repo_format,
1735
optimiser._matching_repo_format
1737
# if there are specific combinations we want to use, we can add them
1742
class CopyConverter(object):
1743
"""A repository conversion tool which just performs a copy of the content.
1745
This is slow but quite reliable.
1748
def __init__(self, target_format):
1749
"""Create a CopyConverter.
1751
:param target_format: The format the resulting repository should be.
1753
self.target_format = target_format
1755
def convert(self, repo, pb):
1756
"""Perform the conversion of to_convert, giving feedback via pb.
1758
:param to_convert: The disk object to convert.
1759
:param pb: a progress bar to use for progress information.
1764
# this is only useful with metadir layouts - separated repo content.
1765
# trigger an assertion if not such
1766
repo._format.get_format_string()
1767
self.repo_dir = repo.bzrdir
1768
self.step('Moving repository to repository.backup')
1769
self.repo_dir.transport.move('repository', 'repository.backup')
1770
backup_transport = self.repo_dir.transport.clone('repository.backup')
1771
self.source_repo = repo._format.open(self.repo_dir,
1773
_override_transport=backup_transport)
1774
self.step('Creating new repository')
1775
converted = self.target_format.initialize(self.repo_dir,
1776
self.source_repo.is_shared())
1777
converted.lock_write()
1779
self.step('Copying content into repository.')
1780
self.source_repo.copy_content_into(converted)
1783
self.step('Deleting old repository content.')
1784
self.repo_dir.transport.delete_tree('repository.backup')
1785
self.pb.note('repository converted')
1787
def step(self, message):
1788
"""Update the pb by a step."""
1790
self.pb.update(message, self.count, self.total)
1793
# Copied from xml.sax.saxutils
1794
def _unescape_xml(data):
1795
"""Unescape &, <, and > in a string of data.
1797
data = data.replace("<", "<")
1798
data = data.replace(">", ">")
1799
# must do ampersand last
1800
return data.replace("&", "&")