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.
258
This is a destructive operation! Do not use it on existing
261
destination.lock_write()
264
destination.set_make_working_trees(self.make_working_trees())
265
except NotImplementedError:
269
if self._compatible_formats(destination):
270
if basis is not None:
271
# copy the basis in, then fetch remaining data.
272
basis.copy_content_into(destination, revision_id)
273
destination.fetch(self, revision_id=revision_id)
276
if self.control_files._transport.listable():
277
destination.control_weaves.copy_multi(self.control_weaves,
279
copy_all(self.weave_store, destination.weave_store)
280
copy_all(self.revision_store, destination.revision_store)
282
destination.fetch(self, revision_id=revision_id)
283
# compatible v4 stores
284
elif isinstance(self._format, RepositoryFormat4):
285
if not isinstance(destination._format, RepositoryFormat4):
286
raise BzrError('cannot copy v4 branches to anything other than v4 branches.')
287
store_pairs = ((self.text_store, destination.text_store),
288
(self.inventory_store, destination.inventory_store),
289
(self.revision_store, destination.revision_store))
291
for from_store, to_store in store_pairs:
292
copy_all(from_store, to_store)
293
except UnlistableStore:
294
raise UnlistableBranch(from_store)
297
destination.fetch(self, revision_id=revision_id)
302
def fetch(self, source, revision_id=None):
303
"""Fetch the content required to construct revision_id from source.
305
If revision_id is None all content is copied.
307
from bzrlib.fetch import RepoFetcher
308
mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
309
source, source._format, self, self._format)
310
RepoFetcher(to_repository=self, from_repository=source, last_revision=revision_id)
313
self.control_files.unlock()
316
def clone(self, a_bzrdir, revision_id=None, basis=None):
317
"""Clone this repository into a_bzrdir using the current format.
319
Currently no check is made that the format of this repository and
320
the bzrdir format are compatible. FIXME RBC 20060201.
322
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
323
# use target default format.
324
result = a_bzrdir.create_repository()
325
# FIXME RBC 20060209 split out the repository type to avoid this check ?
326
elif isinstance(a_bzrdir._format,
327
(bzrdir.BzrDirFormat4,
328
bzrdir.BzrDirFormat5,
329
bzrdir.BzrDirFormat6)):
330
result = a_bzrdir.open_repository()
332
result = self._format.initialize(a_bzrdir, shared=self.is_shared())
333
self.copy_content_into(result, revision_id, basis)
336
def has_revision(self, revision_id):
337
"""True if this branch has a copy of the revision.
339
This does not necessarily imply the revision is merge
340
or on the mainline."""
341
return (revision_id is None
342
or self.revision_store.has_id(revision_id))
345
def get_revision_xml_file(self, revision_id):
346
"""Return XML file object for revision object."""
347
if not revision_id or not isinstance(revision_id, basestring):
348
raise InvalidRevisionId(revision_id=revision_id, branch=self)
350
return self.revision_store.get(revision_id)
351
except (IndexError, KeyError):
352
raise bzrlib.errors.NoSuchRevision(self, revision_id)
355
def get_revision_xml(self, revision_id):
356
return self.get_revision_xml_file(revision_id).read()
359
def get_revision(self, revision_id):
360
"""Return the Revision object for a named revision"""
361
xml_file = self.get_revision_xml_file(revision_id)
364
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
365
except SyntaxError, e:
366
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
370
assert r.revision_id == revision_id
374
def get_revision_sha1(self, revision_id):
375
"""Hash the stored value of a revision, and return it."""
376
# In the future, revision entries will be signed. At that
377
# point, it is probably best *not* to include the signature
378
# in the revision hash. Because that lets you re-sign
379
# the revision, (add signatures/remove signatures) and still
380
# have all hash pointers stay consistent.
381
# But for now, just hash the contents.
382
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
385
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
386
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
389
def fileid_involved_between_revs(self, from_revid, to_revid):
390
"""Find file_id(s) which are involved in the changes between revisions.
392
This determines the set of revisions which are involved, and then
393
finds all file ids affected by those revisions.
395
# TODO: jam 20060119 This code assumes that w.inclusions will
396
# always be correct. But because of the presence of ghosts
397
# it is possible to be wrong.
398
# One specific example from Robert Collins:
399
# Two branches, with revisions ABC, and AD
400
# C is a ghost merge of D.
401
# Inclusions doesn't recognize D as an ancestor.
402
# If D is ever merged in the future, the weave
403
# won't be fixed, because AD never saw revision C
404
# to cause a conflict which would force a reweave.
405
w = self.get_inventory_weave()
406
from_set = set(w.inclusions([w.lookup(from_revid)]))
407
to_set = set(w.inclusions([w.lookup(to_revid)]))
408
included = to_set.difference(from_set)
409
changed = map(w.idx_to_name, included)
410
return self._fileid_involved_by_set(changed)
412
def fileid_involved(self, last_revid=None):
413
"""Find all file_ids modified in the ancestry of last_revid.
415
:param last_revid: If None, last_revision() will be used.
417
w = self.get_inventory_weave()
419
changed = set(w._names)
421
included = w.inclusions([w.lookup(last_revid)])
422
changed = map(w.idx_to_name, included)
423
return self._fileid_involved_by_set(changed)
425
def fileid_involved_by_set(self, changes):
426
"""Find all file_ids modified by the set of revisions passed in.
428
:param changes: A set() of revision ids
430
# TODO: jam 20060119 This line does *nothing*, remove it.
431
# or better yet, change _fileid_involved_by_set so
432
# that it takes the inventory weave, rather than
433
# pulling it out by itself.
434
return self._fileid_involved_by_set(changes)
436
def _fileid_involved_by_set(self, changes):
437
"""Find the set of file-ids affected by the set of revisions.
439
:param changes: A set() of revision ids.
440
:return: A set() of file ids.
442
This peaks at the Weave, interpreting each line, looking to
443
see if it mentions one of the revisions. And if so, includes
444
the file id mentioned.
445
This expects both the Weave format, and the serialization
446
to have a single line per file/directory, and to have
447
fileid="" and revision="" on that line.
449
assert isinstance(self._format, (RepositoryFormat5,
451
RepositoryFormat7)), \
452
"fileid_involved only supported for branches which store inventory as unnested xml"
454
w = self.get_inventory_weave()
456
for line in w._weave:
458
# it is ugly, but it is due to the weave structure
459
if not isinstance(line, basestring): continue
461
start = line.find('file_id="')+9
462
if start < 9: continue
463
end = line.find('"', start)
465
file_id = xml.sax.saxutils.unescape(line[start:end])
467
# check if file_id is already present
468
if file_id in file_ids: continue
470
start = line.find('revision="')+10
471
if start < 10: continue
472
end = line.find('"', start)
474
revision_id = xml.sax.saxutils.unescape(line[start:end])
476
if revision_id in changes:
477
file_ids.add(file_id)
481
def get_inventory_weave(self):
482
return self.control_weaves.get_weave('inventory',
483
self.get_transaction())
486
def get_inventory(self, revision_id):
487
"""Get Inventory object by hash."""
488
xml = self.get_inventory_xml(revision_id)
489
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
492
def get_inventory_xml(self, revision_id):
493
"""Get inventory XML as a file object."""
495
assert isinstance(revision_id, basestring), type(revision_id)
496
iw = self.get_inventory_weave()
497
return iw.get_text(iw.lookup(revision_id))
499
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
502
def get_inventory_sha1(self, revision_id):
503
"""Return the sha1 hash of the inventory entry
505
return self.get_revision(revision_id).inventory_sha1
508
def get_revision_inventory(self, revision_id):
509
"""Return inventory of a past revision."""
510
# TODO: Unify this with get_inventory()
511
# bzr 0.0.6 and later imposes the constraint that the inventory_id
512
# must be the same as its revision, so this is trivial.
513
if revision_id is None:
514
# This does not make sense: if there is no revision,
515
# then it is the current tree inventory surely ?!
516
# and thus get_root_id() is something that looks at the last
517
# commit on the branch, and the get_root_id is an inventory check.
518
raise NotImplementedError
519
# return Inventory(self.get_root_id())
521
return self.get_inventory(revision_id)
525
"""Return True if this repository is flagged as a shared repository."""
526
# FIXME format 4-6 cannot be shared, this is technically faulty.
527
return self.control_files._transport.has('shared-storage')
530
def revision_tree(self, revision_id):
531
"""Return Tree for a revision on this branch.
533
`revision_id` may be None for the null revision, in which case
534
an `EmptyTree` is returned."""
535
# TODO: refactor this to use an existing revision object
536
# so we don't need to read it in twice.
537
if revision_id is None or revision_id == NULL_REVISION:
540
inv = self.get_revision_inventory(revision_id)
541
return RevisionTree(self, inv, revision_id)
544
def get_ancestry(self, revision_id):
545
"""Return a list of revision-ids integrated by a revision.
547
This is topologically sorted.
549
if revision_id is None:
551
if not self.has_revision(revision_id):
552
raise errors.NoSuchRevision(self, revision_id)
553
w = self.get_inventory_weave()
554
return [None] + map(w.idx_to_name,
555
w.inclusions([w.lookup(revision_id)]))
558
def print_file(self, file, revision_id):
559
"""Print `file` to stdout.
561
FIXME RBC 20060125 as John Meinel points out this is a bad api
562
- it writes to stdout, it assumes that that is valid etc. Fix
563
by creating a new more flexible convenience function.
565
tree = self.revision_tree(revision_id)
566
# use inventory as it was in that revision
567
file_id = tree.inventory.path2id(file)
569
raise BzrError("%r is not present in revision %s" % (file, revno))
571
revno = self.revision_id_to_revno(revision_id)
572
except errors.NoSuchRevision:
573
# TODO: This should not be BzrError,
574
# but NoSuchFile doesn't fit either
575
raise BzrError('%r is not present in revision %s'
576
% (file, revision_id))
578
raise BzrError('%r is not present in revision %s'
580
tree.print_file(file_id)
582
def get_transaction(self):
583
return self.control_files.get_transaction()
586
def set_make_working_trees(self, new_value):
587
"""Set the policy flag for making working trees when creating branches.
589
This only applies to branches that use this repository.
591
The default is 'True'.
592
:param new_value: True to restore the default, False to disable making
595
# FIXME: split out into a new class/strategy ?
596
if isinstance(self._format, (RepositoryFormat4,
599
raise NotImplementedError(self.set_make_working_trees)
602
self.control_files._transport.delete('no-working-trees')
603
except errors.NoSuchFile:
606
self.control_files.put_utf8('no-working-trees', '')
608
def make_working_trees(self):
609
"""Returns the policy for making working trees on new branches."""
610
# FIXME: split out into a new class/strategy ?
611
if isinstance(self._format, (RepositoryFormat4,
615
return not self.control_files._transport.has('no-working-trees')
618
def sign_revision(self, revision_id, gpg_strategy):
619
plaintext = Testament.from_revision(self, revision_id).as_short_text()
620
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
623
class RepositoryFormat(object):
624
"""A repository format.
626
Formats provide three things:
627
* An initialization routine to construct repository data on disk.
628
* a format string which is used when the BzrDir supports versioned
630
* an open routine which returns a Repository instance.
632
Formats are placed in an dict by their format string for reference
633
during opening. These should be subclasses of RepositoryFormat
636
Once a format is deprecated, just deprecate the initialize and open
637
methods on the format class. Do not deprecate the object, as the
638
object will be created every system load.
640
Common instance attributes:
641
_matchingbzrdir - the bzrdir format that the repository format was
642
originally written to work with. This can be used if manually
643
constructing a bzrdir and repository, or more commonly for test suite
647
_default_format = None
648
"""The default format used for new repositories."""
651
"""The known formats."""
654
def find_format(klass, a_bzrdir):
655
"""Return the format for the repository object in a_bzrdir."""
657
transport = a_bzrdir.get_repository_transport(None)
658
format_string = transport.get("format").read()
659
return klass._formats[format_string]
660
except errors.NoSuchFile:
661
raise errors.NoRepositoryPresent(a_bzrdir)
663
raise errors.UnknownFormatError(format_string)
666
def get_default_format(klass):
667
"""Return the current default format."""
668
return klass._default_format
670
def get_format_string(self):
671
"""Return the ASCII format string that identifies this format.
673
Note that in pre format ?? repositories the format string is
674
not permitted nor written to disk.
676
raise NotImplementedError(self.get_format_string)
678
def initialize(self, a_bzrdir, shared=False):
679
"""Initialize a repository of this format in a_bzrdir.
681
:param a_bzrdir: The bzrdir to put the new repository in it.
682
:param shared: The repository should be initialized as a sharable one.
684
This may raise UninitializableFormat if shared repository are not
685
compatible the a_bzrdir.
688
def is_supported(self):
689
"""Is this format supported?
691
Supported formats must be initializable and openable.
692
Unsupported formats may not support initialization or committing or
693
some other features depending on the reason for not being supported.
697
def open(self, a_bzrdir, _found=False):
698
"""Return an instance of this format for the bzrdir a_bzrdir.
700
_found is a private parameter, do not use it.
703
# we are being called directly and must probe.
704
raise NotImplementedError
705
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
708
def register_format(klass, format):
709
klass._formats[format.get_format_string()] = format
712
def set_default_format(klass, format):
713
klass._default_format = format
716
def unregister_format(klass, format):
717
assert klass._formats[format.get_format_string()] is format
718
del klass._formats[format.get_format_string()]
721
class PreSplitOutRepositoryFormat(RepositoryFormat):
722
"""Base class for the pre split out repository formats."""
724
def initialize(self, a_bzrdir, shared=False, _internal=False):
725
"""Create a weave repository.
727
TODO: when creating split out bzr branch formats, move this to a common
728
base for Format5, Format6. or something like that.
730
from bzrlib.weavefile import write_weave_v5
731
from bzrlib.weave import Weave
734
raise errors.IncompatibleFormat(self, a_bzrdir._format)
737
# always initialized when the bzrdir is.
738
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
740
# Create an empty weave
742
bzrlib.weavefile.write_weave_v5(Weave(), sio)
743
empty_weave = sio.getvalue()
745
mutter('creating repository in %s.', a_bzrdir.transport.base)
746
dirs = ['revision-store', 'weaves']
747
lock_file = 'branch-lock'
748
files = [('inventory.weave', StringIO(empty_weave)),
751
# FIXME: RBC 20060125 dont peek under the covers
752
# NB: no need to escape relative paths that are url safe.
753
control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
754
control_files.lock_write()
755
control_files._transport.mkdir_multi(dirs,
756
mode=control_files._dir_mode)
758
for file, content in files:
759
control_files.put(file, content)
761
control_files.unlock()
762
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
765
class RepositoryFormat4(PreSplitOutRepositoryFormat):
766
"""Bzr repository format 4.
768
This repository format has:
770
- TextStores for texts, inventories,revisions.
772
This format is deprecated: it indexes texts using a text id which is
773
removed in format 5; initializationa and write support for this format
778
super(RepositoryFormat4, self).__init__()
779
self._matchingbzrdir = bzrdir.BzrDirFormat4()
781
def initialize(self, url, shared=False, _internal=False):
782
"""Format 4 branches cannot be created."""
783
raise errors.UninitializableFormat(self)
785
def is_supported(self):
786
"""Format 4 is not supported.
788
It is not supported because the model changed from 4 to 5 and the
789
conversion logic is expensive - so doing it on the fly was not
795
class RepositoryFormat5(PreSplitOutRepositoryFormat):
796
"""Bzr control format 5.
798
This repository format has:
799
- weaves for file texts and inventory
801
- TextStores for revisions and signatures.
805
super(RepositoryFormat5, self).__init__()
806
self._matchingbzrdir = bzrdir.BzrDirFormat5()
809
class RepositoryFormat6(PreSplitOutRepositoryFormat):
810
"""Bzr control format 6.
812
This repository format has:
813
- weaves for file texts and inventory
814
- hash subdirectory based stores.
815
- TextStores for revisions and signatures.
819
super(RepositoryFormat6, self).__init__()
820
self._matchingbzrdir = bzrdir.BzrDirFormat6()
823
class RepositoryFormat7(RepositoryFormat):
826
This repository format has:
827
- weaves for file texts and inventory
828
- hash subdirectory based stores.
829
- TextStores for revisions and signatures.
830
- a format marker of its own
831
- an optional 'shared-storage' flag
834
def get_format_string(self):
835
"""See RepositoryFormat.get_format_string()."""
836
return "Bazaar-NG Repository format 7"
838
def initialize(self, a_bzrdir, shared=False):
839
"""Create a weave repository.
841
:param shared: If true the repository will be initialized as a shared
844
from bzrlib.weavefile import write_weave_v5
845
from bzrlib.weave import Weave
847
# Create an empty weave
849
bzrlib.weavefile.write_weave_v5(Weave(), sio)
850
empty_weave = sio.getvalue()
852
mutter('creating repository in %s.', a_bzrdir.transport.base)
853
dirs = ['revision-store', 'weaves']
854
files = [('inventory.weave', StringIO(empty_weave)),
856
utf8_files = [('format', self.get_format_string())]
858
# FIXME: RBC 20060125 dont peek under the covers
859
# NB: no need to escape relative paths that are url safe.
861
repository_transport = a_bzrdir.get_repository_transport(self)
862
repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
863
control_files = LockableFiles(repository_transport, 'lock')
864
control_files.lock_write()
865
control_files._transport.mkdir_multi(dirs,
866
mode=control_files._dir_mode)
868
for file, content in files:
869
control_files.put(file, content)
870
for file, content in utf8_files:
871
control_files.put_utf8(file, content)
873
control_files.put_utf8('shared-storage', '')
875
control_files.unlock()
876
return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
879
super(RepositoryFormat7, self).__init__()
880
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
883
# formats which have no format string are not discoverable
884
# and not independently creatable, so are not registered.
885
__default_format = RepositoryFormat7()
886
RepositoryFormat.register_format(__default_format)
887
RepositoryFormat.set_default_format(__default_format)
888
_legacy_formats = [RepositoryFormat4(),
893
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
894
# make sure that ancestry.weave is deleted (it is never used, but
895
# used to be created)
897
class RepositoryTestProviderAdapter(object):
898
"""A tool to generate a suite testing multiple repository formats at once.
900
This is done by copying the test once for each transport and injecting
901
the transport_server, transport_readonly_server, and bzrdir_format and
902
repository_format classes into each copy. Each copy is also given a new id()
903
to make it easy to identify.
906
def __init__(self, transport_server, transport_readonly_server, formats):
907
self._transport_server = transport_server
908
self._transport_readonly_server = transport_readonly_server
909
self._formats = formats
911
def adapt(self, test):
913
for repository_format, bzrdir_format in self._formats:
914
new_test = deepcopy(test)
915
new_test.transport_server = self._transport_server
916
new_test.transport_readonly_server = self._transport_readonly_server
917
new_test.bzrdir_format = bzrdir_format
918
new_test.repository_format = repository_format
919
def make_new_test_id():
920
new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
921
return lambda: new_id
922
new_test.id = make_new_test_id()
923
result.addTest(new_test)