~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitpack_repo.py

  • Committer: Kit Randel
  • Date: 2014-12-15 20:24:42 UTC
  • mto: This revision was merged to the branch mainline in revision 6602.
  • Revision ID: kit.randel@canonical.com-20141215202442-usf2ixhypqg8yh6q
added a note for bug-1400567 to the 2.7b release notes

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007-2011 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Knit-based pack repository formats."""
 
18
 
 
19
from __future__ import absolute_import
 
20
 
 
21
from bzrlib.lazy_import import lazy_import
 
22
lazy_import(globals(), """
 
23
from itertools import izip
 
24
import time
 
25
 
 
26
from bzrlib import (
 
27
    controldir,
 
28
    debug,
 
29
    errors,
 
30
    knit,
 
31
    osutils,
 
32
    pack,
 
33
    revision as _mod_revision,
 
34
    trace,
 
35
    tsort,
 
36
    ui,
 
37
    xml5,
 
38
    xml6,
 
39
    xml7,
 
40
    )
 
41
from bzrlib.knit import (
 
42
    _KnitGraphIndex,
 
43
    KnitPlainFactory,
 
44
    KnitVersionedFiles,
 
45
    )
 
46
""")
 
47
 
 
48
from bzrlib import (
 
49
    btree_index,
 
50
    )
 
51
from bzrlib.index import (
 
52
    CombinedGraphIndex,
 
53
    GraphIndex,
 
54
    GraphIndexPrefixAdapter,
 
55
    InMemoryGraphIndex,
 
56
    )
 
57
from bzrlib.repofmt.knitrepo import (
 
58
    KnitRepository,
 
59
    )
 
60
from bzrlib.repofmt.pack_repo import (
 
61
    _DirectPackAccess,
 
62
    NewPack,
 
63
    RepositoryFormatPack,
 
64
    ResumedPack,
 
65
    Packer,
 
66
    PackCommitBuilder,
 
67
    PackRepository,
 
68
    PackRootCommitBuilder,
 
69
    RepositoryPackCollection,
 
70
    )
 
71
from bzrlib.vf_repository import (
 
72
    StreamSource,
 
73
    )
 
74
 
 
75
 
 
76
class KnitPackRepository(PackRepository, KnitRepository):
 
77
 
 
78
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
 
79
        _serializer):
 
80
        PackRepository.__init__(self, _format, a_bzrdir, control_files,
 
81
            _commit_builder_class, _serializer)
 
82
        if self._format.supports_chks:
 
83
            raise AssertionError("chk not supported")
 
84
        index_transport = self._transport.clone('indices')
 
85
        self._pack_collection = KnitRepositoryPackCollection(self,
 
86
            self._transport,
 
87
            index_transport,
 
88
            self._transport.clone('upload'),
 
89
            self._transport.clone('packs'),
 
90
            _format.index_builder_class,
 
91
            _format.index_class,
 
92
            use_chk_index=False,
 
93
            )
 
94
        self.inventories = KnitVersionedFiles(
 
95
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
 
96
                add_callback=self._pack_collection.inventory_index.add_callback,
 
97
                deltas=True, parents=True, is_locked=self.is_locked),
 
98
            data_access=self._pack_collection.inventory_index.data_access,
 
99
            max_delta_chain=200)
 
100
        self.revisions = KnitVersionedFiles(
 
101
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
 
102
                add_callback=self._pack_collection.revision_index.add_callback,
 
103
                deltas=False, parents=True, is_locked=self.is_locked,
 
104
                track_external_parent_refs=True),
 
105
            data_access=self._pack_collection.revision_index.data_access,
 
106
            max_delta_chain=0)
 
107
        self.signatures = KnitVersionedFiles(
 
108
            _KnitGraphIndex(self._pack_collection.signature_index.combined_index,
 
109
                add_callback=self._pack_collection.signature_index.add_callback,
 
110
                deltas=False, parents=False, is_locked=self.is_locked),
 
111
            data_access=self._pack_collection.signature_index.data_access,
 
112
            max_delta_chain=0)
 
113
        self.texts = KnitVersionedFiles(
 
114
            _KnitGraphIndex(self._pack_collection.text_index.combined_index,
 
115
                add_callback=self._pack_collection.text_index.add_callback,
 
116
                deltas=True, parents=True, is_locked=self.is_locked),
 
117
            data_access=self._pack_collection.text_index.data_access,
 
118
            max_delta_chain=200)
 
119
        self.chk_bytes = None
 
120
        # True when the repository object is 'write locked' (as opposed to the
 
121
        # physical lock only taken out around changes to the pack-names list.)
 
122
        # Another way to represent this would be a decorator around the control
 
123
        # files object that presents logical locks as physical ones - if this
 
124
        # gets ugly consider that alternative design. RBC 20071011
 
125
        self._write_lock_count = 0
 
126
        self._transaction = None
 
127
        # for tests
 
128
        self._reconcile_does_inventory_gc = True
 
129
        self._reconcile_fixes_text_parents = True
 
130
        self._reconcile_backsup_inventory = False
 
131
 
 
132
    def _get_source(self, to_format):
 
133
        if to_format.network_name() == self._format.network_name():
 
134
            return KnitPackStreamSource(self, to_format)
 
135
        return PackRepository._get_source(self, to_format)
 
136
 
 
137
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
 
138
        packer = KnitReconcilePacker(collection, packs, extension, revs)
 
139
        return packer.pack(pb)
 
140
 
 
141
 
 
142
class RepositoryFormatKnitPack1(RepositoryFormatPack):
 
143
    """A no-subtrees parameterized Pack repository.
 
144
 
 
145
    This format was introduced in 0.92.
 
146
    """
 
147
 
 
148
    repository_class = KnitPackRepository
 
149
    _commit_builder_class = PackCommitBuilder
 
150
    @property
 
151
    def _serializer(self):
 
152
        return xml5.serializer_v5
 
153
    # What index classes to use
 
154
    index_builder_class = InMemoryGraphIndex
 
155
    index_class = GraphIndex
 
156
 
 
157
    def _get_matching_bzrdir(self):
 
158
        return controldir.format_registry.make_bzrdir('pack-0.92')
 
159
 
 
160
    def _ignore_setting_bzrdir(self, format):
 
161
        pass
 
162
 
 
163
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
164
 
 
165
    @classmethod
 
166
    def get_format_string(cls):
 
167
        """See RepositoryFormat.get_format_string()."""
 
168
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
 
169
 
 
170
    def get_format_description(self):
 
171
        """See RepositoryFormat.get_format_description()."""
 
172
        return "Packs containing knits without subtree support"
 
173
 
 
174
 
 
175
class RepositoryFormatKnitPack3(RepositoryFormatPack):
 
176
    """A subtrees parameterized Pack repository.
 
177
 
 
178
    This repository format uses the xml7 serializer to get:
 
179
     - support for recording full info about the tree root
 
180
     - support for recording tree-references
 
181
 
 
182
    This format was introduced in 0.92.
 
183
    """
 
184
 
 
185
    repository_class = KnitPackRepository
 
186
    _commit_builder_class = PackRootCommitBuilder
 
187
    rich_root_data = True
 
188
    experimental = True
 
189
    supports_tree_reference = True
 
190
    @property
 
191
    def _serializer(self):
 
192
        return xml7.serializer_v7
 
193
    # What index classes to use
 
194
    index_builder_class = InMemoryGraphIndex
 
195
    index_class = GraphIndex
 
196
 
 
197
    def _get_matching_bzrdir(self):
 
198
        return controldir.format_registry.make_bzrdir(
 
199
            'pack-0.92-subtree')
 
200
 
 
201
    def _ignore_setting_bzrdir(self, format):
 
202
        pass
 
203
 
 
204
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
205
 
 
206
    @classmethod
 
207
    def get_format_string(cls):
 
208
        """See RepositoryFormat.get_format_string()."""
 
209
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
 
210
 
 
211
    def get_format_description(self):
 
212
        """See RepositoryFormat.get_format_description()."""
 
213
        return "Packs containing knits with subtree support\n"
 
214
 
 
215
 
 
216
class RepositoryFormatKnitPack4(RepositoryFormatPack):
 
217
    """A rich-root, no subtrees parameterized Pack repository.
 
218
 
 
219
    This repository format uses the xml6 serializer to get:
 
220
     - support for recording full info about the tree root
 
221
 
 
222
    This format was introduced in 1.0.
 
223
    """
 
224
 
 
225
    repository_class = KnitPackRepository
 
226
    _commit_builder_class = PackRootCommitBuilder
 
227
    rich_root_data = True
 
228
    supports_tree_reference = False
 
229
    @property
 
230
    def _serializer(self):
 
231
        return xml6.serializer_v6
 
232
    # What index classes to use
 
233
    index_builder_class = InMemoryGraphIndex
 
234
    index_class = GraphIndex
 
235
 
 
236
    def _get_matching_bzrdir(self):
 
237
        return controldir.format_registry.make_bzrdir(
 
238
            'rich-root-pack')
 
239
 
 
240
    def _ignore_setting_bzrdir(self, format):
 
241
        pass
 
242
 
 
243
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
244
 
 
245
    @classmethod
 
246
    def get_format_string(cls):
 
247
        """See RepositoryFormat.get_format_string()."""
 
248
        return ("Bazaar pack repository format 1 with rich root"
 
249
                " (needs bzr 1.0)\n")
 
250
 
 
251
    def get_format_description(self):
 
252
        """See RepositoryFormat.get_format_description()."""
 
253
        return "Packs containing knits with rich root support\n"
 
254
 
 
255
 
 
256
class RepositoryFormatKnitPack5(RepositoryFormatPack):
 
257
    """Repository that supports external references to allow stacking.
 
258
 
 
259
    New in release 1.6.
 
260
 
 
261
    Supports external lookups, which results in non-truncated ghosts after
 
262
    reconcile compared to pack-0.92 formats.
 
263
    """
 
264
 
 
265
    repository_class = KnitPackRepository
 
266
    _commit_builder_class = PackCommitBuilder
 
267
    supports_external_lookups = True
 
268
    # What index classes to use
 
269
    index_builder_class = InMemoryGraphIndex
 
270
    index_class = GraphIndex
 
271
 
 
272
    @property
 
273
    def _serializer(self):
 
274
        return xml5.serializer_v5
 
275
 
 
276
    def _get_matching_bzrdir(self):
 
277
        return controldir.format_registry.make_bzrdir('1.6')
 
278
 
 
279
    def _ignore_setting_bzrdir(self, format):
 
280
        pass
 
281
 
 
282
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
283
 
 
284
    @classmethod
 
285
    def get_format_string(cls):
 
286
        """See RepositoryFormat.get_format_string()."""
 
287
        return "Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n"
 
288
 
 
289
    def get_format_description(self):
 
290
        """See RepositoryFormat.get_format_description()."""
 
291
        return "Packs 5 (adds stacking support, requires bzr 1.6)"
 
292
 
 
293
 
 
294
class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
 
295
    """A repository with rich roots and stacking.
 
296
 
 
297
    New in release 1.6.1.
 
298
 
 
299
    Supports stacking on other repositories, allowing data to be accessed
 
300
    without being stored locally.
 
301
    """
 
302
 
 
303
    repository_class = KnitPackRepository
 
304
    _commit_builder_class = PackRootCommitBuilder
 
305
    rich_root_data = True
 
306
    supports_tree_reference = False # no subtrees
 
307
    supports_external_lookups = True
 
308
    # What index classes to use
 
309
    index_builder_class = InMemoryGraphIndex
 
310
    index_class = GraphIndex
 
311
 
 
312
    @property
 
313
    def _serializer(self):
 
314
        return xml6.serializer_v6
 
315
 
 
316
    def _get_matching_bzrdir(self):
 
317
        return controldir.format_registry.make_bzrdir(
 
318
            '1.6.1-rich-root')
 
319
 
 
320
    def _ignore_setting_bzrdir(self, format):
 
321
        pass
 
322
 
 
323
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
324
 
 
325
    @classmethod
 
326
    def get_format_string(cls):
 
327
        """See RepositoryFormat.get_format_string()."""
 
328
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n"
 
329
 
 
330
    def get_format_description(self):
 
331
        return "Packs 5 rich-root (adds stacking support, requires bzr 1.6.1)"
 
332
 
 
333
 
 
334
class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):
 
335
    """A repository with rich roots and external references.
 
336
 
 
337
    New in release 1.6.
 
338
 
 
339
    Supports external lookups, which results in non-truncated ghosts after
 
340
    reconcile compared to pack-0.92 formats.
 
341
 
 
342
    This format was deprecated because the serializer it uses accidentally
 
343
    supported subtrees, when the format was not intended to. This meant that
 
344
    someone could accidentally fetch from an incorrect repository.
 
345
    """
 
346
 
 
347
    repository_class = KnitPackRepository
 
348
    _commit_builder_class = PackRootCommitBuilder
 
349
    rich_root_data = True
 
350
    supports_tree_reference = False # no subtrees
 
351
 
 
352
    supports_external_lookups = True
 
353
    # What index classes to use
 
354
    index_builder_class = InMemoryGraphIndex
 
355
    index_class = GraphIndex
 
356
 
 
357
    @property
 
358
    def _serializer(self):
 
359
        return xml7.serializer_v7
 
360
 
 
361
    def _get_matching_bzrdir(self):
 
362
        matching = controldir.format_registry.make_bzrdir(
 
363
            '1.6.1-rich-root')
 
364
        matching.repository_format = self
 
365
        return matching
 
366
 
 
367
    def _ignore_setting_bzrdir(self, format):
 
368
        pass
 
369
 
 
370
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
371
 
 
372
    @classmethod
 
373
    def get_format_string(cls):
 
374
        """See RepositoryFormat.get_format_string()."""
 
375
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n"
 
376
 
 
377
    def get_format_description(self):
 
378
        return ("Packs 5 rich-root (adds stacking support, requires bzr 1.6)"
 
379
                " (deprecated)")
 
380
 
 
381
    def is_deprecated(self):
 
382
        return True
 
383
 
 
384
 
 
385
class RepositoryFormatKnitPack6(RepositoryFormatPack):
 
386
    """A repository with stacking and btree indexes,
 
387
    without rich roots or subtrees.
 
388
 
 
389
    This is equivalent to pack-1.6 with B+Tree indices.
 
390
    """
 
391
 
 
392
    repository_class = KnitPackRepository
 
393
    _commit_builder_class = PackCommitBuilder
 
394
    supports_external_lookups = True
 
395
    # What index classes to use
 
396
    index_builder_class = btree_index.BTreeBuilder
 
397
    index_class = btree_index.BTreeGraphIndex
 
398
 
 
399
    @property
 
400
    def _serializer(self):
 
401
        return xml5.serializer_v5
 
402
 
 
403
    def _get_matching_bzrdir(self):
 
404
        return controldir.format_registry.make_bzrdir('1.9')
 
405
 
 
406
    def _ignore_setting_bzrdir(self, format):
 
407
        pass
 
408
 
 
409
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
410
 
 
411
    @classmethod
 
412
    def get_format_string(cls):
 
413
        """See RepositoryFormat.get_format_string()."""
 
414
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
 
415
 
 
416
    def get_format_description(self):
 
417
        """See RepositoryFormat.get_format_description()."""
 
418
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
 
419
 
 
420
 
 
421
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
 
422
    """A repository with rich roots, no subtrees, stacking and btree indexes.
 
423
 
 
424
    1.6-rich-root with B+Tree indices.
 
425
    """
 
426
 
 
427
    repository_class = KnitPackRepository
 
428
    _commit_builder_class = PackRootCommitBuilder
 
429
    rich_root_data = True
 
430
    supports_tree_reference = False # no subtrees
 
431
    supports_external_lookups = True
 
432
    # What index classes to use
 
433
    index_builder_class = btree_index.BTreeBuilder
 
434
    index_class = btree_index.BTreeGraphIndex
 
435
 
 
436
    @property
 
437
    def _serializer(self):
 
438
        return xml6.serializer_v6
 
439
 
 
440
    def _get_matching_bzrdir(self):
 
441
        return controldir.format_registry.make_bzrdir(
 
442
            '1.9-rich-root')
 
443
 
 
444
    def _ignore_setting_bzrdir(self, format):
 
445
        pass
 
446
 
 
447
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
448
 
 
449
    @classmethod
 
450
    def get_format_string(cls):
 
451
        """See RepositoryFormat.get_format_string()."""
 
452
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
 
453
 
 
454
    def get_format_description(self):
 
455
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
 
456
 
 
457
 
 
458
class RepositoryFormatPackDevelopment2Subtree(RepositoryFormatPack):
 
459
    """A subtrees development repository.
 
460
 
 
461
    This format should be retained in 2.3, to provide an upgrade path from this
 
462
    to RepositoryFormat2aSubtree.  It can be removed in later releases.
 
463
 
 
464
    1.6.1-subtree[as it might have been] with B+Tree indices.
 
465
    """
 
466
 
 
467
    repository_class = KnitPackRepository
 
468
    _commit_builder_class = PackRootCommitBuilder
 
469
    rich_root_data = True
 
470
    experimental = True
 
471
    supports_tree_reference = True
 
472
    supports_external_lookups = True
 
473
    # What index classes to use
 
474
    index_builder_class = btree_index.BTreeBuilder
 
475
    index_class = btree_index.BTreeGraphIndex
 
476
 
 
477
    @property
 
478
    def _serializer(self):
 
479
        return xml7.serializer_v7
 
480
 
 
481
    def _get_matching_bzrdir(self):
 
482
        return controldir.format_registry.make_bzrdir(
 
483
            'development5-subtree')
 
484
 
 
485
    def _ignore_setting_bzrdir(self, format):
 
486
        pass
 
487
 
 
488
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
489
 
 
490
    @classmethod
 
491
    def get_format_string(cls):
 
492
        """See RepositoryFormat.get_format_string()."""
 
493
        return ("Bazaar development format 2 with subtree support "
 
494
            "(needs bzr.dev from before 1.8)\n")
 
495
 
 
496
    def get_format_description(self):
 
497
        """See RepositoryFormat.get_format_description()."""
 
498
        return ("Development repository format, currently the same as "
 
499
            "1.6.1-subtree with B+Tree indices.\n")
 
500
 
 
501
 
 
502
class KnitPackStreamSource(StreamSource):
 
503
    """A StreamSource used to transfer data between same-format KnitPack repos.
 
504
 
 
505
    This source assumes:
 
506
        1) Same serialization format for all objects
 
507
        2) Same root information
 
508
        3) XML format inventories
 
509
        4) Atomic inserts (so we can stream inventory texts before text
 
510
           content)
 
511
        5) No chk_bytes
 
512
    """
 
513
 
 
514
    def __init__(self, from_repository, to_format):
 
515
        super(KnitPackStreamSource, self).__init__(from_repository, to_format)
 
516
        self._text_keys = None
 
517
        self._text_fetch_order = 'unordered'
 
518
 
 
519
    def _get_filtered_inv_stream(self, revision_ids):
 
520
        from_repo = self.from_repository
 
521
        parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
 
522
        parent_keys = [(p,) for p in parent_ids]
 
523
        find_text_keys = from_repo._serializer._find_text_key_references
 
524
        parent_text_keys = set(find_text_keys(
 
525
            from_repo._inventory_xml_lines_for_keys(parent_keys)))
 
526
        content_text_keys = set()
 
527
        knit = KnitVersionedFiles(None, None)
 
528
        factory = KnitPlainFactory()
 
529
        def find_text_keys_from_content(record):
 
530
            if record.storage_kind not in ('knit-delta-gz', 'knit-ft-gz'):
 
531
                raise ValueError("Unknown content storage kind for"
 
532
                    " inventory text: %s" % (record.storage_kind,))
 
533
            # It's a knit record, it has a _raw_record field (even if it was
 
534
            # reconstituted from a network stream).
 
535
            raw_data = record._raw_record
 
536
            # read the entire thing
 
537
            revision_id = record.key[-1]
 
538
            content, _ = knit._parse_record(revision_id, raw_data)
 
539
            if record.storage_kind == 'knit-delta-gz':
 
540
                line_iterator = factory.get_linedelta_content(content)
 
541
            elif record.storage_kind == 'knit-ft-gz':
 
542
                line_iterator = factory.get_fulltext_content(content)
 
543
            content_text_keys.update(find_text_keys(
 
544
                [(line, revision_id) for line in line_iterator]))
 
545
        revision_keys = [(r,) for r in revision_ids]
 
546
        def _filtered_inv_stream():
 
547
            source_vf = from_repo.inventories
 
548
            stream = source_vf.get_record_stream(revision_keys,
 
549
                                                 'unordered', False)
 
550
            for record in stream:
 
551
                if record.storage_kind == 'absent':
 
552
                    raise errors.NoSuchRevision(from_repo, record.key)
 
553
                find_text_keys_from_content(record)
 
554
                yield record
 
555
            self._text_keys = content_text_keys - parent_text_keys
 
556
        return ('inventories', _filtered_inv_stream())
 
557
 
 
558
    def _get_text_stream(self):
 
559
        # Note: We know we don't have to handle adding root keys, because both
 
560
        # the source and target are the identical network name.
 
561
        text_stream = self.from_repository.texts.get_record_stream(
 
562
                        self._text_keys, self._text_fetch_order, False)
 
563
        return ('texts', text_stream)
 
564
 
 
565
    def get_stream(self, search):
 
566
        revision_ids = search.get_keys()
 
567
        for stream_info in self._fetch_revision_texts(revision_ids):
 
568
            yield stream_info
 
569
        self._revision_keys = [(rev_id,) for rev_id in revision_ids]
 
570
        yield self._get_filtered_inv_stream(revision_ids)
 
571
        yield self._get_text_stream()
 
572
 
 
573
 
 
574
class KnitPacker(Packer):
 
575
    """Packer that works with knit packs."""
 
576
 
 
577
    def __init__(self, pack_collection, packs, suffix, revision_ids=None,
 
578
                 reload_func=None):
 
579
        super(KnitPacker, self).__init__(pack_collection, packs, suffix,
 
580
                                          revision_ids=revision_ids,
 
581
                                          reload_func=reload_func)
 
582
 
 
583
    def _pack_map_and_index_list(self, index_attribute):
 
584
        """Convert a list of packs to an index pack map and index list.
 
585
 
 
586
        :param index_attribute: The attribute that the desired index is found
 
587
            on.
 
588
        :return: A tuple (map, list) where map contains the dict from
 
589
            index:pack_tuple, and list contains the indices in the preferred
 
590
            access order.
 
591
        """
 
592
        indices = []
 
593
        pack_map = {}
 
594
        for pack_obj in self.packs:
 
595
            index = getattr(pack_obj, index_attribute)
 
596
            indices.append(index)
 
597
            pack_map[index] = pack_obj
 
598
        return pack_map, indices
 
599
 
 
600
    def _index_contents(self, indices, key_filter=None):
 
601
        """Get an iterable of the index contents from a pack_map.
 
602
 
 
603
        :param indices: The list of indices to query
 
604
        :param key_filter: An optional filter to limit the keys returned.
 
605
        """
 
606
        all_index = CombinedGraphIndex(indices)
 
607
        if key_filter is None:
 
608
            return all_index.iter_all_entries()
 
609
        else:
 
610
            return all_index.iter_entries(key_filter)
 
611
 
 
612
    def _copy_nodes(self, nodes, index_map, writer, write_index,
 
613
        output_lines=None):
 
614
        """Copy knit nodes between packs with no graph references.
 
615
 
 
616
        :param output_lines: Output full texts of copied items.
 
617
        """
 
618
        pb = ui.ui_factory.nested_progress_bar()
 
619
        try:
 
620
            return self._do_copy_nodes(nodes, index_map, writer,
 
621
                write_index, pb, output_lines=output_lines)
 
622
        finally:
 
623
            pb.finished()
 
624
 
 
625
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb,
 
626
        output_lines=None):
 
627
        # for record verification
 
628
        knit = KnitVersionedFiles(None, None)
 
629
        # plan a readv on each source pack:
 
630
        # group by pack
 
631
        nodes = sorted(nodes)
 
632
        # how to map this into knit.py - or knit.py into this?
 
633
        # we don't want the typical knit logic, we want grouping by pack
 
634
        # at this point - perhaps a helper library for the following code
 
635
        # duplication points?
 
636
        request_groups = {}
 
637
        for index, key, value in nodes:
 
638
            if index not in request_groups:
 
639
                request_groups[index] = []
 
640
            request_groups[index].append((key, value))
 
641
        record_index = 0
 
642
        pb.update("Copied record", record_index, len(nodes))
 
643
        for index, items in request_groups.iteritems():
 
644
            pack_readv_requests = []
 
645
            for key, value in items:
 
646
                # ---- KnitGraphIndex.get_position
 
647
                bits = value[1:].split(' ')
 
648
                offset, length = int(bits[0]), int(bits[1])
 
649
                pack_readv_requests.append((offset, length, (key, value[0])))
 
650
            # linear scan up the pack
 
651
            pack_readv_requests.sort()
 
652
            # copy the data
 
653
            pack_obj = index_map[index]
 
654
            transport, path = pack_obj.access_tuple()
 
655
            try:
 
656
                reader = pack.make_readv_reader(transport, path,
 
657
                    [offset[0:2] for offset in pack_readv_requests])
 
658
            except errors.NoSuchFile:
 
659
                if self._reload_func is not None:
 
660
                    self._reload_func()
 
661
                raise
 
662
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
 
663
                izip(reader.iter_records(), pack_readv_requests):
 
664
                raw_data = read_func(None)
 
665
                # check the header only
 
666
                if output_lines is not None:
 
667
                    output_lines(knit._parse_record(key[-1], raw_data)[0])
 
668
                else:
 
669
                    df, _ = knit._parse_record_header(key, raw_data)
 
670
                    df.close()
 
671
                pos, size = writer.add_bytes_record(raw_data, names)
 
672
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
 
673
                pb.update("Copied record", record_index)
 
674
                record_index += 1
 
675
 
 
676
    def _copy_nodes_graph(self, index_map, writer, write_index,
 
677
        readv_group_iter, total_items, output_lines=False):
 
678
        """Copy knit nodes between packs.
 
679
 
 
680
        :param output_lines: Return lines present in the copied data as
 
681
            an iterator of line,version_id.
 
682
        """
 
683
        pb = ui.ui_factory.nested_progress_bar()
 
684
        try:
 
685
            for result in self._do_copy_nodes_graph(index_map, writer,
 
686
                write_index, output_lines, pb, readv_group_iter, total_items):
 
687
                yield result
 
688
        except Exception:
 
689
            # Python 2.4 does not permit try:finally: in a generator.
 
690
            pb.finished()
 
691
            raise
 
692
        else:
 
693
            pb.finished()
 
694
 
 
695
    def _do_copy_nodes_graph(self, index_map, writer, write_index,
 
696
        output_lines, pb, readv_group_iter, total_items):
 
697
        # for record verification
 
698
        knit = KnitVersionedFiles(None, None)
 
699
        # for line extraction when requested (inventories only)
 
700
        if output_lines:
 
701
            factory = KnitPlainFactory()
 
702
        record_index = 0
 
703
        pb.update("Copied record", record_index, total_items)
 
704
        for index, readv_vector, node_vector in readv_group_iter:
 
705
            # copy the data
 
706
            pack_obj = index_map[index]
 
707
            transport, path = pack_obj.access_tuple()
 
708
            try:
 
709
                reader = pack.make_readv_reader(transport, path, readv_vector)
 
710
            except errors.NoSuchFile:
 
711
                if self._reload_func is not None:
 
712
                    self._reload_func()
 
713
                raise
 
714
            for (names, read_func), (key, eol_flag, references) in \
 
715
                izip(reader.iter_records(), node_vector):
 
716
                raw_data = read_func(None)
 
717
                if output_lines:
 
718
                    # read the entire thing
 
719
                    content, _ = knit._parse_record(key[-1], raw_data)
 
720
                    if len(references[-1]) == 0:
 
721
                        line_iterator = factory.get_fulltext_content(content)
 
722
                    else:
 
723
                        line_iterator = factory.get_linedelta_content(content)
 
724
                    for line in line_iterator:
 
725
                        yield line, key
 
726
                else:
 
727
                    # check the header only
 
728
                    df, _ = knit._parse_record_header(key, raw_data)
 
729
                    df.close()
 
730
                pos, size = writer.add_bytes_record(raw_data, names)
 
731
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size), references)
 
732
                pb.update("Copied record", record_index)
 
733
                record_index += 1
 
734
 
 
735
    def _process_inventory_lines(self, inv_lines):
 
736
        """Use up the inv_lines generator and setup a text key filter."""
 
737
        repo = self._pack_collection.repo
 
738
        fileid_revisions = repo._find_file_ids_from_xml_inventory_lines(
 
739
            inv_lines, self.revision_keys)
 
740
        text_filter = []
 
741
        for fileid, file_revids in fileid_revisions.iteritems():
 
742
            text_filter.extend([(fileid, file_revid) for file_revid in file_revids])
 
743
        self._text_filter = text_filter
 
744
 
 
745
    def _copy_inventory_texts(self):
 
746
        # select inventory keys
 
747
        inv_keys = self._revision_keys # currently the same keyspace, and note that
 
748
        # querying for keys here could introduce a bug where an inventory item
 
749
        # is missed, so do not change it to query separately without cross
 
750
        # checking like the text key check below.
 
751
        inventory_index_map, inventory_indices = self._pack_map_and_index_list(
 
752
            'inventory_index')
 
753
        inv_nodes = self._index_contents(inventory_indices, inv_keys)
 
754
        # copy inventory keys and adjust values
 
755
        # XXX: Should be a helper function to allow different inv representation
 
756
        # at this point.
 
757
        self.pb.update("Copying inventory texts", 2)
 
758
        total_items, readv_group_iter = self._least_readv_node_readv(inv_nodes)
 
759
        # Only grab the output lines if we will be processing them
 
760
        output_lines = bool(self.revision_ids)
 
761
        inv_lines = self._copy_nodes_graph(inventory_index_map,
 
762
            self.new_pack._writer, self.new_pack.inventory_index,
 
763
            readv_group_iter, total_items, output_lines=output_lines)
 
764
        if self.revision_ids:
 
765
            self._process_inventory_lines(inv_lines)
 
766
        else:
 
767
            # eat the iterator to cause it to execute.
 
768
            list(inv_lines)
 
769
            self._text_filter = None
 
770
        if 'pack' in debug.debug_flags:
 
771
            trace.mutter('%s: create_pack: inventories copied: %s%s %d items t+%6.3fs',
 
772
                time.ctime(), self._pack_collection._upload_transport.base,
 
773
                self.new_pack.random_name,
 
774
                self.new_pack.inventory_index.key_count(),
 
775
                time.time() - self.new_pack.start_time)
 
776
 
 
777
    def _update_pack_order(self, entries, index_to_pack_map):
 
778
        """Determine how we want our packs to be ordered.
 
779
 
 
780
        This changes the sort order of the self.packs list so that packs unused
 
781
        by 'entries' will be at the end of the list, so that future requests
 
782
        can avoid probing them.  Used packs will be at the front of the
 
783
        self.packs list, in the order of their first use in 'entries'.
 
784
 
 
785
        :param entries: A list of (index, ...) tuples
 
786
        :param index_to_pack_map: A mapping from index objects to pack objects.
 
787
        """
 
788
        packs = []
 
789
        seen_indexes = set()
 
790
        for entry in entries:
 
791
            index = entry[0]
 
792
            if index not in seen_indexes:
 
793
                packs.append(index_to_pack_map[index])
 
794
                seen_indexes.add(index)
 
795
        if len(packs) == len(self.packs):
 
796
            if 'pack' in debug.debug_flags:
 
797
                trace.mutter('Not changing pack list, all packs used.')
 
798
            return
 
799
        seen_packs = set(packs)
 
800
        for pack in self.packs:
 
801
            if pack not in seen_packs:
 
802
                packs.append(pack)
 
803
                seen_packs.add(pack)
 
804
        if 'pack' in debug.debug_flags:
 
805
            old_names = [p.access_tuple()[1] for p in self.packs]
 
806
            new_names = [p.access_tuple()[1] for p in packs]
 
807
            trace.mutter('Reordering packs\nfrom: %s\n  to: %s',
 
808
                   old_names, new_names)
 
809
        self.packs = packs
 
810
 
 
811
    def _copy_revision_texts(self):
 
812
        # select revisions
 
813
        if self.revision_ids:
 
814
            revision_keys = [(revision_id,) for revision_id in self.revision_ids]
 
815
        else:
 
816
            revision_keys = None
 
817
        # select revision keys
 
818
        revision_index_map, revision_indices = self._pack_map_and_index_list(
 
819
            'revision_index')
 
820
        revision_nodes = self._index_contents(revision_indices, revision_keys)
 
821
        revision_nodes = list(revision_nodes)
 
822
        self._update_pack_order(revision_nodes, revision_index_map)
 
823
        # copy revision keys and adjust values
 
824
        self.pb.update("Copying revision texts", 1)
 
825
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
 
826
        list(self._copy_nodes_graph(revision_index_map, self.new_pack._writer,
 
827
            self.new_pack.revision_index, readv_group_iter, total_items))
 
828
        if 'pack' in debug.debug_flags:
 
829
            trace.mutter('%s: create_pack: revisions copied: %s%s %d items t+%6.3fs',
 
830
                time.ctime(), self._pack_collection._upload_transport.base,
 
831
                self.new_pack.random_name,
 
832
                self.new_pack.revision_index.key_count(),
 
833
                time.time() - self.new_pack.start_time)
 
834
        self._revision_keys = revision_keys
 
835
 
 
836
    def _get_text_nodes(self):
 
837
        text_index_map, text_indices = self._pack_map_and_index_list(
 
838
            'text_index')
 
839
        return text_index_map, self._index_contents(text_indices,
 
840
            self._text_filter)
 
841
 
 
842
    def _copy_text_texts(self):
 
843
        # select text keys
 
844
        text_index_map, text_nodes = self._get_text_nodes()
 
845
        if self._text_filter is not None:
 
846
            # We could return the keys copied as part of the return value from
 
847
            # _copy_nodes_graph but this doesn't work all that well with the
 
848
            # need to get line output too, so we check separately, and as we're
 
849
            # going to buffer everything anyway, we check beforehand, which
 
850
            # saves reading knit data over the wire when we know there are
 
851
            # mising records.
 
852
            text_nodes = set(text_nodes)
 
853
            present_text_keys = set(_node[1] for _node in text_nodes)
 
854
            missing_text_keys = set(self._text_filter) - present_text_keys
 
855
            if missing_text_keys:
 
856
                # TODO: raise a specific error that can handle many missing
 
857
                # keys.
 
858
                trace.mutter("missing keys during fetch: %r", missing_text_keys)
 
859
                a_missing_key = missing_text_keys.pop()
 
860
                raise errors.RevisionNotPresent(a_missing_key[1],
 
861
                    a_missing_key[0])
 
862
        # copy text keys and adjust values
 
863
        self.pb.update("Copying content texts", 3)
 
864
        total_items, readv_group_iter = self._least_readv_node_readv(text_nodes)
 
865
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
866
            self.new_pack.text_index, readv_group_iter, total_items))
 
867
        self._log_copied_texts()
 
868
 
 
869
    def _create_pack_from_packs(self):
 
870
        self.pb.update("Opening pack", 0, 5)
 
871
        self.new_pack = self.open_pack()
 
872
        new_pack = self.new_pack
 
873
        # buffer data - we won't be reading-back during the pack creation and
 
874
        # this makes a significant difference on sftp pushes.
 
875
        new_pack.set_write_cache_size(1024*1024)
 
876
        if 'pack' in debug.debug_flags:
 
877
            plain_pack_list = ['%s%s' % (a_pack.pack_transport.base, a_pack.name)
 
878
                for a_pack in self.packs]
 
879
            if self.revision_ids is not None:
 
880
                rev_count = len(self.revision_ids)
 
881
            else:
 
882
                rev_count = 'all'
 
883
            trace.mutter('%s: create_pack: creating pack from source packs: '
 
884
                '%s%s %s revisions wanted %s t=0',
 
885
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
886
                plain_pack_list, rev_count)
 
887
        self._copy_revision_texts()
 
888
        self._copy_inventory_texts()
 
889
        self._copy_text_texts()
 
890
        # select signature keys
 
891
        signature_filter = self._revision_keys # same keyspace
 
892
        signature_index_map, signature_indices = self._pack_map_and_index_list(
 
893
            'signature_index')
 
894
        signature_nodes = self._index_contents(signature_indices,
 
895
            signature_filter)
 
896
        # copy signature keys and adjust values
 
897
        self.pb.update("Copying signature texts", 4)
 
898
        self._copy_nodes(signature_nodes, signature_index_map, new_pack._writer,
 
899
            new_pack.signature_index)
 
900
        if 'pack' in debug.debug_flags:
 
901
            trace.mutter('%s: create_pack: revision signatures copied: %s%s %d items t+%6.3fs',
 
902
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
903
                new_pack.signature_index.key_count(),
 
904
                time.time() - new_pack.start_time)
 
905
        new_pack._check_references()
 
906
        if not self._use_pack(new_pack):
 
907
            new_pack.abort()
 
908
            return None
 
909
        self.pb.update("Finishing pack", 5)
 
910
        new_pack.finish()
 
911
        self._pack_collection.allocate(new_pack)
 
912
        return new_pack
 
913
 
 
914
    def _least_readv_node_readv(self, nodes):
 
915
        """Generate request groups for nodes using the least readv's.
 
916
 
 
917
        :param nodes: An iterable of graph index nodes.
 
918
        :return: Total node count and an iterator of the data needed to perform
 
919
            readvs to obtain the data for nodes. Each item yielded by the
 
920
            iterator is a tuple with:
 
921
            index, readv_vector, node_vector. readv_vector is a list ready to
 
922
            hand to the transport readv method, and node_vector is a list of
 
923
            (key, eol_flag, references) for the node retrieved by the
 
924
            matching readv_vector.
 
925
        """
 
926
        # group by pack so we do one readv per pack
 
927
        nodes = sorted(nodes)
 
928
        total = len(nodes)
 
929
        request_groups = {}
 
930
        for index, key, value, references in nodes:
 
931
            if index not in request_groups:
 
932
                request_groups[index] = []
 
933
            request_groups[index].append((key, value, references))
 
934
        result = []
 
935
        for index, items in request_groups.iteritems():
 
936
            pack_readv_requests = []
 
937
            for key, value, references in items:
 
938
                # ---- KnitGraphIndex.get_position
 
939
                bits = value[1:].split(' ')
 
940
                offset, length = int(bits[0]), int(bits[1])
 
941
                pack_readv_requests.append(
 
942
                    ((offset, length), (key, value[0], references)))
 
943
            # linear scan up the pack to maximum range combining.
 
944
            pack_readv_requests.sort()
 
945
            # split out the readv and the node data.
 
946
            pack_readv = [readv for readv, node in pack_readv_requests]
 
947
            node_vector = [node for readv, node in pack_readv_requests]
 
948
            result.append((index, pack_readv, node_vector))
 
949
        return total, result
 
950
 
 
951
    def _revision_node_readv(self, revision_nodes):
 
952
        """Return the total revisions and the readv's to issue.
 
953
 
 
954
        :param revision_nodes: The revision index contents for the packs being
 
955
            incorporated into the new pack.
 
956
        :return: As per _least_readv_node_readv.
 
957
        """
 
958
        return self._least_readv_node_readv(revision_nodes)
 
959
 
 
960
 
 
961
class KnitReconcilePacker(KnitPacker):
 
962
    """A packer which regenerates indices etc as it copies.
 
963
 
 
964
    This is used by ``bzr reconcile`` to cause parent text pointers to be
 
965
    regenerated.
 
966
    """
 
967
 
 
968
    def __init__(self, *args, **kwargs):
 
969
        super(KnitReconcilePacker, self).__init__(*args, **kwargs)
 
970
        self._data_changed = False
 
971
 
 
972
    def _process_inventory_lines(self, inv_lines):
 
973
        """Generate a text key reference map rather for reconciling with."""
 
974
        repo = self._pack_collection.repo
 
975
        refs = repo._serializer._find_text_key_references(inv_lines)
 
976
        self._text_refs = refs
 
977
        # during reconcile we:
 
978
        #  - convert unreferenced texts to full texts
 
979
        #  - correct texts which reference a text not copied to be full texts
 
980
        #  - copy all others as-is but with corrected parents.
 
981
        #  - so at this point we don't know enough to decide what becomes a full
 
982
        #    text.
 
983
        self._text_filter = None
 
984
 
 
985
    def _copy_text_texts(self):
 
986
        """generate what texts we should have and then copy."""
 
987
        self.pb.update("Copying content texts", 3)
 
988
        # we have three major tasks here:
 
989
        # 1) generate the ideal index
 
990
        repo = self._pack_collection.repo
 
991
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
 
992
            _1, key, _2, refs in
 
993
            self.new_pack.revision_index.iter_all_entries()])
 
994
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
 
995
        # 2) generate a text_nodes list that contains all the deltas that can
 
996
        #    be used as-is, with corrected parents.
 
997
        ok_nodes = []
 
998
        bad_texts = []
 
999
        discarded_nodes = []
 
1000
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1001
        text_index_map, text_nodes = self._get_text_nodes()
 
1002
        for node in text_nodes:
 
1003
            # 0 - index
 
1004
            # 1 - key
 
1005
            # 2 - value
 
1006
            # 3 - refs
 
1007
            try:
 
1008
                ideal_parents = tuple(ideal_index[node[1]])
 
1009
            except KeyError:
 
1010
                discarded_nodes.append(node)
 
1011
                self._data_changed = True
 
1012
            else:
 
1013
                if ideal_parents == (NULL_REVISION,):
 
1014
                    ideal_parents = ()
 
1015
                if ideal_parents == node[3][0]:
 
1016
                    # no change needed.
 
1017
                    ok_nodes.append(node)
 
1018
                elif ideal_parents[0:1] == node[3][0][0:1]:
 
1019
                    # the left most parent is the same, or there are no parents
 
1020
                    # today. Either way, we can preserve the representation as
 
1021
                    # long as we change the refs to be inserted.
 
1022
                    self._data_changed = True
 
1023
                    ok_nodes.append((node[0], node[1], node[2],
 
1024
                        (ideal_parents, node[3][1])))
 
1025
                    self._data_changed = True
 
1026
                else:
 
1027
                    # Reinsert this text completely
 
1028
                    bad_texts.append((node[1], ideal_parents))
 
1029
                    self._data_changed = True
 
1030
        # we're finished with some data.
 
1031
        del ideal_index
 
1032
        del text_nodes
 
1033
        # 3) bulk copy the ok data
 
1034
        total_items, readv_group_iter = self._least_readv_node_readv(ok_nodes)
 
1035
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
1036
            self.new_pack.text_index, readv_group_iter, total_items))
 
1037
        # 4) adhoc copy all the other texts.
 
1038
        # We have to topologically insert all texts otherwise we can fail to
 
1039
        # reconcile when parts of a single delta chain are preserved intact,
 
1040
        # and other parts are not. E.g. Discarded->d1->d2->d3. d1 will be
 
1041
        # reinserted, and if d3 has incorrect parents it will also be
 
1042
        # reinserted. If we insert d3 first, d2 is present (as it was bulk
 
1043
        # copied), so we will try to delta, but d2 is not currently able to be
 
1044
        # extracted because its basis d1 is not present. Topologically sorting
 
1045
        # addresses this. The following generates a sort for all the texts that
 
1046
        # are being inserted without having to reference the entire text key
 
1047
        # space (we only topo sort the revisions, which is smaller).
 
1048
        topo_order = tsort.topo_sort(ancestors)
 
1049
        rev_order = dict(zip(topo_order, range(len(topo_order))))
 
1050
        bad_texts.sort(key=lambda key:rev_order.get(key[0][1], 0))
 
1051
        transaction = repo.get_transaction()
 
1052
        file_id_index = GraphIndexPrefixAdapter(
 
1053
            self.new_pack.text_index,
 
1054
            ('blank', ), 1,
 
1055
            add_nodes_callback=self.new_pack.text_index.add_nodes)
 
1056
        data_access = _DirectPackAccess(
 
1057
                {self.new_pack.text_index:self.new_pack.access_tuple()})
 
1058
        data_access.set_writer(self.new_pack._writer, self.new_pack.text_index,
 
1059
            self.new_pack.access_tuple())
 
1060
        output_texts = KnitVersionedFiles(
 
1061
            _KnitGraphIndex(self.new_pack.text_index,
 
1062
                add_callback=self.new_pack.text_index.add_nodes,
 
1063
                deltas=True, parents=True, is_locked=repo.is_locked),
 
1064
            data_access=data_access, max_delta_chain=200)
 
1065
        for key, parent_keys in bad_texts:
 
1066
            # We refer to the new pack to delta data being output.
 
1067
            # A possible improvement would be to catch errors on short reads
 
1068
            # and only flush then.
 
1069
            self.new_pack.flush()
 
1070
            parents = []
 
1071
            for parent_key in parent_keys:
 
1072
                if parent_key[0] != key[0]:
 
1073
                    # Graph parents must match the fileid
 
1074
                    raise errors.BzrError('Mismatched key parent %r:%r' %
 
1075
                        (key, parent_keys))
 
1076
                parents.append(parent_key[1])
 
1077
            text_lines = osutils.split_lines(repo.texts.get_record_stream(
 
1078
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
 
1079
            output_texts.add_lines(key, parent_keys, text_lines,
 
1080
                random_id=True, check_content=False)
 
1081
        # 5) check that nothing inserted has a reference outside the keyspace.
 
1082
        missing_text_keys = self.new_pack.text_index._external_references()
 
1083
        if missing_text_keys:
 
1084
            raise errors.BzrCheckError('Reference to missing compression parents %r'
 
1085
                % (missing_text_keys,))
 
1086
        self._log_copied_texts()
 
1087
 
 
1088
    def _use_pack(self, new_pack):
 
1089
        """Override _use_pack to check for reconcile having changed content."""
 
1090
        # XXX: we might be better checking this at the copy time.
 
1091
        original_inventory_keys = set()
 
1092
        inv_index = self._pack_collection.inventory_index.combined_index
 
1093
        for entry in inv_index.iter_all_entries():
 
1094
            original_inventory_keys.add(entry[1])
 
1095
        new_inventory_keys = set()
 
1096
        for entry in new_pack.inventory_index.iter_all_entries():
 
1097
            new_inventory_keys.add(entry[1])
 
1098
        if new_inventory_keys != original_inventory_keys:
 
1099
            self._data_changed = True
 
1100
        return new_pack.data_inserted() and self._data_changed
 
1101
 
 
1102
 
 
1103
class OptimisingKnitPacker(KnitPacker):
 
1104
    """A packer which spends more time to create better disk layouts."""
 
1105
 
 
1106
    def _revision_node_readv(self, revision_nodes):
 
1107
        """Return the total revisions and the readv's to issue.
 
1108
 
 
1109
        This sort places revisions in topological order with the ancestors
 
1110
        after the children.
 
1111
 
 
1112
        :param revision_nodes: The revision index contents for the packs being
 
1113
            incorporated into the new pack.
 
1114
        :return: As per _least_readv_node_readv.
 
1115
        """
 
1116
        # build an ancestors dict
 
1117
        ancestors = {}
 
1118
        by_key = {}
 
1119
        for index, key, value, references in revision_nodes:
 
1120
            ancestors[key] = references[0]
 
1121
            by_key[key] = (index, value, references)
 
1122
        order = tsort.topo_sort(ancestors)
 
1123
        total = len(order)
 
1124
        # Single IO is pathological, but it will work as a starting point.
 
1125
        requests = []
 
1126
        for key in reversed(order):
 
1127
            index, value, references = by_key[key]
 
1128
            # ---- KnitGraphIndex.get_position
 
1129
            bits = value[1:].split(' ')
 
1130
            offset, length = int(bits[0]), int(bits[1])
 
1131
            requests.append(
 
1132
                (index, [(offset, length)], [(key, value[0], references)]))
 
1133
        # TODO: combine requests in the same index that are in ascending order.
 
1134
        return total, requests
 
1135
 
 
1136
    def open_pack(self):
 
1137
        """Open a pack for the pack we are creating."""
 
1138
        new_pack = super(OptimisingKnitPacker, self).open_pack()
 
1139
        # Turn on the optimization flags for all the index builders.
 
1140
        new_pack.revision_index.set_optimize(for_size=True)
 
1141
        new_pack.inventory_index.set_optimize(for_size=True)
 
1142
        new_pack.text_index.set_optimize(for_size=True)
 
1143
        new_pack.signature_index.set_optimize(for_size=True)
 
1144
        return new_pack
 
1145
 
 
1146
 
 
1147
class KnitRepositoryPackCollection(RepositoryPackCollection):
 
1148
    """A knit pack collection."""
 
1149
 
 
1150
    pack_factory = NewPack
 
1151
    resumed_pack_factory = ResumedPack
 
1152
    normal_packer_class = KnitPacker
 
1153
    optimising_packer_class = OptimisingKnitPacker
 
1154
 
 
1155
 
 
1156