46
def __init__(self, transport, branch_format):
47
# circular dependencies:
48
from bzrlib.branch import (BzrBranchFormat4,
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):
52
87
object.__init__(self)
53
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
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')
55
99
dir_mode = self.control_files._dir_mode
56
100
file_mode = self.control_files._file_mode
101
self._format = _format
102
self.bzrdir = a_bzrdir
58
104
def get_weave(name, prefixed=False):
60
name = bzrlib.BZRDIR + '/' + safe_unicode(name)
106
name = safe_unicode(name)
63
109
relpath = self.control_files._escape(name)
64
weave_transport = transport.clone(relpath)
110
weave_transport = self.control_files._transport.clone(relpath)
65
111
ws = WeaveStore(weave_transport, prefixed=prefixed,
66
112
dir_mode=dir_mode,
67
113
file_mode=file_mode)
90
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()
94
if isinstance(branch_format, BzrBranchFormat4):
153
if isinstance(self._format, RepositoryFormat4):
95
154
self.inventory_store = get_store('inventory-store')
96
155
self.text_store = get_store('text-store')
97
156
self.revision_store = get_store('revision-store')
98
elif isinstance(branch_format, BzrBranchFormat5):
157
elif isinstance(self._format, RepositoryFormat5):
99
158
self.control_weaves = get_weave('')
100
159
self.weave_store = get_weave('weaves')
101
160
self.revision_store = get_store('revision-store', compressed=False)
102
elif isinstance(branch_format, BzrBranchFormat6):
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):
103
167
self.control_weaves = get_weave('')
104
168
self.weave_store = get_weave('weaves', prefixed=True)
105
169
self.revision_store = get_store('revision-store', compressed=False,
112
176
def lock_read(self):
113
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)
115
304
def unlock(self):
116
305
self.control_files.unlock()
119
def copy(self, destination):
120
destination.lock_write()
122
destination.control_weaves.copy_multi(self.control_weaves,
124
copy_all(self.weave_store, destination.weave_store)
125
copy_all(self.revision_store, destination.revision_store)
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)
129
328
def has_revision(self, revision_id):
130
329
"""True if this branch has a copy of the revision.
179
378
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
180
379
revision_id, "sig")
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)
183
473
def get_inventory_weave(self):
184
474
return self.control_weaves.get_weave('inventory',
280
572
def sign_revision(self, revision_id, gpg_strategy):
281
573
plaintext = Testament.from_revision(self, revision_id).as_short_text()
282
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)