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
assert inv.revision_id is None or inv.revision_id == revid, \
68
"Mismatch between inventory revision" \
69
" id and insertion revid (%r, %r)" % (inv.revision_id, revid)
70
inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
71
inv_sha1 = bzrlib.osutils.sha_string(inv_text)
72
inv_vf = self.control_weaves.get_weave('inventory',
73
self.get_transaction())
74
inv_vf.add_lines(revid, parents, bzrlib.osutils.split_lines(inv_text))
78
def add_revision(self, rev_id, rev, inv=None, config=None):
79
"""Add rev to the revision store as rev_id.
81
:param rev_id: the revision id to use.
82
:param rev: The revision object.
83
:param inv: The inventory for the revision. if None, it will be looked
84
up in the inventory storer
85
:param config: If None no digital signature will be created.
86
If supplied its signature_needed method will be used
87
to determine if a signature should be made.
89
if config is not None and config.signature_needed():
91
inv = self.get_inventory(rev_id)
92
plaintext = Testament(rev, inv).as_short_text()
93
self.store_revision_signature(
94
gpg.GPGStrategy(config), plaintext, rev_id)
95
if not rev_id in self.get_inventory_weave():
97
raise errors.WeaveRevisionNotPresent(rev_id,
98
self.get_inventory_weave())
100
# yes, this is not suitable for adding with ghosts.
101
self.add_inventory(rev_id, inv, rev.parent_ids)
102
self._revision_store.add_revision(rev, self.get_transaction())
105
def _all_possible_ids(self):
106
"""Return all the possible revisions that we could find."""
107
return self.get_inventory_weave().versions()
110
def all_revision_ids(self):
111
"""Returns a list of all the revision ids in the repository.
113
These are in as much topological order as the underlying store can
114
present: for weaves ghosts may lead to a lack of correctness until
115
the reweave updates the parents list.
117
if self._revision_store.text_store.listable():
118
return self._revision_store.all_revision_ids(self.get_transaction())
119
result = self._all_possible_ids()
120
return self._eliminate_revisions_not_present(result)
122
def break_lock(self):
123
"""Break a lock if one is present from another instance.
125
Uses the ui factory to ask for confirmation if the lock may be from
128
self.control_files.break_lock()
131
def _eliminate_revisions_not_present(self, revision_ids):
132
"""Check every revision id in revision_ids to see if we have it.
134
Returns a set of the present revisions.
137
for id in revision_ids:
138
if self.has_revision(id):
143
def create(a_bzrdir):
144
"""Construct the current default format repository in a_bzrdir."""
145
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
147
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
148
"""instantiate a Repository.
150
:param _format: The format of the repository on disk.
151
:param a_bzrdir: The BzrDir of the repository.
153
In the future we will have a single api for all stores for
154
getting file texts, inventories and revisions, then
155
this construct will accept instances of those things.
157
super(Repository, self).__init__()
158
self._format = _format
159
# the following are part of the public API for Repository:
160
self.bzrdir = a_bzrdir
161
self.control_files = control_files
162
self._revision_store = _revision_store
163
self.text_store = text_store
164
# backwards compatability
165
self.weave_store = text_store
166
# not right yet - should be more semantically clear ?
168
self.control_store = control_store
169
self.control_weaves = control_store
170
# TODO: make sure to construct the right store classes, etc, depending
171
# on whether escaping is required.
174
return '%s(%r)' % (self.__class__.__name__,
175
self.bzrdir.transport.base)
178
return self.control_files.is_locked()
180
def lock_write(self):
181
self.control_files.lock_write()
184
self.control_files.lock_read()
186
def get_physical_lock_status(self):
187
return self.control_files.get_physical_lock_status()
190
def missing_revision_ids(self, other, revision_id=None):
191
"""Return the revision ids that other has that this does not.
193
These are returned in topological order.
195
revision_id: only return revision ids included by revision_id.
197
return InterRepository.get(other, self).missing_revision_ids(revision_id)
201
"""Open the repository rooted at base.
203
For instance, if the repository is at URL/.bzr/repository,
204
Repository.open(URL) -> a Repository instance.
206
control = bzrlib.bzrdir.BzrDir.open(base)
207
return control.open_repository()
209
def copy_content_into(self, destination, revision_id=None, basis=None):
210
"""Make a complete copy of the content in self into destination.
212
This is a destructive operation! Do not use it on existing
215
return InterRepository.get(self, destination).copy_content(revision_id, basis)
217
def fetch(self, source, revision_id=None, pb=None):
218
"""Fetch the content required to construct revision_id from source.
220
If revision_id is None all content is copied.
222
return InterRepository.get(source, self).fetch(revision_id=revision_id,
226
self.control_files.unlock()
229
def clone(self, a_bzrdir, revision_id=None, basis=None):
230
"""Clone this repository into a_bzrdir using the current format.
232
Currently no check is made that the format of this repository and
233
the bzrdir format are compatible. FIXME RBC 20060201.
235
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
236
# use target default format.
237
result = a_bzrdir.create_repository()
238
# FIXME RBC 20060209 split out the repository type to avoid this check ?
239
elif isinstance(a_bzrdir._format,
240
(bzrlib.bzrdir.BzrDirFormat4,
241
bzrlib.bzrdir.BzrDirFormat5,
242
bzrlib.bzrdir.BzrDirFormat6)):
243
result = a_bzrdir.open_repository()
245
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
246
self.copy_content_into(result, revision_id, basis)
250
def has_revision(self, revision_id):
251
"""True if this repository has a copy of the revision."""
252
return self._revision_store.has_revision_id(revision_id,
253
self.get_transaction())
256
def get_revision_reconcile(self, revision_id):
257
"""'reconcile' helper routine that allows access to a revision always.
259
This variant of get_revision does not cross check the weave graph
260
against the revision one as get_revision does: but it should only
261
be used by reconcile, or reconcile-alike commands that are correcting
262
or testing the revision graph.
264
if not revision_id or not isinstance(revision_id, basestring):
265
raise InvalidRevisionId(revision_id=revision_id, branch=self)
266
return self._revision_store.get_revision(revision_id,
267
self.get_transaction())
270
def get_revision_xml(self, revision_id):
271
rev = self.get_revision(revision_id)
273
# the current serializer..
274
self._revision_store._serializer.write_revision(rev, rev_tmp)
276
return rev_tmp.getvalue()
279
def get_revision(self, revision_id):
280
"""Return the Revision object for a named revision"""
281
r = self.get_revision_reconcile(revision_id)
282
# weave corruption can lead to absent revision markers that should be
284
# the following test is reasonably cheap (it needs a single weave read)
285
# and the weave is cached in read transactions. In write transactions
286
# it is not cached but typically we only read a small number of
287
# revisions. For knits when they are introduced we will probably want
288
# to ensure that caching write transactions are in use.
289
inv = self.get_inventory_weave()
290
self._check_revision_parents(r, inv)
293
def _check_revision_parents(self, revision, inventory):
294
"""Private to Repository and Fetch.
296
This checks the parentage of revision in an inventory weave for
297
consistency and is only applicable to inventory-weave-for-ancestry
298
using repository formats & fetchers.
300
weave_parents = inventory.get_parents(revision.revision_id)
301
weave_names = inventory.versions()
302
for parent_id in revision.parent_ids:
303
if parent_id in weave_names:
304
# this parent must not be a ghost.
305
if not parent_id in weave_parents:
307
raise errors.CorruptRepository(self)
310
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
311
signature = gpg_strategy.sign(plaintext)
312
self._revision_store.add_revision_signature_text(revision_id,
314
self.get_transaction())
316
def fileids_altered_by_revision_ids(self, revision_ids):
317
"""Find the file ids and versions affected by revisions.
319
:param revisions: an iterable containing revision ids.
320
:return: a dictionary mapping altered file-ids to an iterable of
321
revision_ids. Each altered file-ids has the exact revision_ids that
322
altered it listed explicitly.
324
assert isinstance(self._format, (RepositoryFormat5,
327
RepositoryFormatKnit1)), \
328
"fileid_involved only supported for branches which store inventory as unnested xml"
329
selected_revision_ids = set(revision_ids)
330
w = self.get_inventory_weave()
333
# this code needs to read every new line in every inventory for the
334
# inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
335
# not pesent in one of those inventories is unnecessary but not
336
# harmful because we are filtering by the revision id marker in the
337
# inventory lines : we only select file ids altered in one of those
338
# revisions. We dont need to see all lines in the inventory because
339
# only those added in an inventory in rev X can contain a revision=X
341
for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
342
start = line.find('file_id="')+9
343
if start < 9: continue
344
end = line.find('"', start)
346
file_id = _unescape_xml(line[start:end])
348
start = line.find('revision="')+10
349
if start < 10: continue
350
end = line.find('"', start)
352
revision_id = _unescape_xml(line[start:end])
353
if revision_id in selected_revision_ids:
354
result.setdefault(file_id, set()).add(revision_id)
358
def get_inventory_weave(self):
359
return self.control_weaves.get_weave('inventory',
360
self.get_transaction())
363
def get_inventory(self, revision_id):
364
"""Get Inventory object by hash."""
365
return self.deserialise_inventory(
366
revision_id, self.get_inventory_xml(revision_id))
368
def deserialise_inventory(self, revision_id, xml):
369
"""Transform the xml into an inventory object.
371
:param revision_id: The expected revision id of the inventory.
372
:param xml: A serialised inventory.
374
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
377
def get_inventory_xml(self, revision_id):
378
"""Get inventory XML as a file object."""
380
assert isinstance(revision_id, basestring), type(revision_id)
381
iw = self.get_inventory_weave()
382
return iw.get_text(revision_id)
384
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
387
def get_inventory_sha1(self, revision_id):
388
"""Return the sha1 hash of the inventory entry
390
return self.get_revision(revision_id).inventory_sha1
393
def get_revision_graph(self, revision_id=None):
394
"""Return a dictionary containing the revision graph.
396
:return: a dictionary of revision_id->revision_parents_list.
398
weave = self.get_inventory_weave()
399
all_revisions = self._eliminate_revisions_not_present(weave.versions())
400
entire_graph = dict([(node, weave.get_parents(node)) for
401
node in all_revisions])
402
if revision_id is None:
404
elif revision_id not in entire_graph:
405
raise errors.NoSuchRevision(self, revision_id)
407
# add what can be reached from revision_id
409
pending = set([revision_id])
410
while len(pending) > 0:
412
result[node] = entire_graph[node]
413
for revision_id in result[node]:
414
if revision_id not in result:
415
pending.add(revision_id)
419
def get_revision_graph_with_ghosts(self, revision_ids=None):
420
"""Return a graph of the revisions with ghosts marked as applicable.
422
:param revision_ids: an iterable of revisions to graph or None for all.
423
:return: a Graph object with the graph reachable from revision_ids.
427
pending = set(self.all_revision_ids())
430
pending = set(revision_ids)
431
required = set(revision_ids)
434
revision_id = pending.pop()
436
rev = self.get_revision(revision_id)
437
except errors.NoSuchRevision:
438
if revision_id in required:
441
result.add_ghost(revision_id)
443
for parent_id in rev.parent_ids:
444
# is this queued or done ?
445
if (parent_id not in pending and
446
parent_id not in done):
448
pending.add(parent_id)
449
result.add_node(revision_id, rev.parent_ids)
450
done.add(revision_id)
454
def get_revision_inventory(self, revision_id):
455
"""Return inventory of a past revision."""
456
# TODO: Unify this with get_inventory()
457
# bzr 0.0.6 and later imposes the constraint that the inventory_id
458
# must be the same as its revision, so this is trivial.
459
if revision_id is None:
460
# This does not make sense: if there is no revision,
461
# then it is the current tree inventory surely ?!
462
# and thus get_root_id() is something that looks at the last
463
# commit on the branch, and the get_root_id is an inventory check.
464
raise NotImplementedError
465
# return Inventory(self.get_root_id())
467
return self.get_inventory(revision_id)
471
"""Return True if this repository is flagged as a shared repository."""
472
raise NotImplementedError(self.is_shared)
475
def reconcile(self, other=None, thorough=False):
476
"""Reconcile this repository."""
477
from bzrlib.reconcile import RepoReconciler
478
reconciler = RepoReconciler(self, thorough=thorough)
479
reconciler.reconcile()
483
def revision_tree(self, revision_id):
484
"""Return Tree for a revision on this branch.
486
`revision_id` may be None for the null revision, in which case
487
an `EmptyTree` is returned."""
488
# TODO: refactor this to use an existing revision object
489
# so we don't need to read it in twice.
490
if revision_id is None or revision_id == NULL_REVISION:
493
inv = self.get_revision_inventory(revision_id)
494
return RevisionTree(self, inv, revision_id)
497
def get_ancestry(self, revision_id):
498
"""Return a list of revision-ids integrated by a revision.
500
This is topologically sorted.
502
if revision_id is None:
504
if not self.has_revision(revision_id):
505
raise errors.NoSuchRevision(self, revision_id)
506
w = self.get_inventory_weave()
507
candidates = w.get_ancestry(revision_id)
508
return [None] + candidates # self._eliminate_revisions_not_present(candidates)
511
def print_file(self, file, revision_id):
512
"""Print `file` to stdout.
514
FIXME RBC 20060125 as John Meinel points out this is a bad api
515
- it writes to stdout, it assumes that that is valid etc. Fix
516
by creating a new more flexible convenience function.
518
tree = self.revision_tree(revision_id)
519
# use inventory as it was in that revision
520
file_id = tree.inventory.path2id(file)
522
raise BzrError("%r is not present in revision %s" % (file, revno))
524
revno = self.revision_id_to_revno(revision_id)
525
except errors.NoSuchRevision:
526
# TODO: This should not be BzrError,
527
# but NoSuchFile doesn't fit either
528
raise BzrError('%r is not present in revision %s'
529
% (file, revision_id))
531
raise BzrError('%r is not present in revision %s'
533
tree.print_file(file_id)
535
def get_transaction(self):
536
return self.control_files.get_transaction()
538
def revision_parents(self, revid):
539
return self.get_inventory_weave().parent_names(revid)
542
def set_make_working_trees(self, new_value):
543
"""Set the policy flag for making working trees when creating branches.
545
This only applies to branches that use this repository.
547
The default is 'True'.
548
:param new_value: True to restore the default, False to disable making
551
raise NotImplementedError(self.set_make_working_trees)
553
def make_working_trees(self):
554
"""Returns the policy for making working trees on new branches."""
555
raise NotImplementedError(self.make_working_trees)
558
def sign_revision(self, revision_id, gpg_strategy):
559
plaintext = Testament.from_revision(self, revision_id).as_short_text()
560
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
563
def has_signature_for_revision_id(self, revision_id):
564
"""Query for a revision signature for revision_id in the repository."""
565
return self._revision_store.has_signature(revision_id,
566
self.get_transaction())
569
def get_signature_text(self, revision_id):
570
"""Return the text for a signature."""
571
return self._revision_store.get_signature_text(revision_id,
572
self.get_transaction())
575
class AllInOneRepository(Repository):
576
"""Legacy support - the repository behaviour for all-in-one branches."""
578
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
579
# we reuse one control files instance.
580
dir_mode = a_bzrdir._control_files._dir_mode
581
file_mode = a_bzrdir._control_files._file_mode
583
def get_store(name, compressed=True, prefixed=False):
584
# FIXME: This approach of assuming stores are all entirely compressed
585
# or entirely uncompressed is tidy, but breaks upgrade from
586
# some existing branches where there's a mixture; we probably
587
# still want the option to look for both.
588
relpath = a_bzrdir._control_files._escape(name)
589
store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
590
prefixed=prefixed, compressed=compressed,
593
#if self._transport.should_cache():
594
# cache_path = os.path.join(self.cache_root, name)
595
# os.mkdir(cache_path)
596
# store = bzrlib.store.CachedStore(store, cache_path)
599
# not broken out yet because the controlweaves|inventory_store
600
# and text_store | weave_store bits are still different.
601
if isinstance(_format, RepositoryFormat4):
602
# cannot remove these - there is still no consistent api
603
# which allows access to this old info.
604
self.inventory_store = get_store('inventory-store')
605
text_store = get_store('text-store')
606
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
610
"""AllInOne repositories cannot be shared."""
614
def set_make_working_trees(self, new_value):
615
"""Set the policy flag for making working trees when creating branches.
617
This only applies to branches that use this repository.
619
The default is 'True'.
620
:param new_value: True to restore the default, False to disable making
623
raise NotImplementedError(self.set_make_working_trees)
625
def make_working_trees(self):
626
"""Returns the policy for making working trees on new branches."""
630
def install_revision(repository, rev, revision_tree):
631
"""Install all revision data into a repository."""
634
for p_id in rev.parent_ids:
635
if repository.has_revision(p_id):
636
present_parents.append(p_id)
637
parent_trees[p_id] = repository.revision_tree(p_id)
639
parent_trees[p_id] = EmptyTree()
641
inv = revision_tree.inventory
643
# Add the texts that are not already present
644
for path, ie in inv.iter_entries():
645
w = repository.weave_store.get_weave_or_empty(ie.file_id,
646
repository.get_transaction())
647
if ie.revision not in w:
649
# FIXME: TODO: The following loop *may* be overlapping/duplicate
650
# with inventoryEntry.find_previous_heads(). if it is, then there
651
# is a latent bug here where the parents may have ancestors of each
653
for revision, tree in parent_trees.iteritems():
654
if ie.file_id not in tree:
656
parent_id = tree.inventory[ie.file_id].revision
657
if parent_id in text_parents:
659
text_parents.append(parent_id)
661
vfile = repository.weave_store.get_weave_or_empty(ie.file_id,
662
repository.get_transaction())
663
lines = revision_tree.get_file(ie.file_id).readlines()
664
vfile.add_lines(rev.revision_id, text_parents, lines)
666
# install the inventory
667
repository.add_inventory(rev.revision_id, inv, present_parents)
668
except errors.RevisionAlreadyPresent:
670
repository.add_revision(rev.revision_id, rev, inv)
673
class MetaDirRepository(Repository):
674
"""Repositories in the new meta-dir layout."""
676
def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
677
super(MetaDirRepository, self).__init__(_format,
684
dir_mode = self.control_files._dir_mode
685
file_mode = self.control_files._file_mode
689
"""Return True if this repository is flagged as a shared repository."""
690
return self.control_files._transport.has('shared-storage')
693
def set_make_working_trees(self, new_value):
694
"""Set the policy flag for making working trees when creating branches.
696
This only applies to branches that use this repository.
698
The default is 'True'.
699
:param new_value: True to restore the default, False to disable making
704
self.control_files._transport.delete('no-working-trees')
705
except errors.NoSuchFile:
708
self.control_files.put_utf8('no-working-trees', '')
710
def make_working_trees(self):
711
"""Returns the policy for making working trees on new branches."""
712
return not self.control_files._transport.has('no-working-trees')
715
class KnitRepository(MetaDirRepository):
716
"""Knit format repository."""
719
def all_revision_ids(self):
720
"""See Repository.all_revision_ids()."""
721
return self._revision_store.all_revision_ids(self.get_transaction())
723
def fileid_involved_between_revs(self, from_revid, to_revid):
724
"""Find file_id(s) which are involved in the changes between revisions.
726
This determines the set of revisions which are involved, and then
727
finds all file ids affected by those revisions.
729
vf = self._get_revision_vf()
730
from_set = set(vf.get_ancestry(from_revid))
731
to_set = set(vf.get_ancestry(to_revid))
732
changed = to_set.difference(from_set)
733
return self._fileid_involved_by_set(changed)
735
def fileid_involved(self, last_revid=None):
736
"""Find all file_ids modified in the ancestry of last_revid.
738
:param last_revid: If None, last_revision() will be used.
741
changed = set(self.all_revision_ids())
743
changed = set(self.get_ancestry(last_revid))
746
return self._fileid_involved_by_set(changed)
749
def get_ancestry(self, revision_id):
750
"""Return a list of revision-ids integrated by a revision.
752
This is topologically sorted.
754
if revision_id is None:
756
vf = self._get_revision_vf()
758
return [None] + vf.get_ancestry(revision_id)
759
except errors.RevisionNotPresent:
760
raise errors.NoSuchRevision(self, revision_id)
763
def get_revision(self, revision_id):
764
"""Return the Revision object for a named revision"""
765
return self.get_revision_reconcile(revision_id)
768
def get_revision_graph(self, revision_id=None):
769
"""Return a dictionary containing the revision graph.
771
:return: a dictionary of revision_id->revision_parents_list.
773
weave = self._get_revision_vf()
774
entire_graph = weave.get_graph()
775
if revision_id is None:
776
return weave.get_graph()
777
elif revision_id not in weave:
778
raise errors.NoSuchRevision(self, revision_id)
780
# add what can be reached from revision_id
782
pending = set([revision_id])
783
while len(pending) > 0:
785
result[node] = weave.get_parents(node)
786
for revision_id in result[node]:
787
if revision_id not in result:
788
pending.add(revision_id)
792
def get_revision_graph_with_ghosts(self, revision_ids=None):
793
"""Return a graph of the revisions with ghosts marked as applicable.
795
:param revision_ids: an iterable of revisions to graph or None for all.
796
:return: a Graph object with the graph reachable from revision_ids.
799
vf = self._get_revision_vf()
800
versions = set(vf.versions())
802
pending = set(self.all_revision_ids())
805
pending = set(revision_ids)
806
required = set(revision_ids)
809
revision_id = pending.pop()
810
if not revision_id in versions:
811
if revision_id in required:
812
raise errors.NoSuchRevision(self, revision_id)
814
result.add_ghost(revision_id)
815
# mark it as done so we dont try for it again.
816
done.add(revision_id)
818
parent_ids = vf.get_parents_with_ghosts(revision_id)
819
for parent_id in parent_ids:
820
# is this queued or done ?
821
if (parent_id not in pending and
822
parent_id not in done):
824
pending.add(parent_id)
825
result.add_node(revision_id, parent_ids)
826
done.add(revision_id)
829
def _get_revision_vf(self):
830
""":return: a versioned file containing the revisions."""
831
vf = self._revision_store.get_revision_file(self.get_transaction())
835
def reconcile(self, other=None, thorough=False):
836
"""Reconcile this repository."""
837
from bzrlib.reconcile import KnitReconciler
838
reconciler = KnitReconciler(self, thorough=thorough)
839
reconciler.reconcile()
842
def revision_parents(self, revid):
843
return self._get_revision_vf().get_parents(rev_id)
845
class RepositoryFormat(object):
846
"""A repository format.
848
Formats provide three things:
849
* An initialization routine to construct repository data on disk.
850
* a format string which is used when the BzrDir supports versioned
852
* an open routine which returns a Repository instance.
854
Formats are placed in an dict by their format string for reference
855
during opening. These should be subclasses of RepositoryFormat
858
Once a format is deprecated, just deprecate the initialize and open
859
methods on the format class. Do not deprecate the object, as the
860
object will be created every system load.
862
Common instance attributes:
863
_matchingbzrdir - the bzrdir format that the repository format was
864
originally written to work with. This can be used if manually
865
constructing a bzrdir and repository, or more commonly for test suite
869
_default_format = None
870
"""The default format used for new repositories."""
873
"""The known formats."""
876
def find_format(klass, a_bzrdir):
877
"""Return the format for the repository object in a_bzrdir."""
879
transport = a_bzrdir.get_repository_transport(None)
880
format_string = transport.get("format").read()
881
return klass._formats[format_string]
882
except errors.NoSuchFile:
883
raise errors.NoRepositoryPresent(a_bzrdir)
885
raise errors.UnknownFormatError(format_string)
887
def _get_control_store(self, repo_transport, control_files):
888
"""Return the control store for this repository."""
889
raise NotImplementedError(self._get_control_store)
892
def get_default_format(klass):
893
"""Return the current default format."""
894
return klass._default_format
896
def get_format_string(self):
897
"""Return the ASCII format string that identifies this format.
899
Note that in pre format ?? repositories the format string is
900
not permitted nor written to disk.
902
raise NotImplementedError(self.get_format_string)
904
def get_format_description(self):
905
"""Return the short desciption for this format."""
906
raise NotImplementedError(self.get_format_description)
908
def _get_revision_store(self, repo_transport, control_files):
909
"""Return the revision store object for this a_bzrdir."""
910
raise NotImplementedError(self._get_revision_store)
912
def _get_text_rev_store(self,
919
"""Common logic for getting a revision store for a repository.
921
see self._get_revision_store for the subclass-overridable method to
922
get the store for a repository.
924
from bzrlib.store.revision.text import TextRevisionStore
925
dir_mode = control_files._dir_mode
926
file_mode = control_files._file_mode
927
text_store =TextStore(transport.clone(name),
929
compressed=compressed,
932
_revision_store = TextRevisionStore(text_store, serializer)
933
return _revision_store
935
def _get_versioned_file_store(self,
940
versionedfile_class=WeaveFile,
942
weave_transport = control_files._transport.clone(name)
943
dir_mode = control_files._dir_mode
944
file_mode = control_files._file_mode
945
return VersionedFileStore(weave_transport, prefixed=prefixed,
948
versionedfile_class=versionedfile_class,
951
def initialize(self, a_bzrdir, shared=False):
952
"""Initialize a repository of this format in a_bzrdir.
954
:param a_bzrdir: The bzrdir to put the new repository in it.
955
:param shared: The repository should be initialized as a sharable one.
957
This may raise UninitializableFormat if shared repository are not
958
compatible the a_bzrdir.
961
def is_supported(self):
962
"""Is this format supported?
964
Supported formats must be initializable and openable.
965
Unsupported formats may not support initialization or committing or
966
some other features depending on the reason for not being supported.
970
def open(self, a_bzrdir, _found=False):
971
"""Return an instance of this format for the bzrdir a_bzrdir.
973
_found is a private parameter, do not use it.
975
raise NotImplementedError(self.open)
978
def register_format(klass, format):
979
klass._formats[format.get_format_string()] = format
982
def set_default_format(klass, format):
983
klass._default_format = format
986
def unregister_format(klass, format):
987
assert klass._formats[format.get_format_string()] is format
988
del klass._formats[format.get_format_string()]
991
class PreSplitOutRepositoryFormat(RepositoryFormat):
992
"""Base class for the pre split out repository formats."""
994
def initialize(self, a_bzrdir, shared=False, _internal=False):
995
"""Create a weave repository.
997
TODO: when creating split out bzr branch formats, move this to a common
998
base for Format5, Format6. or something like that.
1000
from bzrlib.weavefile import write_weave_v5
1001
from bzrlib.weave import Weave
1004
raise errors.IncompatibleFormat(self, a_bzrdir._format)
1007
# always initialized when the bzrdir is.
1008
return self.open(a_bzrdir, _found=True)
1010
# Create an empty weave
1012
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1013
empty_weave = sio.getvalue()
1015
mutter('creating repository in %s.', a_bzrdir.transport.base)
1016
dirs = ['revision-store', 'weaves']
1017
files = [('inventory.weave', StringIO(empty_weave)),
1020
# FIXME: RBC 20060125 dont peek under the covers
1021
# NB: no need to escape relative paths that are url safe.
1022
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1024
control_files.create_lock()
1025
control_files.lock_write()
1026
control_files._transport.mkdir_multi(dirs,
1027
mode=control_files._dir_mode)
1029
for file, content in files:
1030
control_files.put(file, content)
1032
control_files.unlock()
1033
return self.open(a_bzrdir, _found=True)
1035
def _get_control_store(self, repo_transport, control_files):
1036
"""Return the control store for this repository."""
1037
return self._get_versioned_file_store('',
1042
def _get_text_store(self, transport, control_files):
1043
"""Get a store for file texts for this format."""
1044
raise NotImplementedError(self._get_text_store)
1046
def open(self, a_bzrdir, _found=False):
1047
"""See RepositoryFormat.open()."""
1049
# we are being called directly and must probe.
1050
raise NotImplementedError
1052
repo_transport = a_bzrdir.get_repository_transport(None)
1053
control_files = a_bzrdir._control_files
1054
text_store = self._get_text_store(repo_transport, control_files)
1055
control_store = self._get_control_store(repo_transport, control_files)
1056
_revision_store = self._get_revision_store(repo_transport, control_files)
1057
return AllInOneRepository(_format=self,
1059
_revision_store=_revision_store,
1060
control_store=control_store,
1061
text_store=text_store)
1064
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1065
"""Bzr repository format 4.
1067
This repository format has:
1069
- TextStores for texts, inventories,revisions.
1071
This format is deprecated: it indexes texts using a text id which is
1072
removed in format 5; initializationa and write support for this format
1077
super(RepositoryFormat4, self).__init__()
1078
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
1080
def get_format_description(self):
1081
"""See RepositoryFormat.get_format_description()."""
1082
return "Repository format 4"
1084
def initialize(self, url, shared=False, _internal=False):
1085
"""Format 4 branches cannot be created."""
1086
raise errors.UninitializableFormat(self)
1088
def is_supported(self):
1089
"""Format 4 is not supported.
1091
It is not supported because the model changed from 4 to 5 and the
1092
conversion logic is expensive - so doing it on the fly was not
1097
def _get_control_store(self, repo_transport, control_files):
1098
"""Format 4 repositories have no formal control store at this point.
1100
This will cause any control-file-needing apis to fail - this is desired.
1104
def _get_revision_store(self, repo_transport, control_files):
1105
"""See RepositoryFormat._get_revision_store()."""
1106
from bzrlib.xml4 import serializer_v4
1107
return self._get_text_rev_store(repo_transport,
1110
serializer=serializer_v4)
1112
def _get_text_store(self, transport, control_files):
1113
"""See RepositoryFormat._get_text_store()."""
1116
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1117
"""Bzr control format 5.
1119
This repository format has:
1120
- weaves for file texts and inventory
1122
- TextStores for revisions and signatures.
1126
super(RepositoryFormat5, self).__init__()
1127
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
1129
def get_format_description(self):
1130
"""See RepositoryFormat.get_format_description()."""
1131
return "Weave repository format 5"
1133
def _get_revision_store(self, repo_transport, control_files):
1134
"""See RepositoryFormat._get_revision_store()."""
1135
"""Return the revision store object for this a_bzrdir."""
1136
return self._get_text_rev_store(repo_transport,
1141
def _get_text_store(self, transport, control_files):
1142
"""See RepositoryFormat._get_text_store()."""
1143
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
1146
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1147
"""Bzr control format 6.
1149
This repository format has:
1150
- weaves for file texts and inventory
1151
- hash subdirectory based stores.
1152
- TextStores for revisions and signatures.
1156
super(RepositoryFormat6, self).__init__()
1157
self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
1159
def get_format_description(self):
1160
"""See RepositoryFormat.get_format_description()."""
1161
return "Weave repository format 6"
1163
def _get_revision_store(self, repo_transport, control_files):
1164
"""See RepositoryFormat._get_revision_store()."""
1165
return self._get_text_rev_store(repo_transport,
1171
def _get_text_store(self, transport, control_files):
1172
"""See RepositoryFormat._get_text_store()."""
1173
return self._get_versioned_file_store('weaves', transport, control_files)
1176
class MetaDirRepositoryFormat(RepositoryFormat):
1177
"""Common base class for the new repositories using the metadir layour."""
1180
super(MetaDirRepositoryFormat, self).__init__()
1181
self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
1183
def _create_control_files(self, a_bzrdir):
1184
"""Create the required files and the initial control_files object."""
1185
# FIXME: RBC 20060125 dont peek under the covers
1186
# NB: no need to escape relative paths that are url safe.
1187
repository_transport = a_bzrdir.get_repository_transport(self)
1188
control_files = LockableFiles(repository_transport, 'lock', LockDir)
1189
control_files.create_lock()
1190
return control_files
1192
def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1193
"""Upload the initial blank content."""
1194
control_files = self._create_control_files(a_bzrdir)
1195
control_files.lock_write()
1197
control_files._transport.mkdir_multi(dirs,
1198
mode=control_files._dir_mode)
1199
for file, content in files:
1200
control_files.put(file, content)
1201
for file, content in utf8_files:
1202
control_files.put_utf8(file, content)
1204
control_files.put_utf8('shared-storage', '')
1206
control_files.unlock()
1209
class RepositoryFormat7(MetaDirRepositoryFormat):
1210
"""Bzr repository 7.
1212
This repository format has:
1213
- weaves for file texts and inventory
1214
- hash subdirectory based stores.
1215
- TextStores for revisions and signatures.
1216
- a format marker of its own
1217
- an optional 'shared-storage' flag
1218
- an optional 'no-working-trees' flag
1221
def _get_control_store(self, repo_transport, control_files):
1222
"""Return the control store for this repository."""
1223
return self._get_versioned_file_store('',
1228
def get_format_string(self):
1229
"""See RepositoryFormat.get_format_string()."""
1230
return "Bazaar-NG Repository format 7"
1232
def get_format_description(self):
1233
"""See RepositoryFormat.get_format_description()."""
1234
return "Weave repository format 7"
1236
def _get_revision_store(self, repo_transport, control_files):
1237
"""See RepositoryFormat._get_revision_store()."""
1238
return self._get_text_rev_store(repo_transport,
1245
def _get_text_store(self, transport, control_files):
1246
"""See RepositoryFormat._get_text_store()."""
1247
return self._get_versioned_file_store('weaves',
1251
def initialize(self, a_bzrdir, shared=False):
1252
"""Create a weave repository.
1254
:param shared: If true the repository will be initialized as a shared
1257
from bzrlib.weavefile import write_weave_v5
1258
from bzrlib.weave import Weave
1260
# Create an empty weave
1262
bzrlib.weavefile.write_weave_v5(Weave(), sio)
1263
empty_weave = sio.getvalue()
1265
mutter('creating repository in %s.', a_bzrdir.transport.base)
1266
dirs = ['revision-store', 'weaves']
1267
files = [('inventory.weave', StringIO(empty_weave)),
1269
utf8_files = [('format', self.get_format_string())]
1271
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1272
return self.open(a_bzrdir=a_bzrdir, _found=True)
1274
def open(self, a_bzrdir, _found=False, _override_transport=None):
1275
"""See RepositoryFormat.open().
1277
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1278
repository at a slightly different url
1279
than normal. I.e. during 'upgrade'.
1282
format = RepositoryFormat.find_format(a_bzrdir)
1283
assert format.__class__ == self.__class__
1284
if _override_transport is not None:
1285
repo_transport = _override_transport
1287
repo_transport = a_bzrdir.get_repository_transport(None)
1288
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1289
text_store = self._get_text_store(repo_transport, control_files)
1290
control_store = self._get_control_store(repo_transport, control_files)
1291
_revision_store = self._get_revision_store(repo_transport, control_files)
1292
return MetaDirRepository(_format=self,
1294
control_files=control_files,
1295
_revision_store=_revision_store,
1296
control_store=control_store,
1297
text_store=text_store)
1300
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1301
"""Bzr repository knit format 1.
1303
This repository format has:
1304
- knits for file texts and inventory
1305
- hash subdirectory based stores.
1306
- knits for revisions and signatures
1307
- TextStores for revisions and signatures.
1308
- a format marker of its own
1309
- an optional 'shared-storage' flag
1310
- an optional 'no-working-trees' flag
1313
This format was introduced in bzr 0.8.
1316
def _get_control_store(self, repo_transport, control_files):
1317
"""Return the control store for this repository."""
1318
return VersionedFileStore(
1321
file_mode=control_files._file_mode,
1322
versionedfile_class=KnitVersionedFile,
1323
versionedfile_kwargs={'factory':KnitPlainFactory()},
1326
def get_format_string(self):
1327
"""See RepositoryFormat.get_format_string()."""
1328
return "Bazaar-NG Knit Repository Format 1"
1330
def get_format_description(self):
1331
"""See RepositoryFormat.get_format_description()."""
1332
return "Knit repository format 1"
1334
def _get_revision_store(self, repo_transport, control_files):
1335
"""See RepositoryFormat._get_revision_store()."""
1336
from bzrlib.store.revision.knit import KnitRevisionStore
1337
versioned_file_store = VersionedFileStore(
1339
file_mode=control_files._file_mode,
1342
versionedfile_class=KnitVersionedFile,
1343
versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory()},
1346
return KnitRevisionStore(versioned_file_store)
1348
def _get_text_store(self, transport, control_files):
1349
"""See RepositoryFormat._get_text_store()."""
1350
return self._get_versioned_file_store('knits',
1353
versionedfile_class=KnitVersionedFile,
1356
def initialize(self, a_bzrdir, shared=False):
1357
"""Create a knit format 1 repository.
1359
:param a_bzrdir: bzrdir to contain the new repository; must already
1361
:param shared: If true the repository will be initialized as a shared
1364
mutter('creating repository in %s.', a_bzrdir.transport.base)
1365
dirs = ['revision-store', 'knits']
1367
utf8_files = [('format', self.get_format_string())]
1369
self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1370
repo_transport = a_bzrdir.get_repository_transport(None)
1371
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1372
control_store = self._get_control_store(repo_transport, control_files)
1373
transaction = bzrlib.transactions.WriteTransaction()
1374
# trigger a write of the inventory store.
1375
control_store.get_weave_or_empty('inventory', transaction)
1376
_revision_store = self._get_revision_store(repo_transport, control_files)
1377
_revision_store.has_revision_id('A', transaction)
1378
_revision_store.get_signature_file(transaction)
1379
return self.open(a_bzrdir=a_bzrdir, _found=True)
1381
def open(self, a_bzrdir, _found=False, _override_transport=None):
1382
"""See RepositoryFormat.open().
1384
:param _override_transport: INTERNAL USE ONLY. Allows opening the
1385
repository at a slightly different url
1386
than normal. I.e. during 'upgrade'.
1389
format = RepositoryFormat.find_format(a_bzrdir)
1390
assert format.__class__ == self.__class__
1391
if _override_transport is not None:
1392
repo_transport = _override_transport
1394
repo_transport = a_bzrdir.get_repository_transport(None)
1395
control_files = LockableFiles(repo_transport, 'lock', LockDir)
1396
text_store = self._get_text_store(repo_transport, control_files)
1397
control_store = self._get_control_store(repo_transport, control_files)
1398
_revision_store = self._get_revision_store(repo_transport, control_files)
1399
return KnitRepository(_format=self,
1401
control_files=control_files,
1402
_revision_store=_revision_store,
1403
control_store=control_store,
1404
text_store=text_store)
1407
# formats which have no format string are not discoverable
1408
# and not independently creatable, so are not registered.
1409
RepositoryFormat.register_format(RepositoryFormat7())
1410
_default_format = RepositoryFormatKnit1()
1411
RepositoryFormat.register_format(_default_format)
1412
RepositoryFormat.set_default_format(_default_format)
1413
_legacy_formats = [RepositoryFormat4(),
1414
RepositoryFormat5(),
1415
RepositoryFormat6()]
1418
class InterRepository(InterObject):
1419
"""This class represents operations taking place between two repositories.
1421
Its instances have methods like copy_content and fetch, and contain
1422
references to the source and target repositories these operations can be
1425
Often we will provide convenience methods on 'repository' which carry out
1426
operations with another repository - they will always forward to
1427
InterRepository.get(other).method_name(parameters).
1431
"""The available optimised InterRepository types."""
1434
def copy_content(self, revision_id=None, basis=None):
1435
"""Make a complete copy of the content in self into destination.
1437
This is a destructive operation! Do not use it on existing
1440
:param revision_id: Only copy the content needed to construct
1441
revision_id and its parents.
1442
:param basis: Copy the needed data preferentially from basis.
1445
self.target.set_make_working_trees(self.source.make_working_trees())
1446
except NotImplementedError:
1448
# grab the basis available data
1449
if basis is not None:
1450
self.target.fetch(basis, revision_id=revision_id)
1451
# but dont bother fetching if we have the needed data now.
1452
if (revision_id not in (None, NULL_REVISION) and
1453
self.target.has_revision(revision_id)):
1455
self.target.fetch(self.source, revision_id=revision_id)
1457
def _double_lock(self, lock_source, lock_target):
1458
"""Take out too locks, rolling back the first if the second throws."""
1463
# we want to ensure that we don't leave source locked by mistake.
1464
# and any error on target should not confuse source.
1465
self.source.unlock()
1469
def fetch(self, revision_id=None, pb=None):
1470
"""Fetch the content required to construct revision_id.
1472
The content is copied from source to target.
1474
:param revision_id: if None all content is copied, if NULL_REVISION no
1476
:param pb: optional progress bar to use for progress reports. If not
1477
provided a default one will be created.
1479
Returns the copied revision count and the failed revisions in a tuple:
1482
from bzrlib.fetch import GenericRepoFetcher
1483
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1484
self.source, self.source._format, self.target, self.target._format)
1485
f = GenericRepoFetcher(to_repository=self.target,
1486
from_repository=self.source,
1487
last_revision=revision_id,
1489
return f.count_copied, f.failed_revisions
1491
def lock_read(self):
1492
"""Take out a logical read lock.
1494
This will lock the source branch and the target branch. The source gets
1495
a read lock and the target a read lock.
1497
self._double_lock(self.source.lock_read, self.target.lock_read)
1499
def lock_write(self):
1500
"""Take out a logical write lock.
1502
This will lock the source branch and the target branch. The source gets
1503
a read lock and the target a write lock.
1505
self._double_lock(self.source.lock_read, self.target.lock_write)
1508
def missing_revision_ids(self, revision_id=None):
1509
"""Return the revision ids that source has that target does not.
1511
These are returned in topological order.
1513
:param revision_id: only return revision ids included by this
1516
# generic, possibly worst case, slow code path.
1517
target_ids = set(self.target.all_revision_ids())
1518
if revision_id is not None:
1519
source_ids = self.source.get_ancestry(revision_id)
1520
assert source_ids[0] == None
1523
source_ids = self.source.all_revision_ids()
1524
result_set = set(source_ids).difference(target_ids)
1525
# this may look like a no-op: its not. It preserves the ordering
1526
# other_ids had while only returning the members from other_ids
1527
# that we've decided we need.
1528
return [rev_id for rev_id in source_ids if rev_id in result_set]
1531
"""Release the locks on source and target."""
1533
self.target.unlock()
1535
self.source.unlock()
1538
class InterWeaveRepo(InterRepository):
1539
"""Optimised code paths between Weave based repositories."""
1541
_matching_repo_format = RepositoryFormat7()
1542
"""Repository format for testing with."""
1545
def is_compatible(source, target):
1546
"""Be compatible with known Weave formats.
1548
We dont test for the stores being of specific types becase that
1549
could lead to confusing results, and there is no need to be
1553
return (isinstance(source._format, (RepositoryFormat5,
1555
RepositoryFormat7)) and
1556
isinstance(target._format, (RepositoryFormat5,
1558
RepositoryFormat7)))
1559
except AttributeError:
1563
def copy_content(self, revision_id=None, basis=None):
1564
"""See InterRepository.copy_content()."""
1565
# weave specific optimised path:
1566
if basis is not None:
1567
# copy the basis in, then fetch remaining data.
1568
basis.copy_content_into(self.target, revision_id)
1569
# the basis copy_content_into could misset this.
1571
self.target.set_make_working_trees(self.source.make_working_trees())
1572
except NotImplementedError:
1574
self.target.fetch(self.source, revision_id=revision_id)
1577
self.target.set_make_working_trees(self.source.make_working_trees())
1578
except NotImplementedError:
1580
# FIXME do not peek!
1581
if self.source.control_files._transport.listable():
1582
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1584
self.target.weave_store.copy_all_ids(
1585
self.source.weave_store,
1587
from_transaction=self.source.get_transaction(),
1588
to_transaction=self.target.get_transaction())
1589
pb.update('copying inventory', 0, 1)
1590
self.target.control_weaves.copy_multi(
1591
self.source.control_weaves, ['inventory'],
1592
from_transaction=self.source.get_transaction(),
1593
to_transaction=self.target.get_transaction())
1594
self.target._revision_store.text_store.copy_all_ids(
1595
self.source._revision_store.text_store,
1600
self.target.fetch(self.source, revision_id=revision_id)
1603
def fetch(self, revision_id=None, pb=None):
1604
"""See InterRepository.fetch()."""
1605
from bzrlib.fetch import GenericRepoFetcher
1606
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1607
self.source, self.source._format, self.target, self.target._format)
1608
f = GenericRepoFetcher(to_repository=self.target,
1609
from_repository=self.source,
1610
last_revision=revision_id,
1612
return f.count_copied, f.failed_revisions
1615
def missing_revision_ids(self, revision_id=None):
1616
"""See InterRepository.missing_revision_ids()."""
1617
# we want all revisions to satisfy revision_id in source.
1618
# but we dont want to stat every file here and there.
1619
# we want then, all revisions other needs to satisfy revision_id
1620
# checked, but not those that we have locally.
1621
# so the first thing is to get a subset of the revisions to
1622
# satisfy revision_id in source, and then eliminate those that
1623
# we do already have.
1624
# this is slow on high latency connection to self, but as as this
1625
# disk format scales terribly for push anyway due to rewriting
1626
# inventory.weave, this is considered acceptable.
1628
if revision_id is not None:
1629
source_ids = self.source.get_ancestry(revision_id)
1630
assert source_ids[0] == None
1633
source_ids = self.source._all_possible_ids()
1634
source_ids_set = set(source_ids)
1635
# source_ids is the worst possible case we may need to pull.
1636
# now we want to filter source_ids against what we actually
1637
# have in target, but dont try to check for existence where we know
1638
# we do not have a revision as that would be pointless.
1639
target_ids = set(self.target._all_possible_ids())
1640
possibly_present_revisions = target_ids.intersection(source_ids_set)
1641
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1642
required_revisions = source_ids_set.difference(actually_present_revisions)
1643
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1644
if revision_id is not None:
1645
# we used get_ancestry to determine source_ids then we are assured all
1646
# revisions referenced are present as they are installed in topological order.
1647
# and the tip revision was validated by get_ancestry.
1648
return required_topo_revisions
1650
# if we just grabbed the possibly available ids, then
1651
# we only have an estimate of whats available and need to validate
1652
# that against the revision records.
1653
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1656
class InterKnitRepo(InterRepository):
1657
"""Optimised code paths between Knit based repositories."""
1659
_matching_repo_format = RepositoryFormatKnit1()
1660
"""Repository format for testing with."""
1663
def is_compatible(source, target):
1664
"""Be compatible with known Knit formats.
1666
We dont test for the stores being of specific types becase that
1667
could lead to confusing results, and there is no need to be
1671
return (isinstance(source._format, (RepositoryFormatKnit1)) and
1672
isinstance(target._format, (RepositoryFormatKnit1)))
1673
except AttributeError:
1677
def fetch(self, revision_id=None, pb=None):
1678
"""See InterRepository.fetch()."""
1679
from bzrlib.fetch import KnitRepoFetcher
1680
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1681
self.source, self.source._format, self.target, self.target._format)
1682
f = KnitRepoFetcher(to_repository=self.target,
1683
from_repository=self.source,
1684
last_revision=revision_id,
1686
return f.count_copied, f.failed_revisions
1689
def missing_revision_ids(self, revision_id=None):
1690
"""See InterRepository.missing_revision_ids()."""
1691
if revision_id is not None:
1692
source_ids = self.source.get_ancestry(revision_id)
1693
assert source_ids[0] == None
1696
source_ids = self.source._all_possible_ids()
1697
source_ids_set = set(source_ids)
1698
# source_ids is the worst possible case we may need to pull.
1699
# now we want to filter source_ids against what we actually
1700
# have in target, but dont try to check for existence where we know
1701
# we do not have a revision as that would be pointless.
1702
target_ids = set(self.target._all_possible_ids())
1703
possibly_present_revisions = target_ids.intersection(source_ids_set)
1704
actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1705
required_revisions = source_ids_set.difference(actually_present_revisions)
1706
required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
1707
if revision_id is not None:
1708
# we used get_ancestry to determine source_ids then we are assured all
1709
# revisions referenced are present as they are installed in topological order.
1710
# and the tip revision was validated by get_ancestry.
1711
return required_topo_revisions
1713
# if we just grabbed the possibly available ids, then
1714
# we only have an estimate of whats available and need to validate
1715
# that against the revision records.
1716
return self.source._eliminate_revisions_not_present(required_topo_revisions)
1718
InterRepository.register_optimiser(InterWeaveRepo)
1719
InterRepository.register_optimiser(InterKnitRepo)
1722
class RepositoryTestProviderAdapter(object):
1723
"""A tool to generate a suite testing multiple repository formats at once.
1725
This is done by copying the test once for each transport and injecting
1726
the transport_server, transport_readonly_server, and bzrdir_format and
1727
repository_format classes into each copy. Each copy is also given a new id()
1728
to make it easy to identify.
1731
def __init__(self, transport_server, transport_readonly_server, formats):
1732
self._transport_server = transport_server
1733
self._transport_readonly_server = transport_readonly_server
1734
self._formats = formats
1736
def adapt(self, test):
1737
result = TestSuite()
1738
for repository_format, bzrdir_format in self._formats:
1739
new_test = deepcopy(test)
1740
new_test.transport_server = self._transport_server
1741
new_test.transport_readonly_server = self._transport_readonly_server
1742
new_test.bzrdir_format = bzrdir_format
1743
new_test.repository_format = repository_format
1744
def make_new_test_id():
1745
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1746
return lambda: new_id
1747
new_test.id = make_new_test_id()
1748
result.addTest(new_test)
1752
class InterRepositoryTestProviderAdapter(object):
1753
"""A tool to generate a suite testing multiple inter repository formats.
1755
This is done by copying the test once for each interrepo provider and injecting
1756
the transport_server, transport_readonly_server, repository_format and
1757
repository_to_format classes into each copy.
1758
Each copy is also given a new id() to make it easy to identify.
1761
def __init__(self, transport_server, transport_readonly_server, formats):
1762
self._transport_server = transport_server
1763
self._transport_readonly_server = transport_readonly_server
1764
self._formats = formats
1766
def adapt(self, test):
1767
result = TestSuite()
1768
for interrepo_class, repository_format, repository_format_to in self._formats:
1769
new_test = deepcopy(test)
1770
new_test.transport_server = self._transport_server
1771
new_test.transport_readonly_server = self._transport_readonly_server
1772
new_test.interrepo_class = interrepo_class
1773
new_test.repository_format = repository_format
1774
new_test.repository_format_to = repository_format_to
1775
def make_new_test_id():
1776
new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1777
return lambda: new_id
1778
new_test.id = make_new_test_id()
1779
result.addTest(new_test)
1783
def default_test_list():
1784
"""Generate the default list of interrepo permutations to test."""
1786
# test the default InterRepository between format 6 and the current
1788
# XXX: robertc 20060220 reinstate this when there are two supported
1789
# formats which do not have an optimal code path between them.
1790
result.append((InterRepository,
1791
RepositoryFormat6(),
1792
RepositoryFormatKnit1()))
1793
for optimiser in InterRepository._optimisers:
1794
result.append((optimiser,
1795
optimiser._matching_repo_format,
1796
optimiser._matching_repo_format
1798
# if there are specific combinations we want to use, we can add them
1803
class CopyConverter(object):
1804
"""A repository conversion tool which just performs a copy of the content.
1806
This is slow but quite reliable.
1809
def __init__(self, target_format):
1810
"""Create a CopyConverter.
1812
:param target_format: The format the resulting repository should be.
1814
self.target_format = target_format
1816
def convert(self, repo, pb):
1817
"""Perform the conversion of to_convert, giving feedback via pb.
1819
:param to_convert: The disk object to convert.
1820
:param pb: a progress bar to use for progress information.
1825
# this is only useful with metadir layouts - separated repo content.
1826
# trigger an assertion if not such
1827
repo._format.get_format_string()
1828
self.repo_dir = repo.bzrdir
1829
self.step('Moving repository to repository.backup')
1830
self.repo_dir.transport.move('repository', 'repository.backup')
1831
backup_transport = self.repo_dir.transport.clone('repository.backup')
1832
self.source_repo = repo._format.open(self.repo_dir,
1834
_override_transport=backup_transport)
1835
self.step('Creating new repository')
1836
converted = self.target_format.initialize(self.repo_dir,
1837
self.source_repo.is_shared())
1838
converted.lock_write()
1840
self.step('Copying content into repository.')
1841
self.source_repo.copy_content_into(converted)
1844
self.step('Deleting old repository content.')
1845
self.repo_dir.transport.delete_tree('repository.backup')
1846
self.pb.note('repository converted')
1848
def step(self, message):
1849
"""Update the pb by a step."""
1851
self.pb.update(message, self.count, self.total)
1854
# Copied from xml.sax.saxutils
1855
def _unescape_xml(data):
1856
"""Unescape &, <, and > in a string of data.
1858
data = data.replace("<", "<")
1859
data = data.replace(">", ">")
1860
# must do ampersand last
1861
return data.replace("&", "&")