1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from copy import deepcopy
18
from cStringIO import StringIO
19
from unittest import TestSuite
20
import xml.sax.saxutils
23
import bzrlib.bzrdir as bzrdir
24
from bzrlib.decorators import needs_read_lock, needs_write_lock
25
import bzrlib.errors as errors
26
from bzrlib.errors import InvalidRevisionId
27
from bzrlib.lockable_files import LockableFiles
28
from bzrlib.osutils import safe_unicode
29
from bzrlib.revision import NULL_REVISION
30
from bzrlib.store import copy_all
31
from bzrlib.store.weave import WeaveStore
32
from bzrlib.store.text import TextStore
33
from bzrlib.symbol_versioning import *
34
from bzrlib.trace import mutter
35
from bzrlib.tree import RevisionTree
36
from bzrlib.testament import Testament
37
from bzrlib.tree import EmptyTree
42
class Repository(object):
43
"""Repository holding history for one or more branches.
45
The repository holds and retrieves historical information including
46
revisions and file history. It's normally accessed only by the Branch,
47
which views a particular line of development through that history.
49
The Repository builds on top of Stores and a Transport, which respectively
50
describe the disk data format and the way of accessing the (possibly
55
def _all_possible_ids(self):
56
"""Return all the possible revisions that we could find."""
57
return self.get_inventory_weave().names()
60
def all_revision_ids(self):
61
"""Returns a list of all the revision ids in the repository.
63
These are in as much topological order as the underlying store can
64
present: for weaves ghosts may lead to a lack of correctness until
65
the reweave updates the parents list.
67
result = self._all_possible_ids()
68
return self._eliminate_revisions_not_present(result)
71
def _eliminate_revisions_not_present(self, revision_ids):
72
"""Check every revision id in revision_ids to see if we have it.
74
Returns a set of the present revisions.
77
for id in revision_ids:
78
if self.has_revision(id):
84
"""Construct the current default format repository in a_bzrdir."""
85
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
87
def __init__(self, transport, branch_format, _format=None, a_bzrdir=None):
89
if transport is not None:
90
warn("Repository.__init__(..., transport=XXX): The transport parameter is "
91
"deprecated and was never in a supported release. Please use "
92
"bzrdir.open_repository() or bzrdir.open_branch().repository.",
95
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
97
# TODO: clone into repository if needed
98
self.control_files = LockableFiles(a_bzrdir.get_repository_transport(None), 'README')
100
dir_mode = self.control_files._dir_mode
101
file_mode = self.control_files._file_mode
102
self._format = _format
103
self.bzrdir = a_bzrdir
105
def get_weave(name, prefixed=False):
107
name = safe_unicode(name)
110
relpath = self.control_files._escape(name)
111
weave_transport = self.control_files._transport.clone(relpath)
112
ws = WeaveStore(weave_transport, prefixed=prefixed,
115
if self.control_files._transport.should_cache():
116
ws.enable_cache = True
120
def get_store(name, compressed=True, prefixed=False):
121
# FIXME: This approach of assuming stores are all entirely compressed
122
# or entirely uncompressed is tidy, but breaks upgrade from
123
# some existing branches where there's a mixture; we probably
124
# still want the option to look for both.
126
name = safe_unicode(name)
129
relpath = self.control_files._escape(name)
130
store = TextStore(self.control_files._transport.clone(relpath),
131
prefixed=prefixed, compressed=compressed,
134
#if self._transport.should_cache():
135
# cache_path = os.path.join(self.cache_root, name)
136
# os.mkdir(cache_path)
137
# store = bzrlib.store.CachedStore(store, cache_path)
140
if branch_format is not None:
141
# circular dependencies:
142
from bzrlib.branch import (BzrBranchFormat4,
146
if isinstance(branch_format, BzrBranchFormat4):
147
self._format = RepositoryFormat4()
148
elif isinstance(branch_format, BzrBranchFormat5):
149
self._format = RepositoryFormat5()
150
elif isinstance(branch_format, BzrBranchFormat6):
151
self._format = RepositoryFormat6()
154
if isinstance(self._format, RepositoryFormat4):
155
self.inventory_store = get_store('inventory-store')
156
self.text_store = get_store('text-store')
157
self.revision_store = get_store('revision-store')
158
elif isinstance(self._format, RepositoryFormat5):
159
self.control_weaves = get_weave('')
160
self.weave_store = get_weave('weaves')
161
self.revision_store = get_store('revision-store', compressed=False)
162
elif isinstance(self._format, RepositoryFormat6):
163
self.control_weaves = get_weave('')
164
self.weave_store = get_weave('weaves', prefixed=True)
165
self.revision_store = get_store('revision-store', compressed=False,
167
elif isinstance(self._format, RepositoryFormat7):
168
self.control_weaves = get_weave('')
169
self.weave_store = get_weave('weaves', prefixed=True)
170
self.revision_store = get_store('revision-store', compressed=False,
172
self.revision_store.register_suffix('sig')
174
def lock_write(self):
175
self.control_files.lock_write()
178
self.control_files.lock_read()
181
def missing_revision_ids(self, other, revision_id=None):
182
"""Return the revision ids that other has that this does not.
184
These are returned in topological order.
186
revision_id: only return revision ids included by revision_id.
188
if self._compatible_formats(other):
189
# fast path for weave-inventory based stores.
190
# we want all revisions to satisft revision_id in other.
191
# but we dont want to stat every file here and there.
192
# we want then, all revisions other needs to satisfy revision_id
193
# checked, but not those that we have locally.
194
# so the first thing is to get a subset of the revisions to
195
# satisfy revision_id in other, and then eliminate those that
196
# we do already have.
197
# this is slow on high latency connection to self, but as as this
198
# disk format scales terribly for push anyway due to rewriting
199
# inventory.weave, this is considered acceptable.
201
if revision_id is not None:
202
other_ids = other.get_ancestry(revision_id)
203
assert other_ids.pop(0) == None
205
other_ids = other._all_possible_ids()
206
other_ids_set = set(other_ids)
207
# other ids is the worst case to pull now.
208
# now we want to filter other_ids against what we actually
209
# have, but dont try to stat what we know we dont.
210
my_ids = set(self._all_possible_ids())
211
possibly_present_revisions = my_ids.intersection(other_ids_set)
212
actually_present_revisions = set(self._eliminate_revisions_not_present(possibly_present_revisions))
213
required_revisions = other_ids_set.difference(actually_present_revisions)
214
required_topo_revisions = [rev_id for rev_id in other_ids if rev_id in required_revisions]
215
if revision_id is not None:
216
# we used get_ancestry to determine other_ids then we are assured all
217
# revisions referenced are present as they are installed in topological order.
218
return required_topo_revisions
220
# we only have an estimate of whats available
221
return other._eliminate_revisions_not_present(required_topo_revisions)
223
my_ids = set(self.all_revision_ids())
224
if revision_id is not None:
225
other_ids = other.get_ancestry(revision_id)
226
assert other_ids.pop(0) == None
228
other_ids = other.all_revision_ids()
229
result_set = set(other_ids).difference(my_ids)
230
return [rev_id for rev_id in other_ids if rev_id in result_set]
234
"""Open the repository rooted at base.
236
For instance, if the repository is at URL/.bzr/repository,
237
Repository.open(URL) -> a Repository instance.
239
control = bzrdir.BzrDir.open(base)
240
return control.open_repository()
242
def _compatible_formats(self, other):
243
"""Return True if the stores in self and other are 'compatible'
245
'compatible' means that they are both the same underlying type
246
i.e. both weave stores, or both knits and thus support fast-path
248
return (isinstance(self._format, (RepositoryFormat5,
250
RepositoryFormat7)) and
251
isinstance(other._format, (RepositoryFormat5,
256
def copy_content_into(self, destination, revision_id=None, basis=None):
257
"""Make a complete copy of the content in self into destination."""
258
destination.lock_write()
262
if self._compatible_formats(destination):
263
if basis is not None:
264
# copy the basis in, then fetch remaining data.
265
basis.copy_content_into(destination, revision_id)
266
destination.fetch(self, revision_id=revision_id)
269
if self.control_files._transport.listable():
270
pb = bzrlib.ui.ui_factory.progress_bar()
271
copy_all(self.weave_store,
272
destination.weave_store, pb=pb)
273
pb.update('copying inventory', 0, 1)
274
destination.control_weaves.copy_multi(
275
self.control_weaves, ['inventory'])
276
copy_all(self.revision_store,
277
destination.revision_store, pb=pb)
279
destination.fetch(self, revision_id=revision_id)
280
# compatible v4 stores
281
elif isinstance(self._format, RepositoryFormat4):
282
if not isinstance(destination._format, RepositoryFormat4):
283
raise BzrError('cannot copy v4 branches to anything other than v4 branches.')
284
store_pairs = ((self.text_store, destination.text_store),
285
(self.inventory_store, destination.inventory_store),
286
(self.revision_store, destination.revision_store))
288
for from_store, to_store in store_pairs:
289
copy_all(from_store, to_store)
290
except UnlistableStore:
291
raise UnlistableBranch(from_store)
294
destination.fetch(self, revision_id=revision_id)
299
def fetch(self, source, revision_id=None):
300
"""Fetch the content required to construct revision_id from source.
302
If revision_id is None all content is copied.
304
from bzrlib.fetch import RepoFetcher
305
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
306
source, source._format, self, self._format)
307
RepoFetcher(to_repository=self, from_repository=source, last_revision=revision_id)
310
self.control_files.unlock()
313
def clone(self, a_bzrdir, revision_id=None, basis=None):
314
"""Clone this repository into a_bzrdir using the current format.
316
Currently no check is made that the format of this repository and
317
the bzrdir format are compatible. FIXME RBC 20060201.
319
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
320
# use target default format.
321
result = a_bzrdir.create_repository()
322
# FIXME RBC 20060209 split out the repository type to avoid this check ?
323
elif isinstance(a_bzrdir._format,
324
(bzrdir.BzrDirFormat4,
325
bzrdir.BzrDirFormat5,
326
bzrdir.BzrDirFormat6)):
327
result = a_bzrdir.open_repository()
329
result = self._format.initialize(a_bzrdir)
330
self.copy_content_into(result, revision_id, basis)
333
def has_revision(self, revision_id):
334
"""True if this branch has a copy of the revision.
336
This does not necessarily imply the revision is merge
337
or on the mainline."""
338
return (revision_id is None
339
or self.revision_store.has_id(revision_id))
342
def get_revision_xml_file(self, revision_id):
343
"""Return XML file object for revision object."""
344
if not revision_id or not isinstance(revision_id, basestring):
345
raise InvalidRevisionId(revision_id=revision_id, branch=self)
347
return self.revision_store.get(revision_id)
348
except (IndexError, KeyError):
349
raise bzrlib.errors.NoSuchRevision(self, revision_id)
352
def get_revision_xml(self, revision_id):
353
return self.get_revision_xml_file(revision_id).read()
356
def get_revision(self, revision_id):
357
"""Return the Revision object for a named revision"""
358
xml_file = self.get_revision_xml_file(revision_id)
361
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
362
except SyntaxError, e:
363
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
367
assert r.revision_id == revision_id
371
def get_revision_sha1(self, revision_id):
372
"""Hash the stored value of a revision, and return it."""
373
# In the future, revision entries will be signed. At that
374
# point, it is probably best *not* to include the signature
375
# in the revision hash. Because that lets you re-sign
376
# the revision, (add signatures/remove signatures) and still
377
# have all hash pointers stay consistent.
378
# But for now, just hash the contents.
379
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
382
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
383
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
386
def fileid_involved_between_revs(self, from_revid, to_revid):
387
"""Find file_id(s) which are involved in the changes between revisions.
389
This determines the set of revisions which are involved, and then
390
finds all file ids affected by those revisions.
392
# TODO: jam 20060119 This code assumes that w.inclusions will
393
# always be correct. But because of the presence of ghosts
394
# it is possible to be wrong.
395
# One specific example from Robert Collins:
396
# Two branches, with revisions ABC, and AD
397
# C is a ghost merge of D.
398
# Inclusions doesn't recognize D as an ancestor.
399
# If D is ever merged in the future, the weave
400
# won't be fixed, because AD never saw revision C
401
# to cause a conflict which would force a reweave.
402
w = self.get_inventory_weave()
403
from_set = set(w.inclusions([w.lookup(from_revid)]))
404
to_set = set(w.inclusions([w.lookup(to_revid)]))
405
included = to_set.difference(from_set)
406
changed = map(w.idx_to_name, included)
407
return self._fileid_involved_by_set(changed)
409
def fileid_involved(self, last_revid=None):
410
"""Find all file_ids modified in the ancestry of last_revid.
412
:param last_revid: If None, last_revision() will be used.
414
w = self.get_inventory_weave()
416
changed = set(w._names)
418
included = w.inclusions([w.lookup(last_revid)])
419
changed = map(w.idx_to_name, included)
420
return self._fileid_involved_by_set(changed)
422
def fileid_involved_by_set(self, changes):
423
"""Find all file_ids modified by the set of revisions passed in.
425
:param changes: A set() of revision ids
427
# TODO: jam 20060119 This line does *nothing*, remove it.
428
# or better yet, change _fileid_involved_by_set so
429
# that it takes the inventory weave, rather than
430
# pulling it out by itself.
431
return self._fileid_involved_by_set(changes)
433
def _fileid_involved_by_set(self, changes):
434
"""Find the set of file-ids affected by the set of revisions.
436
:param changes: A set() of revision ids.
437
:return: A set() of file ids.
439
This peaks at the Weave, interpreting each line, looking to
440
see if it mentions one of the revisions. And if so, includes
441
the file id mentioned.
442
This expects both the Weave format, and the serialization
443
to have a single line per file/directory, and to have
444
fileid="" and revision="" on that line.
446
assert isinstance(self._format, (RepositoryFormat5,
448
RepositoryFormat7)), \
449
"fileid_involved only supported for branches which store inventory as unnested xml"
451
w = self.get_inventory_weave()
453
for line in w._weave:
455
# it is ugly, but it is due to the weave structure
456
if not isinstance(line, basestring): continue
458
start = line.find('file_id="')+9
459
if start < 9: continue
460
end = line.find('"', start)
462
file_id = xml.sax.saxutils.unescape(line[start:end])
464
# check if file_id is already present
465
if file_id in file_ids: continue
467
start = line.find('revision="')+10
468
if start < 10: continue
469
end = line.find('"', start)
471
revision_id = xml.sax.saxutils.unescape(line[start:end])
473
if revision_id in changes:
474
file_ids.add(file_id)
478
def get_inventory_weave(self):
479
return self.control_weaves.get_weave('inventory',
480
self.get_transaction())
483
def get_inventory(self, revision_id):
484
"""Get Inventory object by hash."""
485
xml = self.get_inventory_xml(revision_id)
486
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
489
def get_inventory_xml(self, revision_id):
490
"""Get inventory XML as a file object."""
492
assert isinstance(revision_id, basestring), type(revision_id)
493
iw = self.get_inventory_weave()
494
return iw.get_text(iw.lookup(revision_id))
496
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
499
def get_inventory_sha1(self, revision_id):
500
"""Return the sha1 hash of the inventory entry
502
return self.get_revision(revision_id).inventory_sha1
505
def get_revision_inventory(self, revision_id):
506
"""Return inventory of a past revision."""
507
# TODO: Unify this with get_inventory()
508
# bzr 0.0.6 and later imposes the constraint that the inventory_id
509
# must be the same as its revision, so this is trivial.
510
if revision_id is None:
511
# This does not make sense: if there is no revision,
512
# then it is the current tree inventory surely ?!
513
# and thus get_root_id() is something that looks at the last
514
# commit on the branch, and the get_root_id is an inventory check.
515
raise NotImplementedError
516
# return Inventory(self.get_root_id())
518
return self.get_inventory(revision_id)
521
def revision_tree(self, revision_id):
522
"""Return Tree for a revision on this branch.
524
`revision_id` may be None for the null revision, in which case
525
an `EmptyTree` is returned."""
526
# TODO: refactor this to use an existing revision object
527
# so we don't need to read it in twice.
528
if revision_id is None or revision_id == NULL_REVISION:
531
inv = self.get_revision_inventory(revision_id)
532
return RevisionTree(self, inv, revision_id)
535
def get_ancestry(self, revision_id):
536
"""Return a list of revision-ids integrated by a revision.
538
This is topologically sorted.
540
if revision_id is None:
542
if not self.has_revision(revision_id):
543
raise errors.NoSuchRevision(self, revision_id)
544
w = self.get_inventory_weave()
545
return [None] + map(w.idx_to_name,
546
w.inclusions([w.lookup(revision_id)]))
549
def print_file(self, file, revision_id):
550
"""Print `file` to stdout.
552
FIXME RBC 20060125 as John Meinel points out this is a bad api
553
- it writes to stdout, it assumes that that is valid etc. Fix
554
by creating a new more flexible convenience function.
556
tree = self.revision_tree(revision_id)
557
# use inventory as it was in that revision
558
file_id = tree.inventory.path2id(file)
560
raise BzrError("%r is not present in revision %s" % (file, revno))
562
revno = self.revision_id_to_revno(revision_id)
563
except errors.NoSuchRevision:
564
# TODO: This should not be BzrError,
565
# but NoSuchFile doesn't fit either
566
raise BzrError('%r is not present in revision %s'
567
% (file, revision_id))
569
raise BzrError('%r is not present in revision %s'
571
tree.print_file(file_id)
573
def get_transaction(self):
574
return self.control_files.get_transaction()
577
def sign_revision(self, revision_id, gpg_strategy):
578
plaintext = Testament.from_revision(self, revision_id).as_short_text()
579
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
582
class RepositoryFormat(object):
583
"""A repository format.
585
Formats provide three things:
586
* An initialization routine to construct repository data on disk.
587
* a format string which is used when the BzrDir supports versioned
589
* an open routine which returns a Repository instance.
591
Formats are placed in an dict by their format string for reference
592
during opening. These should be subclasses of RepositoryFormat
595
Once a format is deprecated, just deprecate the initialize and open
596
methods on the format class. Do not deprecate the object, as the
597
object will be created every system load.
599
Common instance attributes:
600
_matchingbzrdir - the bzrdir format that the repository format was
601
originally written to work with. This can be used if manually
602
constructing a bzrdir and repository, or more commonly for test suite
606
_default_format = None
607
"""The default format used for new repositories."""
610
"""The known formats."""
613
def find_format(klass, a_bzrdir):
614
"""Return the format for the repository object in a_bzrdir."""
616
transport = a_bzrdir.get_repository_transport(None)
617
format_string = transport.get("format").read()
618
return klass._formats[format_string]
619
except errors.NoSuchFile:
620
raise errors.NoRepositoryPresent(a_bzrdir)
622
raise errors.UnknownFormatError(format_string)
625
def get_default_format(klass):
626
"""Return the current default format."""
627
return klass._default_format
629
def get_format_string(self):
630
"""Return the ASCII format string that identifies this format.
632
Note that in pre format ?? repositories the format string is
633
not permitted nor written to disk.
635
raise NotImplementedError(self.get_format_string)
637
def initialize(self, a_bzrdir, _internal=False):
638
"""Create a weave repository.
640
TODO: when creating split out bzr branch formats, move this to a common
641
base for Format5, Format6. or something like that.
643
from bzrlib.weavefile import write_weave_v5
644
from bzrlib.weave import Weave
647
# always initialized when the bzrdir is.
648
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
650
# Create an empty weave
652
bzrlib.weavefile.write_weave_v5(Weave(), sio)
653
empty_weave = sio.getvalue()
655
mutter('creating repository in %s.', a_bzrdir.transport.base)
656
dirs = ['revision-store', 'weaves']
657
lock_file = 'branch-lock'
658
files = [('inventory.weave', StringIO(empty_weave)),
661
# FIXME: RBC 20060125 dont peek under the covers
662
# NB: no need to escape relative paths that are url safe.
663
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
664
control_files.lock_write()
665
control_files._transport.mkdir_multi(dirs,
666
mode=control_files._dir_mode)
668
for file, content in files:
669
control_files.put(file, content)
671
control_files.unlock()
672
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
674
def is_supported(self):
675
"""Is this format supported?
677
Supported formats must be initializable and openable.
678
Unsupported formats may not support initialization or committing or
679
some other features depending on the reason for not being supported.
683
def open(self, a_bzrdir, _found=False):
684
"""Return an instance of this format for the bzrdir a_bzrdir.
686
_found is a private parameter, do not use it.
689
# we are being called directly and must probe.
690
raise NotImplementedError
691
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
694
def register_format(klass, format):
695
klass._formats[format.get_format_string()] = format
698
def set_default_format(klass, format):
699
klass._default_format = format
702
def unregister_format(klass, format):
703
assert klass._formats[format.get_format_string()] is format
704
del klass._formats[format.get_format_string()]
707
class RepositoryFormat4(RepositoryFormat):
708
"""Bzr repository format 4.
710
This repository format has:
712
- TextStores for texts, inventories,revisions.
714
This format is deprecated: it indexes texts using a text id which is
715
removed in format 5; initializationa and write support for this format
720
super(RepositoryFormat4, self).__init__()
721
self._matchingbzrdir = bzrdir.BzrDirFormat4()
723
def initialize(self, url, _internal=False):
724
"""Format 4 branches cannot be created."""
725
raise errors.UninitializableFormat(self)
727
def is_supported(self):
728
"""Format 4 is not supported.
730
It is not supported because the model changed from 4 to 5 and the
731
conversion logic is expensive - so doing it on the fly was not
737
class RepositoryFormat5(RepositoryFormat):
738
"""Bzr control format 5.
740
This repository format has:
741
- weaves for file texts and inventory
743
- TextStores for revisions and signatures.
747
super(RepositoryFormat5, self).__init__()
748
self._matchingbzrdir = bzrdir.BzrDirFormat5()
751
class RepositoryFormat6(RepositoryFormat):
752
"""Bzr control format 6.
754
This repository format has:
755
- weaves for file texts and inventory
756
- hash subdirectory based stores.
757
- TextStores for revisions and signatures.
761
super(RepositoryFormat6, self).__init__()
762
self._matchingbzrdir = bzrdir.BzrDirFormat6()
765
class RepositoryFormat7(RepositoryFormat):
768
This repository format has:
769
- weaves for file texts and inventory
770
- hash subdirectory based stores.
771
- TextStores for revisions and signatures.
772
- a format marker of its own
775
def get_format_string(self):
776
"""See RepositoryFormat.get_format_string()."""
777
return "Bazaar-NG Repository format 7"
779
def initialize(self, a_bzrdir):
780
"""Create a weave repository.
782
from bzrlib.weavefile import write_weave_v5
783
from bzrlib.weave import Weave
785
# Create an empty weave
787
bzrlib.weavefile.write_weave_v5(Weave(), sio)
788
empty_weave = sio.getvalue()
790
mutter('creating repository in %s.', a_bzrdir.transport.base)
791
dirs = ['revision-store', 'weaves']
792
files = [('inventory.weave', StringIO(empty_weave)),
794
utf8_files = [('format', self.get_format_string())]
796
# FIXME: RBC 20060125 dont peek under the covers
797
# NB: no need to escape relative paths that are url safe.
799
repository_transport = a_bzrdir.get_repository_transport(self)
800
repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
801
control_files = LockableFiles(repository_transport, 'lock')
802
control_files.lock_write()
803
control_files._transport.mkdir_multi(dirs,
804
mode=control_files._dir_mode)
806
for file, content in files:
807
control_files.put(file, content)
808
for file, content in utf8_files:
809
control_files.put_utf8(file, content)
811
control_files.unlock()
812
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
815
super(RepositoryFormat7, self).__init__()
816
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
819
# formats which have no format string are not discoverable
820
# and not independently creatable, so are not registered.
821
__default_format = RepositoryFormat7()
822
RepositoryFormat.register_format(__default_format)
823
RepositoryFormat.set_default_format(__default_format)
824
_legacy_formats = [RepositoryFormat4(),
829
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
830
# make sure that ancestry.weave is deleted (it is never used, but
831
# used to be created)
833
class RepositoryTestProviderAdapter(object):
834
"""A tool to generate a suite testing multiple repository formats at once.
836
This is done by copying the test once for each transport and injecting
837
the transport_server, transport_readonly_server, and bzrdir_format and
838
repository_format classes into each copy. Each copy is also given a new id()
839
to make it easy to identify.
842
def __init__(self, transport_server, transport_readonly_server, formats):
843
self._transport_server = transport_server
844
self._transport_readonly_server = transport_readonly_server
845
self._formats = formats
847
def adapt(self, test):
849
for repository_format, bzrdir_format in self._formats:
850
new_test = deepcopy(test)
851
new_test.transport_server = self._transport_server
852
new_test.transport_readonly_server = self._transport_readonly_server
853
new_test.bzrdir_format = bzrdir_format
854
new_test.repository_format = repository_format
855
def make_new_test_id():
856
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
857
return lambda: new_id
858
new_test.id = make_new_test_id()
859
result.addTest(new_test)