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
41
class Repository(object):
42
"""Repository holding history for one or more branches.
44
The repository holds and retrieves historical information including
45
revisions and file history. It's normally accessed only by the Branch,
46
which views a particular line of development through that history.
48
The Repository builds on top of Stores and a Transport, which respectively
49
describe the disk data format and the way of accessing the (possibly
54
def _all_possible_ids(self):
55
"""Return all the possible revisions that we could find."""
56
return self.get_inventory_weave().names()
59
def all_revision_ids(self):
60
"""Returns a list of all the revision ids in the repository.
62
These are in as much topological order as the underlying store can
63
present: for weaves ghosts may lead to a lack of correctness until
64
the reweave updates the parents list.
66
result = self._all_possible_ids()
67
return self._eliminate_revisions_not_present(result)
70
def _eliminate_revisions_not_present(self, revision_ids):
71
"""Check every revision id in revision_ids to see if we have it.
73
Returns a set of the present revisions.
76
for id in revision_ids:
77
if self.has_revision(id):
83
"""Construct the current default format repository in a_bzrdir."""
84
return RepositoryFormat.get_default_format().initialize(a_bzrdir)
86
def __init__(self, transport, branch_format, _format=None, a_bzrdir=None):
88
if transport is not None:
89
warn("Repository.__init__(..., transport=XXX): The transport parameter is "
90
"deprecated and was never in a supported release. Please use "
91
"bzrdir.open_repository() or bzrdir.open_branch().repository.",
94
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
96
# TODO: clone into repository if needed
97
self.control_files = LockableFiles(a_bzrdir.get_repository_transport(None), 'README')
99
dir_mode = self.control_files._dir_mode
100
file_mode = self.control_files._file_mode
101
self._format = _format
102
self.bzrdir = a_bzrdir
104
def get_weave(name, prefixed=False):
106
name = safe_unicode(name)
109
relpath = self.control_files._escape(name)
110
weave_transport = self.control_files._transport.clone(relpath)
111
ws = WeaveStore(weave_transport, prefixed=prefixed,
114
if self.control_files._transport.should_cache():
115
ws.enable_cache = True
119
def get_store(name, compressed=True, prefixed=False):
120
# FIXME: This approach of assuming stores are all entirely compressed
121
# or entirely uncompressed is tidy, but breaks upgrade from
122
# some existing branches where there's a mixture; we probably
123
# still want the option to look for both.
125
name = safe_unicode(name)
128
relpath = self.control_files._escape(name)
129
store = TextStore(self.control_files._transport.clone(relpath),
130
prefixed=prefixed, compressed=compressed,
133
#if self._transport.should_cache():
134
# cache_path = os.path.join(self.cache_root, name)
135
# os.mkdir(cache_path)
136
# store = bzrlib.store.CachedStore(store, cache_path)
139
if branch_format is not None:
140
# circular dependencies:
141
from bzrlib.branch import (BzrBranchFormat4,
145
if isinstance(branch_format, BzrBranchFormat4):
146
self._format = RepositoryFormat4()
147
elif isinstance(branch_format, BzrBranchFormat5):
148
self._format = RepositoryFormat5()
149
elif isinstance(branch_format, BzrBranchFormat6):
150
self._format = RepositoryFormat6()
153
if isinstance(self._format, RepositoryFormat4):
154
self.inventory_store = get_store('inventory-store')
155
self.text_store = get_store('text-store')
156
self.revision_store = get_store('revision-store')
157
elif isinstance(self._format, RepositoryFormat5):
158
self.control_weaves = get_weave('')
159
self.weave_store = get_weave('weaves')
160
self.revision_store = get_store('revision-store', compressed=False)
161
elif isinstance(self._format, RepositoryFormat6):
162
self.control_weaves = get_weave('')
163
self.weave_store = get_weave('weaves', prefixed=True)
164
self.revision_store = get_store('revision-store', compressed=False,
166
elif isinstance(self._format, RepositoryFormat7):
167
self.control_weaves = get_weave('')
168
self.weave_store = get_weave('weaves', prefixed=True)
169
self.revision_store = get_store('revision-store', compressed=False,
171
self.revision_store.register_suffix('sig')
173
def lock_write(self):
174
self.control_files.lock_write()
177
self.control_files.lock_read()
180
def missing_revision_ids(self, other, revision_id=None):
181
"""Return the revision ids that other has that this does not.
183
These are returned in topological order.
185
revision_id: only return revision ids included by revision_id.
187
if self._compatible_formats(other):
188
# fast path for weave-inventory based stores.
189
# we want all revisions to satisft revision_id in other.
190
# but we dont want to stat every file here and there.
191
# we want then, all revisions other needs to satisfy revision_id
192
# checked, but not those that we have locally.
193
# so the first thing is to get a subset of the revisions to
194
# satisfy revision_id in other, and then eliminate those that
195
# we do already have.
196
# this is slow on high latency connection to self, but as as this
197
# disk format scales terribly for push anyway due to rewriting
198
# inventory.weave, this is considered acceptable.
200
if revision_id is not None:
201
other_ids = other.get_ancestry(revision_id)
202
assert other_ids.pop(0) == None
204
other_ids = other._all_possible_ids()
205
other_ids_set = set(other_ids)
206
# other ids is the worst case to pull now.
207
# now we want to filter other_ids against what we actually
208
# have, but dont try to stat what we know we dont.
209
my_ids = set(self._all_possible_ids())
210
possibly_present_revisions = my_ids.intersection(other_ids_set)
211
actually_present_revisions = set(self._eliminate_revisions_not_present(possibly_present_revisions))
212
required_revisions = other_ids_set.difference(actually_present_revisions)
213
required_topo_revisions = [rev_id for rev_id in other_ids if rev_id in required_revisions]
214
if revision_id is not None:
215
# we used get_ancestry to determine other_ids then we are assured all
216
# revisions referenced are present as they are installed in topological order.
217
return required_topo_revisions
219
# we only have an estimate of whats available
220
return other._eliminate_revisions_not_present(required_topo_revisions)
222
my_ids = set(self.all_revision_ids())
223
if revision_id is not None:
224
other_ids = other.get_ancestry(revision_id)
225
assert other_ids.pop(0) == None
227
other_ids = other.all_revision_ids()
228
result_set = set(other_ids).difference(my_ids)
229
return [rev_id for rev_id in other_ids if rev_id in result_set]
233
"""Open the repository rooted at base.
235
For instance, if the repository is at URL/.bzr/repository,
236
Repository.open(URL) -> a Repository instance.
238
control = bzrdir.BzrDir.open(base)
239
return control.open_repository()
241
def _compatible_formats(self, other):
242
"""Return True if the stores in self and other are 'compatible'
244
'compatible' means that they are both the same underlying type
245
i.e. both weave stores, or both knits and thus support fast-path
247
return (isinstance(self._format, (RepositoryFormat5,
249
RepositoryFormat7)) and
250
isinstance(other._format, (RepositoryFormat5,
255
def copy_content_into(self, destination, revision_id=None, basis=None):
256
"""Make a complete copy of the content in self into destination."""
257
destination.lock_write()
261
if self._compatible_formats(destination):
262
if basis is not None:
263
# copy the basis in, then fetch remaining data.
264
basis.copy_content_into(destination, revision_id)
265
destination.fetch(self, revision_id=revision_id)
268
if self.control_files._transport.listable():
269
destination.control_weaves.copy_multi(self.control_weaves,
271
copy_all(self.weave_store, destination.weave_store)
272
copy_all(self.revision_store, destination.revision_store)
274
destination.fetch(self, revision_id=revision_id)
275
# compatible v4 stores
276
elif isinstance(self._format, RepositoryFormat4):
277
if not isinstance(destination._format, RepositoryFormat4):
278
raise BzrError('cannot copy v4 branches to anything other than v4 branches.')
279
store_pairs = ((self.text_store, destination.text_store),
280
(self.inventory_store, destination.inventory_store),
281
(self.revision_store, destination.revision_store))
283
for from_store, to_store in store_pairs:
284
copy_all(from_store, to_store)
285
except UnlistableStore:
286
raise UnlistableBranch(from_store)
289
destination.fetch(self, revision_id=revision_id)
294
def fetch(self, source, revision_id=None):
295
"""Fetch the content required to construct revision_id from source.
297
If revision_id is None all content is copied.
299
from bzrlib.fetch import RepoFetcher
300
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
301
source, source._format, self, self._format)
302
RepoFetcher(to_repository=self, from_repository=source, last_revision=revision_id)
305
self.control_files.unlock()
308
def clone(self, a_bzrdir, revision_id=None, basis=None):
309
"""Clone this repository into a_bzrdir using the current format.
311
Currently no check is made that the format of this repository and
312
the bzrdir format are compatible. FIXME RBC 20060201.
314
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
315
# use target default format.
316
result = a_bzrdir.create_repository()
317
# FIXME RBC 20060209 split out the repository type to avoid this check ?
318
elif isinstance(a_bzrdir._format,
319
(bzrdir.BzrDirFormat4,
320
bzrdir.BzrDirFormat5,
321
bzrdir.BzrDirFormat6)):
322
result = a_bzrdir.open_repository()
324
result = self._format.initialize(a_bzrdir)
325
self.copy_content_into(result, revision_id, basis)
328
def has_revision(self, revision_id):
329
"""True if this branch has a copy of the revision.
331
This does not necessarily imply the revision is merge
332
or on the mainline."""
333
return (revision_id is None
334
or self.revision_store.has_id(revision_id))
337
def get_revision_xml_file(self, revision_id):
338
"""Return XML file object for revision object."""
339
if not revision_id or not isinstance(revision_id, basestring):
340
raise InvalidRevisionId(revision_id=revision_id, branch=self)
342
return self.revision_store.get(revision_id)
343
except (IndexError, KeyError):
344
raise bzrlib.errors.NoSuchRevision(self, revision_id)
347
def get_revision_xml(self, revision_id):
348
return self.get_revision_xml_file(revision_id).read()
351
def get_revision(self, revision_id):
352
"""Return the Revision object for a named revision"""
353
xml_file = self.get_revision_xml_file(revision_id)
356
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
357
except SyntaxError, e:
358
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
362
assert r.revision_id == revision_id
366
def get_revision_sha1(self, revision_id):
367
"""Hash the stored value of a revision, and return it."""
368
# In the future, revision entries will be signed. At that
369
# point, it is probably best *not* to include the signature
370
# in the revision hash. Because that lets you re-sign
371
# the revision, (add signatures/remove signatures) and still
372
# have all hash pointers stay consistent.
373
# But for now, just hash the contents.
374
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
377
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
378
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
381
def fileid_involved_between_revs(self, from_revid, to_revid):
382
"""Find file_id(s) which are involved in the changes between revisions.
384
This determines the set of revisions which are involved, and then
385
finds all file ids affected by those revisions.
387
# TODO: jam 20060119 This code assumes that w.inclusions will
388
# always be correct. But because of the presence of ghosts
389
# it is possible to be wrong.
390
# One specific example from Robert Collins:
391
# Two branches, with revisions ABC, and AD
392
# C is a ghost merge of D.
393
# Inclusions doesn't recognize D as an ancestor.
394
# If D is ever merged in the future, the weave
395
# won't be fixed, because AD never saw revision C
396
# to cause a conflict which would force a reweave.
397
w = self.get_inventory_weave()
398
from_set = set(w.inclusions([w.lookup(from_revid)]))
399
to_set = set(w.inclusions([w.lookup(to_revid)]))
400
included = to_set.difference(from_set)
401
changed = map(w.idx_to_name, included)
402
return self._fileid_involved_by_set(changed)
404
def fileid_involved(self, last_revid=None):
405
"""Find all file_ids modified in the ancestry of last_revid.
407
:param last_revid: If None, last_revision() will be used.
409
w = self.get_inventory_weave()
411
changed = set(w._names)
413
included = w.inclusions([w.lookup(last_revid)])
414
changed = map(w.idx_to_name, included)
415
return self._fileid_involved_by_set(changed)
417
def fileid_involved_by_set(self, changes):
418
"""Find all file_ids modified by the set of revisions passed in.
420
:param changes: A set() of revision ids
422
# TODO: jam 20060119 This line does *nothing*, remove it.
423
# or better yet, change _fileid_involved_by_set so
424
# that it takes the inventory weave, rather than
425
# pulling it out by itself.
426
return self._fileid_involved_by_set(changes)
428
def _fileid_involved_by_set(self, changes):
429
"""Find the set of file-ids affected by the set of revisions.
431
:param changes: A set() of revision ids.
432
:return: A set() of file ids.
434
This peaks at the Weave, interpreting each line, looking to
435
see if it mentions one of the revisions. And if so, includes
436
the file id mentioned.
437
This expects both the Weave format, and the serialization
438
to have a single line per file/directory, and to have
439
fileid="" and revision="" on that line.
441
assert isinstance(self._format, (RepositoryFormat5,
443
RepositoryFormat7)), \
444
"fileid_involved only supported for branches which store inventory as unnested xml"
446
w = self.get_inventory_weave()
448
for line in w._weave:
450
# it is ugly, but it is due to the weave structure
451
if not isinstance(line, basestring): continue
453
start = line.find('file_id="')+9
454
if start < 9: continue
455
end = line.find('"', start)
457
file_id = xml.sax.saxutils.unescape(line[start:end])
459
# check if file_id is already present
460
if file_id in file_ids: continue
462
start = line.find('revision="')+10
463
if start < 10: continue
464
end = line.find('"', start)
466
revision_id = xml.sax.saxutils.unescape(line[start:end])
468
if revision_id in changes:
469
file_ids.add(file_id)
473
def get_inventory_weave(self):
474
return self.control_weaves.get_weave('inventory',
475
self.get_transaction())
478
def get_inventory(self, revision_id):
479
"""Get Inventory object by hash."""
480
xml = self.get_inventory_xml(revision_id)
481
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
484
def get_inventory_xml(self, revision_id):
485
"""Get inventory XML as a file object."""
487
assert isinstance(revision_id, basestring), type(revision_id)
488
iw = self.get_inventory_weave()
489
return iw.get_text(iw.lookup(revision_id))
491
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
494
def get_inventory_sha1(self, revision_id):
495
"""Return the sha1 hash of the inventory entry
497
return self.get_revision(revision_id).inventory_sha1
500
def get_revision_inventory(self, revision_id):
501
"""Return inventory of a past revision."""
502
# TODO: Unify this with get_inventory()
503
# bzr 0.0.6 and later imposes the constraint that the inventory_id
504
# must be the same as its revision, so this is trivial.
505
if revision_id is None:
506
# This does not make sense: if there is no revision,
507
# then it is the current tree inventory surely ?!
508
# and thus get_root_id() is something that looks at the last
509
# commit on the branch, and the get_root_id is an inventory check.
510
raise NotImplementedError
511
# return Inventory(self.get_root_id())
513
return self.get_inventory(revision_id)
516
def revision_tree(self, revision_id):
517
"""Return Tree for a revision on this branch.
519
`revision_id` may be None for the null revision, in which case
520
an `EmptyTree` is returned."""
521
# TODO: refactor this to use an existing revision object
522
# so we don't need to read it in twice.
523
if revision_id is None or revision_id == NULL_REVISION:
526
inv = self.get_revision_inventory(revision_id)
527
return RevisionTree(self, inv, revision_id)
530
def get_ancestry(self, revision_id):
531
"""Return a list of revision-ids integrated by a revision.
533
This is topologically sorted.
535
if revision_id is None:
537
if not self.has_revision(revision_id):
538
raise errors.NoSuchRevision(self, revision_id)
539
w = self.get_inventory_weave()
540
return [None] + map(w.idx_to_name,
541
w.inclusions([w.lookup(revision_id)]))
544
def print_file(self, file, revision_id):
545
"""Print `file` to stdout.
547
FIXME RBC 20060125 as John Meinel points out this is a bad api
548
- it writes to stdout, it assumes that that is valid etc. Fix
549
by creating a new more flexible convenience function.
551
tree = self.revision_tree(revision_id)
552
# use inventory as it was in that revision
553
file_id = tree.inventory.path2id(file)
555
raise BzrError("%r is not present in revision %s" % (file, revno))
557
revno = self.revision_id_to_revno(revision_id)
558
except errors.NoSuchRevision:
559
# TODO: This should not be BzrError,
560
# but NoSuchFile doesn't fit either
561
raise BzrError('%r is not present in revision %s'
562
% (file, revision_id))
564
raise BzrError('%r is not present in revision %s'
566
tree.print_file(file_id)
568
def get_transaction(self):
569
return self.control_files.get_transaction()
572
def sign_revision(self, revision_id, gpg_strategy):
573
plaintext = Testament.from_revision(self, revision_id).as_short_text()
574
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
577
class RepositoryFormat(object):
578
"""A repository format.
580
Formats provide three things:
581
* An initialization routine to construct repository data on disk.
582
* a format string which is used when the BzrDir supports versioned
584
* an open routine which returns a Repository instance.
586
Formats are placed in an dict by their format string for reference
587
during opening. These should be subclasses of RepositoryFormat
590
Once a format is deprecated, just deprecate the initialize and open
591
methods on the format class. Do not deprecate the object, as the
592
object will be created every system load.
594
Common instance attributes:
595
_matchingbzrdir - the bzrdir format that the repository format was
596
originally written to work with. This can be used if manually
597
constructing a bzrdir and repository, or more commonly for test suite
601
_default_format = None
602
"""The default format used for new repositories."""
605
"""The known formats."""
608
def find_format(klass, a_bzrdir):
609
"""Return the format for the repository object in a_bzrdir."""
611
transport = a_bzrdir.get_repository_transport(None)
612
format_string = transport.get("format").read()
613
return klass._formats[format_string]
614
except errors.NoSuchFile:
615
raise errors.NoRepositoryPresent(a_bzrdir)
617
raise errors.UnknownFormatError(format_string)
620
def get_default_format(klass):
621
"""Return the current default format."""
622
return klass._default_format
624
def get_format_string(self):
625
"""Return the ASCII format string that identifies this format.
627
Note that in pre format ?? repositories the format string is
628
not permitted nor written to disk.
630
raise NotImplementedError(self.get_format_string)
632
def initialize(self, a_bzrdir, _internal=False):
633
"""Create a weave repository.
635
TODO: when creating split out bzr branch formats, move this to a common
636
base for Format5, Format6. or something like that.
638
from bzrlib.weavefile import write_weave_v5
639
from bzrlib.weave import Weave
642
# always initialized when the bzrdir is.
643
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
645
# Create an empty weave
647
bzrlib.weavefile.write_weave_v5(Weave(), sio)
648
empty_weave = sio.getvalue()
650
mutter('creating repository in %s.', a_bzrdir.transport.base)
651
dirs = ['revision-store', 'weaves']
652
lock_file = 'branch-lock'
653
files = [('inventory.weave', StringIO(empty_weave)),
656
# FIXME: RBC 20060125 dont peek under the covers
657
# NB: no need to escape relative paths that are url safe.
658
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
659
control_files.lock_write()
660
control_files._transport.mkdir_multi(dirs,
661
mode=control_files._dir_mode)
663
for file, content in files:
664
control_files.put(file, content)
666
control_files.unlock()
667
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
669
def is_supported(self):
670
"""Is this format supported?
672
Supported formats must be initializable and openable.
673
Unsupported formats may not support initialization or committing or
674
some other features depending on the reason for not being supported.
678
def open(self, a_bzrdir, _found=False):
679
"""Return an instance of this format for the bzrdir a_bzrdir.
681
_found is a private parameter, do not use it.
684
# we are being called directly and must probe.
685
raise NotImplementedError
686
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
689
def register_format(klass, format):
690
klass._formats[format.get_format_string()] = format
693
def set_default_format(klass, format):
694
klass._default_format = format
697
def unregister_format(klass, format):
698
assert klass._formats[format.get_format_string()] is format
699
del klass._formats[format.get_format_string()]
702
class RepositoryFormat4(RepositoryFormat):
703
"""Bzr repository format 4.
705
This repository format has:
707
- TextStores for texts, inventories,revisions.
709
This format is deprecated: it indexes texts using a text id which is
710
removed in format 5; initializationa and write support for this format
715
super(RepositoryFormat4, self).__init__()
716
self._matchingbzrdir = bzrdir.BzrDirFormat4()
718
def initialize(self, url, _internal=False):
719
"""Format 4 branches cannot be created."""
720
raise errors.UninitializableFormat(self)
722
def is_supported(self):
723
"""Format 4 is not supported.
725
It is not supported because the model changed from 4 to 5 and the
726
conversion logic is expensive - so doing it on the fly was not
732
class RepositoryFormat5(RepositoryFormat):
733
"""Bzr control format 5.
735
This repository format has:
736
- weaves for file texts and inventory
738
- TextStores for revisions and signatures.
742
super(RepositoryFormat5, self).__init__()
743
self._matchingbzrdir = bzrdir.BzrDirFormat5()
746
class RepositoryFormat6(RepositoryFormat):
747
"""Bzr control format 6.
749
This repository format has:
750
- weaves for file texts and inventory
751
- hash subdirectory based stores.
752
- TextStores for revisions and signatures.
756
super(RepositoryFormat6, self).__init__()
757
self._matchingbzrdir = bzrdir.BzrDirFormat6()
760
class RepositoryFormat7(RepositoryFormat):
763
This repository format has:
764
- weaves for file texts and inventory
765
- hash subdirectory based stores.
766
- TextStores for revisions and signatures.
767
- a format marker of its own
770
def get_format_string(self):
771
"""See RepositoryFormat.get_format_string()."""
772
return "Bazaar-NG Repository format 7"
774
def initialize(self, a_bzrdir):
775
"""Create a weave repository.
777
from bzrlib.weavefile import write_weave_v5
778
from bzrlib.weave import Weave
780
# Create an empty weave
782
bzrlib.weavefile.write_weave_v5(Weave(), sio)
783
empty_weave = sio.getvalue()
785
mutter('creating repository in %s.', a_bzrdir.transport.base)
786
dirs = ['revision-store', 'weaves']
787
files = [('inventory.weave', StringIO(empty_weave)),
789
utf8_files = [('format', self.get_format_string())]
791
# FIXME: RBC 20060125 dont peek under the covers
792
# NB: no need to escape relative paths that are url safe.
794
repository_transport = a_bzrdir.get_repository_transport(self)
795
repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
796
control_files = LockableFiles(repository_transport, 'lock')
797
control_files.lock_write()
798
control_files._transport.mkdir_multi(dirs,
799
mode=control_files._dir_mode)
801
for file, content in files:
802
control_files.put(file, content)
803
for file, content in utf8_files:
804
control_files.put_utf8(file, content)
806
control_files.unlock()
807
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
810
super(RepositoryFormat7, self).__init__()
811
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
814
# formats which have no format string are not discoverable
815
# and not independently creatable, so are not registered.
816
__default_format = RepositoryFormat7()
817
RepositoryFormat.register_format(__default_format)
818
RepositoryFormat.set_default_format(__default_format)
819
_legacy_formats = [RepositoryFormat4(),
824
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
825
# make sure that ancestry.weave is deleted (it is never used, but
826
# used to be created)
828
class RepositoryTestProviderAdapter(object):
829
"""A tool to generate a suite testing multiple repository formats at once.
831
This is done by copying the test once for each transport and injecting
832
the transport_server, transport_readonly_server, and bzrdir_format and
833
repository_format classes into each copy. Each copy is also given a new id()
834
to make it easy to identify.
837
def __init__(self, transport_server, transport_readonly_server, formats):
838
self._transport_server = transport_server
839
self._transport_readonly_server = transport_readonly_server
840
self._formats = formats
842
def adapt(self, test):
844
for repository_format, bzrdir_format in self._formats:
845
new_test = deepcopy(test)
846
new_test.transport_server = self._transport_server
847
new_test.transport_readonly_server = self._transport_readonly_server
848
new_test.bzrdir_format = bzrdir_format
849
new_test.repository_format = repository_format
850
def make_new_test_id():
851
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
852
return lambda: new_id
853
new_test.id = make_new_test_id()
854
result.addTest(new_test)