/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-10-26 08:56:09 UTC
  • mfrom: (2592.3.247 mbp-writegroups)
  • Revision ID: pqm@pqm.ubuntu.com-20071026085609-c3r8skfrmjj21j0m
Unlock while in a write group now aborts the write group, unlocks, and errors.  Also includes the knit extraction one-liner tweak.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
from bzrlib.lazy_import import lazy_import
 
18
lazy_import(globals(), """
 
19
from itertools import izip
 
20
import math
 
21
import md5
 
22
import time
 
23
 
 
24
from bzrlib import (
 
25
        debug,
 
26
        pack,
 
27
        ui,
 
28
        )
 
29
from bzrlib.index import (
 
30
    GraphIndex,
 
31
    GraphIndexBuilder,
 
32
    InMemoryGraphIndex,
 
33
    CombinedGraphIndex,
 
34
    GraphIndexPrefixAdapter,
 
35
    )
 
36
from bzrlib.knit import KnitGraphIndex, _PackAccess, _KnitData
 
37
from bzrlib.osutils import rand_chars
 
38
from bzrlib.pack import ContainerWriter
 
39
from bzrlib.store import revision
 
40
""")
 
41
from bzrlib import (
 
42
    bzrdir,
 
43
    deprecated_graph,
 
44
    errors,
 
45
    knit,
 
46
    lockable_files,
 
47
    lockdir,
 
48
    osutils,
 
49
    transactions,
 
50
    xml5,
 
51
    xml7,
 
52
    )
 
53
 
 
54
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
55
from bzrlib.repofmt.knitrepo import KnitRepository
 
56
from bzrlib.repository import (
 
57
    CommitBuilder,
 
58
    MetaDirRepository,
 
59
    MetaDirRepositoryFormat,
 
60
    RootCommitBuilder,
 
61
    )
 
62
import bzrlib.revision as _mod_revision
 
63
from bzrlib.store.revision.knit import KnitRevisionStore
 
64
from bzrlib.store.versioned import VersionedFileStore
 
65
from bzrlib.trace import mutter, note, warning
 
66
 
 
67
 
 
68
class PackCommitBuilder(CommitBuilder):
 
69
    """A subclass of CommitBuilder to add texts with pack semantics.
 
70
    
 
71
    Specifically this uses one knit object rather than one knit object per
 
72
    added text, reducing memory and object pressure.
 
73
    """
 
74
 
 
75
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
76
        return self.repository._pack_collection._add_text_to_weave(file_id,
 
77
            self._new_revision_id, new_lines, parents, nostore_sha,
 
78
            self.random_revid)
 
79
 
 
80
 
 
81
class PackRootCommitBuilder(RootCommitBuilder):
 
82
    """A subclass of RootCommitBuilder to add texts with pack semantics.
 
83
    
 
84
    Specifically this uses one knit object rather than one knit object per
 
85
    added text, reducing memory and object pressure.
 
86
    """
 
87
 
 
88
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
89
        return self.repository._pack_collection._add_text_to_weave(file_id,
 
90
            self._new_revision_id, new_lines, parents, nostore_sha,
 
91
            self.random_revid)
 
92
 
 
93
 
 
94
class Pack(object):
 
95
    """An in memory proxy for a pack and its indices.
 
96
 
 
97
    This is a base class that is not directly used, instead the classes
 
98
    ExistingPack and NewPack are used.
 
99
    """
 
100
 
 
101
    def __init__(self, revision_index, inventory_index, text_index,
 
102
        signature_index):
 
103
        """Create a pack instance.
 
104
 
 
105
        :param revision_index: A GraphIndex for determining what revisions are
 
106
            present in the Pack and accessing the locations of their texts.
 
107
        :param inventory_index: A GraphIndex for determining what inventories are
 
108
            present in the Pack and accessing the locations of their
 
109
            texts/deltas.
 
110
        :param text_index: A GraphIndex for determining what file texts
 
111
            are present in the pack and accessing the locations of their
 
112
            texts/deltas (via (fileid, revisionid) tuples).
 
113
        :param revision_index: A GraphIndex for determining what signatures are
 
114
            present in the Pack and accessing the locations of their texts.
 
115
        """
 
116
        self.revision_index = revision_index
 
117
        self.inventory_index = inventory_index
 
118
        self.text_index = text_index
 
119
        self.signature_index = signature_index
 
120
 
 
121
    def access_tuple(self):
 
122
        """Return a tuple (transport, name) for the pack content."""
 
123
        return self.pack_transport, self.file_name()
 
124
 
 
125
    def file_name(self):
 
126
        """Get the file name for the pack on disk."""
 
127
        return self.name + '.pack'
 
128
 
 
129
    def get_revision_count(self):
 
130
        return self.revision_index.key_count()
 
131
 
 
132
    def inventory_index_name(self, name):
 
133
        """The inv index is the name + .iix."""
 
134
        return self.index_name('inventory', name)
 
135
 
 
136
    def revision_index_name(self, name):
 
137
        """The revision index is the name + .rix."""
 
138
        return self.index_name('revision', name)
 
139
 
 
140
    def signature_index_name(self, name):
 
141
        """The signature index is the name + .six."""
 
142
        return self.index_name('signature', name)
 
143
 
 
144
    def text_index_name(self, name):
 
145
        """The text index is the name + .tix."""
 
146
        return self.index_name('text', name)
 
147
 
 
148
 
 
149
class ExistingPack(Pack):
 
150
    """An in memory proxy for an existing .pack and its disk indices."""
 
151
 
 
152
    def __init__(self, pack_transport, name, revision_index, inventory_index,
 
153
        text_index, signature_index):
 
154
        """Create an ExistingPack object.
 
155
 
 
156
        :param pack_transport: The transport where the pack file resides.
 
157
        :param name: The name of the pack on disk in the pack_transport.
 
158
        """
 
159
        Pack.__init__(self, revision_index, inventory_index, text_index,
 
160
            signature_index)
 
161
        self.name = name
 
162
        self.pack_transport = pack_transport
 
163
        assert None not in (revision_index, inventory_index, text_index,
 
164
            signature_index, name, pack_transport)
 
165
 
 
166
    def __eq__(self, other):
 
167
        return self.__dict__ == other.__dict__
 
168
 
 
169
    def __ne__(self, other):
 
170
        return not self.__eq__(other)
 
171
 
 
172
    def __repr__(self):
 
173
        return "<bzrlib.repofmt.pack_repo.Pack object at 0x%x, %s, %s" % (
 
174
            id(self), self.transport, self.name)
 
175
 
 
176
 
 
177
class NewPack(Pack):
 
178
    """An in memory proxy for a pack which is being created."""
 
179
 
 
180
    # A map of index 'type' to the file extension and position in the
 
181
    # index_sizes array.
 
182
    index_definitions = {
 
183
        'revision': ('.rix', 0),
 
184
        'inventory': ('.iix', 1),
 
185
        'text': ('.tix', 2),
 
186
        'signature': ('.six', 3),
 
187
        }
 
188
 
 
189
    def __init__(self, upload_transport, index_transport, pack_transport,
 
190
        upload_suffix=''):
 
191
        """Create a NewPack instance.
 
192
 
 
193
        :param upload_transport: A writable transport for the pack to be
 
194
            incrementally uploaded to.
 
195
        :param index_transport: A writable transport for the pack's indices to
 
196
            be written to when the pack is finished.
 
197
        :param pack_transport: A writable transport for the pack to be renamed
 
198
            to when the upload is complete. This *must* be the same as
 
199
            upload_transport.clone('../packs').
 
200
        :param upload_suffix: An optional suffix to be given to any temporary
 
201
            files created during the pack creation. e.g '.autopack'
 
202
        """
 
203
        # The relative locations of the packs are constrained, but all are
 
204
        # passed in because the caller has them, so as to avoid object churn.
 
205
        Pack.__init__(self,
 
206
            # Revisions: parents list, no text compression.
 
207
            InMemoryGraphIndex(reference_lists=1),
 
208
            # Inventory: We want to map compression only, but currently the
 
209
            # knit code hasn't been updated enough to understand that, so we
 
210
            # have a regular 2-list index giving parents and compression
 
211
            # source.
 
212
            InMemoryGraphIndex(reference_lists=2),
 
213
            # Texts: compression and per file graph, for all fileids - so two
 
214
            # reference lists and two elements in the key tuple.
 
215
            InMemoryGraphIndex(reference_lists=2, key_elements=2),
 
216
            # Signatures: Just blobs to store, no compression, no parents
 
217
            # listing.
 
218
            InMemoryGraphIndex(reference_lists=0),
 
219
            )
 
220
        # where should the new pack be opened
 
221
        self.upload_transport = upload_transport
 
222
        # where are indices written out to
 
223
        self.index_transport = index_transport
 
224
        # where is the pack renamed to when it is finished?
 
225
        self.pack_transport = pack_transport
 
226
        # tracks the content written to the .pack file.
 
227
        self._hash = md5.new()
 
228
        # a four-tuple with the length in bytes of the indices, once the pack
 
229
        # is finalised. (rev, inv, text, sigs)
 
230
        self.index_sizes = None
 
231
        # How much data to cache when writing packs. Note that this is not
 
232
        # synchronised with reads, because it's not in the transport layer, so
 
233
        # is not safe unless the client knows it won't be reading from the pack
 
234
        # under creation.
 
235
        self._cache_limit = 0
 
236
        # the temporary pack file name.
 
237
        self.random_name = rand_chars(20) + upload_suffix
 
238
        # when was this pack started ?
 
239
        self.start_time = time.time()
 
240
        # open an output stream for the data added to the pack.
 
241
        self.write_stream = self.upload_transport.open_write_stream(
 
242
            self.random_name)
 
243
        if 'pack' in debug.debug_flags:
 
244
            mutter('%s: create_pack: pack stream open: %s%s t+%6.3fs',
 
245
                time.ctime(), self.upload_transport.base, self.random_name,
 
246
                time.time() - self.start_time)
 
247
        # A list of byte sequences to be written to the new pack, and the 
 
248
        # aggregate size of them.  Stored as a list rather than separate 
 
249
        # variables so that the _write_data closure below can update them.
 
250
        self._buffer = [[], 0]
 
251
        # create a callable for adding data 
 
252
        #
 
253
        # robertc says- this is a closure rather than a method on the object
 
254
        # so that the variables are locals, and faster than accessing object
 
255
        # members.
 
256
        def _write_data(bytes, flush=False, _buffer=self._buffer,
 
257
            _write=self.write_stream.write, _update=self._hash.update):
 
258
            _buffer[0].append(bytes)
 
259
            _buffer[1] += len(bytes)
 
260
            # buffer cap
 
261
            if _buffer[1] > self._cache_limit or flush:
 
262
                bytes = ''.join(_buffer[0])
 
263
                _write(bytes)
 
264
                _update(bytes)
 
265
                _buffer[:] = [[], 0]
 
266
        # expose this on self, for the occasion when clients want to add data.
 
267
        self._write_data = _write_data
 
268
        # a pack writer object to serialise pack records.
 
269
        self._writer = pack.ContainerWriter(self._write_data)
 
270
        self._writer.begin()
 
271
        # what state is the pack in? (open, finished, aborted)
 
272
        self._state = 'open'
 
273
 
 
274
    def abort(self):
 
275
        """Cancel creating this pack."""
 
276
        self._state = 'aborted'
 
277
        self.write_stream.close()
 
278
        # Remove the temporary pack file.
 
279
        self.upload_transport.delete(self.random_name)
 
280
        # The indices have no state on disk.
 
281
 
 
282
    def access_tuple(self):
 
283
        """Return a tuple (transport, name) for the pack content."""
 
284
        assert self._state in ('open', 'finished')
 
285
        if self._state == 'finished':
 
286
            return Pack.access_tuple(self)
 
287
        else:
 
288
            return self.upload_transport, self.random_name
 
289
 
 
290
    def data_inserted(self):
 
291
        """True if data has been added to this pack."""
 
292
        return bool(self.get_revision_count() or
 
293
            self.inventory_index.key_count() or
 
294
            self.text_index.key_count() or
 
295
            self.signature_index.key_count())
 
296
 
 
297
    def finish(self):
 
298
        """Finish the new pack.
 
299
 
 
300
        This:
 
301
         - finalises the content
 
302
         - assigns a name (the md5 of the content, currently)
 
303
         - writes out the associated indices
 
304
         - renames the pack into place.
 
305
         - stores the index size tuple for the pack in the index_sizes
 
306
           attribute.
 
307
        """
 
308
        self._writer.end()
 
309
        if self._buffer[1]:
 
310
            self._write_data('', flush=True)
 
311
        self.name = self._hash.hexdigest()
 
312
        # write indices
 
313
        # XXX: It'd be better to write them all to temporary names, then
 
314
        # rename them all into place, so that the window when only some are
 
315
        # visible is smaller.  On the other hand none will be seen until
 
316
        # they're in the names list.
 
317
        self.index_sizes = [None, None, None, None]
 
318
        self._write_index('revision', self.revision_index, 'revision')
 
319
        self._write_index('inventory', self.inventory_index, 'inventory')
 
320
        self._write_index('text', self.text_index, 'file texts')
 
321
        self._write_index('signature', self.signature_index,
 
322
            'revision signatures')
 
323
        self.write_stream.close()
 
324
        # Note that this will clobber an existing pack with the same name,
 
325
        # without checking for hash collisions. While this is undesirable this
 
326
        # is something that can be rectified in a subsequent release. One way
 
327
        # to rectify it may be to leave the pack at the original name, writing
 
328
        # its pack-names entry as something like 'HASH: index-sizes
 
329
        # temporary-name'. Allocate that and check for collisions, if it is
 
330
        # collision free then rename it into place. If clients know this scheme
 
331
        # they can handle missing-file errors by:
 
332
        #  - try for HASH.pack
 
333
        #  - try for temporary-name
 
334
        #  - refresh the pack-list to see if the pack is now absent
 
335
        self.upload_transport.rename(self.random_name,
 
336
                '../packs/' + self.name + '.pack')
 
337
        self._state = 'finished'
 
338
        if 'pack' in debug.debug_flags:
 
339
            # XXX: size might be interesting?
 
340
            mutter('%s: create_pack: pack renamed into place: %s%s->%s%s t+%6.3fs',
 
341
                time.ctime(), self.upload_transport.base, self.random_name,
 
342
                self.pack_transport, self.name,
 
343
                time.time() - self.start_time)
 
344
 
 
345
    def index_name(self, index_type, name):
 
346
        """Get the disk name of an index type for pack name 'name'."""
 
347
        return name + NewPack.index_definitions[index_type][0]
 
348
 
 
349
    def index_offset(self, index_type):
 
350
        """Get the position in a index_size array for a given index type."""
 
351
        return NewPack.index_definitions[index_type][1]
 
352
 
 
353
    def _replace_index_with_readonly(self, index_type):
 
354
        setattr(self, index_type + '_index',
 
355
            GraphIndex(self.index_transport,
 
356
                self.index_name(index_type, self.name),
 
357
                self.index_sizes[self.index_offset(index_type)]))
 
358
 
 
359
    def set_write_cache_size(self, size):
 
360
        self._cache_limit = size
 
361
 
 
362
    def _write_index(self, index_type, index, label):
 
363
        """Write out an index.
 
364
 
 
365
        :param index_type: The type of index to write - e.g. 'revision'.
 
366
        :param index: The index object to serialise.
 
367
        :param label: What label to give the index e.g. 'revision'.
 
368
        """
 
369
        index_name = self.index_name(index_type, self.name)
 
370
        self.index_sizes[self.index_offset(index_type)] = \
 
371
            self.index_transport.put_file(index_name, index.finish())
 
372
        if 'pack' in debug.debug_flags:
 
373
            # XXX: size might be interesting?
 
374
            mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs',
 
375
                time.ctime(), label, self.upload_transport.base,
 
376
                self.random_name, time.time() - self.start_time)
 
377
        # Replace the writable index on this object with a readonly, 
 
378
        # presently unloaded index. We should alter
 
379
        # the index layer to make its finish() error if add_node is
 
380
        # subsequently used. RBC
 
381
        self._replace_index_with_readonly(index_type)
 
382
 
 
383
 
 
384
class AggregateIndex(object):
 
385
    """An aggregated index for the RepositoryPackCollection.
 
386
 
 
387
    AggregateIndex is reponsible for managing the PackAccess object,
 
388
    Index-To-Pack mapping, and all indices list for a specific type of index
 
389
    such as 'revision index'.
 
390
 
 
391
    A CombinedIndex provides an index on a single key space built up
 
392
    from several on-disk indices.  The AggregateIndex builds on this 
 
393
    to provide a knit access layer, and allows having up to one writable
 
394
    index within the collection.
 
395
    """
 
396
    # XXX: Probably 'can be written to' could/should be separated from 'acts
 
397
    # like a knit index' -- mbp 20071024
 
398
 
 
399
    def __init__(self):
 
400
        """Create an AggregateIndex."""
 
401
        self.index_to_pack = {}
 
402
        self.combined_index = CombinedGraphIndex([])
 
403
        self.knit_access = _PackAccess(self.index_to_pack)
 
404
 
 
405
    def replace_indices(self, index_to_pack, indices):
 
406
        """Replace the current mappings with fresh ones.
 
407
 
 
408
        This should probably not be used eventually, rather incremental add and
 
409
        removal of indices. It has been added during refactoring of existing
 
410
        code.
 
411
 
 
412
        :param index_to_pack: A mapping from index objects to
 
413
            (transport, name) tuples for the pack file data.
 
414
        :param indices: A list of indices.
 
415
        """
 
416
        # refresh the revision pack map dict without replacing the instance.
 
417
        self.index_to_pack.clear()
 
418
        self.index_to_pack.update(index_to_pack)
 
419
        # XXX: API break - clearly a 'replace' method would be good?
 
420
        self.combined_index._indices[:] = indices
 
421
        # the current add nodes callback for the current writable index if
 
422
        # there is one.
 
423
        self.add_callback = None
 
424
 
 
425
    def add_index(self, index, pack):
 
426
        """Add index to the aggregate, which is an index for Pack pack.
 
427
 
 
428
        Future searches on the aggregate index will seach this new index
 
429
        before all previously inserted indices.
 
430
        
 
431
        :param index: An Index for the pack.
 
432
        :param pack: A Pack instance.
 
433
        """
 
434
        # expose it to the index map
 
435
        self.index_to_pack[index] = pack.access_tuple()
 
436
        # put it at the front of the linear index list
 
437
        self.combined_index.insert_index(0, index)
 
438
 
 
439
    def add_writable_index(self, index, pack):
 
440
        """Add an index which is able to have data added to it.
 
441
 
 
442
        There can be at most one writable index at any time.  Any
 
443
        modifications made to the knit are put into this index.
 
444
        
 
445
        :param index: An index from the pack parameter.
 
446
        :param pack: A Pack instance.
 
447
        """
 
448
        assert self.add_callback is None, \
 
449
            "%s already has a writable index through %s" % \
 
450
            (self, self.add_callback)
 
451
        # allow writing: queue writes to a new index
 
452
        self.add_index(index, pack)
 
453
        # Updates the index to packs mapping as a side effect,
 
454
        self.knit_access.set_writer(pack._writer, index, pack.access_tuple())
 
455
        self.add_callback = index.add_nodes
 
456
 
 
457
    def clear(self):
 
458
        """Reset all the aggregate data to nothing."""
 
459
        self.knit_access.set_writer(None, None, (None, None))
 
460
        self.index_to_pack.clear()
 
461
        del self.combined_index._indices[:]
 
462
        self.add_callback = None
 
463
 
 
464
    def remove_index(self, index, pack):
 
465
        """Remove index from the indices used to answer queries.
 
466
        
 
467
        :param index: An index from the pack parameter.
 
468
        :param pack: A Pack instance.
 
469
        """
 
470
        del self.index_to_pack[index]
 
471
        self.combined_index._indices.remove(index)
 
472
        if (self.add_callback is not None and
 
473
            getattr(index, 'add_nodes', None) == self.add_callback):
 
474
            self.add_callback = None
 
475
            self.knit_access.set_writer(None, None, (None, None))
 
476
 
 
477
 
 
478
class RepositoryPackCollection(object):
 
479
    """Management of packs within a repository."""
 
480
 
 
481
    def __init__(self, repo, transport, index_transport, upload_transport,
 
482
                 pack_transport):
 
483
        """Create a new RepositoryPackCollection.
 
484
 
 
485
        :param transport: Addresses the repository base directory 
 
486
            (typically .bzr/repository/).
 
487
        :param index_transport: Addresses the directory containing indices.
 
488
        :param upload_transport: Addresses the directory into which packs are written
 
489
            while they're being created.
 
490
        :param pack_transport: Addresses the directory of existing complete packs.
 
491
        """
 
492
        self.repo = repo
 
493
        self.transport = transport
 
494
        self._index_transport = index_transport
 
495
        self._upload_transport = upload_transport
 
496
        self._pack_transport = pack_transport
 
497
        self._suffix_offsets = {'.rix': 0, '.iix': 1, '.tix': 2, '.six': 3}
 
498
        self.packs = []
 
499
        # name:Pack mapping
 
500
        self._packs_by_name = {}
 
501
        # the previous pack-names content
 
502
        self._packs_at_load = None
 
503
        # when a pack is being created by this object, the state of that pack.
 
504
        self._new_pack = None
 
505
        # aggregated revision index data
 
506
        self.revision_index = AggregateIndex()
 
507
        self.inventory_index = AggregateIndex()
 
508
        self.text_index = AggregateIndex()
 
509
        self.signature_index = AggregateIndex()
 
510
 
 
511
    def add_pack_to_memory(self, pack):
 
512
        """Make a Pack object available to the repository to satisfy queries.
 
513
        
 
514
        :param pack: A Pack object.
 
515
        """
 
516
        assert pack.name not in self._packs_by_name
 
517
        self.packs.append(pack)
 
518
        self._packs_by_name[pack.name] = pack
 
519
        self.revision_index.add_index(pack.revision_index, pack)
 
520
        self.inventory_index.add_index(pack.inventory_index, pack)
 
521
        self.text_index.add_index(pack.text_index, pack)
 
522
        self.signature_index.add_index(pack.signature_index, pack)
 
523
        
 
524
    def _add_text_to_weave(self, file_id, revision_id, new_lines, parents,
 
525
        nostore_sha, random_revid):
 
526
        file_id_index = GraphIndexPrefixAdapter(
 
527
            self.text_index.combined_index,
 
528
            (file_id, ), 1,
 
529
            add_nodes_callback=self.text_index.add_callback)
 
530
        self.repo._text_knit._index._graph_index = file_id_index
 
531
        self.repo._text_knit._index._add_callback = file_id_index.add_nodes
 
532
        return self.repo._text_knit.add_lines_with_ghosts(
 
533
            revision_id, parents, new_lines, nostore_sha=nostore_sha,
 
534
            random_id=random_revid, check_content=False)[0:2]
 
535
 
 
536
    def all_packs(self):
 
537
        """Return a list of all the Pack objects this repository has.
 
538
 
 
539
        Note that an in-progress pack being created is not returned.
 
540
 
 
541
        :return: A list of Pack objects for all the packs in the repository.
 
542
        """
 
543
        result = []
 
544
        for name in self.names():
 
545
            result.append(self.get_pack_by_name(name))
 
546
        return result
 
547
 
 
548
    def autopack(self):
 
549
        """Pack the pack collection incrementally.
 
550
        
 
551
        This will not attempt global reorganisation or recompression,
 
552
        rather it will just ensure that the total number of packs does
 
553
        not grow without bound. It uses the _max_pack_count method to
 
554
        determine if autopacking is needed, and the pack_distribution
 
555
        method to determine the number of revisions in each pack.
 
556
 
 
557
        If autopacking takes place then the packs name collection will have
 
558
        been flushed to disk - packing requires updating the name collection
 
559
        in synchronisation with certain steps. Otherwise the names collection
 
560
        is not flushed.
 
561
 
 
562
        :return: True if packing took place.
 
563
        """
 
564
        # XXX: Should not be needed when the management of indices is sane.
 
565
        total_revisions = self.revision_index.combined_index.key_count()
 
566
        total_packs = len(self._names)
 
567
        if self._max_pack_count(total_revisions) >= total_packs:
 
568
            return False
 
569
        # XXX: the following may want to be a class, to pack with a given
 
570
        # policy.
 
571
        mutter('Auto-packing repository %s, which has %d pack files, '
 
572
            'containing %d revisions into %d packs.', self, total_packs,
 
573
            total_revisions, self._max_pack_count(total_revisions))
 
574
        # determine which packs need changing
 
575
        pack_distribution = self.pack_distribution(total_revisions)
 
576
        existing_packs = []
 
577
        for pack in self.all_packs():
 
578
            revision_count = pack.get_revision_count()
 
579
            if revision_count == 0:
 
580
                # revision less packs are not generated by normal operation,
 
581
                # only by operations like sign-my-commits, and thus will not
 
582
                # tend to grow rapdily or without bound like commit containing
 
583
                # packs do - leave them alone as packing them really should
 
584
                # group their data with the relevant commit, and that may
 
585
                # involve rewriting ancient history - which autopack tries to
 
586
                # avoid. Alternatively we could not group the data but treat
 
587
                # each of these as having a single revision, and thus add 
 
588
                # one revision for each to the total revision count, to get
 
589
                # a matching distribution.
 
590
                continue
 
591
            existing_packs.append((revision_count, pack))
 
592
        pack_operations = self.plan_autopack_combinations(
 
593
            existing_packs, pack_distribution)
 
594
        self._execute_pack_operations(pack_operations)
 
595
        return True
 
596
 
 
597
    def create_pack_from_packs(self, packs, suffix, revision_ids=None):
 
598
        """Create a new pack by reading data from other packs.
 
599
 
 
600
        This does little more than a bulk copy of data. One key difference
 
601
        is that data with the same item key across multiple packs is elided
 
602
        from the output. The new pack is written into the current pack store
 
603
        along with its indices, and the name added to the pack names. The 
 
604
        source packs are not altered and are not required to be in the current
 
605
        pack collection.
 
606
 
 
607
        :param packs: An iterable of Packs to combine.
 
608
        :param revision_ids: Either None, to copy all data, or a list
 
609
            of revision_ids to limit the copied data to the data they
 
610
            introduced.
 
611
        :return: A Pack object, or None if nothing was copied.
 
612
        """
 
613
        # open a pack - using the same name as the last temporary file
 
614
        # - which has already been flushed, so its safe.
 
615
        # XXX: - duplicate code warning with start_write_group; fix before
 
616
        #      considering 'done'.
 
617
        if self._new_pack is not None:
 
618
            raise errors.BzrError('call to create_pack_from_packs while '
 
619
                'another pack is being written.')
 
620
        if revision_ids is not None and len(revision_ids) == 0:
 
621
            # silly fetch request.
 
622
            return None
 
623
        new_pack = NewPack(self._upload_transport, self._index_transport,
 
624
            self._pack_transport, upload_suffix=suffix)
 
625
        # buffer data - we won't be reading-back during the pack creation and
 
626
        # this makes a significant difference on sftp pushes.
 
627
        new_pack.set_write_cache_size(1024*1024)
 
628
        if 'pack' in debug.debug_flags:
 
629
            plain_pack_list = ['%s%s' % (a_pack.pack_transport.base, a_pack.name)
 
630
                for a_pack in packs]
 
631
            if revision_ids is not None:
 
632
                rev_count = len(revision_ids)
 
633
            else:
 
634
                rev_count = 'all'
 
635
            mutter('%s: create_pack: creating pack from source packs: '
 
636
                '%s%s %s revisions wanted %s t=0',
 
637
                time.ctime(), self._upload_transport.base, new_pack.random_name,
 
638
                plain_pack_list, rev_count)
 
639
        # select revisions
 
640
        if revision_ids:
 
641
            revision_keys = [(revision_id,) for revision_id in revision_ids]
 
642
        else:
 
643
            revision_keys = None
 
644
 
 
645
        # select revision keys
 
646
        revision_index_map = self._packs_list_to_pack_map_and_index_list(
 
647
            packs, 'revision_index')[0]
 
648
        revision_nodes = self._index_contents(revision_index_map, revision_keys)
 
649
        # copy revision keys and adjust values
 
650
        list(self._copy_nodes_graph(revision_nodes, revision_index_map,
 
651
            new_pack._writer, new_pack.revision_index))
 
652
        if 'pack' in debug.debug_flags:
 
653
            mutter('%s: create_pack: revisions copied: %s%s %d items t+%6.3fs',
 
654
                time.ctime(), self._upload_transport.base, new_pack.random_name,
 
655
                new_pack.revision_index.key_count(),
 
656
                time.time() - new_pack.start_time)
 
657
        # select inventory keys
 
658
        inv_keys = revision_keys # currently the same keyspace, and note that
 
659
        # querying for keys here could introduce a bug where an inventory item
 
660
        # is missed, so do not change it to query separately without cross
 
661
        # checking like the text key check below.
 
662
        inventory_index_map = self._packs_list_to_pack_map_and_index_list(
 
663
            packs, 'inventory_index')[0]
 
664
        inv_nodes = self._index_contents(inventory_index_map, inv_keys)
 
665
        # copy inventory keys and adjust values
 
666
        # XXX: Should be a helper function to allow different inv representation
 
667
        # at this point.
 
668
        inv_lines = self._copy_nodes_graph(inv_nodes, inventory_index_map,
 
669
            new_pack._writer, new_pack.inventory_index, output_lines=True)
 
670
        if revision_ids:
 
671
            fileid_revisions = self.repo._find_file_ids_from_xml_inventory_lines(
 
672
                inv_lines, revision_ids)
 
673
            text_filter = []
 
674
            for fileid, file_revids in fileid_revisions.iteritems():
 
675
                text_filter.extend(
 
676
                    [(fileid, file_revid) for file_revid in file_revids])
 
677
        else:
 
678
            # eat the iterator to cause it to execute.
 
679
            list(inv_lines)
 
680
            text_filter = None
 
681
        if 'pack' in debug.debug_flags:
 
682
            mutter('%s: create_pack: inventories copied: %s%s %d items t+%6.3fs',
 
683
                time.ctime(), self._upload_transport.base, new_pack.random_name,
 
684
                new_pack.inventory_index.key_count(),
 
685
                time.time() - new_pack.start_time)
 
686
        # select text keys
 
687
        text_index_map = self._packs_list_to_pack_map_and_index_list(
 
688
            packs, 'text_index')[0]
 
689
        text_nodes = self._index_contents(text_index_map, text_filter)
 
690
        if text_filter is not None:
 
691
            # We could return the keys copied as part of the return value from
 
692
            # _copy_nodes_graph but this doesn't work all that well with the
 
693
            # need to get line output too, so we check separately, and as we're
 
694
            # going to buffer everything anyway, we check beforehand, which
 
695
            # saves reading knit data over the wire when we know there are
 
696
            # mising records.
 
697
            text_nodes = set(text_nodes)
 
698
            present_text_keys = set(_node[1] for _node in text_nodes)
 
699
            missing_text_keys = set(text_filter) - present_text_keys
 
700
            if missing_text_keys:
 
701
                # TODO: raise a specific error that can handle many missing
 
702
                # keys.
 
703
                a_missing_key = missing_text_keys.pop()
 
704
                raise errors.RevisionNotPresent(a_missing_key[1],
 
705
                    a_missing_key[0])
 
706
        # copy text keys and adjust values
 
707
        list(self._copy_nodes_graph(text_nodes, text_index_map,
 
708
            new_pack._writer, new_pack.text_index))
 
709
        if 'pack' in debug.debug_flags:
 
710
            mutter('%s: create_pack: file texts copied: %s%s %d items t+%6.3fs',
 
711
                time.ctime(), self._upload_transport.base, new_pack.random_name,
 
712
                new_pack.text_index.key_count(),
 
713
                time.time() - new_pack.start_time)
 
714
        # select signature keys
 
715
        signature_filter = revision_keys # same keyspace
 
716
        signature_index_map = self._packs_list_to_pack_map_and_index_list(
 
717
            packs, 'signature_index')[0]
 
718
        signature_nodes = self._index_contents(signature_index_map,
 
719
            signature_filter)
 
720
        # copy signature keys and adjust values
 
721
        self._copy_nodes(signature_nodes, signature_index_map, new_pack._writer,
 
722
            new_pack.signature_index)
 
723
        if 'pack' in debug.debug_flags:
 
724
            mutter('%s: create_pack: revision signatures copied: %s%s %d items t+%6.3fs',
 
725
                time.ctime(), self._upload_transport.base, new_pack.random_name,
 
726
                new_pack.signature_index.key_count(),
 
727
                time.time() - new_pack.start_time)
 
728
        if not new_pack.data_inserted():
 
729
            new_pack.abort()
 
730
            return None
 
731
        new_pack.finish()
 
732
        self.allocate(new_pack)
 
733
        return new_pack
 
734
 
 
735
    def _execute_pack_operations(self, pack_operations):
 
736
        """Execute a series of pack operations.
 
737
 
 
738
        :param pack_operations: A list of [revision_count, packs_to_combine].
 
739
        :return: None.
 
740
        """
 
741
        for revision_count, packs in pack_operations:
 
742
            # we may have no-ops from the setup logic
 
743
            if len(packs) == 0:
 
744
                continue
 
745
            # have a progress bar?
 
746
            self.create_pack_from_packs(packs, '.autopack')
 
747
            for pack in packs:
 
748
                self._remove_pack_from_memory(pack)
 
749
        # record the newly available packs and stop advertising the old
 
750
        # packs
 
751
        self._save_pack_names()
 
752
        # Move the old packs out of the way now they are no longer referenced.
 
753
        for revision_count, packs in pack_operations:
 
754
            self._obsolete_packs(packs)
 
755
 
 
756
    def lock_names(self):
 
757
        """Acquire the mutex around the pack-names index.
 
758
        
 
759
        This cannot be used in the middle of a read-only transaction on the
 
760
        repository.
 
761
        """
 
762
        self.repo.control_files.lock_write()
 
763
 
 
764
    def pack(self):
 
765
        """Pack the pack collection totally."""
 
766
        self.ensure_loaded()
 
767
        total_packs = len(self._names)
 
768
        if total_packs < 2:
 
769
            return
 
770
        total_revisions = self.revision_index.combined_index.key_count()
 
771
        # XXX: the following may want to be a class, to pack with a given
 
772
        # policy.
 
773
        mutter('Packing repository %s, which has %d pack files, '
 
774
            'containing %d revisions into 1 packs.', self, total_packs,
 
775
            total_revisions)
 
776
        # determine which packs need changing
 
777
        pack_distribution = [1]
 
778
        pack_operations = [[0, []]]
 
779
        for pack in self.all_packs():
 
780
            revision_count = pack.get_revision_count()
 
781
            pack_operations[-1][0] += revision_count
 
782
            pack_operations[-1][1].append(pack)
 
783
        self._execute_pack_operations(pack_operations)
 
784
 
 
785
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
 
786
        """Plan a pack operation.
 
787
 
 
788
        :param existing_packs: The packs to pack. (A list of (revcount, Pack)
 
789
            tuples).
 
790
        :param pack_distribution: A list with the number of revisions desired
 
791
            in each pack.
 
792
        """
 
793
        if len(existing_packs) <= len(pack_distribution):
 
794
            return []
 
795
        existing_packs.sort(reverse=True)
 
796
        pack_operations = [[0, []]]
 
797
        # plan out what packs to keep, and what to reorganise
 
798
        while len(existing_packs):
 
799
            # take the largest pack, and if its less than the head of the
 
800
            # distribution chart we will include its contents in the new pack for
 
801
            # that position. If its larger, we remove its size from the
 
802
            # distribution chart
 
803
            next_pack_rev_count, next_pack = existing_packs.pop(0)
 
804
            if next_pack_rev_count >= pack_distribution[0]:
 
805
                # this is already packed 'better' than this, so we can
 
806
                # not waste time packing it.
 
807
                while next_pack_rev_count > 0:
 
808
                    next_pack_rev_count -= pack_distribution[0]
 
809
                    if next_pack_rev_count >= 0:
 
810
                        # more to go
 
811
                        del pack_distribution[0]
 
812
                    else:
 
813
                        # didn't use that entire bucket up
 
814
                        pack_distribution[0] = -next_pack_rev_count
 
815
            else:
 
816
                # add the revisions we're going to add to the next output pack
 
817
                pack_operations[-1][0] += next_pack_rev_count
 
818
                # allocate this pack to the next pack sub operation
 
819
                pack_operations[-1][1].append(next_pack)
 
820
                if pack_operations[-1][0] >= pack_distribution[0]:
 
821
                    # this pack is used up, shift left.
 
822
                    del pack_distribution[0]
 
823
                    pack_operations.append([0, []])
 
824
        
 
825
        return pack_operations
 
826
 
 
827
    def _copy_nodes(self, nodes, index_map, writer, write_index):
 
828
        # plan a readv on each source pack:
 
829
        # group by pack
 
830
        nodes = sorted(nodes)
 
831
        # how to map this into knit.py - or knit.py into this?
 
832
        # we don't want the typical knit logic, we want grouping by pack
 
833
        # at this point - perhaps a helper library for the following code 
 
834
        # duplication points?
 
835
        request_groups = {}
 
836
        for index, key, value in nodes:
 
837
            if index not in request_groups:
 
838
                request_groups[index] = []
 
839
            request_groups[index].append((key, value))
 
840
        for index, items in request_groups.iteritems():
 
841
            pack_readv_requests = []
 
842
            for key, value in items:
 
843
                # ---- KnitGraphIndex.get_position
 
844
                bits = value[1:].split(' ')
 
845
                offset, length = int(bits[0]), int(bits[1])
 
846
                pack_readv_requests.append((offset, length, (key, value[0])))
 
847
            # linear scan up the pack
 
848
            pack_readv_requests.sort()
 
849
            # copy the data
 
850
            transport, path = index_map[index]
 
851
            reader = pack.make_readv_reader(transport, path,
 
852
                [offset[0:2] for offset in pack_readv_requests])
 
853
            for (names, read_func), (_1, _2, (key, eol_flag)) in \
 
854
                izip(reader.iter_records(), pack_readv_requests):
 
855
                raw_data = read_func(None)
 
856
                pos, size = writer.add_bytes_record(raw_data, names)
 
857
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size))
 
858
 
 
859
    def _copy_nodes_graph(self, nodes, index_map, writer, write_index,
 
860
        output_lines=False):
 
861
        """Copy knit nodes between packs.
 
862
 
 
863
        :param output_lines: Return lines present in the copied data as
 
864
            an iterator.
 
865
        """
 
866
        # for record verification
 
867
        knit_data = _KnitData(None)
 
868
        # for line extraction when requested (inventories only)
 
869
        if output_lines:
 
870
            factory = knit.KnitPlainFactory()
 
871
        # plan a readv on each source pack:
 
872
        # group by pack
 
873
        nodes = sorted(nodes)
 
874
        # how to map this into knit.py - or knit.py into this?
 
875
        # we don't want the typical knit logic, we want grouping by pack
 
876
        # at this point - perhaps a helper library for the following code 
 
877
        # duplication points?
 
878
        request_groups = {}
 
879
        for index, key, value, references in nodes:
 
880
            if index not in request_groups:
 
881
                request_groups[index] = []
 
882
            request_groups[index].append((key, value, references))
 
883
        for index, items in request_groups.iteritems():
 
884
            pack_readv_requests = []
 
885
            for key, value, references in items:
 
886
                # ---- KnitGraphIndex.get_position
 
887
                bits = value[1:].split(' ')
 
888
                offset, length = int(bits[0]), int(bits[1])
 
889
                pack_readv_requests.append((offset, length, (key, value[0], references)))
 
890
            # linear scan up the pack
 
891
            pack_readv_requests.sort()
 
892
            # copy the data
 
893
            transport, path = index_map[index]
 
894
            reader = pack.make_readv_reader(transport, path,
 
895
                [offset[0:2] for offset in pack_readv_requests])
 
896
            for (names, read_func), (_1, _2, (key, eol_flag, references)) in \
 
897
                izip(reader.iter_records(), pack_readv_requests):
 
898
                raw_data = read_func(None)
 
899
                if output_lines:
 
900
                    # read the entire thing
 
901
                    content, _ = knit_data._parse_record(key[-1], raw_data)
 
902
                    if len(references[-1]) == 0:
 
903
                        line_iterator = factory.get_fulltext_content(content)
 
904
                    else:
 
905
                        line_iterator = factory.get_linedelta_content(content)
 
906
                    for line in line_iterator:
 
907
                        yield line
 
908
                else:
 
909
                    # check the header only
 
910
                    df, _ = knit_data._parse_record_header(key[-1], raw_data)
 
911
                    df.close()
 
912
                pos, size = writer.add_bytes_record(raw_data, names)
 
913
                write_index.add_node(key, eol_flag + "%d %d" % (pos, size), references)
 
914
 
 
915
    def ensure_loaded(self):
 
916
        # NB: if you see an assertion error here, its probably access against
 
917
        # an unlocked repo. Naughty.
 
918
        assert self.repo.is_locked()
 
919
        if self._names is None:
 
920
            self._names = {}
 
921
            self._packs_at_load = set()
 
922
            for index, key, value in self._iter_disk_pack_index():
 
923
                name = key[0]
 
924
                self._names[name] = self._parse_index_sizes(value)
 
925
                self._packs_at_load.add((key, value))
 
926
        # populate all the metadata.
 
927
        self.all_packs()
 
928
 
 
929
    def _parse_index_sizes(self, value):
 
930
        """Parse a string of index sizes."""
 
931
        return tuple([int(digits) for digits in value.split(' ')])
 
932
 
 
933
    def get_pack_by_name(self, name):
 
934
        """Get a Pack object by name.
 
935
 
 
936
        :param name: The name of the pack - e.g. '123456'
 
937
        :return: A Pack object.
 
938
        """
 
939
        try:
 
940
            return self._packs_by_name[name]
 
941
        except KeyError:
 
942
            rev_index = self._make_index(name, '.rix')
 
943
            inv_index = self._make_index(name, '.iix')
 
944
            txt_index = self._make_index(name, '.tix')
 
945
            sig_index = self._make_index(name, '.six')
 
946
            result = ExistingPack(self._pack_transport, name, rev_index,
 
947
                inv_index, txt_index, sig_index)
 
948
            self.add_pack_to_memory(result)
 
949
            return result
 
950
 
 
951
    def allocate(self, a_new_pack):
 
952
        """Allocate name in the list of packs.
 
953
 
 
954
        :param a_new_pack: A NewPack instance to be added to the collection of
 
955
            packs for this repository.
 
956
        """
 
957
        self.ensure_loaded()
 
958
        if a_new_pack.name in self._names:
 
959
            # a collision with the packs we know about (not the only possible
 
960
            # collision, see NewPack.finish() for some discussion). Remove our
 
961
            # prior reference to it.
 
962
            self._remove_pack_from_memory(a_new_pack)
 
963
        self._names[a_new_pack.name] = tuple(a_new_pack.index_sizes)
 
964
        self.add_pack_to_memory(a_new_pack)
 
965
 
 
966
    def _iter_disk_pack_index(self):
 
967
        """Iterate over the contents of the pack-names index.
 
968
        
 
969
        This is used when loading the list from disk, and before writing to
 
970
        detect updates from others during our write operation.
 
971
        :return: An iterator of the index contents.
 
972
        """
 
973
        return GraphIndex(self.transport, 'pack-names', None
 
974
                ).iter_all_entries()
 
975
 
 
976
    def _make_index(self, name, suffix):
 
977
        size_offset = self._suffix_offsets[suffix]
 
978
        index_name = name + suffix
 
979
        index_size = self._names[name][size_offset]
 
980
        return GraphIndex(
 
981
            self._index_transport, index_name, index_size)
 
982
 
 
983
    def _max_pack_count(self, total_revisions):
 
984
        """Return the maximum number of packs to use for total revisions.
 
985
        
 
986
        :param total_revisions: The total number of revisions in the
 
987
            repository.
 
988
        """
 
989
        if not total_revisions:
 
990
            return 1
 
991
        digits = str(total_revisions)
 
992
        result = 0
 
993
        for digit in digits:
 
994
            result += int(digit)
 
995
        return result
 
996
 
 
997
    def names(self):
 
998
        """Provide an order to the underlying names."""
 
999
        return sorted(self._names.keys())
 
1000
 
 
1001
    def _obsolete_packs(self, packs):
 
1002
        """Move a number of packs which have been obsoleted out of the way.
 
1003
 
 
1004
        Each pack and its associated indices are moved out of the way.
 
1005
 
 
1006
        Note: for correctness this function should only be called after a new
 
1007
        pack names index has been written without these pack names, and with
 
1008
        the names of packs that contain the data previously available via these
 
1009
        packs.
 
1010
 
 
1011
        :param packs: The packs to obsolete.
 
1012
        :param return: None.
 
1013
        """
 
1014
        for pack in packs:
 
1015
            pack.pack_transport.rename(pack.file_name(),
 
1016
                '../obsolete_packs/' + pack.file_name())
 
1017
            # TODO: Probably needs to know all possible indices for this pack
 
1018
            # - or maybe list the directory and move all indices matching this
 
1019
            # name whether we recognize it or not?
 
1020
            for suffix in ('.iix', '.six', '.tix', '.rix'):
 
1021
                self._index_transport.rename(pack.name + suffix,
 
1022
                    '../obsolete_packs/' + pack.name + suffix)
 
1023
 
 
1024
    def pack_distribution(self, total_revisions):
 
1025
        """Generate a list of the number of revisions to put in each pack.
 
1026
 
 
1027
        :param total_revisions: The total number of revisions in the
 
1028
            repository.
 
1029
        """
 
1030
        if total_revisions == 0:
 
1031
            return [0]
 
1032
        digits = reversed(str(total_revisions))
 
1033
        result = []
 
1034
        for exponent, count in enumerate(digits):
 
1035
            size = 10 ** exponent
 
1036
            for pos in range(int(count)):
 
1037
                result.append(size)
 
1038
        return list(reversed(result))
 
1039
 
 
1040
    def _pack_tuple(self, name):
 
1041
        """Return a tuple with the transport and file name for a pack name."""
 
1042
        return self._pack_transport, name + '.pack'
 
1043
 
 
1044
    def _remove_pack_from_memory(self, pack):
 
1045
        """Remove pack from the packs accessed by this repository.
 
1046
        
 
1047
        Only affects memory state, until self._save_pack_names() is invoked.
 
1048
        """
 
1049
        self._names.pop(pack.name)
 
1050
        self._packs_by_name.pop(pack.name)
 
1051
        self._remove_pack_indices(pack)
 
1052
 
 
1053
    def _remove_pack_indices(self, pack):
 
1054
        """Remove the indices for pack from the aggregated indices."""
 
1055
        self.revision_index.remove_index(pack.revision_index, pack)
 
1056
        self.inventory_index.remove_index(pack.inventory_index, pack)
 
1057
        self.text_index.remove_index(pack.text_index, pack)
 
1058
        self.signature_index.remove_index(pack.signature_index, pack)
 
1059
 
 
1060
    def reset(self):
 
1061
        """Clear all cached data."""
 
1062
        # cached revision data
 
1063
        self.repo._revision_knit = None
 
1064
        self.revision_index.clear()
 
1065
        # cached signature data
 
1066
        self.repo._signature_knit = None
 
1067
        self.signature_index.clear()
 
1068
        # cached file text data
 
1069
        self.text_index.clear()
 
1070
        self.repo._text_knit = None
 
1071
        # cached inventory data
 
1072
        self.inventory_index.clear()
 
1073
        # remove the open pack
 
1074
        self._new_pack = None
 
1075
        # information about packs.
 
1076
        self._names = None
 
1077
        self.packs = []
 
1078
        self._packs_by_name = {}
 
1079
        self._packs_at_load = None
 
1080
 
 
1081
    def _make_index_map(self, index_suffix):
 
1082
        """Return information on existing indices.
 
1083
 
 
1084
        :param suffix: Index suffix added to pack name.
 
1085
 
 
1086
        :returns: (pack_map, indices) where indices is a list of GraphIndex 
 
1087
        objects, and pack_map is a mapping from those objects to the 
 
1088
        pack tuple they describe.
 
1089
        """
 
1090
        # TODO: stop using this; it creates new indices unnecessarily.
 
1091
        self.ensure_loaded()
 
1092
        suffix_map = {'.rix': 'revision_index',
 
1093
            '.six': 'signature_index',
 
1094
            '.iix': 'inventory_index',
 
1095
            '.tix': 'text_index',
 
1096
        }
 
1097
        return self._packs_list_to_pack_map_and_index_list(self.all_packs(),
 
1098
            suffix_map[index_suffix])
 
1099
 
 
1100
    def _packs_list_to_pack_map_and_index_list(self, packs, index_attribute):
 
1101
        """Convert a list of packs to an index pack map and index list.
 
1102
 
 
1103
        :param packs: The packs list to process.
 
1104
        :param index_attribute: The attribute that the desired index is found
 
1105
            on.
 
1106
        :return: A tuple (map, list) where map contains the dict from
 
1107
            index:pack_tuple, and lsit contains the indices in the same order
 
1108
            as the packs list.
 
1109
        """
 
1110
        indices = []
 
1111
        pack_map = {}
 
1112
        for pack in packs:
 
1113
            index = getattr(pack, index_attribute)
 
1114
            indices.append(index)
 
1115
            pack_map[index] = (pack.pack_transport, pack.file_name())
 
1116
        return pack_map, indices
 
1117
 
 
1118
    def _index_contents(self, pack_map, key_filter=None):
 
1119
        """Get an iterable of the index contents from a pack_map.
 
1120
 
 
1121
        :param pack_map: A map from indices to pack details.
 
1122
        :param key_filter: An optional filter to limit the
 
1123
            keys returned.
 
1124
        """
 
1125
        indices = [index for index in pack_map.iterkeys()]
 
1126
        all_index = CombinedGraphIndex(indices)
 
1127
        if key_filter is None:
 
1128
            return all_index.iter_all_entries()
 
1129
        else:
 
1130
            return all_index.iter_entries(key_filter)
 
1131
 
 
1132
    def _unlock_names(self):
 
1133
        """Release the mutex around the pack-names index."""
 
1134
        self.repo.control_files.unlock()
 
1135
 
 
1136
    def _save_pack_names(self):
 
1137
        """Save the list of packs.
 
1138
 
 
1139
        This will take out the mutex around the pack names list for the
 
1140
        duration of the method call. If concurrent updates have been made, a
 
1141
        three-way merge between the current list and the current in memory list
 
1142
        is performed.
 
1143
        """
 
1144
        self.lock_names()
 
1145
        try:
 
1146
            builder = GraphIndexBuilder()
 
1147
            # load the disk nodes across
 
1148
            disk_nodes = set()
 
1149
            for index, key, value in self._iter_disk_pack_index():
 
1150
                disk_nodes.add((key, value))
 
1151
            # do a two-way diff against our original content
 
1152
            current_nodes = set()
 
1153
            for name, sizes in self._names.iteritems():
 
1154
                current_nodes.add(
 
1155
                    ((name, ), ' '.join(str(size) for size in sizes)))
 
1156
            deleted_nodes = self._packs_at_load - current_nodes
 
1157
            new_nodes = current_nodes - self._packs_at_load
 
1158
            disk_nodes.difference_update(deleted_nodes)
 
1159
            disk_nodes.update(new_nodes)
 
1160
            # TODO: handle same-name, index-size-changes here - 
 
1161
            # e.g. use the value from disk, not ours, *unless* we're the one
 
1162
            # changing it.
 
1163
            for key, value in disk_nodes:
 
1164
                builder.add_node(key, value)
 
1165
            self.transport.put_file('pack-names', builder.finish())
 
1166
            # move the baseline forward
 
1167
            self._packs_at_load = disk_nodes
 
1168
        finally:
 
1169
            self._unlock_names()
 
1170
        # synchronise the memory packs list with what we just wrote:
 
1171
        new_names = dict(disk_nodes)
 
1172
        # drop no longer present nodes
 
1173
        for pack in self.all_packs():
 
1174
            if (pack.name,) not in new_names:
 
1175
                self._remove_pack_from_memory(pack)
 
1176
        # add new nodes/refresh existing ones
 
1177
        for key, value in disk_nodes:
 
1178
            name = key[0]
 
1179
            sizes = self._parse_index_sizes(value)
 
1180
            if name in self._names:
 
1181
                # existing
 
1182
                if sizes != self._names[name]:
 
1183
                    # the pack for name has had its indices replaced - rare but
 
1184
                    # important to handle. XXX: probably can never happen today
 
1185
                    # because the three-way merge code above does not handle it
 
1186
                    # - you may end up adding the same key twice to the new
 
1187
                    # disk index because the set values are the same, unless
 
1188
                    # the only index shows up as deleted by the set difference
 
1189
                    # - which it may. Until there is a specific test for this,
 
1190
                    # assume its broken. RBC 20071017.
 
1191
                    self._remove_pack_from_memory(self.get_pack_by_name(name))
 
1192
                    self._names[name] = sizes
 
1193
                    self.get_pack_by_name(name)
 
1194
            else:
 
1195
                # new
 
1196
                self._names[name] = sizes
 
1197
                self.get_pack_by_name(name)
 
1198
 
 
1199
    def _start_write_group(self):
 
1200
        # Do not permit preparation for writing if we're not in a 'write lock'.
 
1201
        if not self.repo.is_write_locked():
 
1202
            raise errors.NotWriteLocked(self)
 
1203
        self._new_pack = NewPack(self._upload_transport, self._index_transport,
 
1204
            self._pack_transport, upload_suffix='.pack')
 
1205
        # allow writing: queue writes to a new index
 
1206
        self.revision_index.add_writable_index(self._new_pack.revision_index,
 
1207
            self._new_pack)
 
1208
        self.inventory_index.add_writable_index(self._new_pack.inventory_index,
 
1209
            self._new_pack)
 
1210
        self.text_index.add_writable_index(self._new_pack.text_index,
 
1211
            self._new_pack)
 
1212
        self.signature_index.add_writable_index(self._new_pack.signature_index,
 
1213
            self._new_pack)
 
1214
 
 
1215
        # reused revision and signature knits may need updating
 
1216
        #
 
1217
        # "Hysterical raisins. client code in bzrlib grabs those knits outside
 
1218
        # of write groups and then mutates it inside the write group."
 
1219
        if self.repo._revision_knit is not None:
 
1220
            self.repo._revision_knit._index._add_callback = \
 
1221
                self.revision_index.add_callback
 
1222
        if self.repo._signature_knit is not None:
 
1223
            self.repo._signature_knit._index._add_callback = \
 
1224
                self.signature_index.add_callback
 
1225
        # create a reused knit object for text addition in commit.
 
1226
        self.repo._text_knit = self.repo.weave_store.get_weave_or_empty(
 
1227
            'all-texts', None)
 
1228
 
 
1229
    def _abort_write_group(self):
 
1230
        # FIXME: just drop the transient index.
 
1231
        # forget what names there are
 
1232
        self._new_pack.abort()
 
1233
        self._remove_pack_indices(self._new_pack)
 
1234
        self._new_pack = None
 
1235
        self.repo._text_knit = None
 
1236
 
 
1237
    def _commit_write_group(self):
 
1238
        self._remove_pack_indices(self._new_pack)
 
1239
        if self._new_pack.data_inserted():
 
1240
            # get all the data to disk and read to use
 
1241
            self._new_pack.finish()
 
1242
            self.allocate(self._new_pack)
 
1243
            self._new_pack = None
 
1244
            if not self.autopack():
 
1245
                # when autopack takes no steps, the names list is still
 
1246
                # unsaved.
 
1247
                self._save_pack_names()
 
1248
        else:
 
1249
            self._new_pack.abort()
 
1250
        self.repo._text_knit = None
 
1251
 
 
1252
 
 
1253
class KnitPackRevisionStore(KnitRevisionStore):
 
1254
    """An object to adapt access from RevisionStore's to use KnitPacks.
 
1255
 
 
1256
    This class works by replacing the original RevisionStore.
 
1257
    We need to do this because the KnitPackRevisionStore is less
 
1258
    isolated in its layering - it uses services from the repo.
 
1259
    """
 
1260
 
 
1261
    def __init__(self, repo, transport, revisionstore):
 
1262
        """Create a KnitPackRevisionStore on repo with revisionstore.
 
1263
 
 
1264
        This will store its state in the Repository, use the
 
1265
        indices to provide a KnitGraphIndex,
 
1266
        and at the end of transactions write new indices.
 
1267
        """
 
1268
        KnitRevisionStore.__init__(self, revisionstore.versioned_file_store)
 
1269
        self.repo = repo
 
1270
        self._serializer = revisionstore._serializer
 
1271
        self.transport = transport
 
1272
 
 
1273
    def get_revision_file(self, transaction):
 
1274
        """Get the revision versioned file object."""
 
1275
        if getattr(self.repo, '_revision_knit', None) is not None:
 
1276
            return self.repo._revision_knit
 
1277
        self.repo._pack_collection.ensure_loaded()
 
1278
        add_callback = self.repo._pack_collection.revision_index.add_callback
 
1279
        # setup knit specific objects
 
1280
        knit_index = KnitGraphIndex(
 
1281
            self.repo._pack_collection.revision_index.combined_index,
 
1282
            add_callback=add_callback)
 
1283
        self.repo._revision_knit = knit.KnitVersionedFile(
 
1284
            'revisions', self.transport.clone('..'),
 
1285
            self.repo.control_files._file_mode,
 
1286
            create=False, access_mode=self.repo._access_mode(),
 
1287
            index=knit_index, delta=False, factory=knit.KnitPlainFactory(),
 
1288
            access_method=self.repo._pack_collection.revision_index.knit_access)
 
1289
        return self.repo._revision_knit
 
1290
 
 
1291
    def get_signature_file(self, transaction):
 
1292
        """Get the signature versioned file object."""
 
1293
        if getattr(self.repo, '_signature_knit', None) is not None:
 
1294
            return self.repo._signature_knit
 
1295
        self.repo._pack_collection.ensure_loaded()
 
1296
        add_callback = self.repo._pack_collection.signature_index.add_callback
 
1297
        # setup knit specific objects
 
1298
        knit_index = KnitGraphIndex(
 
1299
            self.repo._pack_collection.signature_index.combined_index,
 
1300
            add_callback=add_callback, parents=False)
 
1301
        self.repo._signature_knit = knit.KnitVersionedFile(
 
1302
            'signatures', self.transport.clone('..'),
 
1303
            self.repo.control_files._file_mode,
 
1304
            create=False, access_mode=self.repo._access_mode(),
 
1305
            index=knit_index, delta=False, factory=knit.KnitPlainFactory(),
 
1306
            access_method=self.repo._pack_collection.signature_index.knit_access)
 
1307
        return self.repo._signature_knit
 
1308
 
 
1309
 
 
1310
class KnitPackTextStore(VersionedFileStore):
 
1311
    """Presents a TextStore abstraction on top of packs.
 
1312
 
 
1313
    This class works by replacing the original VersionedFileStore.
 
1314
    We need to do this because the KnitPackRevisionStore is less
 
1315
    isolated in its layering - it uses services from the repo and shares them
 
1316
    with all the data written in a single write group.
 
1317
    """
 
1318
 
 
1319
    def __init__(self, repo, transport, weavestore):
 
1320
        """Create a KnitPackTextStore on repo with weavestore.
 
1321
 
 
1322
        This will store its state in the Repository, use the
 
1323
        indices FileNames to provide a KnitGraphIndex,
 
1324
        and at the end of transactions write new indices.
 
1325
        """
 
1326
        # don't call base class constructor - it's not suitable.
 
1327
        # no transient data stored in the transaction
 
1328
        # cache.
 
1329
        self._precious = False
 
1330
        self.repo = repo
 
1331
        self.transport = transport
 
1332
        self.weavestore = weavestore
 
1333
        # XXX for check() which isn't updated yet
 
1334
        self._transport = weavestore._transport
 
1335
 
 
1336
    def get_weave_or_empty(self, file_id, transaction):
 
1337
        """Get a 'Knit' backed by the .tix indices.
 
1338
 
 
1339
        The transaction parameter is ignored.
 
1340
        """
 
1341
        self.repo._pack_collection.ensure_loaded()
 
1342
        add_callback = self.repo._pack_collection.text_index.add_callback
 
1343
        # setup knit specific objects
 
1344
        file_id_index = GraphIndexPrefixAdapter(
 
1345
            self.repo._pack_collection.text_index.combined_index,
 
1346
            (file_id, ), 1, add_nodes_callback=add_callback)
 
1347
        knit_index = KnitGraphIndex(file_id_index,
 
1348
            add_callback=file_id_index.add_nodes,
 
1349
            deltas=True, parents=True)
 
1350
        return knit.KnitVersionedFile('text:' + file_id,
 
1351
            self.transport.clone('..'),
 
1352
            None,
 
1353
            index=knit_index,
 
1354
            access_method=self.repo._pack_collection.text_index.knit_access,
 
1355
            factory=knit.KnitPlainFactory())
 
1356
 
 
1357
    get_weave = get_weave_or_empty
 
1358
 
 
1359
    def __iter__(self):
 
1360
        """Generate a list of the fileids inserted, for use by check."""
 
1361
        self.repo._pack_collection.ensure_loaded()
 
1362
        ids = set()
 
1363
        for index, key, value, refs in \
 
1364
            self.repo._pack_collection.text_index.combined_index.iter_all_entries():
 
1365
            ids.add(key[0])
 
1366
        return iter(ids)
 
1367
 
 
1368
 
 
1369
class InventoryKnitThunk(object):
 
1370
    """An object to manage thunking get_inventory_weave to pack based knits."""
 
1371
 
 
1372
    def __init__(self, repo, transport):
 
1373
        """Create an InventoryKnitThunk for repo at transport.
 
1374
 
 
1375
        This will store its state in the Repository, use the
 
1376
        indices FileNames to provide a KnitGraphIndex,
 
1377
        and at the end of transactions write a new index..
 
1378
        """
 
1379
        self.repo = repo
 
1380
        self.transport = transport
 
1381
 
 
1382
    def get_weave(self):
 
1383
        """Get a 'Knit' that contains inventory data."""
 
1384
        self.repo._pack_collection.ensure_loaded()
 
1385
        add_callback = self.repo._pack_collection.inventory_index.add_callback
 
1386
        # setup knit specific objects
 
1387
        knit_index = KnitGraphIndex(
 
1388
            self.repo._pack_collection.inventory_index.combined_index,
 
1389
            add_callback=add_callback, deltas=True, parents=True)
 
1390
        return knit.KnitVersionedFile(
 
1391
            'inventory', self.transport.clone('..'),
 
1392
            self.repo.control_files._file_mode,
 
1393
            create=False, access_mode=self.repo._access_mode(),
 
1394
            index=knit_index, delta=True, factory=knit.KnitPlainFactory(),
 
1395
            access_method=self.repo._pack_collection.inventory_index.knit_access)
 
1396
 
 
1397
 
 
1398
class KnitPackRepository(KnitRepository):
 
1399
    """Experimental graph-knit using repository."""
 
1400
 
 
1401
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
 
1402
        control_store, text_store, _commit_builder_class, _serializer):
 
1403
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
1404
            _revision_store, control_store, text_store, _commit_builder_class,
 
1405
            _serializer)
 
1406
        index_transport = control_files._transport.clone('indices')
 
1407
        self._pack_collection = RepositoryPackCollection(self, control_files._transport,
 
1408
            index_transport,
 
1409
            control_files._transport.clone('upload'),
 
1410
            control_files._transport.clone('packs'))
 
1411
        self._revision_store = KnitPackRevisionStore(self, index_transport, self._revision_store)
 
1412
        self.weave_store = KnitPackTextStore(self, index_transport, self.weave_store)
 
1413
        self._inv_thunk = InventoryKnitThunk(self, index_transport)
 
1414
        # True when the repository object is 'write locked' (as opposed to the
 
1415
        # physical lock only taken out around changes to the pack-names list.) 
 
1416
        # Another way to represent this would be a decorator around the control
 
1417
        # files object that presents logical locks as physical ones - if this
 
1418
        # gets ugly consider that alternative design. RBC 20071011
 
1419
        self._write_lock_count = 0
 
1420
        self._transaction = None
 
1421
        # for tests
 
1422
        self._reconcile_does_inventory_gc = False
 
1423
        self._reconcile_fixes_text_parents = False
 
1424
 
 
1425
    def _abort_write_group(self):
 
1426
        self._pack_collection._abort_write_group()
 
1427
 
 
1428
    def _access_mode(self):
 
1429
        """Return 'w' or 'r' for depending on whether a write lock is active.
 
1430
        
 
1431
        This method is a helper for the Knit-thunking support objects.
 
1432
        """
 
1433
        if self.is_write_locked():
 
1434
            return 'w'
 
1435
        return 'r'
 
1436
 
 
1437
    def get_parents(self, revision_ids):
 
1438
        """See StackedParentsProvider.get_parents.
 
1439
        
 
1440
        This implementation accesses the combined revision index to provide
 
1441
        answers.
 
1442
        """
 
1443
        index = self._pack_collection.revision_index.combined_index
 
1444
        search_keys = set()
 
1445
        for revision_id in revision_ids:
 
1446
            if revision_id != _mod_revision.NULL_REVISION:
 
1447
                search_keys.add((revision_id,))
 
1448
        found_parents = {_mod_revision.NULL_REVISION:[]}
 
1449
        for index, key, value, refs in index.iter_entries(search_keys):
 
1450
            parents = refs[0]
 
1451
            if not parents:
 
1452
                parents = (_mod_revision.NULL_REVISION,)
 
1453
            else:
 
1454
                parents = tuple(parent[0] for parent in parents)
 
1455
            found_parents[key[0]] = parents
 
1456
        result = []
 
1457
        for revision_id in revision_ids:
 
1458
            try:
 
1459
                result.append(found_parents[revision_id])
 
1460
            except KeyError:
 
1461
                result.append(None)
 
1462
        return result
 
1463
 
 
1464
    def _make_parents_provider(self):
 
1465
        return self
 
1466
 
 
1467
    def _refresh_data(self):
 
1468
        if self._write_lock_count == 1 or self.control_files._lock_count == 1:
 
1469
            # forget what names there are
 
1470
            self._pack_collection.reset()
 
1471
            # XXX: Better to do an in-memory merge when acquiring a new lock -
 
1472
            # factor out code from _save_pack_names.
 
1473
 
 
1474
    def _start_write_group(self):
 
1475
        self._pack_collection._start_write_group()
 
1476
 
 
1477
    def _commit_write_group(self):
 
1478
        return self._pack_collection._commit_write_group()
 
1479
 
 
1480
    def get_inventory_weave(self):
 
1481
        return self._inv_thunk.get_weave()
 
1482
 
 
1483
    def get_transaction(self):
 
1484
        if self._write_lock_count:
 
1485
            return self._transaction
 
1486
        else:
 
1487
            return self.control_files.get_transaction()
 
1488
 
 
1489
    def is_locked(self):
 
1490
        return self._write_lock_count or self.control_files.is_locked()
 
1491
 
 
1492
    def is_write_locked(self):
 
1493
        return self._write_lock_count
 
1494
 
 
1495
    def lock_write(self, token=None):
 
1496
        if not self._write_lock_count and self.is_locked():
 
1497
            raise errors.ReadOnlyError(self)
 
1498
        self._write_lock_count += 1
 
1499
        if self._write_lock_count == 1:
 
1500
            from bzrlib import transactions
 
1501
            self._transaction = transactions.WriteTransaction()
 
1502
        self._refresh_data()
 
1503
 
 
1504
    def lock_read(self):
 
1505
        if self._write_lock_count:
 
1506
            self._write_lock_count += 1
 
1507
        else:
 
1508
            self.control_files.lock_read()
 
1509
        self._refresh_data()
 
1510
 
 
1511
    def leave_lock_in_place(self):
 
1512
        # not supported - raise an error
 
1513
        raise NotImplementedError(self.leave_lock_in_place)
 
1514
 
 
1515
    def dont_leave_lock_in_place(self):
 
1516
        # not supported - raise an error
 
1517
        raise NotImplementedError(self.dont_leave_lock_in_place)
 
1518
 
 
1519
    @needs_write_lock
 
1520
    def pack(self):
 
1521
        """Compress the data within the repository.
 
1522
 
 
1523
        This will pack all the data to a single pack. In future it may
 
1524
        recompress deltas or do other such expensive operations.
 
1525
        """
 
1526
        self._pack_collection.pack()
 
1527
 
 
1528
    @needs_write_lock
 
1529
    def reconcile(self, other=None, thorough=False):
 
1530
        """Reconcile this repository."""
 
1531
        from bzrlib.reconcile import PackReconciler
 
1532
        reconciler = PackReconciler(self, thorough=thorough)
 
1533
        reconciler.reconcile()
 
1534
        return reconciler
 
1535
 
 
1536
    def unlock(self):
 
1537
        if self._write_lock_count == 1 and self._write_group is not None:
 
1538
            self.abort_write_group()
 
1539
            self._transaction = None
 
1540
            self._write_lock_count = 0
 
1541
            raise errors.BzrError(
 
1542
                'Must end write group before releasing write lock on %s'
 
1543
                % self)
 
1544
        if self._write_lock_count:
 
1545
            self._write_lock_count -= 1
 
1546
            if not self._write_lock_count:
 
1547
                transaction = self._transaction
 
1548
                self._transaction = None
 
1549
                transaction.finish()
 
1550
        else:
 
1551
            self.control_files.unlock()
 
1552
 
 
1553
 
 
1554
class RepositoryFormatPack(MetaDirRepositoryFormat):
 
1555
    """Format logic for pack structured repositories.
 
1556
 
 
1557
    This repository format has:
 
1558
     - a list of packs in pack-names
 
1559
     - packs in packs/NAME.pack
 
1560
     - indices in indices/NAME.{iix,six,tix,rix}
 
1561
     - knit deltas in the packs, knit indices mapped to the indices.
 
1562
     - thunk objects to support the knits programming API.
 
1563
     - a format marker of its own
 
1564
     - an optional 'shared-storage' flag
 
1565
     - an optional 'no-working-trees' flag
 
1566
     - a LockDir lock
 
1567
    """
 
1568
 
 
1569
    # Set this attribute in derived classes to control the repository class
 
1570
    # created by open and initialize.
 
1571
    repository_class = None
 
1572
    # Set this attribute in derived classes to control the
 
1573
    # _commit_builder_class that the repository objects will have passed to
 
1574
    # their constructor.
 
1575
    _commit_builder_class = None
 
1576
    # Set this attribute in derived clases to control the _serializer that the
 
1577
    # repository objects will have passed to their constructor.
 
1578
    _serializer = None
 
1579
 
 
1580
    def _get_control_store(self, repo_transport, control_files):
 
1581
        """Return the control store for this repository."""
 
1582
        return VersionedFileStore(
 
1583
            repo_transport,
 
1584
            prefixed=False,
 
1585
            file_mode=control_files._file_mode,
 
1586
            versionedfile_class=knit.KnitVersionedFile,
 
1587
            versionedfile_kwargs={'factory': knit.KnitPlainFactory()},
 
1588
            )
 
1589
 
 
1590
    def _get_revision_store(self, repo_transport, control_files):
 
1591
        """See RepositoryFormat._get_revision_store()."""
 
1592
        versioned_file_store = VersionedFileStore(
 
1593
            repo_transport,
 
1594
            file_mode=control_files._file_mode,
 
1595
            prefixed=False,
 
1596
            precious=True,
 
1597
            versionedfile_class=knit.KnitVersionedFile,
 
1598
            versionedfile_kwargs={'delta': False,
 
1599
                                  'factory': knit.KnitPlainFactory(),
 
1600
                                 },
 
1601
            escaped=True,
 
1602
            )
 
1603
        return KnitRevisionStore(versioned_file_store)
 
1604
 
 
1605
    def _get_text_store(self, transport, control_files):
 
1606
        """See RepositoryFormat._get_text_store()."""
 
1607
        return self._get_versioned_file_store('knits',
 
1608
                                  transport,
 
1609
                                  control_files,
 
1610
                                  versionedfile_class=knit.KnitVersionedFile,
 
1611
                                  versionedfile_kwargs={
 
1612
                                      'create_parent_dir': True,
 
1613
                                      'delay_create': True,
 
1614
                                      'dir_mode': control_files._dir_mode,
 
1615
                                  },
 
1616
                                  escaped=True)
 
1617
 
 
1618
    def initialize(self, a_bzrdir, shared=False):
 
1619
        """Create a pack based repository.
 
1620
 
 
1621
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
1622
            be initialized.
 
1623
        :param shared: If true the repository will be initialized as a shared
 
1624
                       repository.
 
1625
        """
 
1626
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1627
        dirs = ['indices', 'obsolete_packs', 'packs', 'upload']
 
1628
        builder = GraphIndexBuilder()
 
1629
        files = [('pack-names', builder.finish())]
 
1630
        utf8_files = [('format', self.get_format_string())]
 
1631
        
 
1632
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1633
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1634
 
 
1635
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1636
        """See RepositoryFormat.open().
 
1637
        
 
1638
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1639
                                    repository at a slightly different url
 
1640
                                    than normal. I.e. during 'upgrade'.
 
1641
        """
 
1642
        if not _found:
 
1643
            format = RepositoryFormat.find_format(a_bzrdir)
 
1644
            assert format.__class__ ==  self.__class__
 
1645
        if _override_transport is not None:
 
1646
            repo_transport = _override_transport
 
1647
        else:
 
1648
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1649
        control_files = lockable_files.LockableFiles(repo_transport,
 
1650
                                'lock', lockdir.LockDir)
 
1651
        text_store = self._get_text_store(repo_transport, control_files)
 
1652
        control_store = self._get_control_store(repo_transport, control_files)
 
1653
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1654
        return self.repository_class(_format=self,
 
1655
                              a_bzrdir=a_bzrdir,
 
1656
                              control_files=control_files,
 
1657
                              _revision_store=_revision_store,
 
1658
                              control_store=control_store,
 
1659
                              text_store=text_store,
 
1660
                              _commit_builder_class=self._commit_builder_class,
 
1661
                              _serializer=self._serializer)
 
1662
 
 
1663
 
 
1664
class RepositoryFormatKnitPack1(RepositoryFormatPack):
 
1665
    """A no-subtrees parameterised Pack repository.
 
1666
 
 
1667
    This format was introduced in 0.92.
 
1668
    """
 
1669
 
 
1670
    repository_class = KnitPackRepository
 
1671
    _commit_builder_class = PackCommitBuilder
 
1672
    _serializer = xml5.serializer_v5
 
1673
 
 
1674
    def _get_matching_bzrdir(self):
 
1675
        return bzrdir.format_registry.make_bzrdir('knitpack-experimental')
 
1676
 
 
1677
    def _ignore_setting_bzrdir(self, format):
 
1678
        pass
 
1679
 
 
1680
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
1681
 
 
1682
    def get_format_string(self):
 
1683
        """See RepositoryFormat.get_format_string()."""
 
1684
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
 
1685
 
 
1686
    def get_format_description(self):
 
1687
        """See RepositoryFormat.get_format_description()."""
 
1688
        return "Packs containing knits without subtree support"
 
1689
 
 
1690
    def check_conversion_target(self, target_format):
 
1691
        pass
 
1692
 
 
1693
 
 
1694
class RepositoryFormatKnitPack3(RepositoryFormatPack):
 
1695
    """A subtrees parameterised Pack repository.
 
1696
 
 
1697
    This repository format uses the xml7 serializer to get:
 
1698
     - support for recording full info about the tree root
 
1699
     - support for recording tree-references
 
1700
 
 
1701
    This format was introduced in 0.92.
 
1702
    """
 
1703
 
 
1704
    repository_class = KnitPackRepository
 
1705
    _commit_builder_class = PackRootCommitBuilder
 
1706
    rich_root_data = True
 
1707
    supports_tree_reference = True
 
1708
    _serializer = xml7.serializer_v7
 
1709
 
 
1710
    def _get_matching_bzrdir(self):
 
1711
        return bzrdir.format_registry.make_bzrdir(
 
1712
            'knitpack-subtree-experimental')
 
1713
 
 
1714
    def _ignore_setting_bzrdir(self, format):
 
1715
        pass
 
1716
 
 
1717
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
 
1718
 
 
1719
    def check_conversion_target(self, target_format):
 
1720
        if not target_format.rich_root_data:
 
1721
            raise errors.BadConversionTarget(
 
1722
                'Does not support rich root data.', target_format)
 
1723
        if not getattr(target_format, 'supports_tree_reference', False):
 
1724
            raise errors.BadConversionTarget(
 
1725
                'Does not support nested trees', target_format)
 
1726
            
 
1727
    def get_format_string(self):
 
1728
        """See RepositoryFormat.get_format_string()."""
 
1729
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
 
1730
 
 
1731
    def get_format_description(self):
 
1732
        """See RepositoryFormat.get_format_description()."""
 
1733
        return "Packs containing knits with subtree support\n"