~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Robert Collins
  • Date: 2009-08-25 21:09:17 UTC
  • mto: This revision was merged to the branch mainline in revision 4650.
  • Revision ID: robertc@robertcollins.net-20090825210917-dq2i8k6n4z63pneh
Support shelve and unshelve on windows - bug 305006.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import re
 
18
import sys
16
19
 
17
20
from bzrlib.lazy_import import lazy_import
18
21
lazy_import(globals(), """
19
22
from itertools import izip
20
 
import md5
21
23
import time
22
24
 
23
25
from bzrlib import (
 
26
    chk_map,
24
27
    debug,
25
28
    graph,
 
29
    osutils,
26
30
    pack,
27
31
    transactions,
28
32
    ui,
 
33
    xml5,
 
34
    xml6,
 
35
    xml7,
29
36
    )
30
37
from bzrlib.index import (
31
 
    GraphIndex,
32
 
    GraphIndexBuilder,
33
 
    InMemoryGraphIndex,
34
38
    CombinedGraphIndex,
35
39
    GraphIndexPrefixAdapter,
36
40
    )
40
44
    _KnitGraphIndex,
41
45
    _DirectPackAccess,
42
46
    )
43
 
from bzrlib.osutils import rand_chars, split_lines
44
47
from bzrlib import tsort
45
48
""")
46
49
from bzrlib import (
48
51
    errors,
49
52
    lockable_files,
50
53
    lockdir,
51
 
    symbol_versioning,
52
 
    xml5,
53
 
    xml6,
54
 
    xml7,
 
54
    revision as _mod_revision,
55
55
    )
56
56
 
57
57
from bzrlib.decorators import needs_write_lock
 
58
from bzrlib.btree_index import (
 
59
    BTreeGraphIndex,
 
60
    BTreeBuilder,
 
61
    )
 
62
from bzrlib.index import (
 
63
    GraphIndex,
 
64
    InMemoryGraphIndex,
 
65
    )
58
66
from bzrlib.repofmt.knitrepo import KnitRepository
59
67
from bzrlib.repository import (
60
68
    CommitBuilder,
61
69
    MetaDirRepositoryFormat,
62
70
    RepositoryFormat,
63
71
    RootCommitBuilder,
 
72
    StreamSource,
64
73
    )
65
 
import bzrlib.revision as _mod_revision
66
74
from bzrlib.trace import (
67
75
    mutter,
68
76
    warning,
71
79
 
72
80
class PackCommitBuilder(CommitBuilder):
73
81
    """A subclass of CommitBuilder to add texts with pack semantics.
74
 
    
 
82
 
75
83
    Specifically this uses one knit object rather than one knit object per
76
84
    added text, reducing memory and object pressure.
77
85
    """
92
100
 
93
101
class PackRootCommitBuilder(RootCommitBuilder):
94
102
    """A subclass of RootCommitBuilder to add texts with pack semantics.
95
 
    
 
103
 
96
104
    Specifically this uses one knit object rather than one knit object per
97
105
    added text, reducing memory and object pressure.
98
106
    """
118
126
    ExistingPack and NewPack are used.
119
127
    """
120
128
 
 
129
    # A map of index 'type' to the file extension and position in the
 
130
    # index_sizes array.
 
131
    index_definitions = {
 
132
        'chk': ('.cix', 4),
 
133
        'revision': ('.rix', 0),
 
134
        'inventory': ('.iix', 1),
 
135
        'text': ('.tix', 2),
 
136
        'signature': ('.six', 3),
 
137
        }
 
138
 
121
139
    def __init__(self, revision_index, inventory_index, text_index,
122
 
        signature_index):
 
140
        signature_index, chk_index=None):
123
141
        """Create a pack instance.
124
142
 
125
143
        :param revision_index: A GraphIndex for determining what revisions are
132
150
            texts/deltas (via (fileid, revisionid) tuples).
133
151
        :param signature_index: A GraphIndex for determining what signatures are
134
152
            present in the Pack and accessing the locations of their texts.
 
153
        :param chk_index: A GraphIndex for accessing content by CHK, if the
 
154
            pack has one.
135
155
        """
136
156
        self.revision_index = revision_index
137
157
        self.inventory_index = inventory_index
138
158
        self.text_index = text_index
139
159
        self.signature_index = signature_index
 
160
        self.chk_index = chk_index
140
161
 
141
162
    def access_tuple(self):
142
163
        """Return a tuple (transport, name) for the pack content."""
143
164
        return self.pack_transport, self.file_name()
144
165
 
 
166
    def _check_references(self):
 
167
        """Make sure our external references are present.
 
168
 
 
169
        Packs are allowed to have deltas whose base is not in the pack, but it
 
170
        must be present somewhere in this collection.  It is not allowed to
 
171
        have deltas based on a fallback repository.
 
172
        (See <https://bugs.launchpad.net/bzr/+bug/288751>)
 
173
        """
 
174
        missing_items = {}
 
175
        for (index_name, external_refs, index) in [
 
176
            ('texts',
 
177
                self._get_external_refs(self.text_index),
 
178
                self._pack_collection.text_index.combined_index),
 
179
            ('inventories',
 
180
                self._get_external_refs(self.inventory_index),
 
181
                self._pack_collection.inventory_index.combined_index),
 
182
            ]:
 
183
            missing = external_refs.difference(
 
184
                k for (idx, k, v, r) in
 
185
                index.iter_entries(external_refs))
 
186
            if missing:
 
187
                missing_items[index_name] = sorted(list(missing))
 
188
        if missing_items:
 
189
            from pprint import pformat
 
190
            raise errors.BzrCheckError(
 
191
                "Newly created pack file %r has delta references to "
 
192
                "items not in its repository:\n%s"
 
193
                % (self, pformat(missing_items)))
 
194
 
145
195
    def file_name(self):
146
196
        """Get the file name for the pack on disk."""
147
197
        return self.name + '.pack'
149
199
    def get_revision_count(self):
150
200
        return self.revision_index.key_count()
151
201
 
 
202
    def index_name(self, index_type, name):
 
203
        """Get the disk name of an index type for pack name 'name'."""
 
204
        return name + Pack.index_definitions[index_type][0]
 
205
 
 
206
    def index_offset(self, index_type):
 
207
        """Get the position in a index_size array for a given index type."""
 
208
        return Pack.index_definitions[index_type][1]
 
209
 
152
210
    def inventory_index_name(self, name):
153
211
        """The inv index is the name + .iix."""
154
212
        return self.index_name('inventory', name)
165
223
        """The text index is the name + .tix."""
166
224
        return self.index_name('text', name)
167
225
 
168
 
    def _external_compression_parents_of_texts(self):
169
 
        keys = set()
170
 
        refs = set()
171
 
        for node in self.text_index.iter_all_entries():
172
 
            keys.add(node[1])
173
 
            refs.update(node[3][1])
174
 
        return refs - keys
 
226
    def _replace_index_with_readonly(self, index_type):
 
227
        setattr(self, index_type + '_index',
 
228
            self.index_class(self.index_transport,
 
229
                self.index_name(index_type, self.name),
 
230
                self.index_sizes[self.index_offset(index_type)]))
175
231
 
176
232
 
177
233
class ExistingPack(Pack):
178
234
    """An in memory proxy for an existing .pack and its disk indices."""
179
235
 
180
236
    def __init__(self, pack_transport, name, revision_index, inventory_index,
181
 
        text_index, signature_index):
 
237
        text_index, signature_index, chk_index=None):
182
238
        """Create an ExistingPack object.
183
239
 
184
240
        :param pack_transport: The transport where the pack file resides.
185
241
        :param name: The name of the pack on disk in the pack_transport.
186
242
        """
187
243
        Pack.__init__(self, revision_index, inventory_index, text_index,
188
 
            signature_index)
 
244
            signature_index, chk_index)
189
245
        self.name = name
190
246
        self.pack_transport = pack_transport
191
247
        if None in (revision_index, inventory_index, text_index,
199
255
        return not self.__eq__(other)
200
256
 
201
257
    def __repr__(self):
202
 
        return "<bzrlib.repofmt.pack_repo.Pack object at 0x%x, %s, %s" % (
203
 
            id(self), self.pack_transport, self.name)
 
258
        return "<%s.%s object at 0x%x, %s, %s" % (
 
259
            self.__class__.__module__, self.__class__.__name__, id(self),
 
260
            self.pack_transport, self.name)
 
261
 
 
262
 
 
263
class ResumedPack(ExistingPack):
 
264
 
 
265
    def __init__(self, name, revision_index, inventory_index, text_index,
 
266
        signature_index, upload_transport, pack_transport, index_transport,
 
267
        pack_collection, chk_index=None):
 
268
        """Create a ResumedPack object."""
 
269
        ExistingPack.__init__(self, pack_transport, name, revision_index,
 
270
            inventory_index, text_index, signature_index,
 
271
            chk_index=chk_index)
 
272
        self.upload_transport = upload_transport
 
273
        self.index_transport = index_transport
 
274
        self.index_sizes = [None, None, None, None]
 
275
        indices = [
 
276
            ('revision', revision_index),
 
277
            ('inventory', inventory_index),
 
278
            ('text', text_index),
 
279
            ('signature', signature_index),
 
280
            ]
 
281
        if chk_index is not None:
 
282
            indices.append(('chk', chk_index))
 
283
            self.index_sizes.append(None)
 
284
        for index_type, index in indices:
 
285
            offset = self.index_offset(index_type)
 
286
            self.index_sizes[offset] = index._size
 
287
        self.index_class = pack_collection._index_class
 
288
        self._pack_collection = pack_collection
 
289
        self._state = 'resumed'
 
290
        # XXX: perhaps check that the .pack file exists?
 
291
 
 
292
    def access_tuple(self):
 
293
        if self._state == 'finished':
 
294
            return Pack.access_tuple(self)
 
295
        elif self._state == 'resumed':
 
296
            return self.upload_transport, self.file_name()
 
297
        else:
 
298
            raise AssertionError(self._state)
 
299
 
 
300
    def abort(self):
 
301
        self.upload_transport.delete(self.file_name())
 
302
        indices = [self.revision_index, self.inventory_index, self.text_index,
 
303
            self.signature_index]
 
304
        if self.chk_index is not None:
 
305
            indices.append(self.chk_index)
 
306
        for index in indices:
 
307
            index._transport.delete(index._name)
 
308
 
 
309
    def finish(self):
 
310
        self._check_references()
 
311
        index_types = ['revision', 'inventory', 'text', 'signature']
 
312
        if self.chk_index is not None:
 
313
            index_types.append('chk')
 
314
        for index_type in index_types:
 
315
            old_name = self.index_name(index_type, self.name)
 
316
            new_name = '../indices/' + old_name
 
317
            self.upload_transport.rename(old_name, new_name)
 
318
            self._replace_index_with_readonly(index_type)
 
319
        new_name = '../packs/' + self.file_name()
 
320
        self.upload_transport.rename(self.file_name(), new_name)
 
321
        self._state = 'finished'
 
322
 
 
323
    def _get_external_refs(self, index):
 
324
        """Return compression parents for this index that are not present.
 
325
 
 
326
        This returns any compression parents that are referenced by this index,
 
327
        which are not contained *in* this index. They may be present elsewhere.
 
328
        """
 
329
        return index.external_references(1)
204
330
 
205
331
 
206
332
class NewPack(Pack):
207
333
    """An in memory proxy for a pack which is being created."""
208
334
 
209
 
    # A map of index 'type' to the file extension and position in the
210
 
    # index_sizes array.
211
 
    index_definitions = {
212
 
        'revision': ('.rix', 0),
213
 
        'inventory': ('.iix', 1),
214
 
        'text': ('.tix', 2),
215
 
        'signature': ('.six', 3),
216
 
        }
217
 
 
218
 
    def __init__(self, upload_transport, index_transport, pack_transport,
219
 
        upload_suffix='', file_mode=None):
 
335
    def __init__(self, pack_collection, upload_suffix='', file_mode=None):
220
336
        """Create a NewPack instance.
221
337
 
222
 
        :param upload_transport: A writable transport for the pack to be
223
 
            incrementally uploaded to.
224
 
        :param index_transport: A writable transport for the pack's indices to
225
 
            be written to when the pack is finished.
226
 
        :param pack_transport: A writable transport for the pack to be renamed
227
 
            to when the upload is complete. This *must* be the same as
228
 
            upload_transport.clone('../packs').
 
338
        :param pack_collection: A PackCollection into which this is being inserted.
229
339
        :param upload_suffix: An optional suffix to be given to any temporary
230
340
            files created during the pack creation. e.g '.autopack'
231
 
        :param file_mode: An optional file mode to create the new files with.
 
341
        :param file_mode: Unix permissions for newly created file.
232
342
        """
233
343
        # The relative locations of the packs are constrained, but all are
234
344
        # passed in because the caller has them, so as to avoid object churn.
 
345
        index_builder_class = pack_collection._index_builder_class
 
346
        if pack_collection.chk_index is not None:
 
347
            chk_index = index_builder_class(reference_lists=0)
 
348
        else:
 
349
            chk_index = None
235
350
        Pack.__init__(self,
236
351
            # Revisions: parents list, no text compression.
237
 
            InMemoryGraphIndex(reference_lists=1),
 
352
            index_builder_class(reference_lists=1),
238
353
            # Inventory: We want to map compression only, but currently the
239
354
            # knit code hasn't been updated enough to understand that, so we
240
355
            # have a regular 2-list index giving parents and compression
241
356
            # source.
242
 
            InMemoryGraphIndex(reference_lists=2),
 
357
            index_builder_class(reference_lists=2),
243
358
            # Texts: compression and per file graph, for all fileids - so two
244
359
            # reference lists and two elements in the key tuple.
245
 
            InMemoryGraphIndex(reference_lists=2, key_elements=2),
 
360
            index_builder_class(reference_lists=2, key_elements=2),
246
361
            # Signatures: Just blobs to store, no compression, no parents
247
362
            # listing.
248
 
            InMemoryGraphIndex(reference_lists=0),
 
363
            index_builder_class(reference_lists=0),
 
364
            # CHK based storage - just blobs, no compression or parents.
 
365
            chk_index=chk_index
249
366
            )
 
367
        self._pack_collection = pack_collection
 
368
        # When we make readonly indices, we need this.
 
369
        self.index_class = pack_collection._index_class
250
370
        # where should the new pack be opened
251
 
        self.upload_transport = upload_transport
 
371
        self.upload_transport = pack_collection._upload_transport
252
372
        # where are indices written out to
253
 
        self.index_transport = index_transport
 
373
        self.index_transport = pack_collection._index_transport
254
374
        # where is the pack renamed to when it is finished?
255
 
        self.pack_transport = pack_transport
 
375
        self.pack_transport = pack_collection._pack_transport
256
376
        # What file mode to upload the pack and indices with.
257
377
        self._file_mode = file_mode
258
378
        # tracks the content written to the .pack file.
259
 
        self._hash = md5.new()
260
 
        # a four-tuple with the length in bytes of the indices, once the pack
261
 
        # is finalised. (rev, inv, text, sigs)
 
379
        self._hash = osutils.md5()
 
380
        # a tuple with the length in bytes of the indices, once the pack
 
381
        # is finalised. (rev, inv, text, sigs, chk_if_in_use)
262
382
        self.index_sizes = None
263
383
        # How much data to cache when writing packs. Note that this is not
264
384
        # synchronised with reads, because it's not in the transport layer, so
266
386
        # under creation.
267
387
        self._cache_limit = 0
268
388
        # the temporary pack file name.
269
 
        self.random_name = rand_chars(20) + upload_suffix
 
389
        self.random_name = osutils.rand_chars(20) + upload_suffix
270
390
        # when was this pack started ?
271
391
        self.start_time = time.time()
272
392
        # open an output stream for the data added to the pack.
276
396
            mutter('%s: create_pack: pack stream open: %s%s t+%6.3fs',
277
397
                time.ctime(), self.upload_transport.base, self.random_name,
278
398
                time.time() - self.start_time)
279
 
        # A list of byte sequences to be written to the new pack, and the 
280
 
        # aggregate size of them.  Stored as a list rather than separate 
 
399
        # A list of byte sequences to be written to the new pack, and the
 
400
        # aggregate size of them.  Stored as a list rather than separate
281
401
        # variables so that the _write_data closure below can update them.
282
402
        self._buffer = [[], 0]
283
 
        # create a callable for adding data 
 
403
        # create a callable for adding data
284
404
        #
285
405
        # robertc says- this is a closure rather than a method on the object
286
406
        # so that the variables are locals, and faster than accessing object
302
422
        self._writer.begin()
303
423
        # what state is the pack in? (open, finished, aborted)
304
424
        self._state = 'open'
 
425
        # no name until we finish writing the content
 
426
        self.name = None
305
427
 
306
428
    def abort(self):
307
429
        """Cancel creating this pack."""
325
447
        return bool(self.get_revision_count() or
326
448
            self.inventory_index.key_count() or
327
449
            self.text_index.key_count() or
328
 
            self.signature_index.key_count())
329
 
 
330
 
    def finish(self):
 
450
            self.signature_index.key_count() or
 
451
            (self.chk_index is not None and self.chk_index.key_count()))
 
452
 
 
453
    def finish_content(self):
 
454
        if self.name is not None:
 
455
            return
 
456
        self._writer.end()
 
457
        if self._buffer[1]:
 
458
            self._write_data('', flush=True)
 
459
        self.name = self._hash.hexdigest()
 
460
 
 
461
    def finish(self, suspend=False):
331
462
        """Finish the new pack.
332
463
 
333
464
        This:
338
469
         - stores the index size tuple for the pack in the index_sizes
339
470
           attribute.
340
471
        """
341
 
        self._writer.end()
342
 
        if self._buffer[1]:
343
 
            self._write_data('', flush=True)
344
 
        self.name = self._hash.hexdigest()
 
472
        self.finish_content()
 
473
        if not suspend:
 
474
            self._check_references()
345
475
        # write indices
346
476
        # XXX: It'd be better to write them all to temporary names, then
347
477
        # rename them all into place, so that the window when only some are
348
478
        # visible is smaller.  On the other hand none will be seen until
349
479
        # they're in the names list.
350
480
        self.index_sizes = [None, None, None, None]
351
 
        self._write_index('revision', self.revision_index, 'revision')
352
 
        self._write_index('inventory', self.inventory_index, 'inventory')
353
 
        self._write_index('text', self.text_index, 'file texts')
 
481
        self._write_index('revision', self.revision_index, 'revision', suspend)
 
482
        self._write_index('inventory', self.inventory_index, 'inventory',
 
483
            suspend)
 
484
        self._write_index('text', self.text_index, 'file texts', suspend)
354
485
        self._write_index('signature', self.signature_index,
355
 
            'revision signatures')
 
486
            'revision signatures', suspend)
 
487
        if self.chk_index is not None:
 
488
            self.index_sizes.append(None)
 
489
            self._write_index('chk', self.chk_index,
 
490
                'content hash bytes', suspend)
356
491
        self.write_stream.close()
357
492
        # Note that this will clobber an existing pack with the same name,
358
493
        # without checking for hash collisions. While this is undesirable this
365
500
        #  - try for HASH.pack
366
501
        #  - try for temporary-name
367
502
        #  - refresh the pack-list to see if the pack is now absent
368
 
        self.upload_transport.rename(self.random_name,
369
 
                '../packs/' + self.name + '.pack')
 
503
        new_name = self.name + '.pack'
 
504
        if not suspend:
 
505
            new_name = '../packs/' + new_name
 
506
        self.upload_transport.rename(self.random_name, new_name)
370
507
        self._state = 'finished'
371
508
        if 'pack' in debug.debug_flags:
372
509
            # XXX: size might be interesting?
373
 
            mutter('%s: create_pack: pack renamed into place: %s%s->%s%s t+%6.3fs',
 
510
            mutter('%s: create_pack: pack finished: %s%s->%s t+%6.3fs',
374
511
                time.ctime(), self.upload_transport.base, self.random_name,
375
 
                self.pack_transport, self.name,
376
 
                time.time() - self.start_time)
 
512
                new_name, time.time() - self.start_time)
377
513
 
378
514
    def flush(self):
379
515
        """Flush any current data."""
383
519
            self._hash.update(bytes)
384
520
            self._buffer[:] = [[], 0]
385
521
 
386
 
    def index_name(self, index_type, name):
387
 
        """Get the disk name of an index type for pack name 'name'."""
388
 
        return name + NewPack.index_definitions[index_type][0]
389
 
 
390
 
    def index_offset(self, index_type):
391
 
        """Get the position in a index_size array for a given index type."""
392
 
        return NewPack.index_definitions[index_type][1]
393
 
 
394
 
    def _replace_index_with_readonly(self, index_type):
395
 
        setattr(self, index_type + '_index',
396
 
            GraphIndex(self.index_transport,
397
 
                self.index_name(index_type, self.name),
398
 
                self.index_sizes[self.index_offset(index_type)]))
 
522
    def _get_external_refs(self, index):
 
523
        return index._external_references()
399
524
 
400
525
    def set_write_cache_size(self, size):
401
526
        self._cache_limit = size
402
527
 
403
 
    def _write_index(self, index_type, index, label):
 
528
    def _write_index(self, index_type, index, label, suspend=False):
404
529
        """Write out an index.
405
530
 
406
531
        :param index_type: The type of index to write - e.g. 'revision'.
408
533
        :param label: What label to give the index e.g. 'revision'.
409
534
        """
410
535
        index_name = self.index_name(index_type, self.name)
411
 
        self.index_sizes[self.index_offset(index_type)] = \
412
 
            self.index_transport.put_file(index_name, index.finish(),
413
 
            mode=self._file_mode)
 
536
        if suspend:
 
537
            transport = self.upload_transport
 
538
        else:
 
539
            transport = self.index_transport
 
540
        self.index_sizes[self.index_offset(index_type)] = transport.put_file(
 
541
            index_name, index.finish(), mode=self._file_mode)
414
542
        if 'pack' in debug.debug_flags:
415
543
            # XXX: size might be interesting?
416
544
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
417
545
                time.ctime(), label, self.upload_transport.base,
418
546
                self.random_name, time.time() - self.start_time)
419
 
        # Replace the writable index on this object with a readonly, 
 
547
        # Replace the writable index on this object with a readonly,
420
548
        # presently unloaded index. We should alter
421
549
        # the index layer to make its finish() error if add_node is
422
550
        # subsequently used. RBC
431
559
    such as 'revision index'.
432
560
 
433
561
    A CombinedIndex provides an index on a single key space built up
434
 
    from several on-disk indices.  The AggregateIndex builds on this 
 
562
    from several on-disk indices.  The AggregateIndex builds on this
435
563
    to provide a knit access layer, and allows having up to one writable
436
564
    index within the collection.
437
565
    """
438
566
    # XXX: Probably 'can be written to' could/should be separated from 'acts
439
567
    # like a knit index' -- mbp 20071024
440
568
 
441
 
    def __init__(self):
442
 
        """Create an AggregateIndex."""
 
569
    def __init__(self, reload_func=None, flush_func=None):
 
570
        """Create an AggregateIndex.
 
571
 
 
572
        :param reload_func: A function to call if we find we are missing an
 
573
            index. Should have the form reload_func() => True if the list of
 
574
            active pack files has changed.
 
575
        """
 
576
        self._reload_func = reload_func
443
577
        self.index_to_pack = {}
444
 
        self.combined_index = CombinedGraphIndex([])
445
 
        self.data_access = _DirectPackAccess(self.index_to_pack)
 
578
        self.combined_index = CombinedGraphIndex([], reload_func=reload_func)
 
579
        self.data_access = _DirectPackAccess(self.index_to_pack,
 
580
                                             reload_func=reload_func,
 
581
                                             flush_func=flush_func)
446
582
        self.add_callback = None
447
583
 
448
584
    def replace_indices(self, index_to_pack, indices):
470
606
 
471
607
        Future searches on the aggregate index will seach this new index
472
608
        before all previously inserted indices.
473
 
        
 
609
 
474
610
        :param index: An Index for the pack.
475
611
        :param pack: A Pack instance.
476
612
        """
484
620
 
485
621
        There can be at most one writable index at any time.  Any
486
622
        modifications made to the knit are put into this index.
487
 
        
 
623
 
488
624
        :param index: An index from the pack parameter.
489
625
        :param pack: A Pack instance.
490
626
        """
507
643
 
508
644
    def remove_index(self, index, pack):
509
645
        """Remove index from the indices used to answer queries.
510
 
        
 
646
 
511
647
        :param index: An index from the pack parameter.
512
648
        :param pack: A Pack instance.
513
649
        """
522
658
class Packer(object):
523
659
    """Create a pack from packs."""
524
660
 
525
 
    def __init__(self, pack_collection, packs, suffix, revision_ids=None):
 
661
    def __init__(self, pack_collection, packs, suffix, revision_ids=None,
 
662
                 reload_func=None):
526
663
        """Create a Packer.
527
664
 
528
665
        :param pack_collection: A RepositoryPackCollection object where the
530
667
        :param packs: The packs to combine.
531
668
        :param suffix: The suffix to use on the temporary files for the pack.
532
669
        :param revision_ids: Revision ids to limit the pack to.
 
670
        :param reload_func: A function to call if a pack file/index goes
 
671
            missing. The side effect of calling this function should be to
 
672
            update self.packs. See also AggregateIndex
533
673
        """
534
674
        self.packs = packs
535
675
        self.suffix = suffix
537
677
        # The pack object we are creating.
538
678
        self.new_pack = None
539
679
        self._pack_collection = pack_collection
 
680
        self._reload_func = reload_func
540
681
        # The index layer keys for the revisions being copied. None for 'all
541
682
        # objects'.
542
683
        self._revision_keys = None
548
689
    def _extra_init(self):
549
690
        """A template hook to allow extending the constructor trivially."""
550
691
 
 
692
    def _pack_map_and_index_list(self, index_attribute):
 
693
        """Convert a list of packs to an index pack map and index list.
 
694
 
 
695
        :param index_attribute: The attribute that the desired index is found
 
696
            on.
 
697
        :return: A tuple (map, list) where map contains the dict from
 
698
            index:pack_tuple, and list contains the indices in the preferred
 
699
            access order.
 
700
        """
 
701
        indices = []
 
702
        pack_map = {}
 
703
        for pack_obj in self.packs:
 
704
            index = getattr(pack_obj, index_attribute)
 
705
            indices.append(index)
 
706
            pack_map[index] = pack_obj
 
707
        return pack_map, indices
 
708
 
 
709
    def _index_contents(self, indices, key_filter=None):
 
710
        """Get an iterable of the index contents from a pack_map.
 
711
 
 
712
        :param indices: The list of indices to query
 
713
        :param key_filter: An optional filter to limit the keys returned.
 
714
        """
 
715
        all_index = CombinedGraphIndex(indices)
 
716
        if key_filter is None:
 
717
            return all_index.iter_all_entries()
 
718
        else:
 
719
            return all_index.iter_entries(key_filter)
 
720
 
551
721
    def pack(self, pb=None):
552
722
        """Create a new pack by reading data from other packs.
553
723
 
554
724
        This does little more than a bulk copy of data. One key difference
555
725
        is that data with the same item key across multiple packs is elided
556
726
        from the output. The new pack is written into the current pack store
557
 
        along with its indices, and the name added to the pack names. The 
 
727
        along with its indices, and the name added to the pack names. The
558
728
        source packs are not altered and are not required to be in the current
559
729
        pack collection.
560
730
 
567
737
        # XXX: - duplicate code warning with start_write_group; fix before
568
738
        #      considering 'done'.
569
739
        if self._pack_collection._new_pack is not None:
570
 
            raise errors.BzrError('call to create_pack_from_packs while '
571
 
                'another pack is being written.')
 
740
            raise errors.BzrError('call to %s.pack() while another pack is'
 
741
                                  ' being written.'
 
742
                                  % (self.__class__.__name__,))
572
743
        if self.revision_ids is not None:
573
744
            if len(self.revision_ids) == 0:
574
745
                # silly fetch request.
589
760
 
590
761
    def open_pack(self):
591
762
        """Open a pack for the pack we are creating."""
592
 
        return NewPack(self._pack_collection._upload_transport,
593
 
            self._pack_collection._index_transport,
594
 
            self._pack_collection._pack_transport, upload_suffix=self.suffix,
595
 
            file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
 
763
        new_pack = self._pack_collection.pack_factory(self._pack_collection,
 
764
                upload_suffix=self.suffix,
 
765
                file_mode=self._pack_collection.repo.bzrdir._get_file_mode())
 
766
        # We know that we will process all nodes in order, and don't need to
 
767
        # query, so don't combine any indices spilled to disk until we are done
 
768
        new_pack.revision_index.set_optimize(combine_backing_indices=False)
 
769
        new_pack.inventory_index.set_optimize(combine_backing_indices=False)
 
770
        new_pack.text_index.set_optimize(combine_backing_indices=False)
 
771
        new_pack.signature_index.set_optimize(combine_backing_indices=False)
 
772
        return new_pack
 
773
 
 
774
    def _update_pack_order(self, entries, index_to_pack_map):
 
775
        """Determine how we want our packs to be ordered.
 
776
 
 
777
        This changes the sort order of the self.packs list so that packs unused
 
778
        by 'entries' will be at the end of the list, so that future requests
 
779
        can avoid probing them.  Used packs will be at the front of the
 
780
        self.packs list, in the order of their first use in 'entries'.
 
781
 
 
782
        :param entries: A list of (index, ...) tuples
 
783
        :param index_to_pack_map: A mapping from index objects to pack objects.
 
784
        """
 
785
        packs = []
 
786
        seen_indexes = set()
 
787
        for entry in entries:
 
788
            index = entry[0]
 
789
            if index not in seen_indexes:
 
790
                packs.append(index_to_pack_map[index])
 
791
                seen_indexes.add(index)
 
792
        if len(packs) == len(self.packs):
 
793
            if 'pack' in debug.debug_flags:
 
794
                mutter('Not changing pack list, all packs used.')
 
795
            return
 
796
        seen_packs = set(packs)
 
797
        for pack in self.packs:
 
798
            if pack not in seen_packs:
 
799
                packs.append(pack)
 
800
                seen_packs.add(pack)
 
801
        if 'pack' in debug.debug_flags:
 
802
            old_names = [p.access_tuple()[1] for p in self.packs]
 
803
            new_names = [p.access_tuple()[1] for p in packs]
 
804
            mutter('Reordering packs\nfrom: %s\n  to: %s',
 
805
                   old_names, new_names)
 
806
        self.packs = packs
596
807
 
597
808
    def _copy_revision_texts(self):
598
809
        """Copy revision data to the new pack."""
602
813
        else:
603
814
            revision_keys = None
604
815
        # select revision keys
605
 
        revision_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
606
 
            self.packs, 'revision_index')[0]
607
 
        revision_nodes = self._pack_collection._index_contents(revision_index_map, revision_keys)
 
816
        revision_index_map, revision_indices = self._pack_map_and_index_list(
 
817
            'revision_index')
 
818
        revision_nodes = self._index_contents(revision_indices, revision_keys)
 
819
        revision_nodes = list(revision_nodes)
 
820
        self._update_pack_order(revision_nodes, revision_index_map)
608
821
        # copy revision keys and adjust values
609
822
        self.pb.update("Copying revision texts", 1)
610
823
        total_items, readv_group_iter = self._revision_node_readv(revision_nodes)
630
843
        # querying for keys here could introduce a bug where an inventory item
631
844
        # is missed, so do not change it to query separately without cross
632
845
        # checking like the text key check below.
633
 
        inventory_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
634
 
            self.packs, 'inventory_index')[0]
635
 
        inv_nodes = self._pack_collection._index_contents(inventory_index_map, inv_keys)
 
846
        inventory_index_map, inventory_indices = self._pack_map_and_index_list(
 
847
            'inventory_index')
 
848
        inv_nodes = self._index_contents(inventory_indices, inv_keys)
636
849
        # copy inventory keys and adjust values
637
850
        # XXX: Should be a helper function to allow different inv representation
638
851
        # at this point.
672
885
            if missing_text_keys:
673
886
                # TODO: raise a specific error that can handle many missing
674
887
                # keys.
 
888
                mutter("missing keys during fetch: %r", missing_text_keys)
675
889
                a_missing_key = missing_text_keys.pop()
676
890
                raise errors.RevisionNotPresent(a_missing_key[1],
677
891
                    a_missing_key[0])
682
896
            self.new_pack.text_index, readv_group_iter, total_items))
683
897
        self._log_copied_texts()
684
898
 
685
 
    def _check_references(self):
686
 
        """Make sure our external refereneces are present."""
687
 
        external_refs = self.new_pack._external_compression_parents_of_texts()
688
 
        if external_refs:
689
 
            index = self._pack_collection.text_index.combined_index
690
 
            found_items = list(index.iter_entries(external_refs))
691
 
            if len(found_items) != len(external_refs):
692
 
                found_keys = set(k for idx, k, refs, value in found_items)
693
 
                missing_items = external_refs - found_keys
694
 
                missing_file_id, missing_revision_id = missing_items.pop()
695
 
                raise errors.RevisionNotPresent(missing_revision_id,
696
 
                                                missing_file_id)
697
 
 
698
899
    def _create_pack_from_packs(self):
699
900
        self.pb.update("Opening pack", 0, 5)
700
901
        self.new_pack = self.open_pack()
718
919
        self._copy_text_texts()
719
920
        # select signature keys
720
921
        signature_filter = self._revision_keys # same keyspace
721
 
        signature_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
722
 
            self.packs, 'signature_index')[0]
723
 
        signature_nodes = self._pack_collection._index_contents(signature_index_map,
 
922
        signature_index_map, signature_indices = self._pack_map_and_index_list(
 
923
            'signature_index')
 
924
        signature_nodes = self._index_contents(signature_indices,
724
925
            signature_filter)
725
926
        # copy signature keys and adjust values
726
927
        self.pb.update("Copying signature texts", 4)
731
932
                time.ctime(), self._pack_collection._upload_transport.base, new_pack.random_name,
732
933
                new_pack.signature_index.key_count(),
733
934
                time.time() - new_pack.start_time)
734
 
        self._check_references()
 
935
        # copy chk contents
 
936
        # NB XXX: how to check CHK references are present? perhaps by yielding
 
937
        # the items? How should that interact with stacked repos?
 
938
        if new_pack.chk_index is not None:
 
939
            self._copy_chks()
 
940
            if 'pack' in debug.debug_flags:
 
941
                mutter('%s: create_pack: chk content copied: %s%s %d items t+%6.3fs',
 
942
                    time.ctime(), self._pack_collection._upload_transport.base,
 
943
                    new_pack.random_name,
 
944
                    new_pack.chk_index.key_count(),
 
945
                    time.time() - new_pack.start_time)
 
946
        new_pack._check_references()
735
947
        if not self._use_pack(new_pack):
736
948
            new_pack.abort()
737
949
            return None
740
952
        self._pack_collection.allocate(new_pack)
741
953
        return new_pack
742
954
 
743
 
    def _copy_nodes(self, nodes, index_map, writer, write_index):
744
 
        """Copy knit nodes between packs with no graph references."""
 
955
    def _copy_chks(self, refs=None):
 
956
        # XXX: Todo, recursive follow-pointers facility when fetching some
 
957
        # revisions only.
 
958
        chk_index_map, chk_indices = self._pack_map_and_index_list(
 
959
            'chk_index')
 
960
        chk_nodes = self._index_contents(chk_indices, refs)
 
961
        new_refs = set()
 
962
        # TODO: This isn't strictly tasteful as we are accessing some private
 
963
        #       variables (_serializer). Perhaps a better way would be to have
 
964
        #       Repository._deserialise_chk_node()
 
965
        search_key_func = chk_map.search_key_registry.get(
 
966
            self._pack_collection.repo._serializer.search_key_name)
 
967
        def accumlate_refs(lines):
 
968
            # XXX: move to a generic location
 
969
            # Yay mismatch:
 
970
            bytes = ''.join(lines)
 
971
            node = chk_map._deserialise(bytes, ("unknown",), search_key_func)
 
972
            new_refs.update(node.refs())
 
973
        self._copy_nodes(chk_nodes, chk_index_map, self.new_pack._writer,
 
974
            self.new_pack.chk_index, output_lines=accumlate_refs)
 
975
        return new_refs
 
976
 
 
977
    def _copy_nodes(self, nodes, index_map, writer, write_index,
 
978
        output_lines=None):
 
979
        """Copy knit nodes between packs with no graph references.
 
980
 
 
981
        :param output_lines: Output full texts of copied items.
 
982
        """
745
983
        pb = ui.ui_factory.nested_progress_bar()
746
984
        try:
747
985
            return self._do_copy_nodes(nodes, index_map, writer,
748
 
                write_index, pb)
 
986
                write_index, pb, output_lines=output_lines)
749
987
        finally:
750
988
            pb.finished()
751
989
 
752
 
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb):
 
990
    def _do_copy_nodes(self, nodes, index_map, writer, write_index, pb,
 
991
        output_lines=None):
753
992
        # for record verification
754
993
        knit = KnitVersionedFiles(None, None)
755
994
        # plan a readv on each source pack:
757
996
        nodes = sorted(nodes)
758
997
        # how to map this into knit.py - or knit.py into this?
759
998
        # we don't want the typical knit logic, we want grouping by pack
760
 
        # at this point - perhaps a helper library for the following code 
 
999
        # at this point - perhaps a helper library for the following code
761
1000
        # duplication points?
762
1001
        request_groups = {}
763
1002
        for index, key, value in nodes:
776
1015
            # linear scan up the pack
777
1016
            pack_readv_requests.sort()
778
1017
            # copy the data
779
 
            transport, path = index_map[index]
780
 
            reader = pack.make_readv_reader(transport, path,
781
 
                [offset[0:2] for offset in pack_readv_requests])
 
1018
            pack_obj = index_map[index]
 
1019
            transport, path = pack_obj.access_tuple()
 
1020
            try:
 
1021
                reader = pack.make_readv_reader(transport, path,
 
1022
                    [offset[0:2] for offset in pack_readv_requests])
 
1023
            except errors.NoSuchFile:
 
1024
                if self._reload_func is not None:
 
1025
                    self._reload_func()
 
1026
                raise
782
1027
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
783
1028
                izip(reader.iter_records(), pack_readv_requests):
784
1029
                raw_data = read_func(None)
785
1030
                # check the header only
786
 
                df, _ = knit._parse_record_header(key, raw_data)
787
 
                df.close()
 
1031
                if output_lines is not None:
 
1032
                    output_lines(knit._parse_record(key[-1], raw_data)[0])
 
1033
                else:
 
1034
                    df, _ = knit._parse_record_header(key, raw_data)
 
1035
                    df.close()
788
1036
                pos, size = writer.add_bytes_record(raw_data, names)
789
1037
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
790
1038
                pb.update("Copied record", record_index)
820
1068
        pb.update("Copied record", record_index, total_items)
821
1069
        for index, readv_vector, node_vector in readv_group_iter:
822
1070
            # copy the data
823
 
            transport, path = index_map[index]
824
 
            reader = pack.make_readv_reader(transport, path, readv_vector)
 
1071
            pack_obj = index_map[index]
 
1072
            transport, path = pack_obj.access_tuple()
 
1073
            try:
 
1074
                reader = pack.make_readv_reader(transport, path, readv_vector)
 
1075
            except errors.NoSuchFile:
 
1076
                if self._reload_func is not None:
 
1077
                    self._reload_func()
 
1078
                raise
825
1079
            for (names, read_func), (key, eol_flag, references) in \
826
1080
                izip(reader.iter_records(), node_vector):
827
1081
                raw_data = read_func(None)
844
1098
                record_index += 1
845
1099
 
846
1100
    def _get_text_nodes(self):
847
 
        text_index_map = self._pack_collection._packs_list_to_pack_map_and_index_list(
848
 
            self.packs, 'text_index')[0]
849
 
        return text_index_map, self._pack_collection._index_contents(text_index_map,
 
1101
        text_index_map, text_indices = self._pack_map_and_index_list(
 
1102
            'text_index')
 
1103
        return text_index_map, self._index_contents(text_indices,
850
1104
            self._text_filter)
851
1105
 
852
1106
    def _least_readv_node_readv(self, nodes):
853
1107
        """Generate request groups for nodes using the least readv's.
854
 
        
 
1108
 
855
1109
        :param nodes: An iterable of graph index nodes.
856
1110
        :return: Total node count and an iterator of the data needed to perform
857
1111
            readvs to obtain the data for nodes. Each item yielded by the
955
1209
        # TODO: combine requests in the same index that are in ascending order.
956
1210
        return total, requests
957
1211
 
 
1212
    def open_pack(self):
 
1213
        """Open a pack for the pack we are creating."""
 
1214
        new_pack = super(OptimisingPacker, self).open_pack()
 
1215
        # Turn on the optimization flags for all the index builders.
 
1216
        new_pack.revision_index.set_optimize(for_size=True)
 
1217
        new_pack.inventory_index.set_optimize(for_size=True)
 
1218
        new_pack.text_index.set_optimize(for_size=True)
 
1219
        new_pack.signature_index.set_optimize(for_size=True)
 
1220
        return new_pack
 
1221
 
958
1222
 
959
1223
class ReconcilePacker(Packer):
960
1224
    """A packer which regenerates indices etc as it copies.
961
 
    
 
1225
 
962
1226
    This is used by ``bzr reconcile`` to cause parent text pointers to be
963
1227
    regenerated.
964
1228
    """
987
1251
        # 1) generate the ideal index
988
1252
        repo = self._pack_collection.repo
989
1253
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
990
 
            _1, key, _2, refs in 
 
1254
            _1, key, _2, refs in
991
1255
            self.new_pack.revision_index.iter_all_entries()])
992
1256
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
993
1257
        # 2) generate a text_nodes list that contains all the deltas that can
999
1263
        text_index_map, text_nodes = self._get_text_nodes()
1000
1264
        for node in text_nodes:
1001
1265
            # 0 - index
1002
 
            # 1 - key 
 
1266
            # 1 - key
1003
1267
            # 2 - value
1004
1268
            # 3 - refs
1005
1269
            try:
1045
1309
        # space (we only topo sort the revisions, which is smaller).
1046
1310
        topo_order = tsort.topo_sort(ancestors)
1047
1311
        rev_order = dict(zip(topo_order, range(len(topo_order))))
1048
 
        bad_texts.sort(key=lambda key:rev_order[key[0][1]])
 
1312
        bad_texts.sort(key=lambda key:rev_order.get(key[0][1], 0))
1049
1313
        transaction = repo.get_transaction()
1050
1314
        file_id_index = GraphIndexPrefixAdapter(
1051
1315
            self.new_pack.text_index,
1072
1336
                    raise errors.BzrError('Mismatched key parent %r:%r' %
1073
1337
                        (key, parent_keys))
1074
1338
                parents.append(parent_key[1])
1075
 
            text_lines = split_lines(repo.texts.get_record_stream(
 
1339
            text_lines = osutils.split_lines(repo.texts.get_record_stream(
1076
1340
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
1077
1341
            output_texts.add_lines(key, parent_keys, text_lines,
1078
1342
                random_id=True, check_content=False)
1079
1343
        # 5) check that nothing inserted has a reference outside the keyspace.
1080
 
        missing_text_keys = self.new_pack._external_compression_parents_of_texts()
 
1344
        missing_text_keys = self.new_pack.text_index._external_references()
1081
1345
        if missing_text_keys:
1082
 
            raise errors.BzrError('Reference to missing compression parents %r'
 
1346
            raise errors.BzrCheckError('Reference to missing compression parents %r'
1083
1347
                % (missing_text_keys,))
1084
1348
        self._log_copied_texts()
1085
1349
 
1100
1364
 
1101
1365
class RepositoryPackCollection(object):
1102
1366
    """Management of packs within a repository.
1103
 
    
 
1367
 
1104
1368
    :ivar _names: map of {pack_name: (index_size,)}
1105
1369
    """
1106
1370
 
 
1371
    pack_factory = NewPack
 
1372
    resumed_pack_factory = ResumedPack
 
1373
 
1107
1374
    def __init__(self, repo, transport, index_transport, upload_transport,
1108
 
                 pack_transport):
 
1375
                 pack_transport, index_builder_class, index_class,
 
1376
                 use_chk_index):
1109
1377
        """Create a new RepositoryPackCollection.
1110
1378
 
1111
 
        :param transport: Addresses the repository base directory 
 
1379
        :param transport: Addresses the repository base directory
1112
1380
            (typically .bzr/repository/).
1113
1381
        :param index_transport: Addresses the directory containing indices.
1114
1382
        :param upload_transport: Addresses the directory into which packs are written
1115
1383
            while they're being created.
1116
1384
        :param pack_transport: Addresses the directory of existing complete packs.
 
1385
        :param index_builder_class: The index builder class to use.
 
1386
        :param index_class: The index class to use.
 
1387
        :param use_chk_index: Whether to setup and manage a CHK index.
1117
1388
        """
 
1389
        # XXX: This should call self.reset()
1118
1390
        self.repo = repo
1119
1391
        self.transport = transport
1120
1392
        self._index_transport = index_transport
1121
1393
        self._upload_transport = upload_transport
1122
1394
        self._pack_transport = pack_transport
1123
 
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3}
 
1395
        self._index_builder_class = index_builder_class
 
1396
        self._index_class = index_class
 
1397
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3,
 
1398
            '.cix': 4}
1124
1399
        self.packs = []
1125
1400
        # name:Pack mapping
 
1401
        self._names = None
1126
1402
        self._packs_by_name = {}
1127
1403
        # the previous pack-names content
1128
1404
        self._packs_at_load = None
1129
1405
        # when a pack is being created by this object, the state of that pack.
1130
1406
        self._new_pack = None
1131
1407
        # aggregated revision index data
1132
 
        self.revision_index = AggregateIndex()
1133
 
        self.inventory_index = AggregateIndex()
1134
 
        self.text_index = AggregateIndex()
1135
 
        self.signature_index = AggregateIndex()
 
1408
        flush = self._flush_new_pack
 
1409
        self.revision_index = AggregateIndex(self.reload_pack_names, flush)
 
1410
        self.inventory_index = AggregateIndex(self.reload_pack_names, flush)
 
1411
        self.text_index = AggregateIndex(self.reload_pack_names, flush)
 
1412
        self.signature_index = AggregateIndex(self.reload_pack_names, flush)
 
1413
        if use_chk_index:
 
1414
            self.chk_index = AggregateIndex(self.reload_pack_names, flush)
 
1415
        else:
 
1416
            # used to determine if we're using a chk_index elsewhere.
 
1417
            self.chk_index = None
 
1418
        # resumed packs
 
1419
        self._resumed_packs = []
1136
1420
 
1137
1421
    def add_pack_to_memory(self, pack):
1138
1422
        """Make a Pack object available to the repository to satisfy queries.
1139
 
        
 
1423
 
1140
1424
        :param pack: A Pack object.
1141
1425
        """
1142
1426
        if pack.name in self._packs_by_name:
1143
 
            raise AssertionError()
 
1427
            raise AssertionError(
 
1428
                'pack %s already in _packs_by_name' % (pack.name,))
1144
1429
        self.packs.append(pack)
1145
1430
        self._packs_by_name[pack.name] = pack
1146
1431
        self.revision_index.add_index(pack.revision_index, pack)
1147
1432
        self.inventory_index.add_index(pack.inventory_index, pack)
1148
1433
        self.text_index.add_index(pack.text_index, pack)
1149
1434
        self.signature_index.add_index(pack.signature_index, pack)
1150
 
        
 
1435
        if self.chk_index is not None:
 
1436
            self.chk_index.add_index(pack.chk_index, pack)
 
1437
 
1151
1438
    def all_packs(self):
1152
1439
        """Return a list of all the Pack objects this repository has.
1153
1440
 
1162
1449
 
1163
1450
    def autopack(self):
1164
1451
        """Pack the pack collection incrementally.
1165
 
        
 
1452
 
1166
1453
        This will not attempt global reorganisation or recompression,
1167
1454
        rather it will just ensure that the total number of packs does
1168
1455
        not grow without bound. It uses the _max_pack_count method to
1174
1461
        in synchronisation with certain steps. Otherwise the names collection
1175
1462
        is not flushed.
1176
1463
 
1177
 
        :return: True if packing took place.
 
1464
        :return: Something evaluating true if packing took place.
1178
1465
        """
 
1466
        while True:
 
1467
            try:
 
1468
                return self._do_autopack()
 
1469
            except errors.RetryAutopack:
 
1470
                # If we get a RetryAutopack exception, we should abort the
 
1471
                # current action, and retry.
 
1472
                pass
 
1473
 
 
1474
    def _do_autopack(self):
1179
1475
        # XXX: Should not be needed when the management of indices is sane.
1180
1476
        total_revisions = self.revision_index.combined_index.key_count()
1181
1477
        total_packs = len(self._names)
1182
1478
        if self._max_pack_count(total_revisions) >= total_packs:
1183
 
            return False
1184
 
        # XXX: the following may want to be a class, to pack with a given
1185
 
        # policy.
1186
 
        mutter('Auto-packing repository %s, which has %d pack files, '
1187
 
            'containing %d revisions into %d packs.', self, total_packs,
1188
 
            total_revisions, self._max_pack_count(total_revisions))
 
1479
            return None
1189
1480
        # determine which packs need changing
1190
1481
        pack_distribution = self.pack_distribution(total_revisions)
1191
1482
        existing_packs = []
1199
1490
                # group their data with the relevant commit, and that may
1200
1491
                # involve rewriting ancient history - which autopack tries to
1201
1492
                # avoid. Alternatively we could not group the data but treat
1202
 
                # each of these as having a single revision, and thus add 
 
1493
                # each of these as having a single revision, and thus add
1203
1494
                # one revision for each to the total revision count, to get
1204
1495
                # a matching distribution.
1205
1496
                continue
1206
1497
            existing_packs.append((revision_count, pack))
1207
1498
        pack_operations = self.plan_autopack_combinations(
1208
1499
            existing_packs, pack_distribution)
1209
 
        self._execute_pack_operations(pack_operations)
1210
 
        return True
 
1500
        num_new_packs = len(pack_operations)
 
1501
        num_old_packs = sum([len(po[1]) for po in pack_operations])
 
1502
        num_revs_affected = sum([po[0] for po in pack_operations])
 
1503
        mutter('Auto-packing repository %s, which has %d pack files, '
 
1504
            'containing %d revisions. Packing %d files into %d affecting %d'
 
1505
            ' revisions', self, total_packs, total_revisions, num_old_packs,
 
1506
            num_new_packs, num_revs_affected)
 
1507
        result = self._execute_pack_operations(pack_operations,
 
1508
                                      reload_func=self._restart_autopack)
 
1509
        mutter('Auto-packing repository %s completed', self)
 
1510
        return result
1211
1511
 
1212
 
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer):
 
1512
    def _execute_pack_operations(self, pack_operations, _packer_class=Packer,
 
1513
                                 reload_func=None):
1213
1514
        """Execute a series of pack operations.
1214
1515
 
1215
1516
        :param pack_operations: A list of [revision_count, packs_to_combine].
1216
1517
        :param _packer_class: The class of packer to use (default: Packer).
1217
 
        :return: None.
 
1518
        :return: The new pack names.
1218
1519
        """
1219
1520
        for revision_count, packs in pack_operations:
1220
1521
            # we may have no-ops from the setup logic
1221
1522
            if len(packs) == 0:
1222
1523
                continue
1223
 
            _packer_class(self, packs, '.autopack').pack()
 
1524
            packer = _packer_class(self, packs, '.autopack',
 
1525
                                   reload_func=reload_func)
 
1526
            try:
 
1527
                packer.pack()
 
1528
            except errors.RetryWithNewPacks:
 
1529
                # An exception is propagating out of this context, make sure
 
1530
                # this packer has cleaned up. Packer() doesn't set its new_pack
 
1531
                # state into the RepositoryPackCollection object, so we only
 
1532
                # have access to it directly here.
 
1533
                if packer.new_pack is not None:
 
1534
                    packer.new_pack.abort()
 
1535
                raise
1224
1536
            for pack in packs:
1225
1537
                self._remove_pack_from_memory(pack)
1226
1538
        # record the newly available packs and stop advertising the old
1227
1539
        # packs
1228
 
        self._save_pack_names(clear_obsolete_packs=True)
 
1540
        result = self._save_pack_names(clear_obsolete_packs=True)
1229
1541
        # Move the old packs out of the way now they are no longer referenced.
1230
1542
        for revision_count, packs in pack_operations:
1231
1543
            self._obsolete_packs(packs)
 
1544
        return result
 
1545
 
 
1546
    def _flush_new_pack(self):
 
1547
        if self._new_pack is not None:
 
1548
            self._new_pack.flush()
1232
1549
 
1233
1550
    def lock_names(self):
1234
1551
        """Acquire the mutex around the pack-names index.
1235
 
        
 
1552
 
1236
1553
        This cannot be used in the middle of a read-only transaction on the
1237
1554
        repository.
1238
1555
        """
1239
1556
        self.repo.control_files.lock_write()
1240
1557
 
1241
 
    def pack(self):
 
1558
    def _already_packed(self):
 
1559
        """Is the collection already packed?"""
 
1560
        return not (self.repo._format.pack_compresses or (len(self._names) > 1))
 
1561
 
 
1562
    def pack(self, hint=None):
1242
1563
        """Pack the pack collection totally."""
1243
1564
        self.ensure_loaded()
1244
1565
        total_packs = len(self._names)
1245
 
        if total_packs < 2:
1246
 
            # This is arguably wrong because we might not be optimal, but for
1247
 
            # now lets leave it in. (e.g. reconcile -> one pack. But not
1248
 
            # optimal.
 
1566
        if self._already_packed():
1249
1567
            return
1250
1568
        total_revisions = self.revision_index.combined_index.key_count()
1251
1569
        # XXX: the following may want to be a class, to pack with a given
1252
1570
        # policy.
1253
1571
        mutter('Packing repository %s, which has %d pack files, '
1254
 
            'containing %d revisions into 1 packs.', self, total_packs,
1255
 
            total_revisions)
 
1572
            'containing %d revisions with hint %r.', self, total_packs,
 
1573
            total_revisions, hint)
1256
1574
        # determine which packs need changing
1257
 
        pack_distribution = [1]
1258
1575
        pack_operations = [[0, []]]
1259
1576
        for pack in self.all_packs():
1260
 
            pack_operations[-1][0] += pack.get_revision_count()
1261
 
            pack_operations[-1][1].append(pack)
 
1577
            if hint is None or pack.name in hint:
 
1578
                # Either no hint was provided (so we are packing everything),
 
1579
                # or this pack was included in the hint.
 
1580
                pack_operations[-1][0] += pack.get_revision_count()
 
1581
                pack_operations[-1][1].append(pack)
1262
1582
        self._execute_pack_operations(pack_operations, OptimisingPacker)
1263
1583
 
1264
1584
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1276
1596
        # plan out what packs to keep, and what to reorganise
1277
1597
        while len(existing_packs):
1278
1598
            # take the largest pack, and if its less than the head of the
1279
 
            # distribution chart we will include its contents in the new pack for
1280
 
            # that position. If its larger, we remove its size from the
 
1599
            # distribution chart we will include its contents in the new pack
 
1600
            # for that position. If its larger, we remove its size from the
1281
1601
            # distribution chart
1282
1602
            next_pack_rev_count, next_pack = existing_packs.pop(0)
1283
1603
            if next_pack_rev_count >= pack_distribution[0]:
1300
1620
                    # this pack is used up, shift left.
1301
1621
                    del pack_distribution[0]
1302
1622
                    pack_operations.append([0, []])
1303
 
        
1304
 
        return pack_operations
 
1623
        # Now that we know which pack files we want to move, shove them all
 
1624
        # into a single pack file.
 
1625
        final_rev_count = 0
 
1626
        final_pack_list = []
 
1627
        for num_revs, pack_files in pack_operations:
 
1628
            final_rev_count += num_revs
 
1629
            final_pack_list.extend(pack_files)
 
1630
        if len(final_pack_list) == 1:
 
1631
            raise AssertionError('We somehow generated an autopack with a'
 
1632
                ' single pack file being moved.')
 
1633
            return []
 
1634
        return [[final_rev_count, final_pack_list]]
1305
1635
 
1306
1636
    def ensure_loaded(self):
 
1637
        """Ensure we have read names from disk.
 
1638
 
 
1639
        :return: True if the disk names had not been previously read.
 
1640
        """
1307
1641
        # NB: if you see an assertion error here, its probably access against
1308
1642
        # an unlocked repo. Naughty.
1309
1643
        if not self.repo.is_locked():
1315
1649
                name = key[0]
1316
1650
                self._names[name] = self._parse_index_sizes(value)
1317
1651
                self._packs_at_load.add((key, value))
 
1652
            result = True
 
1653
        else:
 
1654
            result = False
1318
1655
        # populate all the metadata.
1319
1656
        self.all_packs()
 
1657
        return result
1320
1658
 
1321
1659
    def _parse_index_sizes(self, value):
1322
1660
        """Parse a string of index sizes."""
1335
1673
            inv_index = self._make_index(name, '.iix')
1336
1674
            txt_index = self._make_index(name, '.tix')
1337
1675
            sig_index = self._make_index(name, '.six')
 
1676
            if self.chk_index is not None:
 
1677
                chk_index = self._make_index(name, '.cix')
 
1678
            else:
 
1679
                chk_index = None
1338
1680
            result = ExistingPack(self._pack_transport, name, rev_index,
1339
 
                inv_index, txt_index, sig_index)
 
1681
                inv_index, txt_index, sig_index, chk_index)
1340
1682
            self.add_pack_to_memory(result)
1341
1683
            return result
1342
1684
 
 
1685
    def _resume_pack(self, name):
 
1686
        """Get a suspended Pack object by name.
 
1687
 
 
1688
        :param name: The name of the pack - e.g. '123456'
 
1689
        :return: A Pack object.
 
1690
        """
 
1691
        if not re.match('[a-f0-9]{32}', name):
 
1692
            # Tokens should be md5sums of the suspended pack file, i.e. 32 hex
 
1693
            # digits.
 
1694
            raise errors.UnresumableWriteGroup(
 
1695
                self.repo, [name], 'Malformed write group token')
 
1696
        try:
 
1697
            rev_index = self._make_index(name, '.rix', resume=True)
 
1698
            inv_index = self._make_index(name, '.iix', resume=True)
 
1699
            txt_index = self._make_index(name, '.tix', resume=True)
 
1700
            sig_index = self._make_index(name, '.six', resume=True)
 
1701
            if self.chk_index is not None:
 
1702
                chk_index = self._make_index(name, '.cix', resume=True)
 
1703
            else:
 
1704
                chk_index = None
 
1705
            result = self.resumed_pack_factory(name, rev_index, inv_index,
 
1706
                txt_index, sig_index, self._upload_transport,
 
1707
                self._pack_transport, self._index_transport, self,
 
1708
                chk_index=chk_index)
 
1709
        except errors.NoSuchFile, e:
 
1710
            raise errors.UnresumableWriteGroup(self.repo, [name], str(e))
 
1711
        self.add_pack_to_memory(result)
 
1712
        self._resumed_packs.append(result)
 
1713
        return result
 
1714
 
1343
1715
    def allocate(self, a_new_pack):
1344
1716
        """Allocate name in the list of packs.
1345
1717
 
1355
1727
 
1356
1728
    def _iter_disk_pack_index(self):
1357
1729
        """Iterate over the contents of the pack-names index.
1358
 
        
 
1730
 
1359
1731
        This is used when loading the list from disk, and before writing to
1360
1732
        detect updates from others during our write operation.
1361
1733
        :return: An iterator of the index contents.
1362
1734
        """
1363
 
        return GraphIndex(self.transport, 'pack-names', None
 
1735
        return self._index_class(self.transport, 'pack-names', None
1364
1736
                ).iter_all_entries()
1365
1737
 
1366
 
    def _make_index(self, name, suffix):
 
1738
    def _make_index(self, name, suffix, resume=False):
1367
1739
        size_offset = self._suffix_offsets[suffix]
1368
1740
        index_name = name + suffix
1369
 
        index_size = self._names[name][size_offset]
1370
 
        return GraphIndex(
1371
 
            self._index_transport, index_name, index_size)
 
1741
        if resume:
 
1742
            transport = self._upload_transport
 
1743
            index_size = transport.stat(index_name).st_size
 
1744
        else:
 
1745
            transport = self._index_transport
 
1746
            index_size = self._names[name][size_offset]
 
1747
        return self._index_class(transport, index_name, index_size)
1372
1748
 
1373
1749
    def _max_pack_count(self, total_revisions):
1374
1750
        """Return the maximum number of packs to use for total revisions.
1375
 
        
 
1751
 
1376
1752
        :param total_revisions: The total number of revisions in the
1377
1753
            repository.
1378
1754
        """
1407
1783
            # TODO: Probably needs to know all possible indices for this pack
1408
1784
            # - or maybe list the directory and move all indices matching this
1409
1785
            # name whether we recognize it or not?
1410
 
            for suffix in ('.iix', '.six', '.tix', '.rix'):
 
1786
            suffixes = ['.iix', '.six', '.tix', '.rix']
 
1787
            if self.chk_index is not None:
 
1788
                suffixes.append('.cix')
 
1789
            for suffix in suffixes:
1411
1790
                self._index_transport.rename(pack.name + suffix,
1412
1791
                    '../obsolete_packs/' + pack.name + suffix)
1413
1792
 
1433
1812
 
1434
1813
    def _remove_pack_from_memory(self, pack):
1435
1814
        """Remove pack from the packs accessed by this repository.
1436
 
        
 
1815
 
1437
1816
        Only affects memory state, until self._save_pack_names() is invoked.
1438
1817
        """
1439
1818
        self._names.pop(pack.name)
1440
1819
        self._packs_by_name.pop(pack.name)
1441
1820
        self._remove_pack_indices(pack)
 
1821
        self.packs.remove(pack)
1442
1822
 
1443
1823
    def _remove_pack_indices(self, pack):
1444
1824
        """Remove the indices for pack from the aggregated indices."""
1446
1826
        self.inventory_index.remove_index(pack.inventory_index, pack)
1447
1827
        self.text_index.remove_index(pack.text_index, pack)
1448
1828
        self.signature_index.remove_index(pack.signature_index, pack)
 
1829
        if self.chk_index is not None:
 
1830
            self.chk_index.remove_index(pack.chk_index, pack)
1449
1831
 
1450
1832
    def reset(self):
1451
1833
        """Clear all cached data."""
1452
1834
        # cached revision data
1453
 
        self.repo._revision_knit = None
1454
1835
        self.revision_index.clear()
1455
1836
        # cached signature data
1456
 
        self.repo._signature_knit = None
1457
1837
        self.signature_index.clear()
1458
1838
        # cached file text data
1459
1839
        self.text_index.clear()
1460
 
        self.repo._text_knit = None
1461
1840
        # cached inventory data
1462
1841
        self.inventory_index.clear()
 
1842
        # cached chk data
 
1843
        if self.chk_index is not None:
 
1844
            self.chk_index.clear()
1463
1845
        # remove the open pack
1464
1846
        self._new_pack = None
1465
1847
        # information about packs.
1468
1850
        self._packs_by_name = {}
1469
1851
        self._packs_at_load = None
1470
1852
 
1471
 
    def _make_index_map(self, index_suffix):
1472
 
        """Return information on existing indices.
1473
 
 
1474
 
        :param suffix: Index suffix added to pack name.
1475
 
 
1476
 
        :returns: (pack_map, indices) where indices is a list of GraphIndex 
1477
 
        objects, and pack_map is a mapping from those objects to the 
1478
 
        pack tuple they describe.
1479
 
        """
1480
 
        # TODO: stop using this; it creates new indices unnecessarily.
1481
 
        self.ensure_loaded()
1482
 
        suffix_map = {'.rix': 'revision_index',
1483
 
            '.six': 'signature_index',
1484
 
            '.iix': 'inventory_index',
1485
 
            '.tix': 'text_index',
1486
 
        }
1487
 
        return self._packs_list_to_pack_map_and_index_list(self.all_packs(),
1488
 
            suffix_map[index_suffix])
1489
 
 
1490
 
    def _packs_list_to_pack_map_and_index_list(self, packs, index_attribute):
1491
 
        """Convert a list of packs to an index pack map and index list.
1492
 
 
1493
 
        :param packs: The packs list to process.
1494
 
        :param index_attribute: The attribute that the desired index is found
1495
 
            on.
1496
 
        :return: A tuple (map, list) where map contains the dict from
1497
 
            index:pack_tuple, and lsit contains the indices in the same order
1498
 
            as the packs list.
1499
 
        """
1500
 
        indices = []
1501
 
        pack_map = {}
1502
 
        for pack in packs:
1503
 
            index = getattr(pack, index_attribute)
1504
 
            indices.append(index)
1505
 
            pack_map[index] = (pack.pack_transport, pack.file_name())
1506
 
        return pack_map, indices
1507
 
 
1508
 
    def _index_contents(self, pack_map, key_filter=None):
1509
 
        """Get an iterable of the index contents from a pack_map.
1510
 
 
1511
 
        :param pack_map: A map from indices to pack details.
1512
 
        :param key_filter: An optional filter to limit the
1513
 
            keys returned.
1514
 
        """
1515
 
        indices = [index for index in pack_map.iterkeys()]
1516
 
        all_index = CombinedGraphIndex(indices)
1517
 
        if key_filter is None:
1518
 
            return all_index.iter_all_entries()
1519
 
        else:
1520
 
            return all_index.iter_entries(key_filter)
1521
 
 
1522
1853
    def _unlock_names(self):
1523
1854
        """Release the mutex around the pack-names index."""
1524
1855
        self.repo.control_files.unlock()
1525
1856
 
1526
 
    def _save_pack_names(self, clear_obsolete_packs=False):
1527
 
        """Save the list of packs.
1528
 
 
1529
 
        This will take out the mutex around the pack names list for the
1530
 
        duration of the method call. If concurrent updates have been made, a
1531
 
        three-way merge between the current list and the current in memory list
1532
 
        is performed.
1533
 
 
1534
 
        :param clear_obsolete_packs: If True, clear out the contents of the
1535
 
            obsolete_packs directory.
1536
 
        """
1537
 
        self.lock_names()
1538
 
        try:
1539
 
            builder = GraphIndexBuilder()
1540
 
            # load the disk nodes across
1541
 
            disk_nodes = set()
1542
 
            for index, key, value in self._iter_disk_pack_index():
1543
 
                disk_nodes.add((key, value))
1544
 
            # do a two-way diff against our original content
1545
 
            current_nodes = set()
1546
 
            for name, sizes in self._names.iteritems():
1547
 
                current_nodes.add(
1548
 
                    ((name, ), ' '.join(str(size) for size in sizes)))
1549
 
            deleted_nodes = self._packs_at_load - current_nodes
1550
 
            new_nodes = current_nodes - self._packs_at_load
1551
 
            disk_nodes.difference_update(deleted_nodes)
1552
 
            disk_nodes.update(new_nodes)
1553
 
            # TODO: handle same-name, index-size-changes here - 
1554
 
            # e.g. use the value from disk, not ours, *unless* we're the one
1555
 
            # changing it.
1556
 
            for key, value in disk_nodes:
1557
 
                builder.add_node(key, value)
1558
 
            self.transport.put_file('pack-names', builder.finish(),
1559
 
                mode=self.repo.bzrdir._get_file_mode())
1560
 
            # move the baseline forward
1561
 
            self._packs_at_load = disk_nodes
1562
 
            if clear_obsolete_packs:
1563
 
                self._clear_obsolete_packs()
1564
 
        finally:
1565
 
            self._unlock_names()
1566
 
        # synchronise the memory packs list with what we just wrote:
 
1857
    def _diff_pack_names(self):
 
1858
        """Read the pack names from disk, and compare it to the one in memory.
 
1859
 
 
1860
        :return: (disk_nodes, deleted_nodes, new_nodes)
 
1861
            disk_nodes    The final set of nodes that should be referenced
 
1862
            deleted_nodes Nodes which have been removed from when we started
 
1863
            new_nodes     Nodes that are newly introduced
 
1864
        """
 
1865
        # load the disk nodes across
 
1866
        disk_nodes = set()
 
1867
        for index, key, value in self._iter_disk_pack_index():
 
1868
            disk_nodes.add((key, value))
 
1869
 
 
1870
        # do a two-way diff against our original content
 
1871
        current_nodes = set()
 
1872
        for name, sizes in self._names.iteritems():
 
1873
            current_nodes.add(
 
1874
                ((name, ), ' '.join(str(size) for size in sizes)))
 
1875
 
 
1876
        # Packs no longer present in the repository, which were present when we
 
1877
        # locked the repository
 
1878
        deleted_nodes = self._packs_at_load - current_nodes
 
1879
        # Packs which this process is adding
 
1880
        new_nodes = current_nodes - self._packs_at_load
 
1881
 
 
1882
        # Update the disk_nodes set to include the ones we are adding, and
 
1883
        # remove the ones which were removed by someone else
 
1884
        disk_nodes.difference_update(deleted_nodes)
 
1885
        disk_nodes.update(new_nodes)
 
1886
 
 
1887
        return disk_nodes, deleted_nodes, new_nodes
 
1888
 
 
1889
    def _syncronize_pack_names_from_disk_nodes(self, disk_nodes):
 
1890
        """Given the correct set of pack files, update our saved info.
 
1891
 
 
1892
        :return: (removed, added, modified)
 
1893
            removed     pack names removed from self._names
 
1894
            added       pack names added to self._names
 
1895
            modified    pack names that had changed value
 
1896
        """
 
1897
        removed = []
 
1898
        added = []
 
1899
        modified = []
 
1900
        ## self._packs_at_load = disk_nodes
1567
1901
        new_names = dict(disk_nodes)
1568
1902
        # drop no longer present nodes
1569
1903
        for pack in self.all_packs():
1570
1904
            if (pack.name,) not in new_names:
 
1905
                removed.append(pack.name)
1571
1906
                self._remove_pack_from_memory(pack)
1572
1907
        # add new nodes/refresh existing ones
1573
1908
        for key, value in disk_nodes:
1587
1922
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
1588
1923
                    self._names[name] = sizes
1589
1924
                    self.get_pack_by_name(name)
 
1925
                    modified.append(name)
1590
1926
            else:
1591
1927
                # new
1592
1928
                self._names[name] = sizes
1593
1929
                self.get_pack_by_name(name)
 
1930
                added.append(name)
 
1931
        return removed, added, modified
 
1932
 
 
1933
    def _save_pack_names(self, clear_obsolete_packs=False):
 
1934
        """Save the list of packs.
 
1935
 
 
1936
        This will take out the mutex around the pack names list for the
 
1937
        duration of the method call. If concurrent updates have been made, a
 
1938
        three-way merge between the current list and the current in memory list
 
1939
        is performed.
 
1940
 
 
1941
        :param clear_obsolete_packs: If True, clear out the contents of the
 
1942
            obsolete_packs directory.
 
1943
        :return: A list of the names saved that were not previously on disk.
 
1944
        """
 
1945
        self.lock_names()
 
1946
        try:
 
1947
            builder = self._index_builder_class()
 
1948
            disk_nodes, deleted_nodes, new_nodes = self._diff_pack_names()
 
1949
            # TODO: handle same-name, index-size-changes here -
 
1950
            # e.g. use the value from disk, not ours, *unless* we're the one
 
1951
            # changing it.
 
1952
            for key, value in disk_nodes:
 
1953
                builder.add_node(key, value)
 
1954
            self.transport.put_file('pack-names', builder.finish(),
 
1955
                mode=self.repo.bzrdir._get_file_mode())
 
1956
            # move the baseline forward
 
1957
            self._packs_at_load = disk_nodes
 
1958
            if clear_obsolete_packs:
 
1959
                self._clear_obsolete_packs()
 
1960
        finally:
 
1961
            self._unlock_names()
 
1962
        # synchronise the memory packs list with what we just wrote:
 
1963
        self._syncronize_pack_names_from_disk_nodes(disk_nodes)
 
1964
        return [new_node[0][0] for new_node in new_nodes]
 
1965
 
 
1966
    def reload_pack_names(self):
 
1967
        """Sync our pack listing with what is present in the repository.
 
1968
 
 
1969
        This should be called when we find out that something we thought was
 
1970
        present is now missing. This happens when another process re-packs the
 
1971
        repository, etc.
 
1972
 
 
1973
        :return: True if the in-memory list of packs has been altered at all.
 
1974
        """
 
1975
        # The ensure_loaded call is to handle the case where the first call
 
1976
        # made involving the collection was to reload_pack_names, where we 
 
1977
        # don't have a view of disk contents. Its a bit of a bandaid, and
 
1978
        # causes two reads of pack-names, but its a rare corner case not struck
 
1979
        # with regular push/pull etc.
 
1980
        first_read = self.ensure_loaded()
 
1981
        if first_read:
 
1982
            return True
 
1983
        # out the new value.
 
1984
        disk_nodes, _, _ = self._diff_pack_names()
 
1985
        self._packs_at_load = disk_nodes
 
1986
        (removed, added,
 
1987
         modified) = self._syncronize_pack_names_from_disk_nodes(disk_nodes)
 
1988
        if removed or added or modified:
 
1989
            return True
 
1990
        return False
 
1991
 
 
1992
    def _restart_autopack(self):
 
1993
        """Reload the pack names list, and restart the autopack code."""
 
1994
        if not self.reload_pack_names():
 
1995
            # Re-raise the original exception, because something went missing
 
1996
            # and a restart didn't find it
 
1997
            raise
 
1998
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
1594
1999
 
1595
2000
    def _clear_obsolete_packs(self):
1596
2001
        """Delete everything from the obsolete-packs directory.
1606
2011
        # Do not permit preparation for writing if we're not in a 'write lock'.
1607
2012
        if not self.repo.is_write_locked():
1608
2013
            raise errors.NotWriteLocked(self)
1609
 
        self._new_pack = NewPack(self._upload_transport, self._index_transport,
1610
 
            self._pack_transport, upload_suffix='.pack',
 
2014
        self._new_pack = self.pack_factory(self, upload_suffix='.pack',
1611
2015
            file_mode=self.repo.bzrdir._get_file_mode())
1612
2016
        # allow writing: queue writes to a new index
1613
2017
        self.revision_index.add_writable_index(self._new_pack.revision_index,
1616
2020
            self._new_pack)
1617
2021
        self.text_index.add_writable_index(self._new_pack.text_index,
1618
2022
            self._new_pack)
 
2023
        self._new_pack.text_index.set_optimize(combine_backing_indices=False)
1619
2024
        self.signature_index.add_writable_index(self._new_pack.signature_index,
1620
2025
            self._new_pack)
 
2026
        if self.chk_index is not None:
 
2027
            self.chk_index.add_writable_index(self._new_pack.chk_index,
 
2028
                self._new_pack)
 
2029
            self.repo.chk_bytes._index._add_callback = self.chk_index.add_callback
 
2030
            self._new_pack.chk_index.set_optimize(combine_backing_indices=False)
1621
2031
 
1622
2032
        self.repo.inventories._index._add_callback = self.inventory_index.add_callback
1623
2033
        self.repo.revisions._index._add_callback = self.revision_index.add_callback
1628
2038
        # FIXME: just drop the transient index.
1629
2039
        # forget what names there are
1630
2040
        if self._new_pack is not None:
1631
 
            self._new_pack.abort()
1632
 
            self._remove_pack_indices(self._new_pack)
1633
 
            self._new_pack = None
1634
 
        self.repo._text_knit = None
 
2041
            try:
 
2042
                self._new_pack.abort()
 
2043
            finally:
 
2044
                # XXX: If we aborted while in the middle of finishing the write
 
2045
                # group, _remove_pack_indices can fail because the indexes are
 
2046
                # already gone.  If they're not there we shouldn't fail in this
 
2047
                # case.  -- mbp 20081113
 
2048
                self._remove_pack_indices(self._new_pack)
 
2049
                self._new_pack = None
 
2050
        for resumed_pack in self._resumed_packs:
 
2051
            try:
 
2052
                resumed_pack.abort()
 
2053
            finally:
 
2054
                # See comment in previous finally block.
 
2055
                try:
 
2056
                    self._remove_pack_indices(resumed_pack)
 
2057
                except KeyError:
 
2058
                    pass
 
2059
        del self._resumed_packs[:]
 
2060
 
 
2061
    def _remove_resumed_pack_indices(self):
 
2062
        for resumed_pack in self._resumed_packs:
 
2063
            self._remove_pack_indices(resumed_pack)
 
2064
        del self._resumed_packs[:]
1635
2065
 
1636
2066
    def _commit_write_group(self):
 
2067
        all_missing = set()
 
2068
        for prefix, versioned_file in (
 
2069
                ('revisions', self.repo.revisions),
 
2070
                ('inventories', self.repo.inventories),
 
2071
                ('texts', self.repo.texts),
 
2072
                ('signatures', self.repo.signatures),
 
2073
                ):
 
2074
            missing = versioned_file.get_missing_compression_parent_keys()
 
2075
            all_missing.update([(prefix,) + key for key in missing])
 
2076
        if all_missing:
 
2077
            raise errors.BzrCheckError(
 
2078
                "Repository %s has missing compression parent(s) %r "
 
2079
                 % (self.repo, sorted(all_missing)))
1637
2080
        self._remove_pack_indices(self._new_pack)
 
2081
        should_autopack = False
1638
2082
        if self._new_pack.data_inserted():
1639
2083
            # get all the data to disk and read to use
1640
2084
            self._new_pack.finish()
1641
2085
            self.allocate(self._new_pack)
1642
2086
            self._new_pack = None
 
2087
            should_autopack = True
 
2088
        else:
 
2089
            self._new_pack.abort()
 
2090
            self._new_pack = None
 
2091
        for resumed_pack in self._resumed_packs:
 
2092
            # XXX: this is a pretty ugly way to turn the resumed pack into a
 
2093
            # properly committed pack.
 
2094
            self._names[resumed_pack.name] = None
 
2095
            self._remove_pack_from_memory(resumed_pack)
 
2096
            resumed_pack.finish()
 
2097
            self.allocate(resumed_pack)
 
2098
            should_autopack = True
 
2099
        del self._resumed_packs[:]
 
2100
        if should_autopack:
1643
2101
            if not self.autopack():
1644
2102
                # when autopack takes no steps, the names list is still
1645
2103
                # unsaved.
1646
 
                self._save_pack_names()
 
2104
                return self._save_pack_names()
 
2105
        return []
 
2106
 
 
2107
    def _suspend_write_group(self):
 
2108
        tokens = [pack.name for pack in self._resumed_packs]
 
2109
        self._remove_pack_indices(self._new_pack)
 
2110
        if self._new_pack.data_inserted():
 
2111
            # get all the data to disk and read to use
 
2112
            self._new_pack.finish(suspend=True)
 
2113
            tokens.append(self._new_pack.name)
 
2114
            self._new_pack = None
1647
2115
        else:
1648
2116
            self._new_pack.abort()
1649
2117
            self._new_pack = None
1650
 
        self.repo._text_knit = None
 
2118
        self._remove_resumed_pack_indices()
 
2119
        return tokens
 
2120
 
 
2121
    def _resume_write_group(self, tokens):
 
2122
        for token in tokens:
 
2123
            self._resume_pack(token)
1651
2124
 
1652
2125
 
1653
2126
class KnitPackRepository(KnitRepository):
1654
2127
    """Repository with knit objects stored inside pack containers.
1655
 
    
 
2128
 
1656
2129
    The layering for a KnitPackRepository is:
1657
2130
 
1658
2131
    Graph        |  HPSS    | Repository public layer |
1672
2145
      pack file. The GraphIndex layer works in N-tuples and is unaware of any
1673
2146
      semantic value.
1674
2147
    ===================================================
1675
 
    
 
2148
 
1676
2149
    """
1677
2150
 
1678
2151
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
1683
2156
        self._pack_collection = RepositoryPackCollection(self, self._transport,
1684
2157
            index_transport,
1685
2158
            self._transport.clone('upload'),
1686
 
            self._transport.clone('packs'))
 
2159
            self._transport.clone('packs'),
 
2160
            _format.index_builder_class,
 
2161
            _format.index_class,
 
2162
            use_chk_index=self._format.supports_chks,
 
2163
            )
1687
2164
        self.inventories = KnitVersionedFiles(
1688
2165
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
1689
2166
                add_callback=self._pack_collection.inventory_index.add_callback,
1693
2170
        self.revisions = KnitVersionedFiles(
1694
2171
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
1695
2172
                add_callback=self._pack_collection.revision_index.add_callback,
1696
 
                deltas=False, parents=True, is_locked=self.is_locked),
 
2173
                deltas=False, parents=True, is_locked=self.is_locked,
 
2174
                track_external_parent_refs=True),
1697
2175
            data_access=self._pack_collection.revision_index.data_access,
1698
2176
            max_delta_chain=0)
1699
2177
        self.signatures = KnitVersionedFiles(
1708
2186
                deltas=True, parents=True, is_locked=self.is_locked),
1709
2187
            data_access=self._pack_collection.text_index.data_access,
1710
2188
            max_delta_chain=200)
 
2189
        if _format.supports_chks:
 
2190
            # No graph, no compression:- references from chks are between
 
2191
            # different objects not temporal versions of the same; and without
 
2192
            # some sort of temporal structure knit compression will just fail.
 
2193
            self.chk_bytes = KnitVersionedFiles(
 
2194
                _KnitGraphIndex(self._pack_collection.chk_index.combined_index,
 
2195
                    add_callback=self._pack_collection.chk_index.add_callback,
 
2196
                    deltas=False, parents=False, is_locked=self.is_locked),
 
2197
                data_access=self._pack_collection.chk_index.data_access,
 
2198
                max_delta_chain=0)
 
2199
        else:
 
2200
            self.chk_bytes = None
1711
2201
        # True when the repository object is 'write locked' (as opposed to the
1712
 
        # physical lock only taken out around changes to the pack-names list.) 
 
2202
        # physical lock only taken out around changes to the pack-names list.)
1713
2203
        # Another way to represent this would be a decorator around the control
1714
2204
        # files object that presents logical locks as physical ones - if this
1715
2205
        # gets ugly consider that alternative design. RBC 20071011
1719
2209
        self._reconcile_does_inventory_gc = True
1720
2210
        self._reconcile_fixes_text_parents = True
1721
2211
        self._reconcile_backsup_inventory = False
1722
 
        self._fetch_order = 'unordered'
1723
2212
 
1724
2213
    def _warn_if_deprecated(self):
1725
2214
        # This class isn't deprecated, but one sub-format is
1733
2222
                    % (self._format, self.bzrdir.transport.base))
1734
2223
 
1735
2224
    def _abort_write_group(self):
 
2225
        self.revisions._index._key_dependencies.refs.clear()
1736
2226
        self._pack_collection._abort_write_group()
1737
2227
 
1738
 
    def _find_inconsistent_revision_parents(self):
1739
 
        """Find revisions with incorrectly cached parents.
1740
 
 
1741
 
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
1742
 
            parents-in-revision).
1743
 
        """
1744
 
        if not self.is_locked():
1745
 
            raise errors.ObjectNotLocked(self)
1746
 
        pb = ui.ui_factory.nested_progress_bar()
1747
 
        result = []
1748
 
        try:
1749
 
            revision_nodes = self._pack_collection.revision_index \
1750
 
                .combined_index.iter_all_entries()
1751
 
            index_positions = []
1752
 
            # Get the cached index values for all revisions, and also the location
1753
 
            # in each index of the revision text so we can perform linear IO.
1754
 
            for index, key, value, refs in revision_nodes:
1755
 
                pos, length = value[1:].split(' ')
1756
 
                index_positions.append((index, int(pos), key[0],
1757
 
                    tuple(parent[0] for parent in refs[0])))
1758
 
                pb.update("Reading revision index.", 0, 0)
1759
 
            index_positions.sort()
1760
 
            batch_count = len(index_positions) / 1000 + 1
1761
 
            pb.update("Checking cached revision graph.", 0, batch_count)
1762
 
            for offset in xrange(batch_count):
1763
 
                pb.update("Checking cached revision graph.", offset)
1764
 
                to_query = index_positions[offset * 1000:(offset + 1) * 1000]
1765
 
                if not to_query:
1766
 
                    break
1767
 
                rev_ids = [item[2] for item in to_query]
1768
 
                revs = self.get_revisions(rev_ids)
1769
 
                for revision, item in zip(revs, to_query):
1770
 
                    index_parents = item[3]
1771
 
                    rev_parents = tuple(revision.parent_ids)
1772
 
                    if index_parents != rev_parents:
1773
 
                        result.append((revision.revision_id, index_parents, rev_parents))
1774
 
        finally:
1775
 
            pb.finished()
1776
 
        return result
1777
 
 
1778
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
1779
 
    def get_parents(self, revision_ids):
1780
 
        """See graph._StackedParentsProvider.get_parents."""
1781
 
        parent_map = self.get_parent_map(revision_ids)
1782
 
        return [parent_map.get(r, None) for r in revision_ids]
 
2228
    def _get_source(self, to_format):
 
2229
        if to_format.network_name() == self._format.network_name():
 
2230
            return KnitPackStreamSource(self, to_format)
 
2231
        return super(KnitPackRepository, self)._get_source(to_format)
1783
2232
 
1784
2233
    def _make_parents_provider(self):
1785
2234
        return graph.CachingParentsProvider(self)
1786
2235
 
1787
2236
    def _refresh_data(self):
1788
 
        if self._write_lock_count == 1 or (
1789
 
            self.control_files._lock_count == 1 and
1790
 
            self.control_files._lock_mode == 'r'):
1791
 
            # forget what names there are
1792
 
            self._pack_collection.reset()
1793
 
            # XXX: Better to do an in-memory merge when acquiring a new lock -
1794
 
            # factor out code from _save_pack_names.
1795
 
            self._pack_collection.ensure_loaded()
 
2237
        if not self.is_locked():
 
2238
            return
 
2239
        self._pack_collection.reload_pack_names()
1796
2240
 
1797
2241
    def _start_write_group(self):
1798
2242
        self._pack_collection._start_write_group()
1799
2243
 
1800
2244
    def _commit_write_group(self):
 
2245
        self.revisions._index._key_dependencies.refs.clear()
1801
2246
        return self._pack_collection._commit_write_group()
1802
2247
 
 
2248
    def suspend_write_group(self):
 
2249
        # XXX check self._write_group is self.get_transaction()?
 
2250
        tokens = self._pack_collection._suspend_write_group()
 
2251
        self.revisions._index._key_dependencies.refs.clear()
 
2252
        self._write_group = None
 
2253
        return tokens
 
2254
 
 
2255
    def _resume_write_group(self, tokens):
 
2256
        self._start_write_group()
 
2257
        try:
 
2258
            self._pack_collection._resume_write_group(tokens)
 
2259
        except errors.UnresumableWriteGroup:
 
2260
            self._abort_write_group()
 
2261
            raise
 
2262
        for pack in self._pack_collection._resumed_packs:
 
2263
            self.revisions._index.scan_unvalidated_index(pack.revision_index)
 
2264
 
1803
2265
    def get_transaction(self):
1804
2266
        if self._write_lock_count:
1805
2267
            return self._transaction
1813
2275
        return self._write_lock_count
1814
2276
 
1815
2277
    def lock_write(self, token=None):
1816
 
        if not self._write_lock_count and self.is_locked():
 
2278
        locked = self.is_locked()
 
2279
        if not self._write_lock_count and locked:
1817
2280
            raise errors.ReadOnlyError(self)
1818
2281
        self._write_lock_count += 1
1819
2282
        if self._write_lock_count == 1:
1820
2283
            self._transaction = transactions.WriteTransaction()
 
2284
        if not locked:
1821
2285
            for repo in self._fallback_repositories:
1822
2286
                # Writes don't affect fallback repos
1823
2287
                repo.lock_read()
1824
 
        self._refresh_data()
 
2288
            self._refresh_data()
1825
2289
 
1826
2290
    def lock_read(self):
 
2291
        locked = self.is_locked()
1827
2292
        if self._write_lock_count:
1828
2293
            self._write_lock_count += 1
1829
2294
        else:
1830
2295
            self.control_files.lock_read()
 
2296
        if not locked:
1831
2297
            for repo in self._fallback_repositories:
1832
 
                # Writes don't affect fallback repos
1833
2298
                repo.lock_read()
1834
 
        self._refresh_data()
 
2299
            self._refresh_data()
1835
2300
 
1836
2301
    def leave_lock_in_place(self):
1837
2302
        # not supported - raise an error
1842
2307
        raise NotImplementedError(self.dont_leave_lock_in_place)
1843
2308
 
1844
2309
    @needs_write_lock
1845
 
    def pack(self):
 
2310
    def pack(self, hint=None):
1846
2311
        """Compress the data within the repository.
1847
2312
 
1848
2313
        This will pack all the data to a single pack. In future it may
1849
2314
        recompress deltas or do other such expensive operations.
1850
2315
        """
1851
 
        self._pack_collection.pack()
 
2316
        self._pack_collection.pack(hint=hint)
1852
2317
 
1853
2318
    @needs_write_lock
1854
2319
    def reconcile(self, other=None, thorough=False):
1858
2323
        reconciler.reconcile()
1859
2324
        return reconciler
1860
2325
 
 
2326
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
 
2327
        packer = ReconcilePacker(collection, packs, extension, revs)
 
2328
        return packer.pack(pb)
 
2329
 
1861
2330
    def unlock(self):
1862
2331
        if self._write_lock_count == 1 and self._write_group is not None:
1863
2332
            self.abort_write_group()
1872
2341
                transaction = self._transaction
1873
2342
                self._transaction = None
1874
2343
                transaction.finish()
1875
 
                for repo in self._fallback_repositories:
1876
 
                    repo.unlock()
1877
2344
        else:
1878
2345
            self.control_files.unlock()
 
2346
 
 
2347
        if not self.is_locked():
1879
2348
            for repo in self._fallback_repositories:
1880
2349
                repo.unlock()
1881
2350
 
1882
2351
 
 
2352
class KnitPackStreamSource(StreamSource):
 
2353
    """A StreamSource used to transfer data between same-format KnitPack repos.
 
2354
 
 
2355
    This source assumes:
 
2356
        1) Same serialization format for all objects
 
2357
        2) Same root information
 
2358
        3) XML format inventories
 
2359
        4) Atomic inserts (so we can stream inventory texts before text
 
2360
           content)
 
2361
        5) No chk_bytes
 
2362
    """
 
2363
 
 
2364
    def __init__(self, from_repository, to_format):
 
2365
        super(KnitPackStreamSource, self).__init__(from_repository, to_format)
 
2366
        self._text_keys = None
 
2367
        self._text_fetch_order = 'unordered'
 
2368
 
 
2369
    def _get_filtered_inv_stream(self, revision_ids):
 
2370
        from_repo = self.from_repository
 
2371
        parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
 
2372
        parent_keys = [(p,) for p in parent_ids]
 
2373
        find_text_keys = from_repo._find_text_key_references_from_xml_inventory_lines
 
2374
        parent_text_keys = set(find_text_keys(
 
2375
            from_repo._inventory_xml_lines_for_keys(parent_keys)))
 
2376
        content_text_keys = set()
 
2377
        knit = KnitVersionedFiles(None, None)
 
2378
        factory = KnitPlainFactory()
 
2379
        def find_text_keys_from_content(record):
 
2380
            if record.storage_kind not in ('knit-delta-gz', 'knit-ft-gz'):
 
2381
                raise ValueError("Unknown content storage kind for"
 
2382
                    " inventory text: %s" % (record.storage_kind,))
 
2383
            # It's a knit record, it has a _raw_record field (even if it was
 
2384
            # reconstituted from a network stream).
 
2385
            raw_data = record._raw_record
 
2386
            # read the entire thing
 
2387
            revision_id = record.key[-1]
 
2388
            content, _ = knit._parse_record(revision_id, raw_data)
 
2389
            if record.storage_kind == 'knit-delta-gz':
 
2390
                line_iterator = factory.get_linedelta_content(content)
 
2391
            elif record.storage_kind == 'knit-ft-gz':
 
2392
                line_iterator = factory.get_fulltext_content(content)
 
2393
            content_text_keys.update(find_text_keys(
 
2394
                [(line, revision_id) for line in line_iterator]))
 
2395
        revision_keys = [(r,) for r in revision_ids]
 
2396
        def _filtered_inv_stream():
 
2397
            source_vf = from_repo.inventories
 
2398
            stream = source_vf.get_record_stream(revision_keys,
 
2399
                                                 'unordered', False)
 
2400
            for record in stream:
 
2401
                if record.storage_kind == 'absent':
 
2402
                    raise errors.NoSuchRevision(from_repo, record.key)
 
2403
                find_text_keys_from_content(record)
 
2404
                yield record
 
2405
            self._text_keys = content_text_keys - parent_text_keys
 
2406
        return ('inventories', _filtered_inv_stream())
 
2407
 
 
2408
    def _get_text_stream(self):
 
2409
        # Note: We know we don't have to handle adding root keys, because both
 
2410
        # the source and target are the identical network name.
 
2411
        text_stream = self.from_repository.texts.get_record_stream(
 
2412
                        self._text_keys, self._text_fetch_order, False)
 
2413
        return ('texts', text_stream)
 
2414
 
 
2415
    def get_stream(self, search):
 
2416
        revision_ids = search.get_keys()
 
2417
        for stream_info in self._fetch_revision_texts(revision_ids):
 
2418
            yield stream_info
 
2419
        self._revision_keys = [(rev_id,) for rev_id in revision_ids]
 
2420
        yield self._get_filtered_inv_stream(revision_ids)
 
2421
        yield self._get_text_stream()
 
2422
 
 
2423
 
 
2424
 
1883
2425
class RepositoryFormatPack(MetaDirRepositoryFormat):
1884
2426
    """Format logic for pack structured repositories.
1885
2427
 
1905
2447
    # Set this attribute in derived clases to control the _serializer that the
1906
2448
    # repository objects will have passed to their constructor.
1907
2449
    _serializer = None
 
2450
    # Packs are not confused by ghosts.
 
2451
    supports_ghosts = True
1908
2452
    # External references are not supported in pack repositories yet.
1909
2453
    supports_external_lookups = False
 
2454
    # Most pack formats do not use chk lookups.
 
2455
    supports_chks = False
 
2456
    # What index classes to use
 
2457
    index_builder_class = None
 
2458
    index_class = None
 
2459
    _fetch_uses_deltas = True
 
2460
    fast_deltas = False
1910
2461
 
1911
2462
    def initialize(self, a_bzrdir, shared=False):
1912
2463
        """Create a pack based repository.
1918
2469
        """
1919
2470
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1920
2471
        dirs = ['indices', 'obsolete_packs', 'packs', 'upload']
1921
 
        builder = GraphIndexBuilder()
 
2472
        builder = self.index_builder_class()
1922
2473
        files = [('pack-names', builder.finish())]
1923
2474
        utf8_files = [('format', self.get_format_string())]
1924
 
        
 
2475
 
1925
2476
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1926
2477
        return self.open(a_bzrdir=a_bzrdir, _found=True)
1927
2478
 
1928
2479
    def open(self, a_bzrdir, _found=False, _override_transport=None):
1929
2480
        """See RepositoryFormat.open().
1930
 
        
 
2481
 
1931
2482
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
1932
2483
                                    repository at a slightly different url
1933
2484
                                    than normal. I.e. during 'upgrade'.
1955
2506
 
1956
2507
    repository_class = KnitPackRepository
1957
2508
    _commit_builder_class = PackCommitBuilder
1958
 
    _serializer = xml5.serializer_v5
 
2509
    @property
 
2510
    def _serializer(self):
 
2511
        return xml5.serializer_v5
 
2512
    # What index classes to use
 
2513
    index_builder_class = InMemoryGraphIndex
 
2514
    index_class = GraphIndex
1959
2515
 
1960
2516
    def _get_matching_bzrdir(self):
1961
2517
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
1973
2529
        """See RepositoryFormat.get_format_description()."""
1974
2530
        return "Packs containing knits without subtree support"
1975
2531
 
1976
 
    def check_conversion_target(self, target_format):
1977
 
        pass
1978
 
 
1979
2532
 
1980
2533
class RepositoryFormatKnitPack3(RepositoryFormatPack):
1981
2534
    """A subtrees parameterized Pack repository.
1991
2544
    _commit_builder_class = PackRootCommitBuilder
1992
2545
    rich_root_data = True
1993
2546
    supports_tree_reference = True
1994
 
    _serializer = xml7.serializer_v7
 
2547
    @property
 
2548
    def _serializer(self):
 
2549
        return xml7.serializer_v7
 
2550
    # What index classes to use
 
2551
    index_builder_class = InMemoryGraphIndex
 
2552
    index_class = GraphIndex
1995
2553
 
1996
2554
    def _get_matching_bzrdir(self):
1997
2555
        return bzrdir.format_registry.make_bzrdir(
2002
2560
 
2003
2561
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2004
2562
 
2005
 
    def check_conversion_target(self, target_format):
2006
 
        if not target_format.rich_root_data:
2007
 
            raise errors.BadConversionTarget(
2008
 
                'Does not support rich root data.', target_format)
2009
 
        if not getattr(target_format, 'supports_tree_reference', False):
2010
 
            raise errors.BadConversionTarget(
2011
 
                'Does not support nested trees', target_format)
2012
 
            
2013
2563
    def get_format_string(self):
2014
2564
        """See RepositoryFormat.get_format_string()."""
2015
2565
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
2032
2582
    _commit_builder_class = PackRootCommitBuilder
2033
2583
    rich_root_data = True
2034
2584
    supports_tree_reference = False
2035
 
    _serializer = xml6.serializer_v6
 
2585
    @property
 
2586
    def _serializer(self):
 
2587
        return xml6.serializer_v6
 
2588
    # What index classes to use
 
2589
    index_builder_class = InMemoryGraphIndex
 
2590
    index_class = GraphIndex
2036
2591
 
2037
2592
    def _get_matching_bzrdir(self):
2038
2593
        return bzrdir.format_registry.make_bzrdir(
2043
2598
 
2044
2599
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2045
2600
 
2046
 
    def check_conversion_target(self, target_format):
2047
 
        if not target_format.rich_root_data:
2048
 
            raise errors.BadConversionTarget(
2049
 
                'Does not support rich root data.', target_format)
2050
 
 
2051
2601
    def get_format_string(self):
2052
2602
        """See RepositoryFormat.get_format_string()."""
2053
2603
        return ("Bazaar pack repository format 1 with rich root"
2069
2619
 
2070
2620
    repository_class = KnitPackRepository
2071
2621
    _commit_builder_class = PackCommitBuilder
2072
 
    _serializer = xml5.serializer_v5
2073
2622
    supports_external_lookups = True
 
2623
    # What index classes to use
 
2624
    index_builder_class = InMemoryGraphIndex
 
2625
    index_class = GraphIndex
 
2626
 
 
2627
    @property
 
2628
    def _serializer(self):
 
2629
        return xml5.serializer_v5
2074
2630
 
2075
2631
    def _get_matching_bzrdir(self):
2076
 
        return bzrdir.format_registry.make_bzrdir('development1')
 
2632
        return bzrdir.format_registry.make_bzrdir('1.6')
2077
2633
 
2078
2634
    def _ignore_setting_bzrdir(self, format):
2079
2635
        pass
2088
2644
        """See RepositoryFormat.get_format_description()."""
2089
2645
        return "Packs 5 (adds stacking support, requires bzr 1.6)"
2090
2646
 
2091
 
    def check_conversion_target(self, target_format):
2092
 
        pass
2093
 
 
2094
2647
 
2095
2648
class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
2096
2649
    """A repository with rich roots and stacking.
2105
2658
    _commit_builder_class = PackRootCommitBuilder
2106
2659
    rich_root_data = True
2107
2660
    supports_tree_reference = False # no subtrees
2108
 
    _serializer = xml6.serializer_v6
2109
2661
    supports_external_lookups = True
 
2662
    # What index classes to use
 
2663
    index_builder_class = InMemoryGraphIndex
 
2664
    index_class = GraphIndex
 
2665
 
 
2666
    @property
 
2667
    def _serializer(self):
 
2668
        return xml6.serializer_v6
2110
2669
 
2111
2670
    def _get_matching_bzrdir(self):
2112
2671
        return bzrdir.format_registry.make_bzrdir(
2117
2676
 
2118
2677
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2119
2678
 
2120
 
    def check_conversion_target(self, target_format):
2121
 
        if not target_format.rich_root_data:
2122
 
            raise errors.BadConversionTarget(
2123
 
                'Does not support rich root data.', target_format)
2124
 
 
2125
2679
    def get_format_string(self):
2126
2680
        """See RepositoryFormat.get_format_string()."""
2127
2681
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n"
2147
2701
    _commit_builder_class = PackRootCommitBuilder
2148
2702
    rich_root_data = True
2149
2703
    supports_tree_reference = False # no subtrees
2150
 
    _serializer = xml7.serializer_v7
2151
2704
 
2152
2705
    supports_external_lookups = True
 
2706
    # What index classes to use
 
2707
    index_builder_class = InMemoryGraphIndex
 
2708
    index_class = GraphIndex
 
2709
 
 
2710
    @property
 
2711
    def _serializer(self):
 
2712
        return xml7.serializer_v7
2153
2713
 
2154
2714
    def _get_matching_bzrdir(self):
2155
 
        return bzrdir.format_registry.make_bzrdir(
2156
 
            'development1-subtree')
 
2715
        matching = bzrdir.format_registry.make_bzrdir(
 
2716
            '1.6.1-rich-root')
 
2717
        matching.repository_format = self
 
2718
        return matching
2157
2719
 
2158
2720
    def _ignore_setting_bzrdir(self, format):
2159
2721
        pass
2160
2722
 
2161
2723
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2162
2724
 
2163
 
    def check_conversion_target(self, target_format):
2164
 
        if not target_format.rich_root_data:
2165
 
            raise errors.BadConversionTarget(
2166
 
                'Does not support rich root data.', target_format)
2167
 
 
2168
2725
    def get_format_string(self):
2169
2726
        """See RepositoryFormat.get_format_string()."""
2170
2727
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n"
2174
2731
                " (deprecated)")
2175
2732
 
2176
2733
 
2177
 
class RepositoryFormatPackDevelopment1(RepositoryFormatPack):
2178
 
    """A no-subtrees development repository.
2179
 
 
2180
 
    This format should be retained until the second release after bzr 1.5.
2181
 
 
2182
 
    Supports external lookups, which results in non-truncated ghosts after
2183
 
    reconcile compared to pack-0.92 formats.
 
2734
class RepositoryFormatKnitPack6(RepositoryFormatPack):
 
2735
    """A repository with stacking and btree indexes,
 
2736
    without rich roots or subtrees.
 
2737
 
 
2738
    This is equivalent to pack-1.6 with B+Tree indices.
2184
2739
    """
2185
2740
 
2186
2741
    repository_class = KnitPackRepository
2187
2742
    _commit_builder_class = PackCommitBuilder
2188
 
    _serializer = xml5.serializer_v5
2189
2743
    supports_external_lookups = True
 
2744
    # What index classes to use
 
2745
    index_builder_class = BTreeBuilder
 
2746
    index_class = BTreeGraphIndex
 
2747
 
 
2748
    @property
 
2749
    def _serializer(self):
 
2750
        return xml5.serializer_v5
2190
2751
 
2191
2752
    def _get_matching_bzrdir(self):
2192
 
        return bzrdir.format_registry.make_bzrdir('development1')
 
2753
        return bzrdir.format_registry.make_bzrdir('1.9')
2193
2754
 
2194
2755
    def _ignore_setting_bzrdir(self, format):
2195
2756
        pass
2198
2759
 
2199
2760
    def get_format_string(self):
2200
2761
        """See RepositoryFormat.get_format_string()."""
2201
 
        return "Bazaar development format 1 (needs bzr.dev from before 1.6)\n"
 
2762
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
2202
2763
 
2203
2764
    def get_format_description(self):
2204
2765
        """See RepositoryFormat.get_format_description()."""
2205
 
        return ("Development repository format, currently the same as "
2206
 
            "pack-0.92 with external reference support.\n")
2207
 
 
2208
 
    def check_conversion_target(self, target_format):
 
2766
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
 
2767
 
 
2768
 
 
2769
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
 
2770
    """A repository with rich roots, no subtrees, stacking and btree indexes.
 
2771
 
 
2772
    1.6-rich-root with B+Tree indices.
 
2773
    """
 
2774
 
 
2775
    repository_class = KnitPackRepository
 
2776
    _commit_builder_class = PackRootCommitBuilder
 
2777
    rich_root_data = True
 
2778
    supports_tree_reference = False # no subtrees
 
2779
    supports_external_lookups = True
 
2780
    # What index classes to use
 
2781
    index_builder_class = BTreeBuilder
 
2782
    index_class = BTreeGraphIndex
 
2783
 
 
2784
    @property
 
2785
    def _serializer(self):
 
2786
        return xml6.serializer_v6
 
2787
 
 
2788
    def _get_matching_bzrdir(self):
 
2789
        return bzrdir.format_registry.make_bzrdir(
 
2790
            '1.9-rich-root')
 
2791
 
 
2792
    def _ignore_setting_bzrdir(self, format):
2209
2793
        pass
2210
2794
 
2211
 
 
2212
 
class RepositoryFormatPackDevelopment1Subtree(RepositoryFormatPack):
 
2795
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
2796
 
 
2797
    def get_format_string(self):
 
2798
        """See RepositoryFormat.get_format_string()."""
 
2799
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
 
2800
 
 
2801
    def get_format_description(self):
 
2802
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
 
2803
 
 
2804
 
 
2805
class RepositoryFormatPackDevelopment2Subtree(RepositoryFormatPack):
2213
2806
    """A subtrees development repository.
2214
2807
 
2215
 
    This format should be retained until the second release after bzr 1.5.
2216
 
 
2217
 
    Supports external lookups, which results in non-truncated ghosts after
2218
 
    reconcile compared to pack-0.92 formats.
 
2808
    This format should be retained until the second release after bzr 1.7.
 
2809
 
 
2810
    1.6.1-subtree[as it might have been] with B+Tree indices.
 
2811
 
 
2812
    This is [now] retained until we have a CHK based subtree format in
 
2813
    development.
2219
2814
    """
2220
2815
 
2221
2816
    repository_class = KnitPackRepository
2222
2817
    _commit_builder_class = PackRootCommitBuilder
2223
2818
    rich_root_data = True
2224
2819
    supports_tree_reference = True
2225
 
    _serializer = xml7.serializer_v7
2226
2820
    supports_external_lookups = True
 
2821
    # What index classes to use
 
2822
    index_builder_class = BTreeBuilder
 
2823
    index_class = BTreeGraphIndex
 
2824
 
 
2825
    @property
 
2826
    def _serializer(self):
 
2827
        return xml7.serializer_v7
2227
2828
 
2228
2829
    def _get_matching_bzrdir(self):
2229
2830
        return bzrdir.format_registry.make_bzrdir(
2230
 
            'development1-subtree')
 
2831
            'development-subtree')
2231
2832
 
2232
2833
    def _ignore_setting_bzrdir(self, format):
2233
2834
        pass
2234
2835
 
2235
2836
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2236
2837
 
2237
 
    def check_conversion_target(self, target_format):
2238
 
        if not target_format.rich_root_data:
2239
 
            raise errors.BadConversionTarget(
2240
 
                'Does not support rich root data.', target_format)
2241
 
        if not getattr(target_format, 'supports_tree_reference', False):
2242
 
            raise errors.BadConversionTarget(
2243
 
                'Does not support nested trees', target_format)
2244
 
            
2245
2838
    def get_format_string(self):
2246
2839
        """See RepositoryFormat.get_format_string()."""
2247
 
        return ("Bazaar development format 1 with subtree support "
2248
 
            "(needs bzr.dev from before 1.6)\n")
 
2840
        return ("Bazaar development format 2 with subtree support "
 
2841
            "(needs bzr.dev from before 1.8)\n")
2249
2842
 
2250
2843
    def get_format_description(self):
2251
2844
        """See RepositoryFormat.get_format_description()."""
2252
2845
        return ("Development repository format, currently the same as "
2253
 
            "pack-0.92-subtree with external reference support.\n")
 
2846
            "1.6.1-subtree with B+Tree indices.\n")
 
2847