14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
22
20
from ..lazy_import import lazy_import
23
21
lazy_import(globals(), """
24
from itertools import izip
27
25
from breezy import (
37
33
from breezy.bzr import (
41
36
from breezy.bzr.index import (
42
37
CombinedGraphIndex,
43
GraphIndexPrefixAdapter,
65
59
RepositoryFormatMetaDir,
67
from ..sixish import (
70
61
from ..bzr.vf_repository import (
71
62
MetaDirVersionedFileRepository,
72
63
MetaDirVersionedFileRepositoryFormat,
73
64
VersionedFileCommitBuilder,
74
VersionedFileRootCommitBuilder,
76
66
from ..trace import (
91
81
timezone=None, committer=None, revprops=None,
92
82
revision_id=None, lossy=False):
93
83
VersionedFileCommitBuilder.__init__(self, repository, parents, config,
94
timestamp=timestamp, timezone=timezone, committer=committer,
95
revprops=revprops, revision_id=revision_id, lossy=lossy)
96
self._file_graph = graph.Graph(
97
repository._pack_collection.text_index.combined_index)
99
def _heads(self, file_id, revision_ids):
100
keys = [(file_id, revision_id) for revision_id in revision_ids]
101
return {key[1] for key in self._file_graph.heads(keys)}
104
class PackRootCommitBuilder(VersionedFileRootCommitBuilder):
105
"""A subclass of RootCommitBuilder to add texts with pack semantics.
107
Specifically this uses one knit object rather than one knit object per
108
added text, reducing memory and object pressure.
111
def __init__(self, repository, parents, config, timestamp=None,
112
timezone=None, committer=None, revprops=None,
113
revision_id=None, lossy=False):
114
super(PackRootCommitBuilder, self).__init__(repository, parents,
115
config, timestamp=timestamp, timezone=timezone,
116
committer=committer, revprops=revprops, revision_id=revision_id,
84
timestamp=timestamp, timezone=timezone, committer=committer,
85
revprops=revprops, revision_id=revision_id, lossy=lossy)
118
86
self._file_graph = graph.Graph(
119
87
repository._pack_collection.text_index.combined_index)
143
111
def __init__(self, revision_index, inventory_index, text_index,
144
signature_index, chk_index=None):
112
signature_index, chk_index=None):
145
113
"""Create a pack instance.
147
115
:param revision_index: A GraphIndex for determining what revisions are
178
146
missing_items = {}
179
147
for (index_name, external_refs, index) in [
181
self._get_external_refs(self.text_index),
182
self._pack_collection.text_index.combined_index),
184
self._get_external_refs(self.inventory_index),
185
self._pack_collection.inventory_index.combined_index),
149
self._get_external_refs(self.text_index),
150
self._pack_collection.text_index.combined_index),
152
self._get_external_refs(self.inventory_index),
153
self._pack_collection.inventory_index.combined_index),
187
155
missing = external_refs.difference(
188
156
k for (idx, k, v, r) in
189
157
index.iter_entries(external_refs))
232
200
if index_type == 'chk':
233
201
unlimited_cache = True
234
202
index = self.index_class(self.index_transport,
235
self.index_name(index_type, self.name),
236
self.index_sizes[self.index_offset(index_type)],
237
unlimited_cache=unlimited_cache)
203
self.index_name(index_type, self.name),
204
self.index_sizes[self.index_offset(
206
unlimited_cache=unlimited_cache)
238
207
if index_type == 'chk':
239
208
index._leaf_factory = btree_index._gcchk_factory
240
209
setattr(self, index_type + '_index', index)
211
def __lt__(self, other):
212
if not isinstance(other, Pack):
213
raise TypeError(other)
214
return (id(self) < id(other))
217
return hash((type(self), self.revision_index, self.inventory_index,
218
self.text_index, self.signature_index, self.chk_index))
243
221
class ExistingPack(Pack):
244
222
"""An in memory proxy for an existing .pack and its disk indices."""
246
224
def __init__(self, pack_transport, name, revision_index, inventory_index,
247
text_index, signature_index, chk_index=None):
225
text_index, signature_index, chk_index=None):
248
226
"""Create an ExistingPack object.
250
228
:param pack_transport: The transport where the pack file resides.
251
229
:param name: The name of the pack on disk in the pack_transport.
253
231
Pack.__init__(self, revision_index, inventory_index, text_index,
254
signature_index, chk_index)
232
signature_index, chk_index)
256
234
self.pack_transport = pack_transport
257
235
if None in (revision_index, inventory_index, text_index,
258
signature_index, name, pack_transport):
236
signature_index, name, pack_transport):
259
237
raise AssertionError()
261
239
def __eq__(self, other):
269
247
self.__class__.__module__, self.__class__.__name__, id(self),
270
248
self.pack_transport, self.name)
251
return hash((type(self), self.name))
273
254
class ResumedPack(ExistingPack):
275
256
def __init__(self, name, revision_index, inventory_index, text_index,
276
signature_index, upload_transport, pack_transport, index_transport,
277
pack_collection, chk_index=None):
257
signature_index, upload_transport, pack_transport, index_transport,
258
pack_collection, chk_index=None):
278
259
"""Create a ResumedPack object."""
279
260
ExistingPack.__init__(self, pack_transport, name, revision_index,
280
inventory_index, text_index, signature_index,
261
inventory_index, text_index, signature_index,
282
263
self.upload_transport = upload_transport
283
264
self.index_transport = index_transport
284
265
self.index_sizes = [None, None, None, None]
360
341
Pack.__init__(self,
361
# Revisions: parents list, no text compression.
362
index_builder_class(reference_lists=1),
363
# Inventory: We want to map compression only, but currently the
364
# knit code hasn't been updated enough to understand that, so we
365
# have a regular 2-list index giving parents and compression
367
index_builder_class(reference_lists=2),
368
# Texts: compression and per file graph, for all fileids - so two
369
# reference lists and two elements in the key tuple.
370
index_builder_class(reference_lists=2, key_elements=2),
371
# Signatures: Just blobs to store, no compression, no parents
373
index_builder_class(reference_lists=0),
374
# CHK based storage - just blobs, no compression or parents.
342
# Revisions: parents list, no text compression.
343
index_builder_class(reference_lists=1),
344
# Inventory: We want to map compression only, but currently the
345
# knit code hasn't been updated enough to understand that, so we
346
# have a regular 2-list index giving parents and compression
348
index_builder_class(reference_lists=2),
349
# Texts: compression and per file graph, for all fileids - so two
350
# reference lists and two elements in the key tuple.
351
index_builder_class(reference_lists=2, key_elements=2),
352
# Signatures: Just blobs to store, no compression, no parents
354
index_builder_class(reference_lists=0),
355
# CHK based storage - just blobs, no compression or parents.
377
358
self._pack_collection = pack_collection
378
359
# When we make readonly indices, we need this.
379
360
self.index_class = pack_collection._index_class
404
385
self.random_name, mode=self._file_mode)
405
386
if 'pack' in debug.debug_flags:
406
387
mutter('%s: create_pack: pack stream open: %s%s t+%6.3fs',
407
time.ctime(), self.upload_transport.base, self.random_name,
408
time.time() - self.start_time)
388
time.ctime(), self.upload_transport.base, self.random_name,
389
time.time() - self.start_time)
409
390
# A list of byte sequences to be written to the new pack, and the
410
391
# aggregate size of them. Stored as a list rather than separate
411
392
# variables so that the _write_data closure below can update them.
415
396
# robertc says- this is a closure rather than a method on the object
416
397
# so that the variables are locals, and faster than accessing object
418
400
def _write_data(bytes, flush=False, _buffer=self._buffer,
419
_write=self.write_stream.write, _update=self._hash.update):
401
_write=self.write_stream.write, _update=self._hash.update):
420
402
_buffer[0].append(bytes)
421
403
_buffer[1] += len(bytes)
455
437
def data_inserted(self):
456
438
"""True if data has been added to this pack."""
457
439
return bool(self.get_revision_count() or
458
self.inventory_index.key_count() or
459
self.text_index.key_count() or
460
self.signature_index.key_count() or
461
(self.chk_index is not None and self.chk_index.key_count()))
440
self.inventory_index.key_count() or
441
self.text_index.key_count() or
442
self.signature_index.key_count() or
443
(self.chk_index is not None and self.chk_index.key_count()))
463
445
def finish_content(self):
464
446
if self.name is not None:
466
448
self._writer.end()
467
449
if self._buffer[1]:
468
self._write_data('', flush=True)
450
self._write_data(b'', flush=True)
469
451
self.name = self._hash.hexdigest()
471
453
def finish(self, suspend=False):
489
471
# they're in the names list.
490
472
self.index_sizes = [None, None, None, None]
491
473
self._write_index('revision', self.revision_index, 'revision',
493
475
self._write_index('inventory', self.inventory_index, 'inventory',
495
477
self._write_index('text', self.text_index, 'file texts', suspend)
496
478
self._write_index('signature', self.signature_index,
497
'revision signatures', suspend)
479
'revision signatures', suspend)
498
480
if self.chk_index is not None:
499
481
self.index_sizes.append(None)
500
482
self._write_index('chk', self.chk_index,
501
'content hash bytes', suspend)
483
'content hash bytes', suspend)
502
484
self.write_stream.close(
503
485
want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
504
486
# Note that this will clobber an existing pack with the same name,
520
502
if 'pack' in debug.debug_flags:
521
503
# XXX: size might be interesting?
522
504
mutter('%s: create_pack: pack finished: %s%s->%s t+%6.3fs',
523
time.ctime(), self.upload_transport.base, self.random_name,
524
new_name, time.time() - self.start_time)
505
time.ctime(), self.upload_transport.base, self.random_name,
506
new_name, time.time() - self.start_time)
527
509
"""Flush any current data."""
552
534
index_tempfile = index.finish()
553
535
index_bytes = index_tempfile.read()
554
536
write_stream = transport.open_write_stream(index_name,
555
mode=self._file_mode)
537
mode=self._file_mode)
556
538
write_stream.write(index_bytes)
557
539
write_stream.close(
558
540
want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync'))
560
542
if 'pack' in debug.debug_flags:
561
543
# XXX: size might be interesting?
562
544
mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
563
time.ctime(), label, self.upload_transport.base,
564
self.random_name, time.time() - self.start_time)
545
time.ctime(), label, self.upload_transport.base,
546
self.random_name, time.time() - self.start_time)
565
547
# Replace the writable index on this object with a readonly,
566
548
# presently unloaded index. We should alter
567
549
# the index layer to make its finish() error if add_node is
625
607
if self.add_callback is not None:
626
608
raise AssertionError(
627
"%s already has a writable index through %s" % \
609
"%s already has a writable index through %s" %
628
610
(self, self.add_callback))
629
611
# allow writing: queue writes to a new index
630
612
self.add_index(index, pack)
650
632
del self.combined_index._indices[pos]
651
633
del self.combined_index._index_names[pos]
652
634
if (self.add_callback is not None and
653
getattr(index, 'add_nodes', None) == self.add_callback):
635
getattr(index, 'add_nodes', None) == self.add_callback):
654
636
self.add_callback = None
655
637
self.data_access.set_writer(None, None, (None, None))
728
710
def open_pack(self):
729
711
"""Open a pack for the pack we are creating."""
730
712
new_pack = self._pack_collection.pack_factory(self._pack_collection,
731
upload_suffix=self.suffix,
732
file_mode=self._pack_collection.repo.controldir._get_file_mode())
713
upload_suffix=self.suffix,
714
file_mode=self._pack_collection.repo.controldir._get_file_mode())
733
715
# We know that we will process all nodes in order, and don't need to
734
716
# query, so don't combine any indices spilled to disk until we are done
735
717
new_pack.revision_index.set_optimize(combine_backing_indices=False)
760
742
def _log_copied_texts(self):
761
743
if 'pack' in debug.debug_flags:
762
744
mutter('%s: create_pack: file texts copied: %s%s %d items t+%6.3fs',
763
time.ctime(), self._pack_collection._upload_transport.base,
764
self.new_pack.random_name,
765
self.new_pack.text_index.key_count(),
766
time.time() - self.new_pack.start_time)
745
time.ctime(), self._pack_collection._upload_transport.base,
746
self.new_pack.random_name,
747
self.new_pack.text_index.key_count(),
748
time.time() - self.new_pack.start_time)
768
750
def _use_pack(self, new_pack):
769
751
"""Return True if new_pack should be used.
825
807
self.text_index = AggregateIndex(self.reload_pack_names, flush)
826
808
self.signature_index = AggregateIndex(self.reload_pack_names, flush)
827
809
all_indices = [self.revision_index, self.inventory_index,
828
self.text_index, self.signature_index]
810
self.text_index, self.signature_index]
829
811
if use_chk_index:
830
812
self.chk_index = AggregateIndex(self.reload_pack_names, flush)
831
813
all_indices.append(self.chk_index)
928
910
num_old_packs = sum([len(po[1]) for po in pack_operations])
929
911
num_revs_affected = sum([po[0] for po in pack_operations])
930
912
mutter('Auto-packing repository %s, which has %d pack files, '
931
'containing %d revisions. Packing %d files into %d affecting %d'
932
' revisions', self, total_packs, total_revisions, num_old_packs,
933
num_new_packs, num_revs_affected)
913
'containing %d revisions. Packing %d files into %d affecting %d'
915
self), total_packs, total_revisions, num_old_packs,
916
num_new_packs, num_revs_affected)
934
917
result = self._execute_pack_operations(pack_operations, packer_class=self.normal_packer_class,
935
reload_func=self._restart_autopack)
936
mutter('Auto-packing repository %s completed', self)
918
reload_func=self._restart_autopack)
919
mutter('Auto-packing repository %s completed', str(self))
939
922
def _execute_pack_operations(self, pack_operations, packer_class,
941
924
"""Execute a series of pack operations.
943
926
:param pack_operations: A list of [revision_count, packs_to_combine].
999
982
# XXX: the following may want to be a class, to pack with a given
1001
984
mutter('Packing repository %s, which has %d pack files, '
1002
'containing %d revisions with hint %r.', self, total_packs,
1003
total_revisions, hint)
985
'containing %d revisions with hint %r.', str(self), total_packs,
986
total_revisions, hint)
1006
989
self._try_pack_operations(hint)
1024
1007
pack_operations[-1][0] += pack.get_revision_count()
1025
1008
pack_operations[-1][1].append(pack)
1026
1009
self._execute_pack_operations(pack_operations,
1027
packer_class=self.optimising_packer_class,
1028
reload_func=self._restart_pack_operations)
1010
packer_class=self.optimising_packer_class,
1011
reload_func=self._restart_pack_operations)
1030
1013
def plan_autopack_combinations(self, existing_packs, pack_distribution):
1031
1014
"""Plan a pack operation.
1075
1058
final_pack_list.extend(pack_files)
1076
1059
if len(final_pack_list) == 1:
1077
1060
raise AssertionError('We somehow generated an autopack with a'
1078
' single pack file being moved.')
1061
' single pack file being moved.')
1080
1063
return [[final_rev_count, final_pack_list]]
1092
1075
self._names = {}
1093
1076
self._packs_at_load = set()
1094
1077
for index, key, value in self._iter_disk_pack_index():
1078
name = key[0].decode('ascii')
1096
1079
self._names[name] = self._parse_index_sizes(value)
1097
self._packs_at_load.add((key, value))
1080
self._packs_at_load.add((name, value))
1105
1088
def _parse_index_sizes(self, value):
1106
1089
"""Parse a string of index sizes."""
1107
return tuple([int(digits) for digits in value.split(' ')])
1090
return tuple(int(digits) for digits in value.split(b' '))
1109
1092
def get_pack_by_name(self, name):
1110
1093
"""Get a Pack object by name.
1125
1108
chk_index = None
1126
1109
result = ExistingPack(self._pack_transport, name, rev_index,
1127
inv_index, txt_index, sig_index, chk_index)
1110
inv_index, txt_index, sig_index, chk_index)
1128
1111
self.add_pack_to_memory(result)
1151
1134
chk_index = None
1152
1135
result = self.resumed_pack_factory(name, rev_index, inv_index,
1153
txt_index, sig_index, self._upload_transport,
1154
self._pack_transport, self._index_transport, self,
1155
chk_index=chk_index)
1136
txt_index, sig_index, self._upload_transport,
1137
self._pack_transport, self._index_transport, self,
1138
chk_index=chk_index)
1156
1139
except errors.NoSuchFile as e:
1157
1140
raise errors.UnresumableWriteGroup(self.repo, [name], str(e))
1158
1141
self.add_pack_to_memory(result)
1180
1163
:return: An iterator of the index contents.
1182
1165
return self._index_class(self.transport, 'pack-names', None
1183
).iter_all_entries()
1166
).iter_all_entries()
1185
1168
def _make_index(self, name, suffix, resume=False, is_chk=False):
1186
1169
size_offset = self._suffix_offsets[suffix]
1193
1176
index_size = self._names[name][size_offset]
1194
1177
index = self._index_class(transport, index_name, index_size,
1195
1178
unlimited_cache=is_chk)
1196
if is_chk and self._index_class is btree_index.BTreeGraphIndex:
1179
if is_chk and self._index_class is btree_index.BTreeGraphIndex:
1197
1180
index._leaf_factory = btree_index._gcchk_factory
1234
1217
pack.pack_transport.move(pack.file_name(),
1235
'../obsolete_packs/' + pack.file_name())
1218
'../obsolete_packs/' + pack.file_name())
1236
1219
except errors.NoSuchFile:
1237
1220
# perhaps obsolete_packs was removed? Let's create it and
1241
1224
except errors.FileExists:
1243
1226
pack.pack_transport.move(pack.file_name(),
1244
'../obsolete_packs/' + pack.file_name())
1227
'../obsolete_packs/' + pack.file_name())
1245
1228
except (errors.PathError, errors.TransportError) as e:
1246
1229
# TODO: Should these be warnings or mutters?
1247
1230
mutter("couldn't rename obsolete pack, skipping it:\n%s"
1255
1238
for suffix in suffixes:
1257
1240
self._index_transport.move(pack.name + suffix,
1258
'../obsolete_packs/' + pack.name + suffix)
1241
'../obsolete_packs/' + pack.name + suffix)
1259
1242
except (errors.PathError, errors.TransportError) as e:
1260
1243
mutter("couldn't rename obsolete index, skipping it:\n%s"
1343
1326
# load the disk nodes across
1344
1327
disk_nodes = set()
1345
1328
for index, key, value in self._iter_disk_pack_index():
1346
disk_nodes.add((key, value))
1329
disk_nodes.add((key[0].decode('ascii'), value))
1347
1330
orig_disk_nodes = set(disk_nodes)
1349
1332
# do a two-way diff against our original content
1350
1333
current_nodes = set()
1351
1334
for name, sizes in self._names.items():
1352
1335
current_nodes.add(
1353
((name, ), ' '.join(str(size) for size in sizes)))
1336
(name, b' '.join(b'%d' % size for size in sizes)))
1355
1338
# Packs no longer present in the repository, which were present when we
1356
1339
# locked the repository
1380
1363
new_names = dict(disk_nodes)
1381
1364
# drop no longer present nodes
1382
1365
for pack in self.all_packs():
1383
if (pack.name,) not in new_names:
1366
if pack.name not in new_names:
1384
1367
removed.append(pack.name)
1385
1368
self._remove_pack_from_memory(pack)
1386
1369
# add new nodes/refresh existing ones
1387
for key, value in disk_nodes:
1370
for name, value in disk_nodes:
1389
1371
sizes = self._parse_index_sizes(value)
1390
1372
if name in self._names:
1432
1414
# TODO: handle same-name, index-size-changes here -
1433
1415
# e.g. use the value from disk, not ours, *unless* we're the one
1435
for key, value in disk_nodes:
1436
builder.add_node(key, value)
1417
for name, value in disk_nodes:
1418
builder.add_node((name.encode('ascii'), ), value)
1437
1419
self.transport.put_file('pack-names', builder.finish(),
1438
mode=self.repo.controldir._get_file_mode())
1420
mode=self.repo.controldir._get_file_mode())
1439
1421
self._packs_at_load = disk_nodes
1440
1422
if clear_obsolete_packs:
1441
1423
to_preserve = None
1455
1437
obsolete_packs = [o for o in obsolete_packs
1456
1438
if o.name not in already_obsolete]
1457
1439
self._obsolete_packs(obsolete_packs)
1458
return [new_node[0][0] for new_node in new_nodes]
1440
return [new_node[0] for new_node in new_nodes]
1460
1442
def reload_pack_names(self):
1461
1443
"""Sync our pack listing with what is present in the repository.
1467
1449
:return: True if the in-memory list of packs has been altered at all.
1469
1451
# The ensure_loaded call is to handle the case where the first call
1470
# made involving the collection was to reload_pack_names, where we
1452
# made involving the collection was to reload_pack_names, where we
1471
1453
# don't have a view of disk contents. It's a bit of a bandaid, and
1472
1454
# causes two reads of pack-names, but it's a rare corner case not
1473
1455
# struck with regular push/pull etc.
1535
1517
if not self.repo.is_write_locked():
1536
1518
raise errors.NotWriteLocked(self)
1537
1519
self._new_pack = self.pack_factory(self, upload_suffix='.pack',
1538
file_mode=self.repo.controldir._get_file_mode())
1520
file_mode=self.repo.controldir._get_file_mode())
1539
1521
# allow writing: queue writes to a new index
1540
1522
self.revision_index.add_writable_index(self._new_pack.revision_index,
1542
1524
self.inventory_index.add_writable_index(self._new_pack.inventory_index,
1544
1526
self.text_index.add_writable_index(self._new_pack.text_index,
1546
1528
self._new_pack.text_index.set_optimize(combine_backing_indices=False)
1547
1529
self.signature_index.add_writable_index(self._new_pack.signature_index,
1549
1531
if self.chk_index is not None:
1550
1532
self.chk_index.add_writable_index(self._new_pack.chk_index,
1552
1534
self.repo.chk_bytes._index._add_callback = self.chk_index.add_callback
1553
self._new_pack.chk_index.set_optimize(combine_backing_indices=False)
1535
self._new_pack.chk_index.set_optimize(
1536
combine_backing_indices=False)
1555
1538
self.repo.inventories._index._add_callback = self.inventory_index.add_callback
1556
1539
self.repo.revisions._index._add_callback = self.revision_index.add_callback
1561
1544
# FIXME: just drop the transient index.
1562
1545
# forget what names there are
1563
1546
if self._new_pack is not None:
1564
operation = cleanup.OperationWithCleanups(self._new_pack.abort)
1565
operation.add_cleanup(setattr, self, '_new_pack', None)
1566
# If we aborted while in the middle of finishing the write
1567
# group, _remove_pack_indices could fail because the indexes are
1568
# already gone. But they're not there we shouldn't fail in this
1569
# case, so we pass ignore_missing=True.
1570
operation.add_cleanup(self._remove_pack_indices, self._new_pack,
1571
ignore_missing=True)
1572
operation.run_simple()
1547
with contextlib.ExitStack() as stack:
1548
stack.callback(setattr, self, '_new_pack', None)
1549
# If we aborted while in the middle of finishing the write
1550
# group, _remove_pack_indices could fail because the indexes are
1551
# already gone. But they're not there we shouldn't fail in this
1552
# case, so we pass ignore_missing=True.
1553
stack.callback(self._remove_pack_indices, self._new_pack,
1554
ignore_missing=True)
1555
self._new_pack.abort()
1573
1556
for resumed_pack in self._resumed_packs:
1574
operation = cleanup.OperationWithCleanups(resumed_pack.abort)
1575
# See comment in previous finally block.
1576
operation.add_cleanup(self._remove_pack_indices, resumed_pack,
1577
ignore_missing=True)
1578
operation.run_simple()
1557
with contextlib.ExitStack() as stack:
1558
# See comment in previous finally block.
1559
stack.callback(self._remove_pack_indices, resumed_pack,
1560
ignore_missing=True)
1561
resumed_pack.abort()
1579
1562
del self._resumed_packs[:]
1581
1564
def _remove_resumed_pack_indices(self):
1606
1589
if all_missing:
1607
1590
raise errors.BzrCheckError(
1608
1591
"Repository %s has missing compression parent(s) %r "
1609
% (self.repo, sorted(all_missing)))
1592
% (self.repo, sorted(all_missing)))
1610
1593
problems = self._check_new_inventories()
1612
1595
problems_summary = '\n'.join(problems)
1693
1676
_serializer = None
1695
1678
def __init__(self, _format, a_controldir, control_files, _commit_builder_class,
1697
1680
MetaDirRepository.__init__(self, _format, a_controldir, control_files)
1698
1681
self._commit_builder_class = _commit_builder_class
1699
1682
self._serializer = _serializer
1825
1808
recompress deltas or do other such expensive operations.
1827
1810
with self.lock_write():
1828
self._pack_collection.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1811
self._pack_collection.pack(
1812
hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1830
1814
def reconcile(self, other=None, thorough=False):
1831
1815
"""Reconcile this repository."""
1832
from breezy.reconcile import PackReconciler
1816
from .reconcile import PackReconciler
1833
1817
with self.lock_write():
1834
1818
reconciler = PackReconciler(self, thorough=thorough)
1835
reconciler.reconcile()
1819
return reconciler.reconcile()
1838
1821
def _reconcile_pack(self, collection, packs, extension, revs, pb):
1839
1822
raise NotImplementedError(self._reconcile_pack)
1914
1897
dirs = ['indices', 'obsolete_packs', 'packs', 'upload']
1915
1898
builder = self.index_builder_class()
1916
1899
files = [('pack-names', builder.finish())]
1917
# GZ 2017-06-09: Where should format strings get decoded...
1918
utf8_files = [('format', self.get_format_string().encode('ascii'))]
1900
utf8_files = [('format', self.get_format_string())]
1920
self._upload_blank_content(a_controldir, dirs, files, utf8_files, shared)
1902
self._upload_blank_content(
1903
a_controldir, dirs, files, utf8_files, shared)
1921
1904
repository = self.open(a_controldir=a_controldir, _found=True)
1922
1905
self._run_post_repo_init_hooks(repository, a_controldir, shared)
1923
1906
return repository
1937
1920
repo_transport = a_controldir.get_repository_transport(None)
1938
1921
control_files = lockable_files.LockableFiles(repo_transport,
1939
'lock', lockdir.LockDir)
1922
'lock', lockdir.LockDir)
1940
1923
return self.repository_class(_format=self,
1941
a_controldir=a_controldir,
1942
control_files=control_files,
1943
_commit_builder_class=self._commit_builder_class,
1944
_serializer=self._serializer)
1924
a_controldir=a_controldir,
1925
control_files=control_files,
1926
_commit_builder_class=self._commit_builder_class,
1927
_serializer=self._serializer)
1947
1930
class RetryPackOperations(errors.RetryWithNewPacks):
1975
1958
self._reload_func = reload_func
1976
1959
self._flush_func = flush_func
1961
def add_raw_record(self, key, size, raw_data):
1962
"""Add raw knit bytes to a storage area.
1964
The data is spooled to the container writer in one bytes-record per
1967
:param key: key of the data segment
1968
:param size: length of the data segment
1969
:param raw_data: A bytestring containing the data.
1970
:return: An opaque index memo For _DirectPackAccess the memo is
1971
(index, pos, length), where the index field is the write_index
1972
object supplied to the PackAccess object.
1974
p_offset, p_length = self._container_writer.add_bytes_record(
1976
return (self._write_index, p_offset, p_length)
1978
1978
def add_raw_records(self, key_sizes, raw_data):
1979
1979
"""Add raw knit bytes to a storage area.
1989
1989
length), where the index field is the write_index object supplied
1990
1990
to the PackAccess object.
1992
raw_data = b''.join(raw_data)
1992
1993
if not isinstance(raw_data, bytes):
1993
1994
raise AssertionError(
1994
1995
'data must be plain bytes was %s' % type(raw_data))
1997
1998
for key, size in key_sizes:
1998
p_offset, p_length = self._container_writer.add_bytes_record(
1999
raw_data[offset:offset+size], [])
2000
self.add_raw_record(key, size, [raw_data[offset:offset + size]]))
2001
result.append((self._write_index, p_offset, p_length))
2004
2004
def flush(self):