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, shared=False):
633
"""Initialize a repository of this format in a_bzrdir.
635
:param a_bzrdir: The bzrdir to put the new repository in it.
636
:param shared: The repository should be initialized as a sharable one.
638
This may raise UninitializableFormat if shared repository are not
639
compatible the a_bzrdir.
642
def is_supported(self):
643
"""Is this format supported?
645
Supported formats must be initializable and openable.
646
Unsupported formats may not support initialization or committing or
647
some other features depending on the reason for not being supported.
651
def open(self, a_bzrdir, _found=False):
652
"""Return an instance of this format for the bzrdir a_bzrdir.
654
_found is a private parameter, do not use it.
657
# we are being called directly and must probe.
658
raise NotImplementedError
659
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
662
def register_format(klass, format):
663
klass._formats[format.get_format_string()] = format
666
def set_default_format(klass, format):
667
klass._default_format = format
670
def unregister_format(klass, format):
671
assert klass._formats[format.get_format_string()] is format
672
del klass._formats[format.get_format_string()]
675
class PreSplitOutRepositoryFormat(RepositoryFormat):
676
"""Base class for the pre split out repository formats."""
678
def initialize(self, a_bzrdir, shared=False, _internal=False):
679
"""Create a weave repository.
681
TODO: when creating split out bzr branch formats, move this to a common
682
base for Format5, Format6. or something like that.
684
from bzrlib.weavefile import write_weave_v5
685
from bzrlib.weave import Weave
688
raise errors.IncompatibleFormat(self, a_bzrdir._format)
691
# always initialized when the bzrdir is.
692
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
694
# Create an empty weave
696
bzrlib.weavefile.write_weave_v5(Weave(), sio)
697
empty_weave = sio.getvalue()
699
mutter('creating repository in %s.', a_bzrdir.transport.base)
700
dirs = ['revision-store', 'weaves']
701
lock_file = 'branch-lock'
702
files = [('inventory.weave', StringIO(empty_weave)),
705
# FIXME: RBC 20060125 dont peek under the covers
706
# NB: no need to escape relative paths that are url safe.
707
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
708
control_files.lock_write()
709
control_files._transport.mkdir_multi(dirs,
710
mode=control_files._dir_mode)
712
for file, content in files:
713
control_files.put(file, content)
715
control_files.unlock()
716
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
719
class RepositoryFormat4(PreSplitOutRepositoryFormat):
720
"""Bzr repository format 4.
722
This repository format has:
724
- TextStores for texts, inventories,revisions.
726
This format is deprecated: it indexes texts using a text id which is
727
removed in format 5; initializationa and write support for this format
732
super(RepositoryFormat4, self).__init__()
733
self._matchingbzrdir = bzrdir.BzrDirFormat4()
735
def initialize(self, url, shared=False, _internal=False):
736
"""Format 4 branches cannot be created."""
737
raise errors.UninitializableFormat(self)
739
def is_supported(self):
740
"""Format 4 is not supported.
742
It is not supported because the model changed from 4 to 5 and the
743
conversion logic is expensive - so doing it on the fly was not
749
class RepositoryFormat5(PreSplitOutRepositoryFormat):
750
"""Bzr control format 5.
752
This repository format has:
753
- weaves for file texts and inventory
755
- TextStores for revisions and signatures.
759
super(RepositoryFormat5, self).__init__()
760
self._matchingbzrdir = bzrdir.BzrDirFormat5()
763
class RepositoryFormat6(PreSplitOutRepositoryFormat):
764
"""Bzr control format 6.
766
This repository format has:
767
- weaves for file texts and inventory
768
- hash subdirectory based stores.
769
- TextStores for revisions and signatures.
773
super(RepositoryFormat6, self).__init__()
774
self._matchingbzrdir = bzrdir.BzrDirFormat6()
777
class RepositoryFormat7(RepositoryFormat):
780
This repository format has:
781
- weaves for file texts and inventory
782
- hash subdirectory based stores.
783
- TextStores for revisions and signatures.
784
- a format marker of its own
785
- an optional 'shared-storage' flag
788
def get_format_string(self):
789
"""See RepositoryFormat.get_format_string()."""
790
return "Bazaar-NG Repository format 7"
792
def initialize(self, a_bzrdir, shared=False):
793
"""Create a weave repository.
795
:param shared: If true the repository will be initialized as a shared
798
from bzrlib.weavefile import write_weave_v5
799
from bzrlib.weave import Weave
801
# Create an empty weave
803
bzrlib.weavefile.write_weave_v5(Weave(), sio)
804
empty_weave = sio.getvalue()
806
mutter('creating repository in %s.', a_bzrdir.transport.base)
807
dirs = ['revision-store', 'weaves']
808
files = [('inventory.weave', StringIO(empty_weave)),
810
utf8_files = [('format', self.get_format_string())]
812
# FIXME: RBC 20060125 dont peek under the covers
813
# NB: no need to escape relative paths that are url safe.
815
repository_transport = a_bzrdir.get_repository_transport(self)
816
repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
817
control_files = LockableFiles(repository_transport, 'lock')
818
control_files.lock_write()
819
control_files._transport.mkdir_multi(dirs,
820
mode=control_files._dir_mode)
822
for file, content in files:
823
control_files.put(file, content)
824
for file, content in utf8_files:
825
control_files.put_utf8(file, content)
827
control_files.put_utf8('shared-storage', '')
829
control_files.unlock()
830
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
833
super(RepositoryFormat7, self).__init__()
834
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
837
# formats which have no format string are not discoverable
838
# and not independently creatable, so are not registered.
839
__default_format = RepositoryFormat7()
840
RepositoryFormat.register_format(__default_format)
841
RepositoryFormat.set_default_format(__default_format)
842
_legacy_formats = [RepositoryFormat4(),
847
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
848
# make sure that ancestry.weave is deleted (it is never used, but
849
# used to be created)
851
class RepositoryTestProviderAdapter(object):
852
"""A tool to generate a suite testing multiple repository formats at once.
854
This is done by copying the test once for each transport and injecting
855
the transport_server, transport_readonly_server, and bzrdir_format and
856
repository_format classes into each copy. Each copy is also given a new id()
857
to make it easy to identify.
860
def __init__(self, transport_server, transport_readonly_server, formats):
861
self._transport_server = transport_server
862
self._transport_readonly_server = transport_readonly_server
863
self._formats = formats
865
def adapt(self, test):
867
for repository_format, bzrdir_format in self._formats:
868
new_test = deepcopy(test)
869
new_test.transport_server = self._transport_server
870
new_test.transport_readonly_server = self._transport_readonly_server
871
new_test.bzrdir_format = bzrdir_format
872
new_test.repository_format = repository_format
873
def make_new_test_id():
874
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
875
return lambda: new_id
876
new_test.id = make_new_test_id()
877
result.addTest(new_test)