45
54
from bzrlib.store.text import TextStore
46
55
from bzrlib.trace import mutter
56
from bzrlib.tuned_gzip import GzipFile, bytes_to_gzip
57
from bzrlib.versionedfile import (
59
FulltextContentFactory,
49
64
class AllInOneRepository(Repository):
50
65
"""Legacy support - the repository behaviour for all-in-one branches."""
52
_serializer = xml5.serializer_v5
54
def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
68
def _serializer(self):
69
return xml5.serializer_v5
71
def _escape(self, file_or_path):
72
if not isinstance(file_or_path, basestring):
73
file_or_path = '/'.join(file_or_path)
74
if file_or_path == '':
76
return urlutils.escape(osutils.safe_unicode(file_or_path))
78
def __init__(self, _format, a_bzrdir):
79
# we reuse one control files instance.
55
80
dir_mode = a_bzrdir._get_dir_mode()
56
81
file_mode = a_bzrdir._get_file_mode()
58
83
def get_store(name, compressed=True, prefixed=False):
59
84
# FIXME: This approach of assuming stores are all entirely compressed
60
# or entirely uncompressed is tidy, but breaks upgrade from
61
# some existing branches where there's a mixture; we probably
85
# or entirely uncompressed is tidy, but breaks upgrade from
86
# some existing branches where there's a mixture; we probably
62
87
# still want the option to look for both.
63
relpath = a_bzrdir._control_files._escape(name)
88
relpath = self._escape(name)
64
89
store = TextStore(a_bzrdir.transport.clone(relpath),
65
90
prefixed=prefixed, compressed=compressed,
70
95
# not broken out yet because the controlweaves|inventory_store
71
# and text_store | weave_store bits are still different.
96
# and texts bits are still different.
72
97
if isinstance(_format, RepositoryFormat4):
73
# cannot remove these - there is still no consistent api
98
# cannot remove these - there is still no consistent api
74
99
# which allows access to this old info.
75
100
self.inventory_store = get_store('inventory-store')
76
text_store = get_store('text-store')
77
super(AllInOneRepository, self).__init__(_format,
78
a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
79
if control_store is not None:
80
control_store.get_scope = self.get_transaction
81
text_store.get_scope = self.get_transaction
101
self._text_store = get_store('text-store')
102
super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files)
84
105
def _all_possible_ids(self):
85
106
"""Return all the possible revisions that we could find."""
86
107
if 'evil' in debug.debug_flags:
87
108
mutter_callsite(3, "_all_possible_ids scales with size of history.")
88
return self.get_inventory_weave().versions()
109
return [key[-1] for key in self.inventories.keys()]
91
112
def _all_revision_ids(self):
92
"""Returns a list of all the revision ids in the repository.
113
"""Returns a list of all the revision ids in the repository.
94
These are in as much topological order as the underlying store can
115
These are in as much topological order as the underlying store can
95
116
present: for weaves ghosts may lead to a lack of correctness until
96
117
the reweave updates the parents list.
98
if self._revision_store.text_store.listable():
99
return self._revision_store.all_revision_ids(self.get_transaction())
100
result = self._all_possible_ids()
101
# TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
102
# ids. (It should, since _revision_store's API should change to
103
# return utf8 revision_ids)
104
return self._eliminate_revisions_not_present(result)
106
def _check_revision_parents(self, revision, inventory):
107
"""Private to Repository and Fetch.
109
This checks the parentage of revision in an inventory weave for
110
consistency and is only applicable to inventory-weave-for-ancestry
111
using repository formats & fetchers.
113
weave_parents = inventory.get_parent_map(
114
[revision.revision_id])[revision.revision_id]
115
parent_map = inventory.get_parent_map(revision.parent_ids)
116
for parent_id in revision.parent_ids:
117
if parent_id in parent_map:
118
# this parent must not be a ghost.
119
if not parent_id in weave_parents:
121
raise errors.CorruptRepository(self)
119
return [key[-1] for key in self.revisions.keys()]
121
def _activate_new_inventory(self):
122
"""Put a replacement inventory.new into use as inventories."""
123
# Copy the content across
124
t = self.bzrdir._control_files._transport
125
t.copy('inventory.new.weave', 'inventory.weave')
126
# delete the temp inventory
127
t.delete('inventory.new.weave')
128
# Check we can parse the new weave properly as a sanity check
129
self.inventories.keys()
131
def _backup_inventory(self):
132
t = self.bzrdir._control_files._transport
133
t.copy('inventory.weave', 'inventory.backup.weave')
135
def _temp_inventories(self):
136
t = self.bzrdir._control_files._transport
137
return self._format._get_inventories(t, self, 'inventory.new')
123
139
def get_commit_builder(self, branch, parents, config, timestamp=None,
124
140
timezone=None, committer=None, revprops=None,
125
141
revision_id=None):
126
142
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
127
result = WeaveCommitBuilder(self, parents, config, timestamp, timezone,
143
result = CommitBuilder(self, parents, config, timestamp, timezone,
128
144
committer, revprops, revision_id)
129
145
self.start_write_group()
133
149
def get_revisions(self, revision_ids):
134
150
revs = self._get_revisions(revision_ids)
135
# weave corruption can lead to absent revision markers that should be
137
# the following test is reasonably cheap (it needs a single weave read)
138
# and the weave is cached in read transactions. In write transactions
139
# it is not cached but typically we only read a small number of
140
# revisions. For knits when they are introduced we will probably want
141
# to ensure that caching write transactions are in use.
142
inv = self.get_inventory_weave()
144
self._check_revision_parents(rev, inv)
147
def has_revisions(self, revision_ids):
148
"""See Repository.has_revisions()."""
150
transaction = self.get_transaction()
151
for revision_id in revision_ids:
152
if self._revision_store.has_revision_id(revision_id, transaction):
153
result.add(revision_id)
153
def _inventory_add_lines(self, revision_id, parents, lines,
155
"""Store lines in inv_vf and return the sha1 of the inventory."""
156
present_parents = self.get_graph().get_parent_map(parents)
158
for parent in parents:
159
if parent in present_parents:
160
final_parents.append((parent,))
161
return self.inventories.add_lines((revision_id,), final_parents, lines,
162
check_content=check_content)[0]
157
164
def is_shared(self):
158
165
"""AllInOne repositories cannot be shared."""
183
190
class WeaveMetaDirRepository(MetaDirVersionedFileRepository):
184
191
"""A subclass of MetaDirRepository to set weave specific policy."""
186
_serializer = xml5.serializer_v5
193
def __init__(self, _format, a_bzrdir, control_files):
194
super(WeaveMetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
195
self._serializer = _format._serializer
189
198
def _all_possible_ids(self):
190
199
"""Return all the possible revisions that we could find."""
191
200
if 'evil' in debug.debug_flags:
192
201
mutter_callsite(3, "_all_possible_ids scales with size of history.")
193
return self.get_inventory_weave().versions()
202
return [key[-1] for key in self.inventories.keys()]
196
205
def _all_revision_ids(self):
197
"""Returns a list of all the revision ids in the repository.
206
"""Returns a list of all the revision ids in the repository.
199
These are in as much topological order as the underlying store can
208
These are in as much topological order as the underlying store can
200
209
present: for weaves ghosts may lead to a lack of correctness until
201
210
the reweave updates the parents list.
203
if self._revision_store.text_store.listable():
204
return self._revision_store.all_revision_ids(self.get_transaction())
205
result = self._all_possible_ids()
206
# TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
207
# ids. (It should, since _revision_store's API should change to
208
# return utf8 revision_ids)
209
return self._eliminate_revisions_not_present(result)
211
def _check_revision_parents(self, revision, inventory):
212
"""Private to Repository and Fetch.
214
This checks the parentage of revision in an inventory weave for
215
consistency and is only applicable to inventory-weave-for-ancestry
216
using repository formats & fetchers.
218
weave_parents = inventory.get_parent_map(
219
[revision.revision_id])[revision.revision_id]
220
parent_map = inventory.get_parent_map(revision.parent_ids)
221
for parent_id in revision.parent_ids:
222
if parent_id in parent_map:
223
# this parent must not be a ghost.
224
if not parent_id in weave_parents:
226
raise errors.CorruptRepository(self)
212
return [key[-1] for key in self.revisions.keys()]
214
def _activate_new_inventory(self):
215
"""Put a replacement inventory.new into use as inventories."""
216
# Copy the content across
218
t.copy('inventory.new.weave', 'inventory.weave')
219
# delete the temp inventory
220
t.delete('inventory.new.weave')
221
# Check we can parse the new weave properly as a sanity check
222
self.inventories.keys()
224
def _backup_inventory(self):
226
t.copy('inventory.weave', 'inventory.backup.weave')
228
def _temp_inventories(self):
230
return self._format._get_inventories(t, self, 'inventory.new')
228
232
def get_commit_builder(self, branch, parents, config, timestamp=None,
229
233
timezone=None, committer=None, revprops=None,
230
234
revision_id=None):
231
235
self._check_ascii_revisionid(revision_id, self.get_commit_builder)
232
result = WeaveCommitBuilder(self, parents, config, timestamp, timezone,
236
result = CommitBuilder(self, parents, config, timestamp, timezone,
233
237
committer, revprops, revision_id)
234
238
self.start_write_group()
238
242
def get_revision(self, revision_id):
239
243
"""Return the Revision object for a named revision"""
240
# TODO: jam 20070210 get_revision_reconcile should do this for us
241
244
r = self.get_revision_reconcile(revision_id)
242
# weave corruption can lead to absent revision markers that should be
244
# the following test is reasonably cheap (it needs a single weave read)
245
# and the weave is cached in read transactions. In write transactions
246
# it is not cached but typically we only read a small number of
247
# revisions. For knits when they are introduced we will probably want
248
# to ensure that caching write transactions are in use.
249
inv = self.get_inventory_weave()
250
self._check_revision_parents(r, inv)
253
def has_revisions(self, revision_ids):
254
"""See Repository.has_revisions()."""
256
transaction = self.get_transaction()
257
for revision_id in revision_ids:
258
if self._revision_store.has_revision_id(revision_id, transaction):
259
result.add(revision_id)
247
def _inventory_add_lines(self, revision_id, parents, lines,
249
"""Store lines in inv_vf and return the sha1 of the inventory."""
250
present_parents = self.get_graph().get_parent_map(parents)
252
for parent in parents:
253
if parent in present_parents:
254
final_parents.append((parent,))
255
return self.inventories.add_lines((revision_id,), final_parents, lines,
256
check_content=check_content)[0]
262
258
def revision_graph_can_have_wrong_parents(self):
263
# XXX: This is an old format that we don't support full checking on, so
264
# just claim that checking for this inconsistency is not required.
366
347
"""Format 4 is not supported.
368
349
It is not supported because the model changed from 4 to 5 and the
369
conversion logic is expensive - so doing it on the fly was not
350
conversion logic is expensive - so doing it on the fly was not
374
def _get_control_store(self, repo_transport, control_files):
375
"""Format 4 repositories have no formal control store at this point.
377
This will cause any control-file-needing apis to fail - this is desired.
355
def _get_inventories(self, repo_transport, repo, name='inventory'):
356
# No inventories store written so far.
381
def _get_revision_store(self, repo_transport, control_files):
382
"""See RepositoryFormat._get_revision_store()."""
359
def _get_revisions(self, repo_transport, repo):
383
360
from bzrlib.xml4 import serializer_v4
384
return self._get_text_rev_store(repo_transport,
387
serializer=serializer_v4)
389
def _get_text_store(self, transport, control_files):
390
"""See RepositoryFormat._get_text_store()."""
361
return RevisionTextStore(repo_transport.clone('revision-store'),
362
serializer_v4, True, versionedfile.PrefixMapper(),
363
repo.is_locked, repo.is_write_locked)
365
def _get_signatures(self, repo_transport, repo):
366
return SignatureTextStore(repo_transport.clone('revision-store'),
367
False, versionedfile.PrefixMapper(),
368
repo.is_locked, repo.is_write_locked)
370
def _get_texts(self, repo_transport, repo):
393
374
class RepositoryFormat5(PreSplitOutRepositoryFormat):
402
383
_versionedfile_class = weave.WeaveFile
403
384
_matchingbzrdir = bzrdir.BzrDirFormat5()
406
super(RepositoryFormat5, self).__init__()
386
def _serializer(self):
387
return xml5.serializer_v5
408
389
def get_format_description(self):
409
390
"""See RepositoryFormat.get_format_description()."""
410
391
return "Weave repository format 5"
412
def _get_revision_store(self, repo_transport, control_files):
413
"""See RepositoryFormat._get_revision_store()."""
414
"""Return the revision store object for this a_bzrdir."""
415
return self._get_text_rev_store(repo_transport,
420
def _get_text_store(self, transport, control_files):
421
"""See RepositoryFormat._get_text_store()."""
422
return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
393
def network_name(self):
394
"""The network name for this format is the control dirs disk label."""
395
return self._matchingbzrdir.get_format_string()
397
def _get_inventories(self, repo_transport, repo, name='inventory'):
398
mapper = versionedfile.ConstantMapper(name)
399
return versionedfile.ThunkedVersionedFiles(repo_transport,
400
weave.WeaveFile, mapper, repo.is_locked)
402
def _get_revisions(self, repo_transport, repo):
403
return RevisionTextStore(repo_transport.clone('revision-store'),
404
xml5.serializer_v5, False, versionedfile.PrefixMapper(),
405
repo.is_locked, repo.is_write_locked)
407
def _get_signatures(self, repo_transport, repo):
408
return SignatureTextStore(repo_transport.clone('revision-store'),
409
False, versionedfile.PrefixMapper(),
410
repo.is_locked, repo.is_write_locked)
412
def _get_texts(self, repo_transport, repo):
413
mapper = versionedfile.PrefixMapper()
414
base_transport = repo_transport.clone('weaves')
415
return versionedfile.ThunkedVersionedFiles(base_transport,
416
weave.WeaveFile, mapper, repo.is_locked)
425
419
class RepositoryFormat6(PreSplitOutRepositoryFormat):
434
428
_versionedfile_class = weave.WeaveFile
435
429
_matchingbzrdir = bzrdir.BzrDirFormat6()
438
super(RepositoryFormat6, self).__init__()
431
def _serializer(self):
432
return xml5.serializer_v5
440
434
def get_format_description(self):
441
435
"""See RepositoryFormat.get_format_description()."""
442
436
return "Weave repository format 6"
444
def _get_revision_store(self, repo_transport, control_files):
445
"""See RepositoryFormat._get_revision_store()."""
446
return self._get_text_rev_store(repo_transport,
452
def _get_text_store(self, transport, control_files):
453
"""See RepositoryFormat._get_text_store()."""
454
return self._get_versioned_file_store('weaves', transport, control_files)
438
def network_name(self):
439
"""The network name for this format is the control dirs disk label."""
440
return self._matchingbzrdir.get_format_string()
442
def _get_inventories(self, repo_transport, repo, name='inventory'):
443
mapper = versionedfile.ConstantMapper(name)
444
return versionedfile.ThunkedVersionedFiles(repo_transport,
445
weave.WeaveFile, mapper, repo.is_locked)
447
def _get_revisions(self, repo_transport, repo):
448
return RevisionTextStore(repo_transport.clone('revision-store'),
449
xml5.serializer_v5, False, versionedfile.HashPrefixMapper(),
450
repo.is_locked, repo.is_write_locked)
452
def _get_signatures(self, repo_transport, repo):
453
return SignatureTextStore(repo_transport.clone('revision-store'),
454
False, versionedfile.HashPrefixMapper(),
455
repo.is_locked, repo.is_write_locked)
457
def _get_texts(self, repo_transport, repo):
458
mapper = versionedfile.HashPrefixMapper()
459
base_transport = repo_transport.clone('weaves')
460
return versionedfile.ThunkedVersionedFiles(base_transport,
461
weave.WeaveFile, mapper, repo.is_locked)
456
464
class RepositoryFormat7(MetaDirRepositoryFormat):
457
465
"""Bzr repository 7.
486
493
def check_conversion_target(self, target_format):
489
def _get_revision_store(self, repo_transport, control_files):
490
"""See RepositoryFormat._get_revision_store()."""
491
return self._get_text_rev_store(repo_transport,
498
def _get_text_store(self, transport, control_files):
499
"""See RepositoryFormat._get_text_store()."""
500
return self._get_versioned_file_store('weaves',
496
def _get_inventories(self, repo_transport, repo, name='inventory'):
497
mapper = versionedfile.ConstantMapper(name)
498
return versionedfile.ThunkedVersionedFiles(repo_transport,
499
weave.WeaveFile, mapper, repo.is_locked)
501
def _get_revisions(self, repo_transport, repo):
502
return RevisionTextStore(repo_transport.clone('revision-store'),
503
xml5.serializer_v5, True, versionedfile.HashPrefixMapper(),
504
repo.is_locked, repo.is_write_locked)
506
def _get_signatures(self, repo_transport, repo):
507
return SignatureTextStore(repo_transport.clone('revision-store'),
508
True, versionedfile.HashPrefixMapper(),
509
repo.is_locked, repo.is_write_locked)
511
def _get_texts(self, repo_transport, repo):
512
mapper = versionedfile.HashPrefixMapper()
513
base_transport = repo_transport.clone('weaves')
514
return versionedfile.ThunkedVersionedFiles(base_transport,
515
weave.WeaveFile, mapper, repo.is_locked)
504
517
def initialize(self, a_bzrdir, shared=False):
505
518
"""Create a weave repository.
536
549
repo_transport = a_bzrdir.get_repository_transport(None)
537
550
control_files = lockable_files.LockableFiles(repo_transport,
538
551
'lock', lockdir.LockDir)
539
text_store = self._get_text_store(repo_transport, control_files)
540
control_store = self._get_control_store(repo_transport, control_files)
541
_revision_store = self._get_revision_store(repo_transport, control_files)
542
return WeaveMetaDirRepository(_format=self,
544
control_files=control_files,
545
_revision_store=_revision_store,
546
control_store=control_store,
547
text_store=text_store)
550
class WeaveCommitBuilder(CommitBuilder):
551
"""A builder for weave based repos that don't support ghosts."""
553
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
554
versionedfile = self.repository.weave_store.get_weave_or_empty(
555
file_id, self.repository.get_transaction())
556
result = versionedfile.add_lines(
557
self._new_revision_id, parents, new_lines,
558
nostore_sha=nostore_sha)[0:2]
552
result = WeaveMetaDirRepository(_format=self, a_bzrdir=a_bzrdir,
553
control_files=control_files)
554
result.revisions = self._get_revisions(repo_transport, result)
555
result.signatures = self._get_signatures(repo_transport, result)
556
result.inventories = self._get_inventories(repo_transport, result)
557
result.texts = self._get_texts(repo_transport, result)
558
result._transport = repo_transport
562
class TextVersionedFiles(VersionedFiles):
563
"""Just-a-bunch-of-files based VersionedFile stores."""
565
def __init__(self, transport, compressed, mapper, is_locked, can_write):
566
self._compressed = compressed
567
self._transport = transport
568
self._mapper = mapper
573
self._is_locked = is_locked
574
self._can_write = can_write
576
def add_lines(self, key, parents, lines):
577
"""Add a revision to the store."""
578
if not self._is_locked():
579
raise errors.ObjectNotLocked(self)
580
if not self._can_write():
581
raise errors.ReadOnlyError(self)
583
raise ValueError('bad idea to put / in %r' % (key,))
584
text = ''.join(lines)
586
text = bytes_to_gzip(text)
587
path = self._map(key)
588
self._transport.put_bytes_non_atomic(path, text, create_parent_dir=True)
590
def insert_record_stream(self, stream):
592
for record in stream:
593
# Raise an error when a record is missing.
594
if record.storage_kind == 'absent':
595
raise errors.RevisionNotPresent([record.key[0]], self)
596
# adapt to non-tuple interface
597
if record.storage_kind == 'fulltext':
598
self.add_lines(record.key, None,
599
osutils.split_lines(record.get_bytes_as('fulltext')))
601
adapter_key = record.storage_kind, 'fulltext'
603
adapter = adapters[adapter_key]
605
adapter_factory = adapter_registry.get(adapter_key)
606
adapter = adapter_factory(self)
607
adapters[adapter_key] = adapter
608
lines = osutils.split_lines(adapter.get_bytes(
609
record, record.get_bytes_as(record.storage_kind)))
611
self.add_lines(record.key, None, lines)
612
except RevisionAlreadyPresent:
615
def _load_text(self, key):
616
if not self._is_locked():
617
raise errors.ObjectNotLocked(self)
618
path = self._map(key)
620
text = self._transport.get_bytes(path)
621
compressed = self._compressed
622
except errors.NoSuchFile:
624
# try without the .gz
627
text = self._transport.get_bytes(path)
629
except errors.NoSuchFile:
634
text = GzipFile(mode='rb', fileobj=StringIO(text)).read()
638
return self._mapper.map(key) + self._ext
641
class RevisionTextStore(TextVersionedFiles):
642
"""Legacy thunk for format 4 repositories."""
644
def __init__(self, transport, serializer, compressed, mapper, is_locked,
646
"""Create a RevisionTextStore at transport with serializer."""
647
TextVersionedFiles.__init__(self, transport, compressed, mapper,
648
is_locked, can_write)
649
self._serializer = serializer
651
def _load_text_parents(self, key):
652
text = self._load_text(key)
655
parents = self._serializer.read_revision_from_string(text).parent_ids
656
return text, tuple((parent,) for parent in parents)
658
def get_parent_map(self, keys):
661
parents = self._load_text_parents(key)[1]
664
result[key] = parents
667
def get_record_stream(self, keys, sort_order, include_delta_closure):
669
text, parents = self._load_text_parents(key)
671
yield AbsentContentFactory(key)
673
yield FulltextContentFactory(key, parents, None, text)
676
if not self._is_locked():
677
raise errors.ObjectNotLocked(self)
679
for quoted_relpath in self._transport.iter_files_recursive():
680
relpath = urllib.unquote(quoted_relpath)
681
path, ext = os.path.splitext(relpath)
684
if '.sig' not in relpath:
685
relpaths.add(relpath)
686
paths = list(relpaths)
687
return set([self._mapper.unmap(path) for path in paths])
690
class SignatureTextStore(TextVersionedFiles):
691
"""Legacy thunk for format 4-7 repositories."""
693
def __init__(self, transport, compressed, mapper, is_locked, can_write):
694
TextVersionedFiles.__init__(self, transport, compressed, mapper,
695
is_locked, can_write)
696
self._ext = '.sig' + self._ext
698
def get_parent_map(self, keys):
701
text = self._load_text(key)
707
def get_record_stream(self, keys, sort_order, include_delta_closure):
709
text = self._load_text(key)
711
yield AbsentContentFactory(key)
713
yield FulltextContentFactory(key, None, None, text)
716
if not self._is_locked():
717
raise errors.ObjectNotLocked(self)
719
for quoted_relpath in self._transport.iter_files_recursive():
720
relpath = urllib.unquote(quoted_relpath)
721
path, ext = os.path.splitext(relpath)
724
if not relpath.endswith('.sig'):
726
relpaths.add(relpath[:-4])
727
paths = list(relpaths)
728
return set([self._mapper.unmap(path) for path in paths])
562
730
_legacy_formats = [RepositoryFormat4(),
563
731
RepositoryFormat5(),