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)
517
"""Return True if this repository is flagged as a shared repository."""
518
# FIXME format 4-6 cannot be shared, this is technically faulty.
519
return self.control_files._transport.has('shared-storage')
522
def revision_tree(self, revision_id):
523
"""Return Tree for a revision on this branch.
525
`revision_id` may be None for the null revision, in which case
526
an `EmptyTree` is returned."""
527
# TODO: refactor this to use an existing revision object
528
# so we don't need to read it in twice.
529
if revision_id is None or revision_id == NULL_REVISION:
532
inv = self.get_revision_inventory(revision_id)
533
return RevisionTree(self, inv, revision_id)
536
def get_ancestry(self, revision_id):
537
"""Return a list of revision-ids integrated by a revision.
539
This is topologically sorted.
541
if revision_id is None:
543
if not self.has_revision(revision_id):
544
raise errors.NoSuchRevision(self, revision_id)
545
w = self.get_inventory_weave()
546
return [None] + map(w.idx_to_name,
547
w.inclusions([w.lookup(revision_id)]))
550
def print_file(self, file, revision_id):
551
"""Print `file` to stdout.
553
FIXME RBC 20060125 as John Meinel points out this is a bad api
554
- it writes to stdout, it assumes that that is valid etc. Fix
555
by creating a new more flexible convenience function.
557
tree = self.revision_tree(revision_id)
558
# use inventory as it was in that revision
559
file_id = tree.inventory.path2id(file)
561
raise BzrError("%r is not present in revision %s" % (file, revno))
563
revno = self.revision_id_to_revno(revision_id)
564
except errors.NoSuchRevision:
565
# TODO: This should not be BzrError,
566
# but NoSuchFile doesn't fit either
567
raise BzrError('%r is not present in revision %s'
568
% (file, revision_id))
570
raise BzrError('%r is not present in revision %s'
572
tree.print_file(file_id)
574
def get_transaction(self):
575
return self.control_files.get_transaction()
578
def sign_revision(self, revision_id, gpg_strategy):
579
plaintext = Testament.from_revision(self, revision_id).as_short_text()
580
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
583
class RepositoryFormat(object):
584
"""A repository format.
586
Formats provide three things:
587
* An initialization routine to construct repository data on disk.
588
* a format string which is used when the BzrDir supports versioned
590
* an open routine which returns a Repository instance.
592
Formats are placed in an dict by their format string for reference
593
during opening. These should be subclasses of RepositoryFormat
596
Once a format is deprecated, just deprecate the initialize and open
597
methods on the format class. Do not deprecate the object, as the
598
object will be created every system load.
600
Common instance attributes:
601
_matchingbzrdir - the bzrdir format that the repository format was
602
originally written to work with. This can be used if manually
603
constructing a bzrdir and repository, or more commonly for test suite
607
_default_format = None
608
"""The default format used for new repositories."""
611
"""The known formats."""
614
def find_format(klass, a_bzrdir):
615
"""Return the format for the repository object in a_bzrdir."""
617
transport = a_bzrdir.get_repository_transport(None)
618
format_string = transport.get("format").read()
619
return klass._formats[format_string]
620
except errors.NoSuchFile:
621
raise errors.NoRepositoryPresent(a_bzrdir)
623
raise errors.UnknownFormatError(format_string)
626
def get_default_format(klass):
627
"""Return the current default format."""
628
return klass._default_format
630
def get_format_string(self):
631
"""Return the ASCII format string that identifies this format.
633
Note that in pre format ?? repositories the format string is
634
not permitted nor written to disk.
636
raise NotImplementedError(self.get_format_string)
638
def initialize(self, a_bzrdir, shared=False):
639
"""Initialize a repository of this format in a_bzrdir.
641
:param a_bzrdir: The bzrdir to put the new repository in it.
642
:param shared: The repository should be initialized as a sharable one.
644
This may raise UninitializableFormat if shared repository are not
645
compatible the a_bzrdir.
648
def is_supported(self):
649
"""Is this format supported?
651
Supported formats must be initializable and openable.
652
Unsupported formats may not support initialization or committing or
653
some other features depending on the reason for not being supported.
657
def open(self, a_bzrdir, _found=False):
658
"""Return an instance of this format for the bzrdir a_bzrdir.
660
_found is a private parameter, do not use it.
663
# we are being called directly and must probe.
664
raise NotImplementedError
665
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
668
def register_format(klass, format):
669
klass._formats[format.get_format_string()] = format
672
def set_default_format(klass, format):
673
klass._default_format = format
676
def unregister_format(klass, format):
677
assert klass._formats[format.get_format_string()] is format
678
del klass._formats[format.get_format_string()]
681
class PreSplitOutRepositoryFormat(RepositoryFormat):
682
"""Base class for the pre split out repository formats."""
684
def initialize(self, a_bzrdir, shared=False, _internal=False):
685
"""Create a weave repository.
687
TODO: when creating split out bzr branch formats, move this to a common
688
base for Format5, Format6. or something like that.
690
from bzrlib.weavefile import write_weave_v5
691
from bzrlib.weave import Weave
694
raise errors.IncompatibleFormat(self, a_bzrdir._format)
697
# always initialized when the bzrdir is.
698
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
700
# Create an empty weave
702
bzrlib.weavefile.write_weave_v5(Weave(), sio)
703
empty_weave = sio.getvalue()
705
mutter('creating repository in %s.', a_bzrdir.transport.base)
706
dirs = ['revision-store', 'weaves']
707
lock_file = 'branch-lock'
708
files = [('inventory.weave', StringIO(empty_weave)),
711
# FIXME: RBC 20060125 dont peek under the covers
712
# NB: no need to escape relative paths that are url safe.
713
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
714
control_files.lock_write()
715
control_files._transport.mkdir_multi(dirs,
716
mode=control_files._dir_mode)
718
for file, content in files:
719
control_files.put(file, content)
721
control_files.unlock()
722
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
725
class RepositoryFormat4(PreSplitOutRepositoryFormat):
726
"""Bzr repository format 4.
728
This repository format has:
730
- TextStores for texts, inventories,revisions.
732
This format is deprecated: it indexes texts using a text id which is
733
removed in format 5; initializationa and write support for this format
738
super(RepositoryFormat4, self).__init__()
739
self._matchingbzrdir = bzrdir.BzrDirFormat4()
741
def initialize(self, url, shared=False, _internal=False):
742
"""Format 4 branches cannot be created."""
743
raise errors.UninitializableFormat(self)
745
def is_supported(self):
746
"""Format 4 is not supported.
748
It is not supported because the model changed from 4 to 5 and the
749
conversion logic is expensive - so doing it on the fly was not
755
class RepositoryFormat5(PreSplitOutRepositoryFormat):
756
"""Bzr control format 5.
758
This repository format has:
759
- weaves for file texts and inventory
761
- TextStores for revisions and signatures.
765
super(RepositoryFormat5, self).__init__()
766
self._matchingbzrdir = bzrdir.BzrDirFormat5()
769
class RepositoryFormat6(PreSplitOutRepositoryFormat):
770
"""Bzr control format 6.
772
This repository format has:
773
- weaves for file texts and inventory
774
- hash subdirectory based stores.
775
- TextStores for revisions and signatures.
779
super(RepositoryFormat6, self).__init__()
780
self._matchingbzrdir = bzrdir.BzrDirFormat6()
783
class RepositoryFormat7(RepositoryFormat):
786
This repository format has:
787
- weaves for file texts and inventory
788
- hash subdirectory based stores.
789
- TextStores for revisions and signatures.
790
- a format marker of its own
791
- an optional 'shared-storage' flag
794
def get_format_string(self):
795
"""See RepositoryFormat.get_format_string()."""
796
return "Bazaar-NG Repository format 7"
798
def initialize(self, a_bzrdir, shared=False):
799
"""Create a weave repository.
801
:param shared: If true the repository will be initialized as a shared
804
from bzrlib.weavefile import write_weave_v5
805
from bzrlib.weave import Weave
807
# Create an empty weave
809
bzrlib.weavefile.write_weave_v5(Weave(), sio)
810
empty_weave = sio.getvalue()
812
mutter('creating repository in %s.', a_bzrdir.transport.base)
813
dirs = ['revision-store', 'weaves']
814
files = [('inventory.weave', StringIO(empty_weave)),
816
utf8_files = [('format', self.get_format_string())]
818
# FIXME: RBC 20060125 dont peek under the covers
819
# NB: no need to escape relative paths that are url safe.
821
repository_transport = a_bzrdir.get_repository_transport(self)
822
repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
823
control_files = LockableFiles(repository_transport, 'lock')
824
control_files.lock_write()
825
control_files._transport.mkdir_multi(dirs,
826
mode=control_files._dir_mode)
828
for file, content in files:
829
control_files.put(file, content)
830
for file, content in utf8_files:
831
control_files.put_utf8(file, content)
833
control_files.put_utf8('shared-storage', '')
835
control_files.unlock()
836
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
839
super(RepositoryFormat7, self).__init__()
840
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
843
# formats which have no format string are not discoverable
844
# and not independently creatable, so are not registered.
845
__default_format = RepositoryFormat7()
846
RepositoryFormat.register_format(__default_format)
847
RepositoryFormat.set_default_format(__default_format)
848
_legacy_formats = [RepositoryFormat4(),
853
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
854
# make sure that ancestry.weave is deleted (it is never used, but
855
# used to be created)
857
class RepositoryTestProviderAdapter(object):
858
"""A tool to generate a suite testing multiple repository formats at once.
860
This is done by copying the test once for each transport and injecting
861
the transport_server, transport_readonly_server, and bzrdir_format and
862
repository_format classes into each copy. Each copy is also given a new id()
863
to make it easy to identify.
866
def __init__(self, transport_server, transport_readonly_server, formats):
867
self._transport_server = transport_server
868
self._transport_readonly_server = transport_readonly_server
869
self._formats = formats
871
def adapt(self, test):
873
for repository_format, bzrdir_format in self._formats:
874
new_test = deepcopy(test)
875
new_test.transport_server = self._transport_server
876
new_test.transport_readonly_server = self._transport_readonly_server
877
new_test.bzrdir_format = bzrdir_format
878
new_test.repository_format = repository_format
879
def make_new_test_id():
880
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
881
return lambda: new_id
882
new_test.id = make_new_test_id()
883
result.addTest(new_test)