~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Andrew Bennetts
  • Date: 2008-07-03 07:56:02 UTC
  • mto: This revision was merged to the branch mainline in revision 3520.
  • Revision ID: andrew.bennetts@canonical.com-20080703075602-8n055qsfkjijcz6i
Better tests for {pre,post}_change_branch_tip hooks.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
import re
18
 
import sys
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
16
 
20
17
from bzrlib.lazy_import import lazy_import
21
18
lazy_import(globals(), """
22
19
from itertools import izip
 
20
import math
 
21
import md5
23
22
import time
24
23
 
25
24
from bzrlib import (
26
 
    chk_map,
27
 
    cleanup,
28
 
    debug,
29
 
    graph,
30
 
    osutils,
31
 
    pack,
32
 
    transactions,
33
 
    tsort,
34
 
    ui,
35
 
    )
 
25
        debug,
 
26
        graph,
 
27
        pack,
 
28
        ui,
 
29
        )
36
30
from bzrlib.index import (
 
31
    GraphIndex,
 
32
    GraphIndexBuilder,
 
33
    InMemoryGraphIndex,
37
34
    CombinedGraphIndex,
38
35
    GraphIndexPrefixAdapter,
39
36
    )
 
37
from bzrlib.knit import (
 
38
    KnitPlainFactory,
 
39
    KnitVersionedFiles,
 
40
    _KnitGraphIndex,
 
41
    _DirectPackAccess,
 
42
    )
 
43
from bzrlib.osutils import rand_chars, split_lines
 
44
from bzrlib.pack import ContainerWriter
 
45
from bzrlib.store import revision
 
46
from bzrlib import tsort
40
47
""")
41
48
from bzrlib import (
42
 
    btree_index,
 
49
    bzrdir,
43
50
    errors,
44
51
    lockable_files,
45
52
    lockdir,
 
53
    osutils,
 
54
    symbol_versioning,
 
55
    transactions,
 
56
    xml5,
 
57
    xml6,
 
58
    xml7,
46
59
    )
47
60
 
48
 
from bzrlib.decorators import (
49
 
    needs_read_lock,
50
 
    needs_write_lock,
51
 
    only_raises,
52
 
    )
53
 
from bzrlib.lock import LogicalLockResult
 
61
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
62
from bzrlib.repofmt.knitrepo import KnitRepository
54
63
from bzrlib.repository import (
55
64
    CommitBuilder,
56
65
    MetaDirRepository,
57
66
    MetaDirRepositoryFormat,
58
67
    RepositoryFormat,
59
 
    RepositoryWriteLockResult,
60
68
    RootCommitBuilder,
61
69
    )
 
70
import bzrlib.revision as _mod_revision
 
71
from bzrlib.store.versioned import VersionedFileStore
62
72
from bzrlib.trace import (
63
73
    mutter,
 
74
    mutter_callsite,
64
75
    note,
65
76
    warning,
66
77
    )
68
79
 
69
80
class PackCommitBuilder(CommitBuilder):
70
81
    """A subclass of CommitBuilder to add texts with pack semantics.
71
 
 
 
82
    
72
83
    Specifically this uses one knit object rather than one knit object per
73
84
    added text, reducing memory and object pressure.
74
85
    """
75
86
 
76
87
    def __init__(self, repository, parents, config, timestamp=None,
77
88
                 timezone=None, committer=None, revprops=None,
78
 
                 revision_id=None, lossy=False):
 
89
                 revision_id=None):
79
90
        CommitBuilder.__init__(self, repository, parents, config,
80
91
            timestamp=timestamp, timezone=timezone, committer=committer,
81
 
            revprops=revprops, revision_id=revision_id, lossy=lossy)
 
92
            revprops=revprops, revision_id=revision_id)
82
93
        self._file_graph = graph.Graph(
83
94
            repository._pack_collection.text_index.combined_index)
84
95
 
89
100
 
90
101
class PackRootCommitBuilder(RootCommitBuilder):
91
102
    """A subclass of RootCommitBuilder to add texts with pack semantics.
92
 
 
 
103
    
93
104
    Specifically this uses one knit object rather than one knit object per
94
105
    added text, reducing memory and object pressure.
95
106
    """
96
107
 
97
108
    def __init__(self, repository, parents, config, timestamp=None,
98
109
                 timezone=None, committer=None, revprops=None,
99
 
                 revision_id=None, lossy=False):
 
110
                 revision_id=None):
100
111
        CommitBuilder.__init__(self, repository, parents, config,
101
112
            timestamp=timestamp, timezone=timezone, committer=committer,
102
 
            revprops=revprops, revision_id=revision_id, lossy=lossy)
 
113
            revprops=revprops, revision_id=revision_id)
103
114
        self._file_graph = graph.Graph(
104
115
            repository._pack_collection.text_index.combined_index)
105
116
 
115
126
    ExistingPack and NewPack are used.
116
127
    """
117
128
 
118
 
    # A map of index 'type' to the file extension and position in the
119
 
    # index_sizes array.
120
 
    index_definitions = {
121
 
        'chk': ('.cix', 4),
122
 
        'revision': ('.rix', 0),
123
 
        'inventory': ('.iix', 1),
124
 
        'text': ('.tix', 2),
125
 
        'signature': ('.six', 3),
126
 
        }
127
 
 
128
129
    def __init__(self, revision_index, inventory_index, text_index,
129
 
        signature_index, chk_index=None):
 
130
        signature_index):
130
131
        """Create a pack instance.
131
132
 
132
133
        :param revision_index: A GraphIndex for determining what revisions are
137
138
        :param text_index: A GraphIndex for determining what file texts
138
139
            are present in the pack and accessing the locations of their
139
140
            texts/deltas (via (fileid, revisionid) tuples).
140
 
        :param signature_index: A GraphIndex for determining what signatures are
 
141
        :param revision_index: A GraphIndex for determining what signatures are
141
142
            present in the Pack and accessing the locations of their texts.
142
 
        :param chk_index: A GraphIndex for accessing content by CHK, if the
143
 
            pack has one.
144
143
        """
145
144
        self.revision_index = revision_index
146
145
        self.inventory_index = inventory_index
147
146
        self.text_index = text_index
148
147
        self.signature_index = signature_index
149
 
        self.chk_index = chk_index
150
148
 
151
149
    def access_tuple(self):
152
150
        """Return a tuple (transport, name) for the pack content."""
153
151
        return self.pack_transport, self.file_name()
154
152
 
155
 
    def _check_references(self):
156
 
        """Make sure our external references are present.
157
 
 
158
 
        Packs are allowed to have deltas whose base is not in the pack, but it
159
 
        must be present somewhere in this collection.  It is not allowed to
160
 
        have deltas based on a fallback repository.
161
 
        (See <https://bugs.launchpad.net/bzr/+bug/288751>)
162
 
        """
163
 
        missing_items = {}
164
 
        for (index_name, external_refs, index) in [
165
 
            ('texts',
166
 
                self._get_external_refs(self.text_index),
167
 
                self._pack_collection.text_index.combined_index),
168
 
            ('inventories',
169
 
                self._get_external_refs(self.inventory_index),
170
 
                self._pack_collection.inventory_index.combined_index),
171
 
            ]:
172
 
            missing = external_refs.difference(
173
 
                k for (idx, k, v, r) in
174
 
                index.iter_entries(external_refs))
175
 
            if missing:
176
 
                missing_items[index_name] = sorted(list(missing))
177
 
        if missing_items:
178
 
            from pprint import pformat
179
 
            raise errors.BzrCheckError(
180
 
                "Newly created pack file %r has delta references to "
181
 
                "items not in its repository:\n%s"
182
 
                % (self, pformat(missing_items)))
183
 
 
184
153
    def file_name(self):
185
154
        """Get the file name for the pack on disk."""
186
155
        return self.name + '.pack'
188
157
    def get_revision_count(self):
189
158
        return self.revision_index.key_count()
190
159
 
191
 
    def index_name(self, index_type, name):
192
 
        """Get the disk name of an index type for pack name 'name'."""
193
 
        return name + Pack.index_definitions[index_type][0]
194
 
 
195
 
    def index_offset(self, index_type):
196
 
        """Get the position in a index_size array for a given index type."""
197
 
        return Pack.index_definitions[index_type][1]
198
 
 
199
160
    def inventory_index_name(self, name):
200
161
        """The inv index is the name + .iix."""
201
162
        return self.index_name('inventory', name)
212
173
        """The text index is the name + .tix."""
213
174
        return self.index_name('text', name)
214
175
 
215
 
    def _replace_index_with_readonly(self, index_type):
216
 
        unlimited_cache = False
217
 
        if index_type == 'chk':
218
 
            unlimited_cache = True
219
 
        index = self.index_class(self.index_transport,
220
 
                    self.index_name(index_type, self.name),
221
 
                    self.index_sizes[self.index_offset(index_type)],
222
 
                    unlimited_cache=unlimited_cache)
223
 
        if index_type == 'chk':
224
 
            index._leaf_factory = btree_index._gcchk_factory
225
 
        setattr(self, index_type + '_index', index)
 
176
    def _external_compression_parents_of_texts(self):
 
177
        keys = set()
 
178
        refs = set()
 
179
        for node in self.text_index.iter_all_entries():
 
180
            keys.add(node[1])
 
181
            refs.update(node[3][1])
 
182
        return refs - keys
226
183
 
227
184
 
228
185
class ExistingPack(Pack):
229
186
    """An in memory proxy for an existing .pack and its disk indices."""
230
187
 
231
188
    def __init__(self, pack_transport, name, revision_index, inventory_index,
232
 
        text_index, signature_index, chk_index=None):
 
189
        text_index, signature_index):
233
190
        """Create an ExistingPack object.
234
191
 
235
192
        :param pack_transport: The transport where the pack file resides.
236
193
        :param name: The name of the pack on disk in the pack_transport.
237
194
        """
238
195
        Pack.__init__(self, revision_index, inventory_index, text_index,
239
 
            signature_index, chk_index)
 
196
            signature_index)
240
197
        self.name = name
241
198
        self.pack_transport = pack_transport
242
199
        if None in (revision_index, inventory_index, text_index,
250
207
        return not self.__eq__(other)
251
208
 
252
209
    def __repr__(self):
253
 
        return "<%s.%s object at 0x%x, %s, %s" % (
254
 
            self.__class__.__module__, self.__class__.__name__, id(self),
255
 
            self.pack_transport, self.name)
256
 
 
257
 
 
258
 
class ResumedPack(ExistingPack):
259
 
 
260
 
    def __init__(self, name, revision_index, inventory_index, text_index,
261
 
        signature_index, upload_transport, pack_transport, index_transport,
262
 
        pack_collection, chk_index=None):
263
 
        """Create a ResumedPack object."""
264
 
        ExistingPack.__init__(self, pack_transport, name, revision_index,
265
 
            inventory_index, text_index, signature_index,
266
 
            chk_index=chk_index)
267
 
        self.upload_transport = upload_transport
268
 
        self.index_transport = index_transport
269
 
        self.index_sizes = [None, None, None, None]
270
 
        indices = [
271
 
            ('revision', revision_index),
272
 
            ('inventory', inventory_index),
273
 
            ('text', text_index),
274
 
            ('signature', signature_index),
275
 
            ]
276
 
        if chk_index is not None:
277
 
            indices.append(('chk', chk_index))
278
 
            self.index_sizes.append(None)
279
 
        for index_type, index in indices:
280
 
            offset = self.index_offset(index_type)
281
 
            self.index_sizes[offset] = index._size
282
 
        self.index_class = pack_collection._index_class
283
 
        self._pack_collection = pack_collection
284
 
        self._state = 'resumed'
285
 
        # XXX: perhaps check that the .pack file exists?
286
 
 
287
 
    def access_tuple(self):
288
 
        if self._state == 'finished':
289
 
            return Pack.access_tuple(self)
290
 
        elif self._state == 'resumed':
291
 
            return self.upload_transport, self.file_name()
292
 
        else:
293
 
            raise AssertionError(self._state)
294
 
 
295
 
    def abort(self):
296
 
        self.upload_transport.delete(self.file_name())
297
 
        indices = [self.revision_index, self.inventory_index, self.text_index,
298
 
            self.signature_index]
299
 
        if self.chk_index is not None:
300
 
            indices.append(self.chk_index)
301
 
        for index in indices:
302
 
            index._transport.delete(index._name)
303
 
 
304
 
    def finish(self):
305
 
        self._check_references()
306
 
        index_types = ['revision', 'inventory', 'text', 'signature']
307
 
        if self.chk_index is not None:
308
 
            index_types.append('chk')
309
 
        for index_type in index_types:
310
 
            old_name = self.index_name(index_type, self.name)
311
 
            new_name = '../indices/' + old_name
312
 
            self.upload_transport.rename(old_name, new_name)
313
 
            self._replace_index_with_readonly(index_type)
314
 
        new_name = '../packs/' + self.file_name()
315
 
        self.upload_transport.rename(self.file_name(), new_name)
316
 
        self._state = 'finished'
317
 
 
318
 
    def _get_external_refs(self, index):
319
 
        """Return compression parents for this index that are not present.
320
 
 
321
 
        This returns any compression parents that are referenced by this index,
322
 
        which are not contained *in* this index. They may be present elsewhere.
323
 
        """
324
 
        return index.external_references(1)
 
210
        return "<bzrlib.repofmt.pack_repo.Pack object at 0x%x, %s, %s" % (
 
211
            id(self), self.transport, self.name)
325
212
 
326
213
 
327
214
class NewPack(Pack):
328
215
    """An in memory proxy for a pack which is being created."""
329
216
 
330
 
    def __init__(self, pack_collection, upload_suffix='', file_mode=None):
 
217
    # A map of index 'type' to the file extension and position in the
 
218
    # index_sizes array.
 
219
    index_definitions = {
 
220
        'revision': ('.rix', 0),
 
221
        'inventory': ('.iix', 1),
 
222
        'text': ('.tix', 2),
 
223
        'signature': ('.six', 3),
 
224
        }
 
225
 
 
226
    def __init__(self, upload_transport, index_transport, pack_transport,
 
227
        upload_suffix='', file_mode=None):
331
228
        """Create a NewPack instance.
332
229
 
333
 
        :param pack_collection: A PackCollection into which this is being inserted.
 
230
        :param upload_transport: A writable transport for the pack to be
 
231
            incrementally uploaded to.
 
232
        :param index_transport: A writable transport for the pack's indices to
 
233
            be written to when the pack is finished.
 
234
        :param pack_transport: A writable transport for the pack to be renamed
 
235
            to when the upload is complete. This *must* be the same as
 
236
            upload_transport.clone('../packs').
334
237
        :param upload_suffix: An optional suffix to be given to any temporary
335
238
            files created during the pack creation. e.g '.autopack'
336
 
        :param file_mode: Unix permissions for newly created file.
 
239
        :param file_mode: An optional file mode to create the new files with.
337
240
        """
338
241
        # The relative locations of the packs are constrained, but all are
339
242
        # passed in because the caller has them, so as to avoid object churn.
340
 
        index_builder_class = pack_collection._index_builder_class
341
 
        if pack_collection.chk_index is not None:
342
 
            chk_index = index_builder_class(reference_lists=0)
343
 
        else:
344
 
            chk_index = None
345
243
        Pack.__init__(self,
346
244
            # Revisions: parents list, no text compression.
347
 
            index_builder_class(reference_lists=1),
 
245
            InMemoryGraphIndex(reference_lists=1),
348
246
            # Inventory: We want to map compression only, but currently the
349
247
            # knit code hasn't been updated enough to understand that, so we
350
248
            # have a regular 2-list index giving parents and compression
351
249
            # source.
352
 
            index_builder_class(reference_lists=2),
 
250
            InMemoryGraphIndex(reference_lists=2),
353
251
            # Texts: compression and per file graph, for all fileids - so two
354
252
            # reference lists and two elements in the key tuple.
355
 
            index_builder_class(reference_lists=2, key_elements=2),
 
253
            InMemoryGraphIndex(reference_lists=2, key_elements=2),
356
254
            # Signatures: Just blobs to store, no compression, no parents
357
255
            # listing.
358
 
            index_builder_class(reference_lists=0),
359
 
            # CHK based storage - just blobs, no compression or parents.
360
 
            chk_index=chk_index
 
256
            InMemoryGraphIndex(reference_lists=0),
361
257
            )
362
 
        self._pack_collection = pack_collection
363
 
        # When we make readonly indices, we need this.
364
 
        self.index_class = pack_collection._index_class
365
258
        # where should the new pack be opened
366
 
        self.upload_transport = pack_collection._upload_transport
 
259
        self.upload_transport = upload_transport
367
260
        # where are indices written out to
368
 
        self.index_transport = pack_collection._index_transport
 
261
        self.index_transport = index_transport
369
262
        # where is the pack renamed to when it is finished?
370
 
        self.pack_transport = pack_collection._pack_transport
 
263
        self.pack_transport = pack_transport
371
264
        # What file mode to upload the pack and indices with.
372
265
        self._file_mode = file_mode
373
266
        # tracks the content written to the .pack file.
374
 
        self._hash = osutils.md5()
375
 
        # a tuple with the length in bytes of the indices, once the pack
376
 
        # is finalised. (rev, inv, text, sigs, chk_if_in_use)
 
267
        self._hash = md5.new()
 
268
        # a four-tuple with the length in bytes of the indices, once the pack
 
269
        # is finalised. (rev, inv, text, sigs)
377
270
        self.index_sizes = None
378
271
        # How much data to cache when writing packs. Note that this is not
379
272
        # synchronised with reads, because it's not in the transport layer, so
381
274
        # under creation.
382
275
        self._cache_limit = 0
383
276
        # the temporary pack file name.
384
 
        self.random_name = osutils.rand_chars(20) + upload_suffix
 
277
        self.random_name = rand_chars(20) + upload_suffix
385
278
        # when was this pack started ?
386
279
        self.start_time = time.time()
387
280
        # open an output stream for the data added to the pack.
391
284
            mutter('%s: create_pack: pack stream open: %s%s t+%6.3fs',
392
285
                time.ctime(), self.upload_transport.base, self.random_name,
393
286
                time.time() - self.start_time)
394
 
        # A list of byte sequences to be written to the new pack, and the
395
 
        # aggregate size of them.  Stored as a list rather than separate
 
287
        # A list of byte sequences to be written to the new pack, and the 
 
288
        # aggregate size of them.  Stored as a list rather than separate 
396
289
        # variables so that the _write_data closure below can update them.
397
290
        self._buffer = [[], 0]
398
 
        # create a callable for adding data
 
291
        # create a callable for adding data 
399
292
        #
400
293
        # robertc says- this is a closure rather than a method on the object
401
294
        # so that the variables are locals, and faster than accessing object
417
310
        self._writer.begin()
418
311
        # what state is the pack in? (open, finished, aborted)
419
312
        self._state = 'open'
420
 
        # no name until we finish writing the content
421
 
        self.name = None
422
313
 
423
314
    def abort(self):
424
315
        """Cancel creating this pack."""
442
333
        return bool(self.get_revision_count() or
443
334
            self.inventory_index.key_count() or
444
335
            self.text_index.key_count() or
445
 
            self.signature_index.key_count() or
446
 
            (self.chk_index is not None and self.chk_index.key_count()))
447
 
 
448
 
    def finish_content(self):
449
 
        if self.name is not None:
450
 
            return
451
 
        self._writer.end()
452
 
        if self._buffer[1]:
453
 
            self._write_data('', flush=True)
454
 
        self.name = self._hash.hexdigest()
455
 
 
456
 
    def finish(self, suspend=False):
 
336
            self.signature_index.key_count())
 
337
 
 
338
    def finish(self):
457
339
        """Finish the new pack.
458
340
 
459
341
        This:
464
346
         - stores the index size tuple for the pack in the index_sizes
465
347
           attribute.
466
348
        """
467
 
        self.finish_content()
468
 
        if not suspend:
469
 
            self._check_references()
 
349
        self._writer.end()
 
350
        if self._buffer[1]:
 
351
            self._write_data('', flush=True)
 
352
        self.name = self._hash.hexdigest()
470
353
        # write indices
471
354
        # XXX: It'd be better to write them all to temporary names, then
472
355
        # rename them all into place, so that the window when only some are
473
356
        # visible is smaller.  On the other hand none will be seen until
474
357
        # they're in the names list.
475
358
        self.index_sizes = [None, None, None, None]
476
 
        self._write_index('revision', self.revision_index, 'revision', suspend)
477
 
        self._write_index('inventory', self.inventory_index, 'inventory',
478
 
            suspend)
479
 
        self._write_index('text', self.text_index, 'file texts', suspend)
 
359
        self._write_index('revision', self.revision_index, 'revision')
 
360
        self._write_index('inventory', self.inventory_index, 'inventory')
 
361
        self._write_index('text', self.text_index, 'file texts')
480
362
        self._write_index('signature', self.signature_index,
481
 
            'revision signatures', suspend)
482
 
        if self.chk_index is not None:
483
 
            self.index_sizes.append(None)
484
 
            self._write_index('chk', self.chk_index,
485
 
                'content hash bytes', suspend)
 
363
            'revision signatures')
486
364
        self.write_stream.close()
487
365
        # Note that this will clobber an existing pack with the same name,
488
366
        # without checking for hash collisions. While this is undesirable this
495
373
        #  - try for HASH.pack
496
374
        #  - try for temporary-name
497
375
        #  - refresh the pack-list to see if the pack is now absent
498
 
        new_name = self.name + '.pack'
499
 
        if not suspend:
500
 
            new_name = '../packs/' + new_name
501
 
        self.upload_transport.rename(self.random_name, new_name)
 
376
        self.upload_transport.rename(self.random_name,
 
377
                '../packs/' + self.name + '.pack')
502
378
        self._state = 'finished'
503
379
        if 'pack' in debug.debug_flags:
504
380
            # XXX: size might be interesting?
505
 
            mutter('%s: create_pack: pack finished: %s%s->%s t+%6.3fs',
 
381
            mutter('%s: create_pack: pack renamed into place: %s%s->%s%s t+%6.3fs',
506
382
                time.ctime(), self.upload_transport.base, self.random_name,
507
 
                new_name, time.time() - self.start_time)
 
383
                self.pack_transport, self.name,
 
384
                time.time() - self.start_time)
508
385
 
509
386
    def flush(self):
510
387
        """Flush any current data."""
514
391
            self._hash.update(bytes)
515
392
            self._buffer[:] = [[], 0]
516
393
 
517
 
    def _get_external_refs(self, index):
518
 
        return index._external_references()
 
394
    def index_name(self, index_type, name):
 
395
        """Get the disk name of an index type for pack name 'name'."""
 
396
        return name + NewPack.index_definitions[index_type][0]
 
397
 
 
398
    def index_offset(self, index_type):
 
399
        """Get the position in a index_size array for a given index type."""
 
400
        return NewPack.index_definitions[index_type][1]
 
401
 
 
402
    def _replace_index_with_readonly(self, index_type):
 
403
        setattr(self, index_type + '_index',
 
404
            GraphIndex(self.index_transport,
 
405
                self.index_name(index_type, self.name),
 
406
                self.index_sizes[self.index_offset(index_type)]))
519
407
 
520
408
    def set_write_cache_size(self, size):
521
409
        self._cache_limit = size
522
410
 
523
 
    def _write_index(self, index_type, index, label, suspend=False):
 
411
    def _write_index(self, index_type, index, label):
524
412
        """Write out an index.
525
413
 
526
414
        :param index_type: The type of index to write - e.g. 'revision'.
528
416
        :param label: What label to give the index e.g. 'revision'.
529
417
        """
530
418
        index_name = self.index_name(index_type, self.name)
531
 
        if suspend:
532
 
            transport = self.upload_transport
533
 
        else:
534
 
            transport = self.index_transport
535
 
        self.index_sizes[self.index_offset(index_type)] = transport.put_file(
536
 
            index_name, index.finish(), mode=self._file_mode)
 
419
        self.index_sizes[self.index_offset(index_type)] = \
 
420
            self.index_transport.put_file(index_name, index.finish(),
 
421
            mode=self._file_mode)
537
422
        if 'pack' in debug.debug_flags:
538
423
            # XXX: size might be interesting?
539
424
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
540
425
                time.ctime(), label, self.upload_transport.base,
541
426
                self.random_name, time.time() - self.start_time)
542
 
        # Replace the writable index on this object with a readonly,
 
427
        # Replace the writable index on this object with a readonly, 
543
428
        # presently unloaded index. We should alter
544
429
        # the index layer to make its finish() error if add_node is
545
430
        # subsequently used. RBC
554
439
    such as 'revision index'.
555
440
 
556
441
    A CombinedIndex provides an index on a single key space built up
557
 
    from several on-disk indices.  The AggregateIndex builds on this
 
442
    from several on-disk indices.  The AggregateIndex builds on this 
558
443
    to provide a knit access layer, and allows having up to one writable
559
444
    index within the collection.
560
445
    """
561
446
    # XXX: Probably 'can be written to' could/should be separated from 'acts
562
447
    # like a knit index' -- mbp 20071024
563
448
 
564
 
    def __init__(self, reload_func=None, flush_func=None):
565
 
        """Create an AggregateIndex.
566
 
 
567
 
        :param reload_func: A function to call if we find we are missing an
568
 
            index. Should have the form reload_func() => True if the list of
569
 
            active pack files has changed.
570
 
        """
571
 
        self._reload_func = reload_func
 
449
    def __init__(self):
 
450
        """Create an AggregateIndex."""
572
451
        self.index_to_pack = {}
573
 
        self.combined_index = CombinedGraphIndex([], reload_func=reload_func)
574
 
        self.data_access = _DirectPackAccess(self.index_to_pack,
575
 
                                             reload_func=reload_func,
576
 
                                             flush_func=flush_func)
 
452
        self.combined_index = CombinedGraphIndex([])
 
453
        self.data_access = _DirectPackAccess(self.index_to_pack)
 
454
        self.add_callback = None
 
455
 
 
456
    def replace_indices(self, index_to_pack, indices):
 
457
        """Replace the current mappings with fresh ones.
 
458
 
 
459
        This should probably not be used eventually, rather incremental add and
 
460
        removal of indices. It has been added during refactoring of existing
 
461
        code.
 
462
 
 
463
        :param index_to_pack: A mapping from index objects to
 
464
            (transport, name) tuples for the pack file data.
 
465
        :param indices: A list of indices.
 
466
        """
 
467
        # refresh the revision pack map dict without replacing the instance.
 
468
        self.index_to_pack.clear()
 
469
        self.index_to_pack.update(index_to_pack)
 
470
        # XXX: API break - clearly a 'replace' method would be good?
 
471
        self.combined_index._indices[:] = indices
 
472
        # the current add nodes callback for the current writable index if
 
473
        # there is one.
577
474
        self.add_callback = None
578
475
 
579
476
    def add_index(self, index, pack):
581
478
 
582
479
        Future searches on the aggregate index will seach this new index
583
480
        before all previously inserted indices.
584
 
 
 
481
        
585
482
        :param index: An Index for the pack.
586
483
        :param pack: A Pack instance.
587
484
        """
588
485
        # expose it to the index map
589
486
        self.index_to_pack[index] = pack.access_tuple()
590
487
        # put it at the front of the linear index list
591
 
        self.combined_index.insert_index(0, index, pack.name)
 
488
        self.combined_index.insert_index(0, index)
592
489
 
593
490
    def add_writable_index(self, index, pack):
594
491
        """Add an index which is able to have data added to it.
595
492
 
596
493
        There can be at most one writable index at any time.  Any
597
494
        modifications made to the knit are put into this index.
598
 
 
 
495
        
599
496
        :param index: An index from the pack parameter.
600
497
        :param pack: A Pack instance.
601
498
        """
614
511
        self.data_access.set_writer(None, None, (None, None))
615
512
        self.index_to_pack.clear()
616
513
        del self.combined_index._indices[:]
617
 
        del self.combined_index._index_names[:]
618
514
        self.add_callback = None
619
515
 
620
 
    def remove_index(self, index):
 
516
    def remove_index(self, index, pack):
621
517
        """Remove index from the indices used to answer queries.
622
 
 
 
518
        
623
519
        :param index: An index from the pack parameter.
 
520
        :param pack: A Pack instance.
624
521
        """
625
522
        del self.index_to_pack[index]
626
 
        pos = self.combined_index._indices.index(index)
627
 
        del self.combined_index._indices[pos]
628
 
        del self.combined_index._index_names[pos]
 
523
        self.combined_index._indices.remove(index)
629
524
        if (self.add_callback is not None and
630
525
            getattr(index, 'add_nodes', None) == self.add_callback):
631
526
            self.add_callback = None
635
530
class Packer(object):
636
531
    """Create a pack from packs."""
637
532
 
638
 
    def __init__(self, pack_collection, packs, suffix, revision_ids=None,
639
 
                 reload_func=None):
 
533
    def __init__(self, pack_collection, packs, suffix, revision_ids=None):
640
534
        """Create a Packer.
641
535
 
642
536
        :param pack_collection: A RepositoryPackCollection object where the
644
538
        :param packs: The packs to combine.
645
539
        :param suffix: The suffix to use on the temporary files for the pack.
646
540
        :param revision_ids: Revision ids to limit the pack to.
647
 
        :param reload_func: A function to call if a pack file/index goes
648
 
            missing. The side effect of calling this function should be to
649
 
            update self.packs. See also AggregateIndex
650
541
        """
651
542
        self.packs = packs
652
543
        self.suffix = suffix
654
545
        # The pack object we are creating.
655
546
        self.new_pack = None
656
547
        self._pack_collection = pack_collection
657
 
        self._reload_func = reload_func
658
548
        # The index layer keys for the revisions being copied. None for 'all
659
549
        # objects'.
660
550
        self._revision_keys = None
661
551
        # What text keys to copy. None for 'all texts'. This is set by
662
552
        # _copy_inventory_texts
663
553
        self._text_filter = None
 
554
        self._extra_init()
 
555
 
 
556
    def _extra_init(self):
 
557
        """A template hook to allow extending the constructor trivially."""
664
558
 
665
559
    def pack(self, pb=None):
666
560
        """Create a new pack by reading data from other packs.
668
562
        This does little more than a bulk copy of data. One key difference
669
563
        is that data with the same item key across multiple packs is elided
670
564
        from the output. The new pack is written into the current pack store
671
 
        along with its indices, and the name added to the pack names. The
 
565
        along with its indices, and the name added to the pack names. The 
672
566
        source packs are not altered and are not required to be in the current
673
567
        pack collection.
674
568
 
677
571
        :return: A Pack object, or None if nothing was copied.
678
572
        """
679
573
        # open a pack - using the same name as the last temporary file
680
 
        # - which has already been flushed, so it's safe.
 
574
        # - which has already been flushed, so its safe.
681
575
        # XXX: - duplicate code warning with start_write_group; fix before
682
576
        #      considering 'done'.
683
577
        if self._pack_collection._new_pack is not None:
684
 
            raise errors.BzrError('call to %s.pack() while another pack is'
685
 
                                  ' being written.'
686
 
                                  % (self.__class__.__name__,))
 
578
            raise errors.BzrError('call to create_pack_from_packs while '
 
579
                'another pack is being written.')
687
580
        if self.revision_ids is not None:
688
581
            if len(self.revision_ids) == 0:
689
582
                # silly fetch request.
704
597
 
705
598
    def open_pack(self):
706
599
        """Open a pack for the pack we are creating."""
707
 
        new_pack = self._pack_collection.pack_factory(self._pack_collection,
708
 
                upload_suffix=self.suffix,
709
 
                file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
710
 
        # We know that we will process all nodes in order, and don't need to
711
 
        # query, so don't combine any indices spilled to disk until we are done
712
 
        new_pack.revision_index.set_optimize(combine_backing_indices=False)
713
 
        new_pack.inventory_index.set_optimize(combine_backing_indices=False)
714
 
        new_pack.text_index.set_optimize(combine_backing_indices=False)
715
 
        new_pack.signature_index.set_optimize(combine_backing_indices=False)
716
 
        return new_pack
 
600
        return NewPack(self._pack_collection._upload_transport,
 
601
            self._pack_collection._index_transport,
 
602
            self._pack_collection._pack_transport, upload_suffix=self.suffix,
 
603
            file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
717
604
 
718
605
    def _copy_revision_texts(self):
719
606
        """Copy revision data to the new pack."""
720
 
        raise NotImplementedError(self._copy_revision_texts)
 
607
        # select revisions
 
608
        if self.revision_ids:
 
609
            revision_keys = [(revision_id,) for revision_id in self.revision_ids]
 
610
        else:
 
611
            revision_keys = None
 
612
        # select revision keys
 
613
        revision_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
 
614
            self.packs, 'revision_index')[0]
 
615
        revision_nodes = self._pack_collection._index_contents(revision_index_map, revision_keys)
 
616
        # copy revision keys and adjust values
 
617
        self.pb.update("Copying revision texts", 1)
 
618
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
 
619
        list(self._copy_nodes_graph(revision_index_map, self.new_pack._writer,
 
620
            self.new_pack.revision_index, readv_group_iter, total_items))
 
621
        if 'pack' in debug.debug_flags:
 
622
            mutter('%s: create_pack: revisions copied: %s%s %d items t+%6.3fs',
 
623
                time.ctime(), self._pack_collection._upload_transport.base,
 
624
                self.new_pack.random_name,
 
625
                self.new_pack.revision_index.key_count(),
 
626
                time.time() - self.new_pack.start_time)
 
627
        self._revision_keys = revision_keys
721
628
 
722
629
    def _copy_inventory_texts(self):
723
630
        """Copy the inventory texts to the new pack.
726
633
 
727
634
        Sets self._text_filter appropriately.
728
635
        """
729
 
        raise NotImplementedError(self._copy_inventory_texts)
 
636
        # select inventory keys
 
637
        inv_keys = self._revision_keys # currently the same keyspace, and note that
 
638
        # querying for keys here could introduce a bug where an inventory item
 
639
        # is missed, so do not change it to query separately without cross
 
640
        # checking like the text key check below.
 
641
        inventory_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
 
642
            self.packs, 'inventory_index')[0]
 
643
        inv_nodes = self._pack_collection._index_contents(inventory_index_map, inv_keys)
 
644
        # copy inventory keys and adjust values
 
645
        # XXX: Should be a helper function to allow different inv representation
 
646
        # at this point.
 
647
        self.pb.update("Copying inventory texts", 2)
 
648
        total_items, readv_group_iter = self._least_readv_node_readv(inv_nodes)
 
649
        # Only grab the output lines if we will be processing them
 
650
        output_lines = bool(self.revision_ids)
 
651
        inv_lines = self._copy_nodes_graph(inventory_index_map,
 
652
            self.new_pack._writer, self.new_pack.inventory_index,
 
653
            readv_group_iter, total_items, output_lines=output_lines)
 
654
        if self.revision_ids:
 
655
            self._process_inventory_lines(inv_lines)
 
656
        else:
 
657
            # eat the iterator to cause it to execute.
 
658
            list(inv_lines)
 
659
            self._text_filter = None
 
660
        if 'pack' in debug.debug_flags:
 
661
            mutter('%s: create_pack: inventories copied: %s%s %d items t+%6.3fs',
 
662
                time.ctime(), self._pack_collection._upload_transport.base,
 
663
                self.new_pack.random_name,
 
664
                self.new_pack.inventory_index.key_count(),
 
665
                time.time() - self.new_pack.start_time)
730
666
 
731
667
    def _copy_text_texts(self):
732
 
        raise NotImplementedError(self._copy_text_texts)
 
668
        # select text keys
 
669
        text_index_map, text_nodes = self._get_text_nodes()
 
670
        if self._text_filter is not None:
 
671
            # We could return the keys copied as part of the return value from
 
672
            # _copy_nodes_graph but this doesn't work all that well with the
 
673
            # need to get line output too, so we check separately, and as we're
 
674
            # going to buffer everything anyway, we check beforehand, which
 
675
            # saves reading knit data over the wire when we know there are
 
676
            # mising records.
 
677
            text_nodes = set(text_nodes)
 
678
            present_text_keys = set(_node[1] for _node in text_nodes)
 
679
            missing_text_keys = set(self._text_filter) - present_text_keys
 
680
            if missing_text_keys:
 
681
                # TODO: raise a specific error that can handle many missing
 
682
                # keys.
 
683
                a_missing_key = missing_text_keys.pop()
 
684
                raise errors.RevisionNotPresent(a_missing_key[1],
 
685
                    a_missing_key[0])
 
686
        # copy text keys and adjust values
 
687
        self.pb.update("Copying content texts", 3)
 
688
        total_items, readv_group_iter = self._least_readv_node_readv(text_nodes)
 
689
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
690
            self.new_pack.text_index, readv_group_iter, total_items))
 
691
        self._log_copied_texts()
 
692
 
 
693
    def _check_references(self):
 
694
        """Make sure our external refereneces are present."""
 
695
        external_refs = self.new_pack._external_compression_parents_of_texts()
 
696
        if external_refs:
 
697
            index = self._pack_collection.text_index.combined_index
 
698
            found_items = list(index.iter_entries(external_refs))
 
699
            if len(found_items) != len(external_refs):
 
700
                found_keys = set(k for idx, k, refs, value in found_items)
 
701
                missing_items = external_refs - found_keys
 
702
                missing_file_id, missing_revision_id = missing_items.pop()
 
703
                raise errors.RevisionNotPresent(missing_revision_id,
 
704
                                                missing_file_id)
733
705
 
734
706
    def _create_pack_from_packs(self):
735
 
        raise NotImplementedError(self._create_pack_from_packs)
 
707
        self.pb.update("Opening pack", 0, 5)
 
708
        self.new_pack = self.open_pack()
 
709
        new_pack = self.new_pack
 
710
        # buffer data - we won't be reading-back during the pack creation and
 
711
        # this makes a significant difference on sftp pushes.
 
712
        new_pack.set_write_cache_size(1024*1024)
 
713
        if 'pack' in debug.debug_flags:
 
714
            plain_pack_list = ['%s%s' % (a_pack.pack_transport.base, a_pack.name)
 
715
                for a_pack in self.packs]
 
716
            if self.revision_ids is not None:
 
717
                rev_count = len(self.revision_ids)
 
718
            else:
 
719
                rev_count = 'all'
 
720
            mutter('%s: create_pack: creating pack from source packs: '
 
721
                '%s%s %s revisions wanted %s t=0',
 
722
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
723
                plain_pack_list, rev_count)
 
724
        self._copy_revision_texts()
 
725
        self._copy_inventory_texts()
 
726
        self._copy_text_texts()
 
727
        # select signature keys
 
728
        signature_filter = self._revision_keys # same keyspace
 
729
        signature_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
 
730
            self.packs, 'signature_index')[0]
 
731
        signature_nodes = self._pack_collection._index_contents(signature_index_map,
 
732
            signature_filter)
 
733
        # copy signature keys and adjust values
 
734
        self.pb.update("Copying signature texts", 4)
 
735
        self._copy_nodes(signature_nodes, signature_index_map, new_pack._writer,
 
736
            new_pack.signature_index)
 
737
        if 'pack' in debug.debug_flags:
 
738
            mutter('%s: create_pack: revision signatures copied: %s%s %d items t+%6.3fs',
 
739
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
 
740
                new_pack.signature_index.key_count(),
 
741
                time.time() - new_pack.start_time)
 
742
        self._check_references()
 
743
        if not self._use_pack(new_pack):
 
744
            new_pack.abort()
 
745
            return None
 
746
        self.pb.update("Finishing pack", 5)
 
747
        new_pack.finish()
 
748
        self._pack_collection.allocate(new_pack)
 
749
        return new_pack
 
750
 
 
751
    def _copy_nodes(self, nodes, index_map, writer, write_index):
 
752
        """Copy knit nodes between packs with no graph references."""
 
753
        pb = ui.ui_factory.nested_progress_bar()
 
754
        try:
 
755
            return self._do_copy_nodes(nodes, index_map, writer,
 
756
                write_index, pb)
 
757
        finally:
 
758
            pb.finished()
 
759
 
 
760
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb):
 
761
        # for record verification
 
762
        knit = KnitVersionedFiles(None, None)
 
763
        # plan a readv on each source pack:
 
764
        # group by pack
 
765
        nodes = sorted(nodes)
 
766
        # how to map this into knit.py - or knit.py into this?
 
767
        # we don't want the typical knit logic, we want grouping by pack
 
768
        # at this point - perhaps a helper library for the following code 
 
769
        # duplication points?
 
770
        request_groups = {}
 
771
        for index, key, value in nodes:
 
772
            if index not in request_groups:
 
773
                request_groups[index] = []
 
774
            request_groups[index].append((key, value))
 
775
        record_index = 0
 
776
        pb.update("Copied record", record_index, len(nodes))
 
777
        for index, items in request_groups.iteritems():
 
778
            pack_readv_requests = []
 
779
            for key, value in items:
 
780
                # ---- KnitGraphIndex.get_position
 
781
                bits = value[1:].split(' ')
 
782
                offset, length = int(bits[0]), int(bits[1])
 
783
                pack_readv_requests.append((offset, length, (key, value[0])))
 
784
            # linear scan up the pack
 
785
            pack_readv_requests.sort()
 
786
            # copy the data
 
787
            transport, path = index_map[index]
 
788
            reader = pack.make_readv_reader(transport, path,
 
789
                [offset[0:2] for offset in pack_readv_requests])
 
790
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
 
791
                izip(reader.iter_records(), pack_readv_requests):
 
792
                raw_data = read_func(None)
 
793
                # check the header only
 
794
                df, _ = knit._parse_record_header(key, raw_data)
 
795
                df.close()
 
796
                pos, size = writer.add_bytes_record(raw_data, names)
 
797
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
 
798
                pb.update("Copied record", record_index)
 
799
                record_index += 1
 
800
 
 
801
    def _copy_nodes_graph(self, index_map, writer, write_index,
 
802
        readv_group_iter, total_items, output_lines=False):
 
803
        """Copy knit nodes between packs.
 
804
 
 
805
        :param output_lines: Return lines present in the copied data as
 
806
            an iterator of line,version_id.
 
807
        """
 
808
        pb = ui.ui_factory.nested_progress_bar()
 
809
        try:
 
810
            for result in self._do_copy_nodes_graph(index_map, writer,
 
811
                write_index, output_lines, pb, readv_group_iter, total_items):
 
812
                yield result
 
813
        except Exception:
 
814
            # Python 2.4 does not permit try:finally: in a generator.
 
815
            pb.finished()
 
816
            raise
 
817
        else:
 
818
            pb.finished()
 
819
 
 
820
    def _do_copy_nodes_graph(self, index_map, writer, write_index,
 
821
        output_lines, pb, readv_group_iter, total_items):
 
822
        # for record verification
 
823
        knit = KnitVersionedFiles(None, None)
 
824
        # for line extraction when requested (inventories only)
 
825
        if output_lines:
 
826
            factory = KnitPlainFactory()
 
827
        record_index = 0
 
828
        pb.update("Copied record", record_index, total_items)
 
829
        for index, readv_vector, node_vector in readv_group_iter:
 
830
            # copy the data
 
831
            transport, path = index_map[index]
 
832
            reader = pack.make_readv_reader(transport, path, readv_vector)
 
833
            for (names, read_func), (key, eol_flag, references) in \
 
834
                izip(reader.iter_records(), node_vector):
 
835
                raw_data = read_func(None)
 
836
                if output_lines:
 
837
                    # read the entire thing
 
838
                    content, _ = knit._parse_record(key[-1], raw_data)
 
839
                    if len(references[-1]) == 0:
 
840
                        line_iterator = factory.get_fulltext_content(content)
 
841
                    else:
 
842
                        line_iterator = factory.get_linedelta_content(content)
 
843
                    for line in line_iterator:
 
844
                        yield line, key
 
845
                else:
 
846
                    # check the header only
 
847
                    df, _ = knit._parse_record_header(key, raw_data)
 
848
                    df.close()
 
849
                pos, size = writer.add_bytes_record(raw_data, names)
 
850
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size), references)
 
851
                pb.update("Copied record", record_index)
 
852
                record_index += 1
 
853
 
 
854
    def _get_text_nodes(self):
 
855
        text_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
 
856
            self.packs, 'text_index')[0]
 
857
        return text_index_map, self._pack_collection._index_contents(text_index_map,
 
858
            self._text_filter)
 
859
 
 
860
    def _least_readv_node_readv(self, nodes):
 
861
        """Generate request groups for nodes using the least readv's.
 
862
        
 
863
        :param nodes: An iterable of graph index nodes.
 
864
        :return: Total node count and an iterator of the data needed to perform
 
865
            readvs to obtain the data for nodes. Each item yielded by the
 
866
            iterator is a tuple with:
 
867
            index, readv_vector, node_vector. readv_vector is a list ready to
 
868
            hand to the transport readv method, and node_vector is a list of
 
869
            (key, eol_flag, references) for the the node retrieved by the
 
870
            matching readv_vector.
 
871
        """
 
872
        # group by pack so we do one readv per pack
 
873
        nodes = sorted(nodes)
 
874
        total = len(nodes)
 
875
        request_groups = {}
 
876
        for index, key, value, references in nodes:
 
877
            if index not in request_groups:
 
878
                request_groups[index] = []
 
879
            request_groups[index].append((key, value, references))
 
880
        result = []
 
881
        for index, items in request_groups.iteritems():
 
882
            pack_readv_requests = []
 
883
            for key, value, references in items:
 
884
                # ---- KnitGraphIndex.get_position
 
885
                bits = value[1:].split(' ')
 
886
                offset, length = int(bits[0]), int(bits[1])
 
887
                pack_readv_requests.append(
 
888
                    ((offset, length), (key, value[0], references)))
 
889
            # linear scan up the pack to maximum range combining.
 
890
            pack_readv_requests.sort()
 
891
            # split out the readv and the node data.
 
892
            pack_readv = [readv for readv, node in pack_readv_requests]
 
893
            node_vector = [node for readv, node in pack_readv_requests]
 
894
            result.append((index, pack_readv, node_vector))
 
895
        return total, result
736
896
 
737
897
    def _log_copied_texts(self):
738
898
        if 'pack' in debug.debug_flags:
742
902
                self.new_pack.text_index.key_count(),
743
903
                time.time() - self.new_pack.start_time)
744
904
 
 
905
    def _process_inventory_lines(self, inv_lines):
 
906
        """Use up the inv_lines generator and setup a text key filter."""
 
907
        repo = self._pack_collection.repo
 
908
        fileid_revisions = repo._find_file_ids_from_xml_inventory_lines(
 
909
            inv_lines, self.revision_keys)
 
910
        text_filter = []
 
911
        for fileid, file_revids in fileid_revisions.iteritems():
 
912
            text_filter.extend([(fileid, file_revid) for file_revid in file_revids])
 
913
        self._text_filter = text_filter
 
914
 
 
915
    def _revision_node_readv(self, revision_nodes):
 
916
        """Return the total revisions and the readv's to issue.
 
917
 
 
918
        :param revision_nodes: The revision index contents for the packs being
 
919
            incorporated into the new pack.
 
920
        :return: As per _least_readv_node_readv.
 
921
        """
 
922
        return self._least_readv_node_readv(revision_nodes)
 
923
 
745
924
    def _use_pack(self, new_pack):
746
925
        """Return True if new_pack should be used.
747
926
 
751
930
        return new_pack.data_inserted()
752
931
 
753
932
 
 
933
class OptimisingPacker(Packer):
 
934
    """A packer which spends more time to create better disk layouts."""
 
935
 
 
936
    def _revision_node_readv(self, revision_nodes):
 
937
        """Return the total revisions and the readv's to issue.
 
938
 
 
939
        This sort places revisions in topological order with the ancestors
 
940
        after the children.
 
941
 
 
942
        :param revision_nodes: The revision index contents for the packs being
 
943
            incorporated into the new pack.
 
944
        :return: As per _least_readv_node_readv.
 
945
        """
 
946
        # build an ancestors dict
 
947
        ancestors = {}
 
948
        by_key = {}
 
949
        for index, key, value, references in revision_nodes:
 
950
            ancestors[key] = references[0]
 
951
            by_key[key] = (index, value, references)
 
952
        order = tsort.topo_sort(ancestors)
 
953
        total = len(order)
 
954
        # Single IO is pathological, but it will work as a starting point.
 
955
        requests = []
 
956
        for key in reversed(order):
 
957
            index, value, references = by_key[key]
 
958
            # ---- KnitGraphIndex.get_position
 
959
            bits = value[1:].split(' ')
 
960
            offset, length = int(bits[0]), int(bits[1])
 
961
            requests.append(
 
962
                (index, [(offset, length)], [(key, value[0], references)]))
 
963
        # TODO: combine requests in the same index that are in ascending order.
 
964
        return total, requests
 
965
 
 
966
 
 
967
class ReconcilePacker(Packer):
 
968
    """A packer which regenerates indices etc as it copies.
 
969
    
 
970
    This is used by ``bzr reconcile`` to cause parent text pointers to be
 
971
    regenerated.
 
972
    """
 
973
 
 
974
    def _extra_init(self):
 
975
        self._data_changed = False
 
976
 
 
977
    def _process_inventory_lines(self, inv_lines):
 
978
        """Generate a text key reference map rather for reconciling with."""
 
979
        repo = self._pack_collection.repo
 
980
        refs = repo._find_text_key_references_from_xml_inventory_lines(
 
981
            inv_lines)
 
982
        self._text_refs = refs
 
983
        # during reconcile we:
 
984
        #  - convert unreferenced texts to full texts
 
985
        #  - correct texts which reference a text not copied to be full texts
 
986
        #  - copy all others as-is but with corrected parents.
 
987
        #  - so at this point we don't know enough to decide what becomes a full
 
988
        #    text.
 
989
        self._text_filter = None
 
990
 
 
991
    def _copy_text_texts(self):
 
992
        """generate what texts we should have and then copy."""
 
993
        self.pb.update("Copying content texts", 3)
 
994
        # we have three major tasks here:
 
995
        # 1) generate the ideal index
 
996
        repo = self._pack_collection.repo
 
997
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
 
998
            _1, key, _2, refs in 
 
999
            self.new_pack.revision_index.iter_all_entries()])
 
1000
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
 
1001
        # 2) generate a text_nodes list that contains all the deltas that can
 
1002
        #    be used as-is, with corrected parents.
 
1003
        ok_nodes = []
 
1004
        bad_texts = []
 
1005
        discarded_nodes = []
 
1006
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1007
        text_index_map, text_nodes = self._get_text_nodes()
 
1008
        for node in text_nodes:
 
1009
            # 0 - index
 
1010
            # 1 - key 
 
1011
            # 2 - value
 
1012
            # 3 - refs
 
1013
            try:
 
1014
                ideal_parents = tuple(ideal_index[node[1]])
 
1015
            except KeyError:
 
1016
                discarded_nodes.append(node)
 
1017
                self._data_changed = True
 
1018
            else:
 
1019
                if ideal_parents == (NULL_REVISION,):
 
1020
                    ideal_parents = ()
 
1021
                if ideal_parents == node[3][0]:
 
1022
                    # no change needed.
 
1023
                    ok_nodes.append(node)
 
1024
                elif ideal_parents[0:1] == node[3][0][0:1]:
 
1025
                    # the left most parent is the same, or there are no parents
 
1026
                    # today. Either way, we can preserve the representation as
 
1027
                    # long as we change the refs to be inserted.
 
1028
                    self._data_changed = True
 
1029
                    ok_nodes.append((node[0], node[1], node[2],
 
1030
                        (ideal_parents, node[3][1])))
 
1031
                    self._data_changed = True
 
1032
                else:
 
1033
                    # Reinsert this text completely
 
1034
                    bad_texts.append((node[1], ideal_parents))
 
1035
                    self._data_changed = True
 
1036
        # we're finished with some data.
 
1037
        del ideal_index
 
1038
        del text_nodes
 
1039
        # 3) bulk copy the ok data
 
1040
        total_items, readv_group_iter = self._least_readv_node_readv(ok_nodes)
 
1041
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
 
1042
            self.new_pack.text_index, readv_group_iter, total_items))
 
1043
        # 4) adhoc copy all the other texts.
 
1044
        # We have to topologically insert all texts otherwise we can fail to
 
1045
        # reconcile when parts of a single delta chain are preserved intact,
 
1046
        # and other parts are not. E.g. Discarded->d1->d2->d3. d1 will be
 
1047
        # reinserted, and if d3 has incorrect parents it will also be
 
1048
        # reinserted. If we insert d3 first, d2 is present (as it was bulk
 
1049
        # copied), so we will try to delta, but d2 is not currently able to be
 
1050
        # extracted because it's basis d1 is not present. Topologically sorting
 
1051
        # addresses this. The following generates a sort for all the texts that
 
1052
        # are being inserted without having to reference the entire text key
 
1053
        # space (we only topo sort the revisions, which is smaller).
 
1054
        topo_order = tsort.topo_sort(ancestors)
 
1055
        rev_order = dict(zip(topo_order, range(len(topo_order))))
 
1056
        bad_texts.sort(key=lambda key:rev_order[key[0][1]])
 
1057
        transaction = repo.get_transaction()
 
1058
        file_id_index = GraphIndexPrefixAdapter(
 
1059
            self.new_pack.text_index,
 
1060
            ('blank', ), 1,
 
1061
            add_nodes_callback=self.new_pack.text_index.add_nodes)
 
1062
        data_access = _DirectPackAccess(
 
1063
                {self.new_pack.text_index:self.new_pack.access_tuple()})
 
1064
        data_access.set_writer(self.new_pack._writer, self.new_pack.text_index,
 
1065
            self.new_pack.access_tuple())
 
1066
        output_texts = KnitVersionedFiles(
 
1067
            _KnitGraphIndex(self.new_pack.text_index,
 
1068
                add_callback=self.new_pack.text_index.add_nodes,
 
1069
                deltas=True, parents=True, is_locked=repo.is_locked),
 
1070
            data_access=data_access, max_delta_chain=200)
 
1071
        for key, parent_keys in bad_texts:
 
1072
            # We refer to the new pack to delta data being output.
 
1073
            # A possible improvement would be to catch errors on short reads
 
1074
            # and only flush then.
 
1075
            self.new_pack.flush()
 
1076
            parents = []
 
1077
            for parent_key in parent_keys:
 
1078
                if parent_key[0] != key[0]:
 
1079
                    # Graph parents must match the fileid
 
1080
                    raise errors.BzrError('Mismatched key parent %r:%r' %
 
1081
                        (key, parent_keys))
 
1082
                parents.append(parent_key[1])
 
1083
            text_lines = split_lines(repo.texts.get_record_stream(
 
1084
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
 
1085
            output_texts.add_lines(key, parent_keys, text_lines,
 
1086
                random_id=True, check_content=False)
 
1087
        # 5) check that nothing inserted has a reference outside the keyspace.
 
1088
        missing_text_keys = self.new_pack._external_compression_parents_of_texts()
 
1089
        if missing_text_keys:
 
1090
            raise errors.BzrError('Reference to missing compression parents %r'
 
1091
                % (missing_text_keys,))
 
1092
        self._log_copied_texts()
 
1093
 
 
1094
    def _use_pack(self, new_pack):
 
1095
        """Override _use_pack to check for reconcile having changed content."""
 
1096
        # XXX: we might be better checking this at the copy time.
 
1097
        original_inventory_keys = set()
 
1098
        inv_index = self._pack_collection.inventory_index.combined_index
 
1099
        for entry in inv_index.iter_all_entries():
 
1100
            original_inventory_keys.add(entry[1])
 
1101
        new_inventory_keys = set()
 
1102
        for entry in new_pack.inventory_index.iter_all_entries():
 
1103
            new_inventory_keys.add(entry[1])
 
1104
        if new_inventory_keys != original_inventory_keys:
 
1105
            self._data_changed = True
 
1106
        return new_pack.data_inserted() and self._data_changed
 
1107
 
 
1108
 
754
1109
class RepositoryPackCollection(object):
755
 
    """Management of packs within a repository.
756
 
 
757
 
    :ivar _names: map of {pack_name: (index_size,)}
758
 
    """
759
 
 
760
 
    pack_factory = None
761
 
    resumed_pack_factory = None
762
 
    normal_packer_class = None
763
 
    optimising_packer_class = None
 
1110
    """Management of packs within a repository."""
764
1111
 
765
1112
    def __init__(self, repo, transport, index_transport, upload_transport,
766
 
                 pack_transport, index_builder_class, index_class,
767
 
                 use_chk_index):
 
1113
                 pack_transport):
768
1114
        """Create a new RepositoryPackCollection.
769
1115
 
770
 
        :param transport: Addresses the repository base directory
 
1116
        :param transport: Addresses the repository base directory 
771
1117
            (typically .bzr/repository/).
772
1118
        :param index_transport: Addresses the directory containing indices.
773
1119
        :param upload_transport: Addresses the directory into which packs are written
774
1120
            while they're being created.
775
1121
        :param pack_transport: Addresses the directory of existing complete packs.
776
 
        :param index_builder_class: The index builder class to use.
777
 
        :param index_class: The index class to use.
778
 
        :param use_chk_index: Whether to setup and manage a CHK index.
779
1122
        """
780
 
        # XXX: This should call self.reset()
781
1123
        self.repo = repo
782
1124
        self.transport = transport
783
1125
        self._index_transport = index_transport
784
1126
        self._upload_transport = upload_transport
785
1127
        self._pack_transport = pack_transport
786
 
        self._index_builder_class = index_builder_class
787
 
        self._index_class = index_class
788
 
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3,
789
 
            '.cix': 4}
 
1128
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3}
790
1129
        self.packs = []
791
1130
        # name:Pack mapping
792
 
        self._names = None
793
1131
        self._packs_by_name = {}
794
1132
        # the previous pack-names content
795
1133
        self._packs_at_load = None
796
1134
        # when a pack is being created by this object, the state of that pack.
797
1135
        self._new_pack = None
798
1136
        # aggregated revision index data
799
 
        flush = self._flush_new_pack
800
 
        self.revision_index = AggregateIndex(self.reload_pack_names, flush)
801
 
        self.inventory_index = AggregateIndex(self.reload_pack_names, flush)
802
 
        self.text_index = AggregateIndex(self.reload_pack_names, flush)
803
 
        self.signature_index = AggregateIndex(self.reload_pack_names, flush)
804
 
        all_indices = [self.revision_index, self.inventory_index,
805
 
                self.text_index, self.signature_index]
806
 
        if use_chk_index:
807
 
            self.chk_index = AggregateIndex(self.reload_pack_names, flush)
808
 
            all_indices.append(self.chk_index)
809
 
        else:
810
 
            # used to determine if we're using a chk_index elsewhere.
811
 
            self.chk_index = None
812
 
        # Tell all the CombinedGraphIndex objects about each other, so they can
813
 
        # share hints about which pack names to search first.
814
 
        all_combined = [agg_idx.combined_index for agg_idx in all_indices]
815
 
        for combined_idx in all_combined:
816
 
            combined_idx.set_sibling_indices(
817
 
                set(all_combined).difference([combined_idx]))
818
 
        # resumed packs
819
 
        self._resumed_packs = []
820
 
 
821
 
    def __repr__(self):
822
 
        return '%s(%r)' % (self.__class__.__name__, self.repo)
 
1137
        self.revision_index = AggregateIndex()
 
1138
        self.inventory_index = AggregateIndex()
 
1139
        self.text_index = AggregateIndex()
 
1140
        self.signature_index = AggregateIndex()
823
1141
 
824
1142
    def add_pack_to_memory(self, pack):
825
1143
        """Make a Pack object available to the repository to satisfy queries.
826
 
 
 
1144
        
827
1145
        :param pack: A Pack object.
828
1146
        """
829
1147
        if pack.name in self._packs_by_name:
830
 
            raise AssertionError(
831
 
                'pack %s already in _packs_by_name' % (pack.name,))
 
1148
            raise AssertionError()
832
1149
        self.packs.append(pack)
833
1150
        self._packs_by_name[pack.name] = pack
834
1151
        self.revision_index.add_index(pack.revision_index, pack)
835
1152
        self.inventory_index.add_index(pack.inventory_index, pack)
836
1153
        self.text_index.add_index(pack.text_index, pack)
837
1154
        self.signature_index.add_index(pack.signature_index, pack)
838
 
        if self.chk_index is not None:
839
 
            self.chk_index.add_index(pack.chk_index, pack)
840
 
 
 
1155
        
841
1156
    def all_packs(self):
842
1157
        """Return a list of all the Pack objects this repository has.
843
1158
 
852
1167
 
853
1168
    def autopack(self):
854
1169
        """Pack the pack collection incrementally.
855
 
 
 
1170
        
856
1171
        This will not attempt global reorganisation or recompression,
857
1172
        rather it will just ensure that the total number of packs does
858
1173
        not grow without bound. It uses the _max_pack_count method to
864
1179
        in synchronisation with certain steps. Otherwise the names collection
865
1180
        is not flushed.
866
1181
 
867
 
        :return: Something evaluating true if packing took place.
 
1182
        :return: True if packing took place.
868
1183
        """
869
 
        while True:
870
 
            try:
871
 
                return self._do_autopack()
872
 
            except errors.RetryAutopack:
873
 
                # If we get a RetryAutopack exception, we should abort the
874
 
                # current action, and retry.
875
 
                pass
876
 
 
877
 
    def _do_autopack(self):
878
1184
        # XXX: Should not be needed when the management of indices is sane.
879
1185
        total_revisions = self.revision_index.combined_index.key_count()
880
1186
        total_packs = len(self._names)
881
1187
        if self._max_pack_count(total_revisions) >= total_packs:
882
 
            return None
 
1188
            return False
 
1189
        # XXX: the following may want to be a class, to pack with a given
 
1190
        # policy.
 
1191
        mutter('Auto-packing repository %s, which has %d pack files, '
 
1192
            'containing %d revisions into %d packs.', self, total_packs,
 
1193
            total_revisions, self._max_pack_count(total_revisions))
883
1194
        # determine which packs need changing
884
1195
        pack_distribution = self.pack_distribution(total_revisions)
885
1196
        existing_packs = []
893
1204
                # group their data with the relevant commit, and that may
894
1205
                # involve rewriting ancient history - which autopack tries to
895
1206
                # avoid. Alternatively we could not group the data but treat
896
 
                # each of these as having a single revision, and thus add
 
1207
                # each of these as having a single revision, and thus add 
897
1208
                # one revision for each to the total revision count, to get
898
1209
                # a matching distribution.
899
1210
                continue
900
1211
            existing_packs.append((revision_count, pack))
901
1212
        pack_operations = self.plan_autopack_combinations(
902
1213
            existing_packs, pack_distribution)
903
 
        num_new_packs = len(pack_operations)
904
 
        num_old_packs = sum([len(po[1]) for po in pack_operations])
905
 
        num_revs_affected = sum([po[0] for po in pack_operations])
906
 
        mutter('Auto-packing repository %s, which has %d pack files, '
907
 
            'containing %d revisions. Packing %d files into %d affecting %d'
908
 
            ' revisions', self, total_packs, total_revisions, num_old_packs,
909
 
            num_new_packs, num_revs_affected)
910
 
        result = self._execute_pack_operations(pack_operations, packer_class=self.normal_packer_class,
911
 
                                      reload_func=self._restart_autopack)
912
 
        mutter('Auto-packing repository %s completed', self)
913
 
        return result
 
1214
        self._execute_pack_operations(pack_operations)
 
1215
        return True
914
1216
 
915
 
    def _execute_pack_operations(self, pack_operations, packer_class,
916
 
            reload_func=None):
 
1217
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer):
917
1218
        """Execute a series of pack operations.
918
1219
 
919
1220
        :param pack_operations: A list of [revision_count, packs_to_combine].
920
 
        :param packer_class: The class of packer to use
921
 
        :return: The new pack names.
 
1221
        :param _packer_class: The class of packer to use (default: Packer).
 
1222
        :return: None.
922
1223
        """
923
1224
        for revision_count, packs in pack_operations:
924
1225
            # we may have no-ops from the setup logic
925
1226
            if len(packs) == 0:
926
1227
                continue
927
 
            packer = packer_class(self, packs, '.autopack',
928
 
                                   reload_func=reload_func)
929
 
            try:
930
 
                result = packer.pack()
931
 
            except errors.RetryWithNewPacks:
932
 
                # An exception is propagating out of this context, make sure
933
 
                # this packer has cleaned up. Packer() doesn't set its new_pack
934
 
                # state into the RepositoryPackCollection object, so we only
935
 
                # have access to it directly here.
936
 
                if packer.new_pack is not None:
937
 
                    packer.new_pack.abort()
938
 
                raise
939
 
            if result is None:
940
 
                return
 
1228
            _packer_class(self, packs, '.autopack').pack()
941
1229
            for pack in packs:
942
1230
                self._remove_pack_from_memory(pack)
943
1231
        # record the newly available packs and stop advertising the old
944
1232
        # packs
945
 
        to_be_obsoleted = []
946
 
        for _, packs in pack_operations:
947
 
            to_be_obsoleted.extend(packs)
948
 
        result = self._save_pack_names(clear_obsolete_packs=True,
949
 
                                       obsolete_packs=to_be_obsoleted)
950
 
        return result
951
 
 
952
 
    def _flush_new_pack(self):
953
 
        if self._new_pack is not None:
954
 
            self._new_pack.flush()
 
1233
        self._save_pack_names(clear_obsolete_packs=True)
 
1234
        # Move the old packs out of the way now they are no longer referenced.
 
1235
        for revision_count, packs in pack_operations:
 
1236
            self._obsolete_packs(packs)
955
1237
 
956
1238
    def lock_names(self):
957
1239
        """Acquire the mutex around the pack-names index.
958
 
 
 
1240
        
959
1241
        This cannot be used in the middle of a read-only transaction on the
960
1242
        repository.
961
1243
        """
962
1244
        self.repo.control_files.lock_write()
963
1245
 
964
 
    def _already_packed(self):
965
 
        """Is the collection already packed?"""
966
 
        return not (self.repo._format.pack_compresses or (len(self._names) > 1))
967
 
 
968
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1246
    def pack(self):
969
1247
        """Pack the pack collection totally."""
970
1248
        self.ensure_loaded()
971
1249
        total_packs = len(self._names)
972
 
        if self._already_packed():
 
1250
        if total_packs < 2:
 
1251
            # This is arguably wrong because we might not be optimal, but for
 
1252
            # now lets leave it in. (e.g. reconcile -> one pack. But not
 
1253
            # optimal.
973
1254
            return
974
1255
        total_revisions = self.revision_index.combined_index.key_count()
975
1256
        # XXX: the following may want to be a class, to pack with a given
976
1257
        # policy.
977
1258
        mutter('Packing repository %s, which has %d pack files, '
978
 
            'containing %d revisions with hint %r.', self, total_packs,
979
 
            total_revisions, hint)
980
 
        while True:
981
 
            try:
982
 
                self._try_pack_operations(hint)
983
 
            except RetryPackOperations:
984
 
                continue
985
 
            break
986
 
 
987
 
        if clean_obsolete_packs:
988
 
            self._clear_obsolete_packs()
989
 
 
990
 
    def _try_pack_operations(self, hint):
991
 
        """Calculate the pack operations based on the hint (if any), and
992
 
        execute them.
993
 
        """
 
1259
            'containing %d revisions into 1 packs.', self, total_packs,
 
1260
            total_revisions)
994
1261
        # determine which packs need changing
 
1262
        pack_distribution = [1]
995
1263
        pack_operations = [[0, []]]
996
1264
        for pack in self.all_packs():
997
 
            if hint is None or pack.name in hint:
998
 
                # Either no hint was provided (so we are packing everything),
999
 
                # or this pack was included in the hint.
1000
 
                pack_operations[-1][0] += pack.get_revision_count()
1001
 
                pack_operations[-1][1].append(pack)
1002
 
        self._execute_pack_operations(pack_operations,
1003
 
            packer_class=self.optimising_packer_class,
1004
 
            reload_func=self._restart_pack_operations)
 
1265
            pack_operations[-1][0] += pack.get_revision_count()
 
1266
            pack_operations[-1][1].append(pack)
 
1267
        self._execute_pack_operations(pack_operations, OptimisingPacker)
1005
1268
 
1006
1269
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1007
1270
        """Plan a pack operation.
1017
1280
        pack_operations = [[0, []]]
1018
1281
        # plan out what packs to keep, and what to reorganise
1019
1282
        while len(existing_packs):
1020
 
            # take the largest pack, and if it's less than the head of the
1021
 
            # distribution chart we will include its contents in the new pack
1022
 
            # for that position. If it's larger, we remove its size from the
 
1283
            # take the largest pack, and if its less than the head of the
 
1284
            # distribution chart we will include its contents in the new pack for
 
1285
            # that position. If its larger, we remove its size from the
1023
1286
            # distribution chart
1024
1287
            next_pack_rev_count, next_pack = existing_packs.pop(0)
1025
1288
            if next_pack_rev_count >= pack_distribution[0]:
1042
1305
                    # this pack is used up, shift left.
1043
1306
                    del pack_distribution[0]
1044
1307
                    pack_operations.append([0, []])
1045
 
        # Now that we know which pack files we want to move, shove them all
1046
 
        # into a single pack file.
1047
 
        final_rev_count = 0
1048
 
        final_pack_list = []
1049
 
        for num_revs, pack_files in pack_operations:
1050
 
            final_rev_count += num_revs
1051
 
            final_pack_list.extend(pack_files)
1052
 
        if len(final_pack_list) == 1:
1053
 
            raise AssertionError('We somehow generated an autopack with a'
1054
 
                ' single pack file being moved.')
1055
 
            return []
1056
 
        return [[final_rev_count, final_pack_list]]
 
1308
        
 
1309
        return pack_operations
1057
1310
 
1058
1311
    def ensure_loaded(self):
1059
 
        """Ensure we have read names from disk.
1060
 
 
1061
 
        :return: True if the disk names had not been previously read.
1062
 
        """
1063
 
        # NB: if you see an assertion error here, it's probably access against
 
1312
        # NB: if you see an assertion error here, its probably access against
1064
1313
        # an unlocked repo. Naughty.
1065
1314
        if not self.repo.is_locked():
1066
1315
            raise errors.ObjectNotLocked(self.repo)
1071
1320
                name = key[0]
1072
1321
                self._names[name] = self._parse_index_sizes(value)
1073
1322
                self._packs_at_load.add((key, value))
1074
 
            result = True
1075
 
        else:
1076
 
            result = False
1077
1323
        # populate all the metadata.
1078
1324
        self.all_packs()
1079
 
        return result
1080
1325
 
1081
1326
    def _parse_index_sizes(self, value):
1082
1327
        """Parse a string of index sizes."""
1095
1340
            inv_index = self._make_index(name, '.iix')
1096
1341
            txt_index = self._make_index(name, '.tix')
1097
1342
            sig_index = self._make_index(name, '.six')
1098
 
            if self.chk_index is not None:
1099
 
                chk_index = self._make_index(name, '.cix', is_chk=True)
1100
 
            else:
1101
 
                chk_index = None
1102
1343
            result = ExistingPack(self._pack_transport, name, rev_index,
1103
 
                inv_index, txt_index, sig_index, chk_index)
 
1344
                inv_index, txt_index, sig_index)
1104
1345
            self.add_pack_to_memory(result)
1105
1346
            return result
1106
1347
 
1107
 
    def _resume_pack(self, name):
1108
 
        """Get a suspended Pack object by name.
1109
 
 
1110
 
        :param name: The name of the pack - e.g. '123456'
1111
 
        :return: A Pack object.
1112
 
        """
1113
 
        if not re.match('[a-f0-9]{32}', name):
1114
 
            # Tokens should be md5sums of the suspended pack file, i.e. 32 hex
1115
 
            # digits.
1116
 
            raise errors.UnresumableWriteGroup(
1117
 
                self.repo, [name], 'Malformed write group token')
1118
 
        try:
1119
 
            rev_index = self._make_index(name, '.rix', resume=True)
1120
 
            inv_index = self._make_index(name, '.iix', resume=True)
1121
 
            txt_index = self._make_index(name, '.tix', resume=True)
1122
 
            sig_index = self._make_index(name, '.six', resume=True)
1123
 
            if self.chk_index is not None:
1124
 
                chk_index = self._make_index(name, '.cix', resume=True,
1125
 
                                             is_chk=True)
1126
 
            else:
1127
 
                chk_index = None
1128
 
            result = self.resumed_pack_factory(name, rev_index, inv_index,
1129
 
                txt_index, sig_index, self._upload_transport,
1130
 
                self._pack_transport, self._index_transport, self,
1131
 
                chk_index=chk_index)
1132
 
        except errors.NoSuchFile, e:
1133
 
            raise errors.UnresumableWriteGroup(self.repo, [name], str(e))
1134
 
        self.add_pack_to_memory(result)
1135
 
        self._resumed_packs.append(result)
1136
 
        return result
1137
 
 
1138
1348
    def allocate(self, a_new_pack):
1139
1349
        """Allocate name in the list of packs.
1140
1350
 
1150
1360
 
1151
1361
    def _iter_disk_pack_index(self):
1152
1362
        """Iterate over the contents of the pack-names index.
1153
 
 
 
1363
        
1154
1364
        This is used when loading the list from disk, and before writing to
1155
1365
        detect updates from others during our write operation.
1156
1366
        :return: An iterator of the index contents.
1157
1367
        """
1158
 
        return self._index_class(self.transport, 'pack-names', None
 
1368
        return GraphIndex(self.transport, 'pack-names', None
1159
1369
                ).iter_all_entries()
1160
1370
 
1161
 
    def _make_index(self, name, suffix, resume=False, is_chk=False):
 
1371
    def _make_index(self, name, suffix):
1162
1372
        size_offset = self._suffix_offsets[suffix]
1163
1373
        index_name = name + suffix
1164
 
        if resume:
1165
 
            transport = self._upload_transport
1166
 
            index_size = transport.stat(index_name).st_size
1167
 
        else:
1168
 
            transport = self._index_transport
1169
 
            index_size = self._names[name][size_offset]
1170
 
        index = self._index_class(transport, index_name, index_size,
1171
 
                                  unlimited_cache=is_chk)
1172
 
        if is_chk and self._index_class is btree_index.BTreeGraphIndex: 
1173
 
            index._leaf_factory = btree_index._gcchk_factory
1174
 
        return index
 
1374
        index_size = self._names[name][size_offset]
 
1375
        return GraphIndex(
 
1376
            self._index_transport, index_name, index_size)
1175
1377
 
1176
1378
    def _max_pack_count(self, total_revisions):
1177
1379
        """Return the maximum number of packs to use for total revisions.
1178
 
 
 
1380
        
1179
1381
        :param total_revisions: The total number of revisions in the
1180
1382
            repository.
1181
1383
        """
1205
1407
        :param return: None.
1206
1408
        """
1207
1409
        for pack in packs:
1208
 
            try:
1209
 
                pack.pack_transport.rename(pack.file_name(),
1210
 
                    '../obsolete_packs/' + pack.file_name())
1211
 
            except (errors.PathError, errors.TransportError), e:
1212
 
                # TODO: Should these be warnings or mutters?
1213
 
                mutter("couldn't rename obsolete pack, skipping it:\n%s"
1214
 
                       % (e,))
 
1410
            pack.pack_transport.rename(pack.file_name(),
 
1411
                '../obsolete_packs/' + pack.file_name())
1215
1412
            # TODO: Probably needs to know all possible indices for this pack
1216
1413
            # - or maybe list the directory and move all indices matching this
1217
1414
            # name whether we recognize it or not?
1218
 
            suffixes = ['.iix', '.six', '.tix', '.rix']
1219
 
            if self.chk_index is not None:
1220
 
                suffixes.append('.cix')
1221
 
            for suffix in suffixes:
1222
 
                try:
1223
 
                    self._index_transport.rename(pack.name + suffix,
1224
 
                        '../obsolete_packs/' + pack.name + suffix)
1225
 
                except (errors.PathError, errors.TransportError), e:
1226
 
                    mutter("couldn't rename obsolete index, skipping it:\n%s"
1227
 
                           % (e,))
 
1415
            for suffix in ('.iix', '.six', '.tix', '.rix'):
 
1416
                self._index_transport.rename(pack.name + suffix,
 
1417
                    '../obsolete_packs/' + pack.name + suffix)
1228
1418
 
1229
1419
    def pack_distribution(self, total_revisions):
1230
1420
        """Generate a list of the number of revisions to put in each pack.
1248
1438
 
1249
1439
    def _remove_pack_from_memory(self, pack):
1250
1440
        """Remove pack from the packs accessed by this repository.
1251
 
 
 
1441
        
1252
1442
        Only affects memory state, until self._save_pack_names() is invoked.
1253
1443
        """
1254
1444
        self._names.pop(pack.name)
1255
1445
        self._packs_by_name.pop(pack.name)
1256
1446
        self._remove_pack_indices(pack)
1257
 
        self.packs.remove(pack)
1258
1447
 
1259
 
    def _remove_pack_indices(self, pack, ignore_missing=False):
1260
 
        """Remove the indices for pack from the aggregated indices.
1261
 
        
1262
 
        :param ignore_missing: Suppress KeyErrors from calling remove_index.
1263
 
        """
1264
 
        for index_type in Pack.index_definitions.keys():
1265
 
            attr_name = index_type + '_index'
1266
 
            aggregate_index = getattr(self, attr_name)
1267
 
            if aggregate_index is not None:
1268
 
                pack_index = getattr(pack, attr_name)
1269
 
                try:
1270
 
                    aggregate_index.remove_index(pack_index)
1271
 
                except KeyError:
1272
 
                    if ignore_missing:
1273
 
                        continue
1274
 
                    raise
 
1448
    def _remove_pack_indices(self, pack):
 
1449
        """Remove the indices for pack from the aggregated indices."""
 
1450
        self.revision_index.remove_index(pack.revision_index, pack)
 
1451
        self.inventory_index.remove_index(pack.inventory_index, pack)
 
1452
        self.text_index.remove_index(pack.text_index, pack)
 
1453
        self.signature_index.remove_index(pack.signature_index, pack)
1275
1454
 
1276
1455
    def reset(self):
1277
1456
        """Clear all cached data."""
1278
1457
        # cached revision data
 
1458
        self.repo._revision_knit = None
1279
1459
        self.revision_index.clear()
1280
1460
        # cached signature data
 
1461
        self.repo._signature_knit = None
1281
1462
        self.signature_index.clear()
1282
1463
        # cached file text data
1283
1464
        self.text_index.clear()
 
1465
        self.repo._text_knit = None
1284
1466
        # cached inventory data
1285
1467
        self.inventory_index.clear()
1286
 
        # cached chk data
1287
 
        if self.chk_index is not None:
1288
 
            self.chk_index.clear()
1289
1468
        # remove the open pack
1290
1469
        self._new_pack = None
1291
1470
        # information about packs.
1294
1473
        self._packs_by_name = {}
1295
1474
        self._packs_at_load = None
1296
1475
 
 
1476
    def _make_index_map(self, index_suffix):
 
1477
        """Return information on existing indices.
 
1478
 
 
1479
        :param suffix: Index suffix added to pack name.
 
1480
 
 
1481
        :returns: (pack_map, indices) where indices is a list of GraphIndex 
 
1482
        objects, and pack_map is a mapping from those objects to the 
 
1483
        pack tuple they describe.
 
1484
        """
 
1485
        # TODO: stop using this; it creates new indices unnecessarily.
 
1486
        self.ensure_loaded()
 
1487
        suffix_map = {'.rix': 'revision_index',
 
1488
            '.six': 'signature_index',
 
1489
            '.iix': 'inventory_index',
 
1490
            '.tix': 'text_index',
 
1491
        }
 
1492
        return self._packs_list_to_pack_map_and_index_list(self.all_packs(),
 
1493
            suffix_map[index_suffix])
 
1494
 
 
1495
    def _packs_list_to_pack_map_and_index_list(self, packs, index_attribute):
 
1496
        """Convert a list of packs to an index pack map and index list.
 
1497
 
 
1498
        :param packs: The packs list to process.
 
1499
        :param index_attribute: The attribute that the desired index is found
 
1500
            on.
 
1501
        :return: A tuple (map, list) where map contains the dict from
 
1502
            index:pack_tuple, and lsit contains the indices in the same order
 
1503
            as the packs list.
 
1504
        """
 
1505
        indices = []
 
1506
        pack_map = {}
 
1507
        for pack in packs:
 
1508
            index = getattr(pack, index_attribute)
 
1509
            indices.append(index)
 
1510
            pack_map[index] = (pack.pack_transport, pack.file_name())
 
1511
        return pack_map, indices
 
1512
 
 
1513
    def _index_contents(self, pack_map, key_filter=None):
 
1514
        """Get an iterable of the index contents from a pack_map.
 
1515
 
 
1516
        :param pack_map: A map from indices to pack details.
 
1517
        :param key_filter: An optional filter to limit the
 
1518
            keys returned.
 
1519
        """
 
1520
        indices = [index for index in pack_map.iterkeys()]
 
1521
        all_index = CombinedGraphIndex(indices)
 
1522
        if key_filter is None:
 
1523
            return all_index.iter_all_entries()
 
1524
        else:
 
1525
            return all_index.iter_entries(key_filter)
 
1526
 
1297
1527
    def _unlock_names(self):
1298
1528
        """Release the mutex around the pack-names index."""
1299
1529
        self.repo.control_files.unlock()
1300
1530
 
1301
 
    def _diff_pack_names(self):
1302
 
        """Read the pack names from disk, and compare it to the one in memory.
1303
 
 
1304
 
        :return: (disk_nodes, deleted_nodes, new_nodes)
1305
 
            disk_nodes    The final set of nodes that should be referenced
1306
 
            deleted_nodes Nodes which have been removed from when we started
1307
 
            new_nodes     Nodes that are newly introduced
1308
 
        """
1309
 
        # load the disk nodes across
1310
 
        disk_nodes = set()
1311
 
        for index, key, value in self._iter_disk_pack_index():
1312
 
            disk_nodes.add((key, value))
1313
 
        orig_disk_nodes = set(disk_nodes)
1314
 
 
1315
 
        # do a two-way diff against our original content
1316
 
        current_nodes = set()
1317
 
        for name, sizes in self._names.iteritems():
1318
 
            current_nodes.add(
1319
 
                ((name, ), ' '.join(str(size) for size in sizes)))
1320
 
 
1321
 
        # Packs no longer present in the repository, which were present when we
1322
 
        # locked the repository
1323
 
        deleted_nodes = self._packs_at_load - current_nodes
1324
 
        # Packs which this process is adding
1325
 
        new_nodes = current_nodes - self._packs_at_load
1326
 
 
1327
 
        # Update the disk_nodes set to include the ones we are adding, and
1328
 
        # remove the ones which were removed by someone else
1329
 
        disk_nodes.difference_update(deleted_nodes)
1330
 
        disk_nodes.update(new_nodes)
1331
 
 
1332
 
        return disk_nodes, deleted_nodes, new_nodes, orig_disk_nodes
1333
 
 
1334
 
    def _syncronize_pack_names_from_disk_nodes(self, disk_nodes):
1335
 
        """Given the correct set of pack files, update our saved info.
1336
 
 
1337
 
        :return: (removed, added, modified)
1338
 
            removed     pack names removed from self._names
1339
 
            added       pack names added to self._names
1340
 
            modified    pack names that had changed value
1341
 
        """
1342
 
        removed = []
1343
 
        added = []
1344
 
        modified = []
1345
 
        ## self._packs_at_load = disk_nodes
 
1531
    def _save_pack_names(self, clear_obsolete_packs=False):
 
1532
        """Save the list of packs.
 
1533
 
 
1534
        This will take out the mutex around the pack names list for the
 
1535
        duration of the method call. If concurrent updates have been made, a
 
1536
        three-way merge between the current list and the current in memory list
 
1537
        is performed.
 
1538
 
 
1539
        :param clear_obsolete_packs: If True, clear out the contents of the
 
1540
            obsolete_packs directory.
 
1541
        """
 
1542
        self.lock_names()
 
1543
        try:
 
1544
            builder = GraphIndexBuilder()
 
1545
            # load the disk nodes across
 
1546
            disk_nodes = set()
 
1547
            for index, key, value in self._iter_disk_pack_index():
 
1548
                disk_nodes.add((key, value))
 
1549
            # do a two-way diff against our original content
 
1550
            current_nodes = set()
 
1551
            for name, sizes in self._names.iteritems():
 
1552
                current_nodes.add(
 
1553
                    ((name, ), ' '.join(str(size) for size in sizes)))
 
1554
            deleted_nodes = self._packs_at_load - current_nodes
 
1555
            new_nodes = current_nodes - self._packs_at_load
 
1556
            disk_nodes.difference_update(deleted_nodes)
 
1557
            disk_nodes.update(new_nodes)
 
1558
            # TODO: handle same-name, index-size-changes here - 
 
1559
            # e.g. use the value from disk, not ours, *unless* we're the one
 
1560
            # changing it.
 
1561
            for key, value in disk_nodes:
 
1562
                builder.add_node(key, value)
 
1563
            self.transport.put_file('pack-names', builder.finish(),
 
1564
                mode=self.repo.bzrdir._get_file_mode())
 
1565
            # move the baseline forward
 
1566
            self._packs_at_load = disk_nodes
 
1567
            if clear_obsolete_packs:
 
1568
                self._clear_obsolete_packs()
 
1569
        finally:
 
1570
            self._unlock_names()
 
1571
        # synchronise the memory packs list with what we just wrote:
1346
1572
        new_names = dict(disk_nodes)
1347
1573
        # drop no longer present nodes
1348
1574
        for pack in self.all_packs():
1349
1575
            if (pack.name,) not in new_names:
1350
 
                removed.append(pack.name)
1351
1576
                self._remove_pack_from_memory(pack)
1352
1577
        # add new nodes/refresh existing ones
1353
1578
        for key, value in disk_nodes:
1363
1588
                    # disk index because the set values are the same, unless
1364
1589
                    # the only index shows up as deleted by the set difference
1365
1590
                    # - which it may. Until there is a specific test for this,
1366
 
                    # assume it's broken. RBC 20071017.
 
1591
                    # assume its broken. RBC 20071017.
1367
1592
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
1368
1593
                    self._names[name] = sizes
1369
1594
                    self.get_pack_by_name(name)
1370
 
                    modified.append(name)
1371
1595
            else:
1372
1596
                # new
1373
1597
                self._names[name] = sizes
1374
1598
                self.get_pack_by_name(name)
1375
 
                added.append(name)
1376
 
        return removed, added, modified
1377
 
 
1378
 
    def _save_pack_names(self, clear_obsolete_packs=False, obsolete_packs=None):
1379
 
        """Save the list of packs.
1380
 
 
1381
 
        This will take out the mutex around the pack names list for the
1382
 
        duration of the method call. If concurrent updates have been made, a
1383
 
        three-way merge between the current list and the current in memory list
1384
 
        is performed.
1385
 
 
1386
 
        :param clear_obsolete_packs: If True, clear out the contents of the
1387
 
            obsolete_packs directory.
1388
 
        :param obsolete_packs: Packs that are obsolete once the new pack-names
1389
 
            file has been written.
1390
 
        :return: A list of the names saved that were not previously on disk.
1391
 
        """
1392
 
        already_obsolete = []
1393
 
        self.lock_names()
1394
 
        try:
1395
 
            builder = self._index_builder_class()
1396
 
            (disk_nodes, deleted_nodes, new_nodes,
1397
 
             orig_disk_nodes) = self._diff_pack_names()
1398
 
            # TODO: handle same-name, index-size-changes here -
1399
 
            # e.g. use the value from disk, not ours, *unless* we're the one
1400
 
            # changing it.
1401
 
            for key, value in disk_nodes:
1402
 
                builder.add_node(key, value)
1403
 
            self.transport.put_file('pack-names', builder.finish(),
1404
 
                mode=self.repo.bzrdir._get_file_mode())
1405
 
            self._packs_at_load = disk_nodes
1406
 
            if clear_obsolete_packs:
1407
 
                to_preserve = None
1408
 
                if obsolete_packs:
1409
 
                    to_preserve = set([o.name for o in obsolete_packs])
1410
 
                already_obsolete = self._clear_obsolete_packs(to_preserve)
1411
 
        finally:
1412
 
            self._unlock_names()
1413
 
        # synchronise the memory packs list with what we just wrote:
1414
 
        self._syncronize_pack_names_from_disk_nodes(disk_nodes)
1415
 
        if obsolete_packs:
1416
 
            # TODO: We could add one more condition here. "if o.name not in
1417
 
            #       orig_disk_nodes and o != the new_pack we haven't written to
1418
 
            #       disk yet. However, the new pack object is not easily
1419
 
            #       accessible here (it would have to be passed through the
1420
 
            #       autopacking code, etc.)
1421
 
            obsolete_packs = [o for o in obsolete_packs
1422
 
                              if o.name not in already_obsolete]
1423
 
            self._obsolete_packs(obsolete_packs)
1424
 
        return [new_node[0][0] for new_node in new_nodes]
1425
 
 
1426
 
    def reload_pack_names(self):
1427
 
        """Sync our pack listing with what is present in the repository.
1428
 
 
1429
 
        This should be called when we find out that something we thought was
1430
 
        present is now missing. This happens when another process re-packs the
1431
 
        repository, etc.
1432
 
 
1433
 
        :return: True if the in-memory list of packs has been altered at all.
1434
 
        """
1435
 
        # The ensure_loaded call is to handle the case where the first call
1436
 
        # made involving the collection was to reload_pack_names, where we 
1437
 
        # don't have a view of disk contents. It's a bit of a bandaid, and
1438
 
        # causes two reads of pack-names, but it's a rare corner case not
1439
 
        # struck with regular push/pull etc.
1440
 
        first_read = self.ensure_loaded()
1441
 
        if first_read:
1442
 
            return True
1443
 
        # out the new value.
1444
 
        (disk_nodes, deleted_nodes, new_nodes,
1445
 
         orig_disk_nodes) = self._diff_pack_names()
1446
 
        # _packs_at_load is meant to be the explicit list of names in
1447
 
        # 'pack-names' at then start. As such, it should not contain any
1448
 
        # pending names that haven't been written out yet.
1449
 
        self._packs_at_load = orig_disk_nodes
1450
 
        (removed, added,
1451
 
         modified) = self._syncronize_pack_names_from_disk_nodes(disk_nodes)
1452
 
        if removed or added or modified:
1453
 
            return True
1454
 
        return False
1455
 
 
1456
 
    def _restart_autopack(self):
1457
 
        """Reload the pack names list, and restart the autopack code."""
1458
 
        if not self.reload_pack_names():
1459
 
            # Re-raise the original exception, because something went missing
1460
 
            # and a restart didn't find it
1461
 
            raise
1462
 
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1463
 
 
1464
 
    def _restart_pack_operations(self):
1465
 
        """Reload the pack names list, and restart the autopack code."""
1466
 
        if not self.reload_pack_names():
1467
 
            # Re-raise the original exception, because something went missing
1468
 
            # and a restart didn't find it
1469
 
            raise
1470
 
        raise RetryPackOperations(self.repo, False, sys.exc_info())
1471
 
 
1472
 
    def _clear_obsolete_packs(self, preserve=None):
 
1599
 
 
1600
    def _clear_obsolete_packs(self):
1473
1601
        """Delete everything from the obsolete-packs directory.
1474
 
 
1475
 
        :return: A list of pack identifiers (the filename without '.pack') that
1476
 
            were found in obsolete_packs.
1477
1602
        """
1478
 
        found = []
1479
1603
        obsolete_pack_transport = self.transport.clone('obsolete_packs')
1480
 
        if preserve is None:
1481
 
            preserve = set()
1482
1604
        for filename in obsolete_pack_transport.list_dir('.'):
1483
 
            name, ext = osutils.splitext(filename)
1484
 
            if ext == '.pack':
1485
 
                found.append(name)
1486
 
            if name in preserve:
1487
 
                continue
1488
1605
            try:
1489
1606
                obsolete_pack_transport.delete(filename)
1490
1607
            except (errors.PathError, errors.TransportError), e:
1491
 
                warning("couldn't delete obsolete pack, skipping it:\n%s"
1492
 
                        % (e,))
1493
 
        return found
 
1608
                warning("couldn't delete obsolete pack, skipping it:\n%s" % (e,))
1494
1609
 
1495
1610
    def _start_write_group(self):
1496
1611
        # Do not permit preparation for writing if we're not in a 'write lock'.
1497
1612
        if not self.repo.is_write_locked():
1498
1613
            raise errors.NotWriteLocked(self)
1499
 
        self._new_pack = self.pack_factory(self, upload_suffix='.pack',
 
1614
        self._new_pack = NewPack(self._upload_transport, self._index_transport,
 
1615
            self._pack_transport, upload_suffix='.pack',
1500
1616
            file_mode=self.repo.bzrdir._get_file_mode())
1501
1617
        # allow writing: queue writes to a new index
1502
1618
        self.revision_index.add_writable_index(self._new_pack.revision_index,
1505
1621
            self._new_pack)
1506
1622
        self.text_index.add_writable_index(self._new_pack.text_index,
1507
1623
            self._new_pack)
1508
 
        self._new_pack.text_index.set_optimize(combine_backing_indices=False)
1509
1624
        self.signature_index.add_writable_index(self._new_pack.signature_index,
1510
1625
            self._new_pack)
1511
 
        if self.chk_index is not None:
1512
 
            self.chk_index.add_writable_index(self._new_pack.chk_index,
1513
 
                self._new_pack)
1514
 
            self.repo.chk_bytes._index._add_callback = self.chk_index.add_callback
1515
 
            self._new_pack.chk_index.set_optimize(combine_backing_indices=False)
1516
1626
 
1517
1627
        self.repo.inventories._index._add_callback = self.inventory_index.add_callback
1518
1628
        self.repo.revisions._index._add_callback = self.revision_index.add_callback
1523
1633
        # FIXME: just drop the transient index.
1524
1634
        # forget what names there are
1525
1635
        if self._new_pack is not None:
1526
 
            operation = cleanup.OperationWithCleanups(self._new_pack.abort)
1527
 
            operation.add_cleanup(setattr, self, '_new_pack', None)
1528
 
            # If we aborted while in the middle of finishing the write
1529
 
            # group, _remove_pack_indices could fail because the indexes are
1530
 
            # already gone.  But they're not there we shouldn't fail in this
1531
 
            # case, so we pass ignore_missing=True.
1532
 
            operation.add_cleanup(self._remove_pack_indices, self._new_pack,
1533
 
                ignore_missing=True)
1534
 
            operation.run_simple()
1535
 
        for resumed_pack in self._resumed_packs:
1536
 
            operation = cleanup.OperationWithCleanups(resumed_pack.abort)
1537
 
            # See comment in previous finally block.
1538
 
            operation.add_cleanup(self._remove_pack_indices, resumed_pack,
1539
 
                ignore_missing=True)
1540
 
            operation.run_simple()
1541
 
        del self._resumed_packs[:]
1542
 
 
1543
 
    def _remove_resumed_pack_indices(self):
1544
 
        for resumed_pack in self._resumed_packs:
1545
 
            self._remove_pack_indices(resumed_pack)
1546
 
        del self._resumed_packs[:]
1547
 
 
1548
 
    def _check_new_inventories(self):
1549
 
        """Detect missing inventories in this write group.
1550
 
 
1551
 
        :returns: list of strs, summarising any problems found.  If the list is
1552
 
            empty no problems were found.
1553
 
        """
1554
 
        # The base implementation does no checks.  GCRepositoryPackCollection
1555
 
        # overrides this.
1556
 
        return []
1557
 
        
 
1636
            self._new_pack.abort()
 
1637
            self._remove_pack_indices(self._new_pack)
 
1638
            self._new_pack = None
 
1639
        self.repo._text_knit = None
 
1640
 
1558
1641
    def _commit_write_group(self):
1559
 
        all_missing = set()
1560
 
        for prefix, versioned_file in (
1561
 
                ('revisions', self.repo.revisions),
1562
 
                ('inventories', self.repo.inventories),
1563
 
                ('texts', self.repo.texts),
1564
 
                ('signatures', self.repo.signatures),
1565
 
                ):
1566
 
            missing = versioned_file.get_missing_compression_parent_keys()
1567
 
            all_missing.update([(prefix,) + key for key in missing])
1568
 
        if all_missing:
1569
 
            raise errors.BzrCheckError(
1570
 
                "Repository %s has missing compression parent(s) %r "
1571
 
                 % (self.repo, sorted(all_missing)))
1572
 
        problems = self._check_new_inventories()
1573
 
        if problems:
1574
 
            problems_summary = '\n'.join(problems)
1575
 
            raise errors.BzrCheckError(
1576
 
                "Cannot add revision(s) to repository: " + problems_summary)
1577
1642
        self._remove_pack_indices(self._new_pack)
1578
 
        any_new_content = False
1579
1643
        if self._new_pack.data_inserted():
1580
1644
            # get all the data to disk and read to use
1581
1645
            self._new_pack.finish()
1582
1646
            self.allocate(self._new_pack)
1583
1647
            self._new_pack = None
1584
 
            any_new_content = True
1585
 
        else:
1586
 
            self._new_pack.abort()
1587
 
            self._new_pack = None
1588
 
        for resumed_pack in self._resumed_packs:
1589
 
            # XXX: this is a pretty ugly way to turn the resumed pack into a
1590
 
            # properly committed pack.
1591
 
            self._names[resumed_pack.name] = None
1592
 
            self._remove_pack_from_memory(resumed_pack)
1593
 
            resumed_pack.finish()
1594
 
            self.allocate(resumed_pack)
1595
 
            any_new_content = True
1596
 
        del self._resumed_packs[:]
1597
 
        if any_new_content:
1598
 
            result = self.autopack()
1599
 
            if not result:
 
1648
            if not self.autopack():
1600
1649
                # when autopack takes no steps, the names list is still
1601
1650
                # unsaved.
1602
 
                return self._save_pack_names()
1603
 
            return result
1604
 
        return []
1605
 
 
1606
 
    def _suspend_write_group(self):
1607
 
        tokens = [pack.name for pack in self._resumed_packs]
1608
 
        self._remove_pack_indices(self._new_pack)
1609
 
        if self._new_pack.data_inserted():
1610
 
            # get all the data to disk and read to use
1611
 
            self._new_pack.finish(suspend=True)
1612
 
            tokens.append(self._new_pack.name)
1613
 
            self._new_pack = None
 
1651
                self._save_pack_names()
1614
1652
        else:
1615
1653
            self._new_pack.abort()
1616
1654
            self._new_pack = None
1617
 
        self._remove_resumed_pack_indices()
1618
 
        return tokens
1619
 
 
1620
 
    def _resume_write_group(self, tokens):
1621
 
        for token in tokens:
1622
 
            self._resume_pack(token)
1623
 
 
1624
 
 
1625
 
class PackRepository(MetaDirRepository):
 
1655
        self.repo._text_knit = None
 
1656
 
 
1657
 
 
1658
class KnitPackRepository(KnitRepository):
1626
1659
    """Repository with knit objects stored inside pack containers.
1627
 
 
 
1660
    
1628
1661
    The layering for a KnitPackRepository is:
1629
1662
 
1630
1663
    Graph        |  HPSS    | Repository public layer |
1631
1664
    ===================================================
1632
1665
    Tuple based apis below, string based, and key based apis above
1633
1666
    ---------------------------------------------------
1634
 
    VersionedFiles
 
1667
    KnitVersionedFiles
1635
1668
      Provides .texts, .revisions etc
1636
1669
      This adapts the N-tuple keys to physical knit records which only have a
1637
1670
      single string identifier (for historical reasons), which in older formats
1644
1677
      pack file. The GraphIndex layer works in N-tuples and is unaware of any
1645
1678
      semantic value.
1646
1679
    ===================================================
1647
 
 
 
1680
    
1648
1681
    """
1649
1682
 
1650
 
    # These attributes are inherited from the Repository base class. Setting
1651
 
    # them to None ensures that if the constructor is changed to not initialize
1652
 
    # them, or a subclass fails to call the constructor, that an error will
1653
 
    # occur rather than the system working but generating incorrect data.
1654
 
    _commit_builder_class = None
1655
 
    _serializer = None
1656
 
 
1657
1683
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
1658
1684
        _serializer):
1659
 
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
1660
 
        self._commit_builder_class = _commit_builder_class
1661
 
        self._serializer = _serializer
 
1685
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
1686
            _commit_builder_class, _serializer)
 
1687
        index_transport = self._transport.clone('indices')
 
1688
        self._pack_collection = RepositoryPackCollection(self, self._transport,
 
1689
            index_transport,
 
1690
            self._transport.clone('upload'),
 
1691
            self._transport.clone('packs'))
 
1692
        self.inventories = KnitVersionedFiles(
 
1693
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
 
1694
                add_callback=self._pack_collection.inventory_index.add_callback,
 
1695
                deltas=True, parents=True, is_locked=self.is_locked),
 
1696
            data_access=self._pack_collection.inventory_index.data_access,
 
1697
            max_delta_chain=200)
 
1698
        self.revisions = KnitVersionedFiles(
 
1699
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
 
1700
                add_callback=self._pack_collection.revision_index.add_callback,
 
1701
                deltas=False, parents=True, is_locked=self.is_locked),
 
1702
            data_access=self._pack_collection.revision_index.data_access,
 
1703
            max_delta_chain=0)
 
1704
        self.signatures = KnitVersionedFiles(
 
1705
            _KnitGraphIndex(self._pack_collection.signature_index.combined_index,
 
1706
                add_callback=self._pack_collection.signature_index.add_callback,
 
1707
                deltas=False, parents=False, is_locked=self.is_locked),
 
1708
            data_access=self._pack_collection.signature_index.data_access,
 
1709
            max_delta_chain=0)
 
1710
        self.texts = KnitVersionedFiles(
 
1711
            _KnitGraphIndex(self._pack_collection.text_index.combined_index,
 
1712
                add_callback=self._pack_collection.text_index.add_callback,
 
1713
                deltas=True, parents=True, is_locked=self.is_locked),
 
1714
            data_access=self._pack_collection.text_index.data_access,
 
1715
            max_delta_chain=200)
 
1716
        # True when the repository object is 'write locked' (as opposed to the
 
1717
        # physical lock only taken out around changes to the pack-names list.) 
 
1718
        # Another way to represent this would be a decorator around the control
 
1719
        # files object that presents logical locks as physical ones - if this
 
1720
        # gets ugly consider that alternative design. RBC 20071011
 
1721
        self._write_lock_count = 0
 
1722
        self._transaction = None
 
1723
        # for tests
 
1724
        self._reconcile_does_inventory_gc = True
1662
1725
        self._reconcile_fixes_text_parents = True
1663
 
 
1664
 
    @needs_read_lock
1665
 
    def _all_revision_ids(self):
1666
 
        """See Repository.all_revision_ids()."""
1667
 
        return [key[0] for key in self.revisions.keys()]
 
1726
        self._reconcile_backsup_inventory = False
1668
1727
 
1669
1728
    def _abort_write_group(self):
1670
 
        self.revisions._index._key_dependencies.clear()
1671
1729
        self._pack_collection._abort_write_group()
1672
1730
 
 
1731
    def _find_inconsistent_revision_parents(self):
 
1732
        """Find revisions with incorrectly cached parents.
 
1733
 
 
1734
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
 
1735
            parents-in-revision).
 
1736
        """
 
1737
        if not self.is_locked():
 
1738
            raise errors.ObjectNotLocked(self)
 
1739
        pb = ui.ui_factory.nested_progress_bar()
 
1740
        result = []
 
1741
        try:
 
1742
            revision_nodes = self._pack_collection.revision_index \
 
1743
                .combined_index.iter_all_entries()
 
1744
            index_positions = []
 
1745
            # Get the cached index values for all revisions, and also the location
 
1746
            # in each index of the revision text so we can perform linear IO.
 
1747
            for index, key, value, refs in revision_nodes:
 
1748
                pos, length = value[1:].split(' ')
 
1749
                index_positions.append((index, int(pos), key[0],
 
1750
                    tuple(parent[0] for parent in refs[0])))
 
1751
                pb.update("Reading revision index.", 0, 0)
 
1752
            index_positions.sort()
 
1753
            batch_count = len(index_positions) / 1000 + 1
 
1754
            pb.update("Checking cached revision graph.", 0, batch_count)
 
1755
            for offset in xrange(batch_count):
 
1756
                pb.update("Checking cached revision graph.", offset)
 
1757
                to_query = index_positions[offset * 1000:(offset + 1) * 1000]
 
1758
                if not to_query:
 
1759
                    break
 
1760
                rev_ids = [item[2] for item in to_query]
 
1761
                revs = self.get_revisions(rev_ids)
 
1762
                for revision, item in zip(revs, to_query):
 
1763
                    index_parents = item[3]
 
1764
                    rev_parents = tuple(revision.parent_ids)
 
1765
                    if index_parents != rev_parents:
 
1766
                        result.append((revision.revision_id, index_parents, rev_parents))
 
1767
        finally:
 
1768
            pb.finished()
 
1769
        return result
 
1770
 
 
1771
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
 
1772
    def get_parents(self, revision_ids):
 
1773
        """See graph._StackedParentsProvider.get_parents."""
 
1774
        parent_map = self.get_parent_map(revision_ids)
 
1775
        return [parent_map.get(r, None) for r in revision_ids]
 
1776
 
 
1777
    def get_parent_map(self, keys):
 
1778
        """See graph._StackedParentsProvider.get_parent_map
 
1779
 
 
1780
        This implementation accesses the combined revision index to provide
 
1781
        answers.
 
1782
        """
 
1783
        self._pack_collection.ensure_loaded()
 
1784
        index = self._pack_collection.revision_index.combined_index
 
1785
        keys = set(keys)
 
1786
        if None in keys:
 
1787
            raise ValueError('get_parent_map(None) is not valid')
 
1788
        if _mod_revision.NULL_REVISION in keys:
 
1789
            keys.discard(_mod_revision.NULL_REVISION)
 
1790
            found_parents = {_mod_revision.NULL_REVISION:()}
 
1791
        else:
 
1792
            found_parents = {}
 
1793
        search_keys = set((revision_id,) for revision_id in keys)
 
1794
        for index, key, value, refs in index.iter_entries(search_keys):
 
1795
            parents = refs[0]
 
1796
            if not parents:
 
1797
                parents = (_mod_revision.NULL_REVISION,)
 
1798
            else:
 
1799
                parents = tuple(parent[0] for parent in parents)
 
1800
            found_parents[key[0]] = parents
 
1801
        return found_parents
 
1802
 
 
1803
    def has_revisions(self, revision_ids):
 
1804
        """See Repository.has_revisions()."""
 
1805
        revision_ids = set(revision_ids)
 
1806
        result = revision_ids.intersection(
 
1807
            set([None, _mod_revision.NULL_REVISION]))
 
1808
        revision_ids.difference_update(result)
 
1809
        index = self._pack_collection.revision_index.combined_index
 
1810
        keys = [(revision_id,) for revision_id in revision_ids]
 
1811
        result.update(node[1][0] for node in index.iter_entries(keys))
 
1812
        return result
 
1813
 
1673
1814
    def _make_parents_provider(self):
1674
1815
        return graph.CachingParentsProvider(self)
1675
1816
 
1676
1817
    def _refresh_data(self):
1677
 
        if not self.is_locked():
1678
 
            return
1679
 
        self._pack_collection.reload_pack_names()
 
1818
        if self._write_lock_count == 1 or (
 
1819
            self.control_files._lock_count == 1 and
 
1820
            self.control_files._lock_mode == 'r'):
 
1821
            # forget what names there are
 
1822
            self._pack_collection.reset()
 
1823
            # XXX: Better to do an in-memory merge when acquiring a new lock -
 
1824
            # factor out code from _save_pack_names.
 
1825
            self._pack_collection.ensure_loaded()
1680
1826
 
1681
1827
    def _start_write_group(self):
1682
1828
        self._pack_collection._start_write_group()
1683
1829
 
1684
1830
    def _commit_write_group(self):
1685
 
        hint = self._pack_collection._commit_write_group()
1686
 
        self.revisions._index._key_dependencies.clear()
1687
 
        return hint
1688
 
 
1689
 
    def suspend_write_group(self):
1690
 
        # XXX check self._write_group is self.get_transaction()?
1691
 
        tokens = self._pack_collection._suspend_write_group()
1692
 
        self.revisions._index._key_dependencies.clear()
1693
 
        self._write_group = None
1694
 
        return tokens
1695
 
 
1696
 
    def _resume_write_group(self, tokens):
1697
 
        self._start_write_group()
1698
 
        try:
1699
 
            self._pack_collection._resume_write_group(tokens)
1700
 
        except errors.UnresumableWriteGroup:
1701
 
            self._abort_write_group()
1702
 
            raise
1703
 
        for pack in self._pack_collection._resumed_packs:
1704
 
            self.revisions._index.scan_unvalidated_index(pack.revision_index)
 
1831
        return self._pack_collection._commit_write_group()
1705
1832
 
1706
1833
    def get_transaction(self):
1707
1834
        if self._write_lock_count:
1716
1843
        return self._write_lock_count
1717
1844
 
1718
1845
    def lock_write(self, token=None):
1719
 
        """Lock the repository for writes.
1720
 
 
1721
 
        :return: A bzrlib.repository.RepositoryWriteLockResult.
1722
 
        """
1723
 
        locked = self.is_locked()
1724
 
        if not self._write_lock_count and locked:
 
1846
        if not self._write_lock_count and self.is_locked():
1725
1847
            raise errors.ReadOnlyError(self)
1726
1848
        self._write_lock_count += 1
1727
1849
        if self._write_lock_count == 1:
 
1850
            from bzrlib import transactions
1728
1851
            self._transaction = transactions.WriteTransaction()
1729
 
        if not locked:
1730
 
            if 'relock' in debug.debug_flags and self._prev_lock == 'w':
1731
 
                note('%r was write locked again', self)
1732
 
            self._prev_lock = 'w'
1733
 
            for repo in self._fallback_repositories:
1734
 
                # Writes don't affect fallback repos
1735
 
                repo.lock_read()
1736
 
            self._refresh_data()
1737
 
        return RepositoryWriteLockResult(self.unlock, None)
 
1852
        self._refresh_data()
1738
1853
 
1739
1854
    def lock_read(self):
1740
 
        """Lock the repository for reads.
1741
 
 
1742
 
        :return: A bzrlib.lock.LogicalLockResult.
1743
 
        """
1744
 
        locked = self.is_locked()
1745
1855
        if self._write_lock_count:
1746
1856
            self._write_lock_count += 1
1747
1857
        else:
1748
1858
            self.control_files.lock_read()
1749
 
        if not locked:
1750
 
            if 'relock' in debug.debug_flags and self._prev_lock == 'r':
1751
 
                note('%r was read locked again', self)
1752
 
            self._prev_lock = 'r'
1753
 
            for repo in self._fallback_repositories:
1754
 
                repo.lock_read()
1755
 
            self._refresh_data()
1756
 
        return LogicalLockResult(self.unlock)
 
1859
        self._refresh_data()
1757
1860
 
1758
1861
    def leave_lock_in_place(self):
1759
1862
        # not supported - raise an error
1764
1867
        raise NotImplementedError(self.dont_leave_lock_in_place)
1765
1868
 
1766
1869
    @needs_write_lock
1767
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1870
    def pack(self):
1768
1871
        """Compress the data within the repository.
1769
1872
 
1770
1873
        This will pack all the data to a single pack. In future it may
1771
1874
        recompress deltas or do other such expensive operations.
1772
1875
        """
1773
 
        self._pack_collection.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
1876
        self._pack_collection.pack()
1774
1877
 
1775
1878
    @needs_write_lock
1776
1879
    def reconcile(self, other=None, thorough=False):
1780
1883
        reconciler.reconcile()
1781
1884
        return reconciler
1782
1885
 
1783
 
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
1784
 
        raise NotImplementedError(self._reconcile_pack)
1785
 
 
1786
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1787
1886
    def unlock(self):
1788
1887
        if self._write_lock_count == 1 and self._write_group is not None:
1789
1888
            self.abort_write_group()
1801
1900
        else:
1802
1901
            self.control_files.unlock()
1803
1902
 
1804
 
        if not self.is_locked():
1805
 
            for repo in self._fallback_repositories:
1806
 
                repo.unlock()
1807
 
 
1808
1903
 
1809
1904
class RepositoryFormatPack(MetaDirRepositoryFormat):
1810
1905
    """Format logic for pack structured repositories.
1831
1926
    # Set this attribute in derived clases to control the _serializer that the
1832
1927
    # repository objects will have passed to their constructor.
1833
1928
    _serializer = None
1834
 
    # Packs are not confused by ghosts.
1835
 
    supports_ghosts = True
1836
1929
    # External references are not supported in pack repositories yet.
1837
1930
    supports_external_lookups = False
1838
 
    # Most pack formats do not use chk lookups.
1839
 
    supports_chks = False
1840
 
    # What index classes to use
1841
 
    index_builder_class = None
1842
 
    index_class = None
1843
 
    _fetch_uses_deltas = True
1844
 
    fast_deltas = False
1845
 
    supports_full_versioned_files = True
1846
 
    supports_funky_characters = True
1847
 
    revision_graph_can_have_wrong_parents = True
1848
1931
 
1849
1932
    def initialize(self, a_bzrdir, shared=False):
1850
1933
        """Create a pack based repository.
1856
1939
        """
1857
1940
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1858
1941
        dirs = ['indices', 'obsolete_packs', 'packs', 'upload']
1859
 
        builder = self.index_builder_class()
 
1942
        builder = GraphIndexBuilder()
1860
1943
        files = [('pack-names', builder.finish())]
1861
1944
        utf8_files = [('format', self.get_format_string())]
1862
 
 
 
1945
        
1863
1946
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1864
 
        repository = self.open(a_bzrdir=a_bzrdir, _found=True)
1865
 
        self._run_post_repo_init_hooks(repository, a_bzrdir, shared)
1866
 
        return repository
 
1947
        return self.open(a_bzrdir=a_bzrdir, _found=True)
1867
1948
 
1868
1949
    def open(self, a_bzrdir, _found=False, _override_transport=None):
1869
1950
        """See RepositoryFormat.open().
1870
 
 
 
1951
        
1871
1952
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
1872
1953
                                    repository at a slightly different url
1873
1954
                                    than normal. I.e. during 'upgrade'.
1887
1968
                              _serializer=self._serializer)
1888
1969
 
1889
1970
 
1890
 
class RetryPackOperations(errors.RetryWithNewPacks):
1891
 
    """Raised when we are packing and we find a missing file.
1892
 
 
1893
 
    Meant as a signaling exception, to tell the RepositoryPackCollection.pack
1894
 
    code it should try again.
1895
 
    """
1896
 
 
1897
 
    internal_error = True
1898
 
 
1899
 
    _fmt = ("Pack files have changed, reload and try pack again."
1900
 
            " context: %(context)s %(orig_error)s")
1901
 
 
1902
 
 
1903
 
class _DirectPackAccess(object):
1904
 
    """Access to data in one or more packs with less translation."""
1905
 
 
1906
 
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
1907
 
        """Create a _DirectPackAccess object.
1908
 
 
1909
 
        :param index_to_packs: A dict mapping index objects to the transport
1910
 
            and file names for obtaining data.
1911
 
        :param reload_func: A function to call if we determine that the pack
1912
 
            files have moved and we need to reload our caches. See
1913
 
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
1914
 
        """
1915
 
        self._container_writer = None
1916
 
        self._write_index = None
1917
 
        self._indices = index_to_packs
1918
 
        self._reload_func = reload_func
1919
 
        self._flush_func = flush_func
1920
 
 
1921
 
    def add_raw_records(self, key_sizes, raw_data):
1922
 
        """Add raw knit bytes to a storage area.
1923
 
 
1924
 
        The data is spooled to the container writer in one bytes-record per
1925
 
        raw data item.
1926
 
 
1927
 
        :param sizes: An iterable of tuples containing the key and size of each
1928
 
            raw data segment.
1929
 
        :param raw_data: A bytestring containing the data.
1930
 
        :return: A list of memos to retrieve the record later. Each memo is an
1931
 
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
1932
 
            length), where the index field is the write_index object supplied
1933
 
            to the PackAccess object.
1934
 
        """
1935
 
        if type(raw_data) is not str:
1936
 
            raise AssertionError(
1937
 
                'data must be plain bytes was %s' % type(raw_data))
1938
 
        result = []
1939
 
        offset = 0
1940
 
        for key, size in key_sizes:
1941
 
            p_offset, p_length = self._container_writer.add_bytes_record(
1942
 
                raw_data[offset:offset+size], [])
1943
 
            offset += size
1944
 
            result.append((self._write_index, p_offset, p_length))
1945
 
        return result
1946
 
 
1947
 
    def flush(self):
1948
 
        """Flush pending writes on this access object.
1949
 
 
1950
 
        This will flush any buffered writes to a NewPack.
1951
 
        """
1952
 
        if self._flush_func is not None:
1953
 
            self._flush_func()
1954
 
 
1955
 
    def get_raw_records(self, memos_for_retrieval):
1956
 
        """Get the raw bytes for a records.
1957
 
 
1958
 
        :param memos_for_retrieval: An iterable containing the (index, pos,
1959
 
            length) memo for retrieving the bytes. The Pack access method
1960
 
            looks up the pack to use for a given record in its index_to_pack
1961
 
            map.
1962
 
        :return: An iterator over the bytes of the records.
1963
 
        """
1964
 
        # first pass, group into same-index requests
1965
 
        request_lists = []
1966
 
        current_index = None
1967
 
        for (index, offset, length) in memos_for_retrieval:
1968
 
            if current_index == index:
1969
 
                current_list.append((offset, length))
1970
 
            else:
1971
 
                if current_index is not None:
1972
 
                    request_lists.append((current_index, current_list))
1973
 
                current_index = index
1974
 
                current_list = [(offset, length)]
1975
 
        # handle the last entry
1976
 
        if current_index is not None:
1977
 
            request_lists.append((current_index, current_list))
1978
 
        for index, offsets in request_lists:
1979
 
            try:
1980
 
                transport, path = self._indices[index]
1981
 
            except KeyError:
1982
 
                # A KeyError here indicates that someone has triggered an index
1983
 
                # reload, and this index has gone missing, we need to start
1984
 
                # over.
1985
 
                if self._reload_func is None:
1986
 
                    # If we don't have a _reload_func there is nothing that can
1987
 
                    # be done
1988
 
                    raise
1989
 
                raise errors.RetryWithNewPacks(index,
1990
 
                                               reload_occurred=True,
1991
 
                                               exc_info=sys.exc_info())
1992
 
            try:
1993
 
                reader = pack.make_readv_reader(transport, path, offsets)
1994
 
                for names, read_func in reader.iter_records():
1995
 
                    yield read_func(None)
1996
 
            except errors.NoSuchFile:
1997
 
                # A NoSuchFile error indicates that a pack file has gone
1998
 
                # missing on disk, we need to trigger a reload, and start over.
1999
 
                if self._reload_func is None:
2000
 
                    raise
2001
 
                raise errors.RetryWithNewPacks(transport.abspath(path),
2002
 
                                               reload_occurred=False,
2003
 
                                               exc_info=sys.exc_info())
2004
 
 
2005
 
    def set_writer(self, writer, index, transport_packname):
2006
 
        """Set a writer to use for adding data."""
2007
 
        if index is not None:
2008
 
            self._indices[index] = transport_packname
2009
 
        self._container_writer = writer
2010
 
        self._write_index = index
2011
 
 
2012
 
    def reload_or_raise(self, retry_exc):
2013
 
        """Try calling the reload function, or re-raise the original exception.
2014
 
 
2015
 
        This should be called after _DirectPackAccess raises a
2016
 
        RetryWithNewPacks exception. This function will handle the common logic
2017
 
        of determining when the error is fatal versus being temporary.
2018
 
        It will also make sure that the original exception is raised, rather
2019
 
        than the RetryWithNewPacks exception.
2020
 
 
2021
 
        If this function returns, then the calling function should retry
2022
 
        whatever operation was being performed. Otherwise an exception will
2023
 
        be raised.
2024
 
 
2025
 
        :param retry_exc: A RetryWithNewPacks exception.
2026
 
        """
2027
 
        is_error = False
2028
 
        if self._reload_func is None:
2029
 
            is_error = True
2030
 
        elif not self._reload_func():
2031
 
            # The reload claimed that nothing changed
2032
 
            if not retry_exc.reload_occurred:
2033
 
                # If there wasn't an earlier reload, then we really were
2034
 
                # expecting to find changes. We didn't find them, so this is a
2035
 
                # hard error
2036
 
                is_error = True
2037
 
        if is_error:
2038
 
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
2039
 
            raise exc_class, exc_value, exc_traceback
2040
 
 
 
1971
class RepositoryFormatKnitPack1(RepositoryFormatPack):
 
1972
    """A no-subtrees parameterized Pack repository.
 
1973
 
 
1974
    This format was introduced in 0.92.
 
1975
    """
 
1976
 
 
1977
    repository_class = KnitPackRepository
 
1978
    _commit_builder_class = PackCommitBuilder
 
1979
    _serializer = xml5.serializer_v5
 
1980
 
 
1981
    def _get_matching_bzrdir(self):
 
1982
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
 
1983
 
 
1984
    def _ignore_setting_bzrdir(self, format):
 
1985
        pass
 
1986
 
 
1987
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
1988
 
 
1989
    def get_format_string(self):
 
1990
        """See RepositoryFormat.get_format_string()."""
 
1991
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
 
1992
 
 
1993
    def get_format_description(self):
 
1994
        """See RepositoryFormat.get_format_description()."""
 
1995
        return "Packs containing knits without subtree support"
 
1996
 
 
1997
    def check_conversion_target(self, target_format):
 
1998
        pass
 
1999
 
 
2000
 
 
2001
class RepositoryFormatKnitPack3(RepositoryFormatPack):
 
2002
    """A subtrees parameterized Pack repository.
 
2003
 
 
2004
    This repository format uses the xml7 serializer to get:
 
2005
     - support for recording full info about the tree root
 
2006
     - support for recording tree-references
 
2007
 
 
2008
    This format was introduced in 0.92.
 
2009
    """
 
2010
 
 
2011
    repository_class = KnitPackRepository
 
2012
    _commit_builder_class = PackRootCommitBuilder
 
2013
    rich_root_data = True
 
2014
    supports_tree_reference = True
 
2015
    _serializer = xml7.serializer_v7
 
2016
 
 
2017
    def _get_matching_bzrdir(self):
 
2018
        return bzrdir.format_registry.make_bzrdir(
 
2019
            'pack-0.92-subtree')
 
2020
 
 
2021
    def _ignore_setting_bzrdir(self, format):
 
2022
        pass
 
2023
 
 
2024
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2025
 
 
2026
    def check_conversion_target(self, target_format):
 
2027
        if not target_format.rich_root_data:
 
2028
            raise errors.BadConversionTarget(
 
2029
                'Does not support rich root data.', target_format)
 
2030
        if not getattr(target_format, 'supports_tree_reference', False):
 
2031
            raise errors.BadConversionTarget(
 
2032
                'Does not support nested trees', target_format)
 
2033
            
 
2034
    def get_format_string(self):
 
2035
        """See RepositoryFormat.get_format_string()."""
 
2036
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
 
2037
 
 
2038
    def get_format_description(self):
 
2039
        """See RepositoryFormat.get_format_description()."""
 
2040
        return "Packs containing knits with subtree support\n"
 
2041
 
 
2042
 
 
2043
class RepositoryFormatKnitPack4(RepositoryFormatPack):
 
2044
    """A rich-root, no subtrees parameterized Pack repository.
 
2045
 
 
2046
    This repository format uses the xml6 serializer to get:
 
2047
     - support for recording full info about the tree root
 
2048
 
 
2049
    This format was introduced in 1.0.
 
2050
    """
 
2051
 
 
2052
    repository_class = KnitPackRepository
 
2053
    _commit_builder_class = PackRootCommitBuilder
 
2054
    rich_root_data = True
 
2055
    supports_tree_reference = False
 
2056
    _serializer = xml6.serializer_v6
 
2057
 
 
2058
    def _get_matching_bzrdir(self):
 
2059
        return bzrdir.format_registry.make_bzrdir(
 
2060
            'rich-root-pack')
 
2061
 
 
2062
    def _ignore_setting_bzrdir(self, format):
 
2063
        pass
 
2064
 
 
2065
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2066
 
 
2067
    def check_conversion_target(self, target_format):
 
2068
        if not target_format.rich_root_data:
 
2069
            raise errors.BadConversionTarget(
 
2070
                'Does not support rich root data.', target_format)
 
2071
 
 
2072
    def get_format_string(self):
 
2073
        """See RepositoryFormat.get_format_string()."""
 
2074
        return ("Bazaar pack repository format 1 with rich root"
 
2075
                " (needs bzr 1.0)\n")
 
2076
 
 
2077
    def get_format_description(self):
 
2078
        """See RepositoryFormat.get_format_description()."""
 
2079
        return "Packs containing knits with rich root support\n"
 
2080
 
 
2081
 
 
2082
class RepositoryFormatPackDevelopment0(RepositoryFormatPack):
 
2083
    """A no-subtrees development repository.
 
2084
 
 
2085
    This format should be retained until the second release after bzr 1.0.
 
2086
 
 
2087
    No changes to the disk behaviour from pack-0.92.
 
2088
    """
 
2089
 
 
2090
    repository_class = KnitPackRepository
 
2091
    _commit_builder_class = PackCommitBuilder
 
2092
    _serializer = xml5.serializer_v5
 
2093
 
 
2094
    def _get_matching_bzrdir(self):
 
2095
        return bzrdir.format_registry.make_bzrdir('development0')
 
2096
 
 
2097
    def _ignore_setting_bzrdir(self, format):
 
2098
        pass
 
2099
 
 
2100
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2101
 
 
2102
    def get_format_string(self):
 
2103
        """See RepositoryFormat.get_format_string()."""
 
2104
        return "Bazaar development format 0 (needs bzr.dev from before 1.3)\n"
 
2105
 
 
2106
    def get_format_description(self):
 
2107
        """See RepositoryFormat.get_format_description()."""
 
2108
        return ("Development repository format, currently the same as "
 
2109
            "pack-0.92\n")
 
2110
 
 
2111
    def check_conversion_target(self, target_format):
 
2112
        pass
 
2113
 
 
2114
 
 
2115
class RepositoryFormatPackDevelopment0Subtree(RepositoryFormatPack):
 
2116
    """A subtrees development repository.
 
2117
 
 
2118
    This format should be retained until the second release after bzr 1.0.
 
2119
 
 
2120
    No changes to the disk behaviour from pack-0.92-subtree.
 
2121
    """
 
2122
 
 
2123
    repository_class = KnitPackRepository
 
2124
    _commit_builder_class = PackRootCommitBuilder
 
2125
    rich_root_data = True
 
2126
    supports_tree_reference = True
 
2127
    _serializer = xml7.serializer_v7
 
2128
 
 
2129
    def _get_matching_bzrdir(self):
 
2130
        return bzrdir.format_registry.make_bzrdir(
 
2131
            'development0-subtree')
 
2132
 
 
2133
    def _ignore_setting_bzrdir(self, format):
 
2134
        pass
 
2135
 
 
2136
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2137
 
 
2138
    def check_conversion_target(self, target_format):
 
2139
        if not target_format.rich_root_data:
 
2140
            raise errors.BadConversionTarget(
 
2141
                'Does not support rich root data.', target_format)
 
2142
        if not getattr(target_format, 'supports_tree_reference', False):
 
2143
            raise errors.BadConversionTarget(
 
2144
                'Does not support nested trees', target_format)
 
2145
            
 
2146
    def get_format_string(self):
 
2147
        """See RepositoryFormat.get_format_string()."""
 
2148
        return ("Bazaar development format 0 with subtree support "
 
2149
            "(needs bzr.dev from before 1.3)\n")
 
2150
 
 
2151
    def get_format_description(self):
 
2152
        """See RepositoryFormat.get_format_description()."""
 
2153
        return ("Development repository format, currently the same as "
 
2154
            "pack-0.92-subtree\n")
2041
2155
 
2042
2156