~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitpack_repo.py

  • Committer: John Arbash Meinel
  • Date: 2011-07-18 14:22:20 UTC
  • mto: This revision was merged to the branch mainline in revision 6033.
  • Revision ID: john@arbash-meinel.com-20110718142220-nwylw659oip1ene9
Start at least testing the package_branch regex.
And start testing the is-up-to-date logic.

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