~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Vincent Ladeuil
  • Date: 2009-06-22 12:52:39 UTC
  • mto: (4471.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4472.
  • Revision ID: v.ladeuil+lp@free.fr-20090622125239-kabo9smxt9c3vnir
Use a consistent scheme for naming pyrex source files.

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