~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/pack_repo.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-04-18 04:55:00 UTC
  • mfrom: (5784.2.1 754188-apport-test)
  • Revision ID: pqm@pqm.ubuntu.com-20110418045500-ce6lkgyiq7f47q43
(mbp) Rewrite test_report_bug_legacy away from using doctest (see bug
 764188) (Martin Pool)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
30
30
    osutils,
31
31
    pack,
32
32
    transactions,
 
33
    tsort,
33
34
    ui,
34
 
    xml5,
35
 
    xml6,
36
 
    xml7,
37
35
    )
38
36
from bzrlib.index import (
39
37
    CombinedGraphIndex,
42
40
from bzrlib.knit import (
43
41
    KnitPlainFactory,
44
42
    KnitVersionedFiles,
45
 
    _KnitGraphIndex,
46
43
    _DirectPackAccess,
47
44
    )
48
 
from bzrlib import tsort
49
45
""")
50
46
from bzrlib import (
51
 
    bzrdir,
52
47
    btree_index,
53
48
    errors,
54
49
    lockable_files,
55
50
    lockdir,
56
 
    revision as _mod_revision,
57
51
    )
58
52
 
59
 
from bzrlib.decorators import needs_write_lock, only_raises
60
 
from bzrlib.index import (
61
 
    GraphIndex,
62
 
    InMemoryGraphIndex,
 
53
from bzrlib.decorators import (
 
54
    needs_read_lock,
 
55
    needs_write_lock,
 
56
    only_raises,
63
57
    )
64
58
from bzrlib.lock import LogicalLockResult
65
 
from bzrlib.repofmt.knitrepo import KnitRepository
66
59
from bzrlib.repository import (
67
60
    CommitBuilder,
 
61
    MetaDirRepository,
68
62
    MetaDirRepositoryFormat,
69
63
    RepositoryFormat,
70
64
    RepositoryWriteLockResult,
71
65
    RootCommitBuilder,
72
 
    StreamSource,
73
66
    )
74
67
from bzrlib.trace import (
75
68
    mutter,
87
80
 
88
81
    def __init__(self, repository, parents, config, timestamp=None,
89
82
                 timezone=None, committer=None, revprops=None,
90
 
                 revision_id=None):
 
83
                 revision_id=None, lossy=False):
91
84
        CommitBuilder.__init__(self, repository, parents, config,
92
85
            timestamp=timestamp, timezone=timezone, committer=committer,
93
 
            revprops=revprops, revision_id=revision_id)
 
86
            revprops=revprops, revision_id=revision_id, lossy=lossy)
94
87
        self._file_graph = graph.Graph(
95
88
            repository._pack_collection.text_index.combined_index)
96
89
 
108
101
 
109
102
    def __init__(self, repository, parents, config, timestamp=None,
110
103
                 timezone=None, committer=None, revprops=None,
111
 
                 revision_id=None):
 
104
                 revision_id=None, lossy=False):
112
105
        CommitBuilder.__init__(self, repository, parents, config,
113
106
            timestamp=timestamp, timezone=timezone, committer=committer,
114
 
            revprops=revprops, revision_id=revision_id)
 
107
            revprops=revprops, revision_id=revision_id, lossy=lossy)
115
108
        self._file_graph = graph.Graph(
116
109
            repository._pack_collection.text_index.combined_index)
117
110
 
673
666
        # What text keys to copy. None for 'all texts'. This is set by
674
667
        # _copy_inventory_texts
675
668
        self._text_filter = None
676
 
        self._extra_init()
677
 
 
678
 
    def _extra_init(self):
679
 
        """A template hook to allow extending the constructor trivially."""
680
669
 
681
670
    def _pack_map_and_index_list(self, index_attribute):
682
671
        """Convert a list of packs to an index pack map and index list.
1209
1198
        return new_pack
1210
1199
 
1211
1200
 
1212
 
class ReconcilePacker(Packer):
1213
 
    """A packer which regenerates indices etc as it copies.
1214
 
 
1215
 
    This is used by ``bzr reconcile`` to cause parent text pointers to be
1216
 
    regenerated.
1217
 
    """
1218
 
 
1219
 
    def _extra_init(self):
1220
 
        self._data_changed = False
1221
 
 
1222
 
    def _process_inventory_lines(self, inv_lines):
1223
 
        """Generate a text key reference map rather for reconciling with."""
1224
 
        repo = self._pack_collection.repo
1225
 
        refs = repo._find_text_key_references_from_xml_inventory_lines(
1226
 
            inv_lines)
1227
 
        self._text_refs = refs
1228
 
        # during reconcile we:
1229
 
        #  - convert unreferenced texts to full texts
1230
 
        #  - correct texts which reference a text not copied to be full texts
1231
 
        #  - copy all others as-is but with corrected parents.
1232
 
        #  - so at this point we don't know enough to decide what becomes a full
1233
 
        #    text.
1234
 
        self._text_filter = None
1235
 
 
1236
 
    def _copy_text_texts(self):
1237
 
        """generate what texts we should have and then copy."""
1238
 
        self.pb.update("Copying content texts", 3)
1239
 
        # we have three major tasks here:
1240
 
        # 1) generate the ideal index
1241
 
        repo = self._pack_collection.repo
1242
 
        ancestors = dict([(key[0], tuple(ref[0] for ref in refs[0])) for
1243
 
            _1, key, _2, refs in
1244
 
            self.new_pack.revision_index.iter_all_entries()])
1245
 
        ideal_index = repo._generate_text_key_index(self._text_refs, ancestors)
1246
 
        # 2) generate a text_nodes list that contains all the deltas that can
1247
 
        #    be used as-is, with corrected parents.
1248
 
        ok_nodes = []
1249
 
        bad_texts = []
1250
 
        discarded_nodes = []
1251
 
        NULL_REVISION = _mod_revision.NULL_REVISION
1252
 
        text_index_map, text_nodes = self._get_text_nodes()
1253
 
        for node in text_nodes:
1254
 
            # 0 - index
1255
 
            # 1 - key
1256
 
            # 2 - value
1257
 
            # 3 - refs
1258
 
            try:
1259
 
                ideal_parents = tuple(ideal_index[node[1]])
1260
 
            except KeyError:
1261
 
                discarded_nodes.append(node)
1262
 
                self._data_changed = True
1263
 
            else:
1264
 
                if ideal_parents == (NULL_REVISION,):
1265
 
                    ideal_parents = ()
1266
 
                if ideal_parents == node[3][0]:
1267
 
                    # no change needed.
1268
 
                    ok_nodes.append(node)
1269
 
                elif ideal_parents[0:1] == node[3][0][0:1]:
1270
 
                    # the left most parent is the same, or there are no parents
1271
 
                    # today. Either way, we can preserve the representation as
1272
 
                    # long as we change the refs to be inserted.
1273
 
                    self._data_changed = True
1274
 
                    ok_nodes.append((node[0], node[1], node[2],
1275
 
                        (ideal_parents, node[3][1])))
1276
 
                    self._data_changed = True
1277
 
                else:
1278
 
                    # Reinsert this text completely
1279
 
                    bad_texts.append((node[1], ideal_parents))
1280
 
                    self._data_changed = True
1281
 
        # we're finished with some data.
1282
 
        del ideal_index
1283
 
        del text_nodes
1284
 
        # 3) bulk copy the ok data
1285
 
        total_items, readv_group_iter = self._least_readv_node_readv(ok_nodes)
1286
 
        list(self._copy_nodes_graph(text_index_map, self.new_pack._writer,
1287
 
            self.new_pack.text_index, readv_group_iter, total_items))
1288
 
        # 4) adhoc copy all the other texts.
1289
 
        # We have to topologically insert all texts otherwise we can fail to
1290
 
        # reconcile when parts of a single delta chain are preserved intact,
1291
 
        # and other parts are not. E.g. Discarded->d1->d2->d3. d1 will be
1292
 
        # reinserted, and if d3 has incorrect parents it will also be
1293
 
        # reinserted. If we insert d3 first, d2 is present (as it was bulk
1294
 
        # copied), so we will try to delta, but d2 is not currently able to be
1295
 
        # extracted because its basis d1 is not present. Topologically sorting
1296
 
        # addresses this. The following generates a sort for all the texts that
1297
 
        # are being inserted without having to reference the entire text key
1298
 
        # space (we only topo sort the revisions, which is smaller).
1299
 
        topo_order = tsort.topo_sort(ancestors)
1300
 
        rev_order = dict(zip(topo_order, range(len(topo_order))))
1301
 
        bad_texts.sort(key=lambda key:rev_order.get(key[0][1], 0))
1302
 
        transaction = repo.get_transaction()
1303
 
        file_id_index = GraphIndexPrefixAdapter(
1304
 
            self.new_pack.text_index,
1305
 
            ('blank', ), 1,
1306
 
            add_nodes_callback=self.new_pack.text_index.add_nodes)
1307
 
        data_access = _DirectPackAccess(
1308
 
                {self.new_pack.text_index:self.new_pack.access_tuple()})
1309
 
        data_access.set_writer(self.new_pack._writer, self.new_pack.text_index,
1310
 
            self.new_pack.access_tuple())
1311
 
        output_texts = KnitVersionedFiles(
1312
 
            _KnitGraphIndex(self.new_pack.text_index,
1313
 
                add_callback=self.new_pack.text_index.add_nodes,
1314
 
                deltas=True, parents=True, is_locked=repo.is_locked),
1315
 
            data_access=data_access, max_delta_chain=200)
1316
 
        for key, parent_keys in bad_texts:
1317
 
            # We refer to the new pack to delta data being output.
1318
 
            # A possible improvement would be to catch errors on short reads
1319
 
            # and only flush then.
1320
 
            self.new_pack.flush()
1321
 
            parents = []
1322
 
            for parent_key in parent_keys:
1323
 
                if parent_key[0] != key[0]:
1324
 
                    # Graph parents must match the fileid
1325
 
                    raise errors.BzrError('Mismatched key parent %r:%r' %
1326
 
                        (key, parent_keys))
1327
 
                parents.append(parent_key[1])
1328
 
            text_lines = osutils.split_lines(repo.texts.get_record_stream(
1329
 
                [key], 'unordered', True).next().get_bytes_as('fulltext'))
1330
 
            output_texts.add_lines(key, parent_keys, text_lines,
1331
 
                random_id=True, check_content=False)
1332
 
        # 5) check that nothing inserted has a reference outside the keyspace.
1333
 
        missing_text_keys = self.new_pack.text_index._external_references()
1334
 
        if missing_text_keys:
1335
 
            raise errors.BzrCheckError('Reference to missing compression parents %r'
1336
 
                % (missing_text_keys,))
1337
 
        self._log_copied_texts()
1338
 
 
1339
 
    def _use_pack(self, new_pack):
1340
 
        """Override _use_pack to check for reconcile having changed content."""
1341
 
        # XXX: we might be better checking this at the copy time.
1342
 
        original_inventory_keys = set()
1343
 
        inv_index = self._pack_collection.inventory_index.combined_index
1344
 
        for entry in inv_index.iter_all_entries():
1345
 
            original_inventory_keys.add(entry[1])
1346
 
        new_inventory_keys = set()
1347
 
        for entry in new_pack.inventory_index.iter_all_entries():
1348
 
            new_inventory_keys.add(entry[1])
1349
 
        if new_inventory_keys != original_inventory_keys:
1350
 
            self._data_changed = True
1351
 
        return new_pack.data_inserted() and self._data_changed
1352
 
 
1353
 
 
1354
1201
class RepositoryPackCollection(object):
1355
1202
    """Management of packs within a repository.
1356
1203
 
1573
1420
        mutter('Packing repository %s, which has %d pack files, '
1574
1421
            'containing %d revisions with hint %r.', self, total_packs,
1575
1422
            total_revisions, hint)
 
1423
        while True:
 
1424
            try:
 
1425
                self._try_pack_operations(hint)
 
1426
            except RetryPackOperations:
 
1427
                continue
 
1428
            break
 
1429
 
 
1430
        if clean_obsolete_packs:
 
1431
            self._clear_obsolete_packs()
 
1432
 
 
1433
    def _try_pack_operations(self, hint):
 
1434
        """Calculate the pack operations based on the hint (if any), and
 
1435
        execute them.
 
1436
        """
1576
1437
        # determine which packs need changing
1577
1438
        pack_operations = [[0, []]]
1578
1439
        for pack in self.all_packs():
1581
1442
                # or this pack was included in the hint.
1582
1443
                pack_operations[-1][0] += pack.get_revision_count()
1583
1444
                pack_operations[-1][1].append(pack)
1584
 
        self._execute_pack_operations(pack_operations, OptimisingPacker)
1585
 
 
1586
 
        if clean_obsolete_packs:
1587
 
            self._clear_obsolete_packs()
 
1445
        self._execute_pack_operations(pack_operations, OptimisingPacker,
 
1446
            reload_func=self._restart_pack_operations)
1588
1447
 
1589
1448
    def plan_autopack_combinations(self, existing_packs, pack_distribution):
1590
1449
        """Plan a pack operation.
2044
1903
            raise
2045
1904
        raise errors.RetryAutopack(self.repo, False, sys.exc_info())
2046
1905
 
 
1906
    def _restart_pack_operations(self):
 
1907
        """Reload the pack names list, and restart the autopack code."""
 
1908
        if not self.reload_pack_names():
 
1909
            # Re-raise the original exception, because something went missing
 
1910
            # and a restart didn't find it
 
1911
            raise
 
1912
        raise RetryPackOperations(self.repo, False, sys.exc_info())
 
1913
 
2047
1914
    def _clear_obsolete_packs(self, preserve=None):
2048
1915
        """Delete everything from the obsolete-packs directory.
2049
1916
 
2197
2064
            self._resume_pack(token)
2198
2065
 
2199
2066
 
2200
 
class KnitPackRepository(KnitRepository):
 
2067
class PackRepository(MetaDirRepository):
2201
2068
    """Repository with knit objects stored inside pack containers.
2202
2069
 
2203
2070
    The layering for a KnitPackRepository is:
2206
2073
    ===================================================
2207
2074
    Tuple based apis below, string based, and key based apis above
2208
2075
    ---------------------------------------------------
2209
 
    KnitVersionedFiles
 
2076
    VersionedFiles
2210
2077
      Provides .texts, .revisions etc
2211
2078
      This adapts the N-tuple keys to physical knit records which only have a
2212
2079
      single string identifier (for historical reasons), which in older formats
2222
2089
 
2223
2090
    """
2224
2091
 
 
2092
    # These attributes are inherited from the Repository base class. Setting
 
2093
    # them to None ensures that if the constructor is changed to not initialize
 
2094
    # them, or a subclass fails to call the constructor, that an error will
 
2095
    # occur rather than the system working but generating incorrect data.
 
2096
    _commit_builder_class = None
 
2097
    _serializer = None
 
2098
 
2225
2099
    def __init__(self, _format, a_bzrdir, control_files, _commit_builder_class,
2226
2100
        _serializer):
2227
 
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
2228
 
            _commit_builder_class, _serializer)
2229
 
        index_transport = self._transport.clone('indices')
2230
 
        self._pack_collection = RepositoryPackCollection(self, self._transport,
2231
 
            index_transport,
2232
 
            self._transport.clone('upload'),
2233
 
            self._transport.clone('packs'),
2234
 
            _format.index_builder_class,
2235
 
            _format.index_class,
2236
 
            use_chk_index=self._format.supports_chks,
2237
 
            )
2238
 
        self.inventories = KnitVersionedFiles(
2239
 
            _KnitGraphIndex(self._pack_collection.inventory_index.combined_index,
2240
 
                add_callback=self._pack_collection.inventory_index.add_callback,
2241
 
                deltas=True, parents=True, is_locked=self.is_locked),
2242
 
            data_access=self._pack_collection.inventory_index.data_access,
2243
 
            max_delta_chain=200)
2244
 
        self.revisions = KnitVersionedFiles(
2245
 
            _KnitGraphIndex(self._pack_collection.revision_index.combined_index,
2246
 
                add_callback=self._pack_collection.revision_index.add_callback,
2247
 
                deltas=False, parents=True, is_locked=self.is_locked,
2248
 
                track_external_parent_refs=True),
2249
 
            data_access=self._pack_collection.revision_index.data_access,
2250
 
            max_delta_chain=0)
2251
 
        self.signatures = KnitVersionedFiles(
2252
 
            _KnitGraphIndex(self._pack_collection.signature_index.combined_index,
2253
 
                add_callback=self._pack_collection.signature_index.add_callback,
2254
 
                deltas=False, parents=False, is_locked=self.is_locked),
2255
 
            data_access=self._pack_collection.signature_index.data_access,
2256
 
            max_delta_chain=0)
2257
 
        self.texts = KnitVersionedFiles(
2258
 
            _KnitGraphIndex(self._pack_collection.text_index.combined_index,
2259
 
                add_callback=self._pack_collection.text_index.add_callback,
2260
 
                deltas=True, parents=True, is_locked=self.is_locked),
2261
 
            data_access=self._pack_collection.text_index.data_access,
2262
 
            max_delta_chain=200)
2263
 
        if _format.supports_chks:
2264
 
            # No graph, no compression:- references from chks are between
2265
 
            # different objects not temporal versions of the same; and without
2266
 
            # some sort of temporal structure knit compression will just fail.
2267
 
            self.chk_bytes = KnitVersionedFiles(
2268
 
                _KnitGraphIndex(self._pack_collection.chk_index.combined_index,
2269
 
                    add_callback=self._pack_collection.chk_index.add_callback,
2270
 
                    deltas=False, parents=False, is_locked=self.is_locked),
2271
 
                data_access=self._pack_collection.chk_index.data_access,
2272
 
                max_delta_chain=0)
2273
 
        else:
2274
 
            self.chk_bytes = None
2275
 
        # True when the repository object is 'write locked' (as opposed to the
2276
 
        # physical lock only taken out around changes to the pack-names list.)
2277
 
        # Another way to represent this would be a decorator around the control
2278
 
        # files object that presents logical locks as physical ones - if this
2279
 
        # gets ugly consider that alternative design. RBC 20071011
2280
 
        self._write_lock_count = 0
2281
 
        self._transaction = None
2282
 
        # for tests
2283
 
        self._reconcile_does_inventory_gc = True
 
2101
        MetaDirRepository.__init__(self, _format, a_bzrdir, control_files)
 
2102
        self._commit_builder_class = _commit_builder_class
 
2103
        self._serializer = _serializer
2284
2104
        self._reconcile_fixes_text_parents = True
2285
 
        self._reconcile_backsup_inventory = False
2286
2105
 
2287
 
    def _warn_if_deprecated(self, branch=None):
2288
 
        # This class isn't deprecated, but one sub-format is
2289
 
        if isinstance(self._format, RepositoryFormatKnitPack5RichRootBroken):
2290
 
            super(KnitPackRepository, self)._warn_if_deprecated(branch)
 
2106
    @needs_read_lock
 
2107
    def _all_revision_ids(self):
 
2108
        """See Repository.all_revision_ids()."""
 
2109
        return [key[0] for key in self.revisions.keys()]
2291
2110
 
2292
2111
    def _abort_write_group(self):
2293
2112
        self.revisions._index._key_dependencies.clear()
2294
2113
        self._pack_collection._abort_write_group()
2295
2114
 
2296
 
    def _get_source(self, to_format):
2297
 
        if to_format.network_name() == self._format.network_name():
2298
 
            return KnitPackStreamSource(self, to_format)
2299
 
        return super(KnitPackRepository, self)._get_source(to_format)
2300
 
 
2301
2115
    def _make_parents_provider(self):
2302
2116
        return graph.CachingParentsProvider(self)
2303
2117
 
2409
2223
        return reconciler
2410
2224
 
2411
2225
    def _reconcile_pack(self, collection, packs, extension, revs, pb):
2412
 
        packer = ReconcilePacker(collection, packs, extension, revs)
2413
 
        return packer.pack(pb)
 
2226
        raise NotImplementedError(self._reconcile_pack)
2414
2227
 
2415
2228
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2416
2229
    def unlock(self):
2435
2248
                repo.unlock()
2436
2249
 
2437
2250
 
2438
 
class KnitPackStreamSource(StreamSource):
2439
 
    """A StreamSource used to transfer data between same-format KnitPack repos.
2440
 
 
2441
 
    This source assumes:
2442
 
        1) Same serialization format for all objects
2443
 
        2) Same root information
2444
 
        3) XML format inventories
2445
 
        4) Atomic inserts (so we can stream inventory texts before text
2446
 
           content)
2447
 
        5) No chk_bytes
2448
 
    """
2449
 
 
2450
 
    def __init__(self, from_repository, to_format):
2451
 
        super(KnitPackStreamSource, self).__init__(from_repository, to_format)
2452
 
        self._text_keys = None
2453
 
        self._text_fetch_order = 'unordered'
2454
 
 
2455
 
    def _get_filtered_inv_stream(self, revision_ids):
2456
 
        from_repo = self.from_repository
2457
 
        parent_ids = from_repo._find_parent_ids_of_revisions(revision_ids)
2458
 
        parent_keys = [(p,) for p in parent_ids]
2459
 
        find_text_keys = from_repo._find_text_key_references_from_xml_inventory_lines
2460
 
        parent_text_keys = set(find_text_keys(
2461
 
            from_repo._inventory_xml_lines_for_keys(parent_keys)))
2462
 
        content_text_keys = set()
2463
 
        knit = KnitVersionedFiles(None, None)
2464
 
        factory = KnitPlainFactory()
2465
 
        def find_text_keys_from_content(record):
2466
 
            if record.storage_kind not in ('knit-delta-gz', 'knit-ft-gz'):
2467
 
                raise ValueError("Unknown content storage kind for"
2468
 
                    " inventory text: %s" % (record.storage_kind,))
2469
 
            # It's a knit record, it has a _raw_record field (even if it was
2470
 
            # reconstituted from a network stream).
2471
 
            raw_data = record._raw_record
2472
 
            # read the entire thing
2473
 
            revision_id = record.key[-1]
2474
 
            content, _ = knit._parse_record(revision_id, raw_data)
2475
 
            if record.storage_kind == 'knit-delta-gz':
2476
 
                line_iterator = factory.get_linedelta_content(content)
2477
 
            elif record.storage_kind == 'knit-ft-gz':
2478
 
                line_iterator = factory.get_fulltext_content(content)
2479
 
            content_text_keys.update(find_text_keys(
2480
 
                [(line, revision_id) for line in line_iterator]))
2481
 
        revision_keys = [(r,) for r in revision_ids]
2482
 
        def _filtered_inv_stream():
2483
 
            source_vf = from_repo.inventories
2484
 
            stream = source_vf.get_record_stream(revision_keys,
2485
 
                                                 'unordered', False)
2486
 
            for record in stream:
2487
 
                if record.storage_kind == 'absent':
2488
 
                    raise errors.NoSuchRevision(from_repo, record.key)
2489
 
                find_text_keys_from_content(record)
2490
 
                yield record
2491
 
            self._text_keys = content_text_keys - parent_text_keys
2492
 
        return ('inventories', _filtered_inv_stream())
2493
 
 
2494
 
    def _get_text_stream(self):
2495
 
        # Note: We know we don't have to handle adding root keys, because both
2496
 
        # the source and target are the identical network name.
2497
 
        text_stream = self.from_repository.texts.get_record_stream(
2498
 
                        self._text_keys, self._text_fetch_order, False)
2499
 
        return ('texts', text_stream)
2500
 
 
2501
 
    def get_stream(self, search):
2502
 
        revision_ids = search.get_keys()
2503
 
        for stream_info in self._fetch_revision_texts(revision_ids):
2504
 
            yield stream_info
2505
 
        self._revision_keys = [(rev_id,) for rev_id in revision_ids]
2506
 
        yield self._get_filtered_inv_stream(revision_ids)
2507
 
        yield self._get_text_stream()
2508
 
 
2509
 
 
2510
 
 
2511
2251
class RepositoryFormatPack(MetaDirRepositoryFormat):
2512
2252
    """Format logic for pack structured repositories.
2513
2253
 
2544
2284
    index_class = None
2545
2285
    _fetch_uses_deltas = True
2546
2286
    fast_deltas = False
 
2287
    supports_full_versioned_files = True
 
2288
    supports_funky_characters = True
 
2289
    revision_graph_can_have_wrong_parents = True
2547
2290
 
2548
2291
    def initialize(self, a_bzrdir, shared=False):
2549
2292
        """Create a pack based repository.
2586
2329
                              _serializer=self._serializer)
2587
2330
 
2588
2331
 
2589
 
class RepositoryFormatKnitPack1(RepositoryFormatPack):
2590
 
    """A no-subtrees parameterized Pack repository.
2591
 
 
2592
 
    This format was introduced in 0.92.
2593
 
    """
2594
 
 
2595
 
    repository_class = KnitPackRepository
2596
 
    _commit_builder_class = PackCommitBuilder
2597
 
    @property
2598
 
    def _serializer(self):
2599
 
        return xml5.serializer_v5
2600
 
    # What index classes to use
2601
 
    index_builder_class = InMemoryGraphIndex
2602
 
    index_class = GraphIndex
2603
 
 
2604
 
    def _get_matching_bzrdir(self):
2605
 
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
2606
 
 
2607
 
    def _ignore_setting_bzrdir(self, format):
2608
 
        pass
2609
 
 
2610
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2611
 
 
2612
 
    def get_format_string(self):
2613
 
        """See RepositoryFormat.get_format_string()."""
2614
 
        return "Bazaar pack repository format 1 (needs bzr 0.92)\n"
2615
 
 
2616
 
    def get_format_description(self):
2617
 
        """See RepositoryFormat.get_format_description()."""
2618
 
        return "Packs containing knits without subtree support"
2619
 
 
2620
 
 
2621
 
class RepositoryFormatKnitPack3(RepositoryFormatPack):
2622
 
    """A subtrees parameterized Pack repository.
2623
 
 
2624
 
    This repository format uses the xml7 serializer to get:
2625
 
     - support for recording full info about the tree root
2626
 
     - support for recording tree-references
2627
 
 
2628
 
    This format was introduced in 0.92.
2629
 
    """
2630
 
 
2631
 
    repository_class = KnitPackRepository
2632
 
    _commit_builder_class = PackRootCommitBuilder
2633
 
    rich_root_data = True
2634
 
    experimental = True
2635
 
    supports_tree_reference = True
2636
 
    @property
2637
 
    def _serializer(self):
2638
 
        return xml7.serializer_v7
2639
 
    # What index classes to use
2640
 
    index_builder_class = InMemoryGraphIndex
2641
 
    index_class = GraphIndex
2642
 
 
2643
 
    def _get_matching_bzrdir(self):
2644
 
        return bzrdir.format_registry.make_bzrdir(
2645
 
            'pack-0.92-subtree')
2646
 
 
2647
 
    def _ignore_setting_bzrdir(self, format):
2648
 
        pass
2649
 
 
2650
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2651
 
 
2652
 
    def get_format_string(self):
2653
 
        """See RepositoryFormat.get_format_string()."""
2654
 
        return "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n"
2655
 
 
2656
 
    def get_format_description(self):
2657
 
        """See RepositoryFormat.get_format_description()."""
2658
 
        return "Packs containing knits with subtree support\n"
2659
 
 
2660
 
 
2661
 
class RepositoryFormatKnitPack4(RepositoryFormatPack):
2662
 
    """A rich-root, no subtrees parameterized Pack repository.
2663
 
 
2664
 
    This repository format uses the xml6 serializer to get:
2665
 
     - support for recording full info about the tree root
2666
 
 
2667
 
    This format was introduced in 1.0.
2668
 
    """
2669
 
 
2670
 
    repository_class = KnitPackRepository
2671
 
    _commit_builder_class = PackRootCommitBuilder
2672
 
    rich_root_data = True
2673
 
    supports_tree_reference = False
2674
 
    @property
2675
 
    def _serializer(self):
2676
 
        return xml6.serializer_v6
2677
 
    # What index classes to use
2678
 
    index_builder_class = InMemoryGraphIndex
2679
 
    index_class = GraphIndex
2680
 
 
2681
 
    def _get_matching_bzrdir(self):
2682
 
        return bzrdir.format_registry.make_bzrdir(
2683
 
            'rich-root-pack')
2684
 
 
2685
 
    def _ignore_setting_bzrdir(self, format):
2686
 
        pass
2687
 
 
2688
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2689
 
 
2690
 
    def get_format_string(self):
2691
 
        """See RepositoryFormat.get_format_string()."""
2692
 
        return ("Bazaar pack repository format 1 with rich root"
2693
 
                " (needs bzr 1.0)\n")
2694
 
 
2695
 
    def get_format_description(self):
2696
 
        """See RepositoryFormat.get_format_description()."""
2697
 
        return "Packs containing knits with rich root support\n"
2698
 
 
2699
 
 
2700
 
class RepositoryFormatKnitPack5(RepositoryFormatPack):
2701
 
    """Repository that supports external references to allow stacking.
2702
 
 
2703
 
    New in release 1.6.
2704
 
 
2705
 
    Supports external lookups, which results in non-truncated ghosts after
2706
 
    reconcile compared to pack-0.92 formats.
2707
 
    """
2708
 
 
2709
 
    repository_class = KnitPackRepository
2710
 
    _commit_builder_class = PackCommitBuilder
2711
 
    supports_external_lookups = True
2712
 
    # What index classes to use
2713
 
    index_builder_class = InMemoryGraphIndex
2714
 
    index_class = GraphIndex
2715
 
 
2716
 
    @property
2717
 
    def _serializer(self):
2718
 
        return xml5.serializer_v5
2719
 
 
2720
 
    def _get_matching_bzrdir(self):
2721
 
        return bzrdir.format_registry.make_bzrdir('1.6')
2722
 
 
2723
 
    def _ignore_setting_bzrdir(self, format):
2724
 
        pass
2725
 
 
2726
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2727
 
 
2728
 
    def get_format_string(self):
2729
 
        """See RepositoryFormat.get_format_string()."""
2730
 
        return "Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n"
2731
 
 
2732
 
    def get_format_description(self):
2733
 
        """See RepositoryFormat.get_format_description()."""
2734
 
        return "Packs 5 (adds stacking support, requires bzr 1.6)"
2735
 
 
2736
 
 
2737
 
class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
2738
 
    """A repository with rich roots and stacking.
2739
 
 
2740
 
    New in release 1.6.1.
2741
 
 
2742
 
    Supports stacking on other repositories, allowing data to be accessed
2743
 
    without being stored locally.
2744
 
    """
2745
 
 
2746
 
    repository_class = KnitPackRepository
2747
 
    _commit_builder_class = PackRootCommitBuilder
2748
 
    rich_root_data = True
2749
 
    supports_tree_reference = False # no subtrees
2750
 
    supports_external_lookups = True
2751
 
    # What index classes to use
2752
 
    index_builder_class = InMemoryGraphIndex
2753
 
    index_class = GraphIndex
2754
 
 
2755
 
    @property
2756
 
    def _serializer(self):
2757
 
        return xml6.serializer_v6
2758
 
 
2759
 
    def _get_matching_bzrdir(self):
2760
 
        return bzrdir.format_registry.make_bzrdir(
2761
 
            '1.6.1-rich-root')
2762
 
 
2763
 
    def _ignore_setting_bzrdir(self, format):
2764
 
        pass
2765
 
 
2766
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2767
 
 
2768
 
    def get_format_string(self):
2769
 
        """See RepositoryFormat.get_format_string()."""
2770
 
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n"
2771
 
 
2772
 
    def get_format_description(self):
2773
 
        return "Packs 5 rich-root (adds stacking support, requires bzr 1.6.1)"
2774
 
 
2775
 
 
2776
 
class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):
2777
 
    """A repository with rich roots and external references.
2778
 
 
2779
 
    New in release 1.6.
2780
 
 
2781
 
    Supports external lookups, which results in non-truncated ghosts after
2782
 
    reconcile compared to pack-0.92 formats.
2783
 
 
2784
 
    This format was deprecated because the serializer it uses accidentally
2785
 
    supported subtrees, when the format was not intended to. This meant that
2786
 
    someone could accidentally fetch from an incorrect repository.
2787
 
    """
2788
 
 
2789
 
    repository_class = KnitPackRepository
2790
 
    _commit_builder_class = PackRootCommitBuilder
2791
 
    rich_root_data = True
2792
 
    supports_tree_reference = False # no subtrees
2793
 
 
2794
 
    supports_external_lookups = True
2795
 
    # What index classes to use
2796
 
    index_builder_class = InMemoryGraphIndex
2797
 
    index_class = GraphIndex
2798
 
 
2799
 
    @property
2800
 
    def _serializer(self):
2801
 
        return xml7.serializer_v7
2802
 
 
2803
 
    def _get_matching_bzrdir(self):
2804
 
        matching = bzrdir.format_registry.make_bzrdir(
2805
 
            '1.6.1-rich-root')
2806
 
        matching.repository_format = self
2807
 
        return matching
2808
 
 
2809
 
    def _ignore_setting_bzrdir(self, format):
2810
 
        pass
2811
 
 
2812
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2813
 
 
2814
 
    def get_format_string(self):
2815
 
        """See RepositoryFormat.get_format_string()."""
2816
 
        return "Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n"
2817
 
 
2818
 
    def get_format_description(self):
2819
 
        return ("Packs 5 rich-root (adds stacking support, requires bzr 1.6)"
2820
 
                " (deprecated)")
2821
 
 
2822
 
 
2823
 
class RepositoryFormatKnitPack6(RepositoryFormatPack):
2824
 
    """A repository with stacking and btree indexes,
2825
 
    without rich roots or subtrees.
2826
 
 
2827
 
    This is equivalent to pack-1.6 with B+Tree indices.
2828
 
    """
2829
 
 
2830
 
    repository_class = KnitPackRepository
2831
 
    _commit_builder_class = PackCommitBuilder
2832
 
    supports_external_lookups = True
2833
 
    # What index classes to use
2834
 
    index_builder_class = btree_index.BTreeBuilder
2835
 
    index_class = btree_index.BTreeGraphIndex
2836
 
 
2837
 
    @property
2838
 
    def _serializer(self):
2839
 
        return xml5.serializer_v5
2840
 
 
2841
 
    def _get_matching_bzrdir(self):
2842
 
        return bzrdir.format_registry.make_bzrdir('1.9')
2843
 
 
2844
 
    def _ignore_setting_bzrdir(self, format):
2845
 
        pass
2846
 
 
2847
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2848
 
 
2849
 
    def get_format_string(self):
2850
 
        """See RepositoryFormat.get_format_string()."""
2851
 
        return "Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n"
2852
 
 
2853
 
    def get_format_description(self):
2854
 
        """See RepositoryFormat.get_format_description()."""
2855
 
        return "Packs 6 (uses btree indexes, requires bzr 1.9)"
2856
 
 
2857
 
 
2858
 
class RepositoryFormatKnitPack6RichRoot(RepositoryFormatPack):
2859
 
    """A repository with rich roots, no subtrees, stacking and btree indexes.
2860
 
 
2861
 
    1.6-rich-root with B+Tree indices.
2862
 
    """
2863
 
 
2864
 
    repository_class = KnitPackRepository
2865
 
    _commit_builder_class = PackRootCommitBuilder
2866
 
    rich_root_data = True
2867
 
    supports_tree_reference = False # no subtrees
2868
 
    supports_external_lookups = True
2869
 
    # What index classes to use
2870
 
    index_builder_class = btree_index.BTreeBuilder
2871
 
    index_class = btree_index.BTreeGraphIndex
2872
 
 
2873
 
    @property
2874
 
    def _serializer(self):
2875
 
        return xml6.serializer_v6
2876
 
 
2877
 
    def _get_matching_bzrdir(self):
2878
 
        return bzrdir.format_registry.make_bzrdir(
2879
 
            '1.9-rich-root')
2880
 
 
2881
 
    def _ignore_setting_bzrdir(self, format):
2882
 
        pass
2883
 
 
2884
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2885
 
 
2886
 
    def get_format_string(self):
2887
 
        """See RepositoryFormat.get_format_string()."""
2888
 
        return "Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n"
2889
 
 
2890
 
    def get_format_description(self):
2891
 
        return "Packs 6 rich-root (uses btree indexes, requires bzr 1.9)"
2892
 
 
2893
 
 
2894
 
class RepositoryFormatPackDevelopment2Subtree(RepositoryFormatPack):
2895
 
    """A subtrees development repository.
2896
 
 
2897
 
    This format should be retained until the second release after bzr 1.7.
2898
 
 
2899
 
    1.6.1-subtree[as it might have been] with B+Tree indices.
2900
 
 
2901
 
    This is [now] retained until we have a CHK based subtree format in
2902
 
    development.
2903
 
    """
2904
 
 
2905
 
    repository_class = KnitPackRepository
2906
 
    _commit_builder_class = PackRootCommitBuilder
2907
 
    rich_root_data = True
2908
 
    experimental = True
2909
 
    supports_tree_reference = True
2910
 
    supports_external_lookups = True
2911
 
    # What index classes to use
2912
 
    index_builder_class = btree_index.BTreeBuilder
2913
 
    index_class = btree_index.BTreeGraphIndex
2914
 
 
2915
 
    @property
2916
 
    def _serializer(self):
2917
 
        return xml7.serializer_v7
2918
 
 
2919
 
    def _get_matching_bzrdir(self):
2920
 
        return bzrdir.format_registry.make_bzrdir(
2921
 
            'development5-subtree')
2922
 
 
2923
 
    def _ignore_setting_bzrdir(self, format):
2924
 
        pass
2925
 
 
2926
 
    _matchingbzrdir = property(_get_matching_bzrdir, _ignore_setting_bzrdir)
2927
 
 
2928
 
    def get_format_string(self):
2929
 
        """See RepositoryFormat.get_format_string()."""
2930
 
        return ("Bazaar development format 2 with subtree support "
2931
 
            "(needs bzr.dev from before 1.8)\n")
2932
 
 
2933
 
    def get_format_description(self):
2934
 
        """See RepositoryFormat.get_format_description()."""
2935
 
        return ("Development repository format, currently the same as "
2936
 
            "1.6.1-subtree with B+Tree indices.\n")
 
2332
class RetryPackOperations(errors.RetryWithNewPacks):
 
2333
    """Raised when we are packing and we find a missing file.
 
2334
 
 
2335
    Meant as a signaling exception, to tell the RepositoryPackCollection.pack
 
2336
    code it should try again.
 
2337
    """
 
2338
 
 
2339
    internal_error = True
 
2340
 
 
2341
    _fmt = ("Pack files have changed, reload and try pack again."
 
2342
            " context: %(context)s %(orig_error)s")
 
2343
 
 
2344
 
 
2345
class _DirectPackAccess(object):
 
2346
    """Access to data in one or more packs with less translation."""
 
2347
 
 
2348
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
 
2349
        """Create a _DirectPackAccess object.
 
2350
 
 
2351
        :param index_to_packs: A dict mapping index objects to the transport
 
2352
            and file names for obtaining data.
 
2353
        :param reload_func: A function to call if we determine that the pack
 
2354
            files have moved and we need to reload our caches. See
 
2355
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
 
2356
        """
 
2357
        self._container_writer = None
 
2358
        self._write_index = None
 
2359
        self._indices = index_to_packs
 
2360
        self._reload_func = reload_func
 
2361
        self._flush_func = flush_func
 
2362
 
 
2363
    def add_raw_records(self, key_sizes, raw_data):
 
2364
        """Add raw knit bytes to a storage area.
 
2365
 
 
2366
        The data is spooled to the container writer in one bytes-record per
 
2367
        raw data item.
 
2368
 
 
2369
        :param sizes: An iterable of tuples containing the key and size of each
 
2370
            raw data segment.
 
2371
        :param raw_data: A bytestring containing the data.
 
2372
        :return: A list of memos to retrieve the record later. Each memo is an
 
2373
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
 
2374
            length), where the index field is the write_index object supplied
 
2375
            to the PackAccess object.
 
2376
        """
 
2377
        if type(raw_data) is not str:
 
2378
            raise AssertionError(
 
2379
                'data must be plain bytes was %s' % type(raw_data))
 
2380
        result = []
 
2381
        offset = 0
 
2382
        for key, size in key_sizes:
 
2383
            p_offset, p_length = self._container_writer.add_bytes_record(
 
2384
                raw_data[offset:offset+size], [])
 
2385
            offset += size
 
2386
            result.append((self._write_index, p_offset, p_length))
 
2387
        return result
 
2388
 
 
2389
    def flush(self):
 
2390
        """Flush pending writes on this access object.
 
2391
 
 
2392
        This will flush any buffered writes to a NewPack.
 
2393
        """
 
2394
        if self._flush_func is not None:
 
2395
            self._flush_func()
 
2396
 
 
2397
    def get_raw_records(self, memos_for_retrieval):
 
2398
        """Get the raw bytes for a records.
 
2399
 
 
2400
        :param memos_for_retrieval: An iterable containing the (index, pos,
 
2401
            length) memo for retrieving the bytes. The Pack access method
 
2402
            looks up the pack to use for a given record in its index_to_pack
 
2403
            map.
 
2404
        :return: An iterator over the bytes of the records.
 
2405
        """
 
2406
        # first pass, group into same-index requests
 
2407
        request_lists = []
 
2408
        current_index = None
 
2409
        for (index, offset, length) in memos_for_retrieval:
 
2410
            if current_index == index:
 
2411
                current_list.append((offset, length))
 
2412
            else:
 
2413
                if current_index is not None:
 
2414
                    request_lists.append((current_index, current_list))
 
2415
                current_index = index
 
2416
                current_list = [(offset, length)]
 
2417
        # handle the last entry
 
2418
        if current_index is not None:
 
2419
            request_lists.append((current_index, current_list))
 
2420
        for index, offsets in request_lists:
 
2421
            try:
 
2422
                transport, path = self._indices[index]
 
2423
            except KeyError:
 
2424
                # A KeyError here indicates that someone has triggered an index
 
2425
                # reload, and this index has gone missing, we need to start
 
2426
                # over.
 
2427
                if self._reload_func is None:
 
2428
                    # If we don't have a _reload_func there is nothing that can
 
2429
                    # be done
 
2430
                    raise
 
2431
                raise errors.RetryWithNewPacks(index,
 
2432
                                               reload_occurred=True,
 
2433
                                               exc_info=sys.exc_info())
 
2434
            try:
 
2435
                reader = pack.make_readv_reader(transport, path, offsets)
 
2436
                for names, read_func in reader.iter_records():
 
2437
                    yield read_func(None)
 
2438
            except errors.NoSuchFile:
 
2439
                # A NoSuchFile error indicates that a pack file has gone
 
2440
                # missing on disk, we need to trigger a reload, and start over.
 
2441
                if self._reload_func is None:
 
2442
                    raise
 
2443
                raise errors.RetryWithNewPacks(transport.abspath(path),
 
2444
                                               reload_occurred=False,
 
2445
                                               exc_info=sys.exc_info())
 
2446
 
 
2447
    def set_writer(self, writer, index, transport_packname):
 
2448
        """Set a writer to use for adding data."""
 
2449
        if index is not None:
 
2450
            self._indices[index] = transport_packname
 
2451
        self._container_writer = writer
 
2452
        self._write_index = index
 
2453
 
 
2454
    def reload_or_raise(self, retry_exc):
 
2455
        """Try calling the reload function, or re-raise the original exception.
 
2456
 
 
2457
        This should be called after _DirectPackAccess raises a
 
2458
        RetryWithNewPacks exception. This function will handle the common logic
 
2459
        of determining when the error is fatal versus being temporary.
 
2460
        It will also make sure that the original exception is raised, rather
 
2461
        than the RetryWithNewPacks exception.
 
2462
 
 
2463
        If this function returns, then the calling function should retry
 
2464
        whatever operation was being performed. Otherwise an exception will
 
2465
        be raised.
 
2466
 
 
2467
        :param retry_exc: A RetryWithNewPacks exception.
 
2468
        """
 
2469
        is_error = False
 
2470
        if self._reload_func is None:
 
2471
            is_error = True
 
2472
        elif not self._reload_func():
 
2473
            # The reload claimed that nothing changed
 
2474
            if not retry_exc.reload_occurred:
 
2475
                # If there wasn't an earlier reload, then we really were
 
2476
                # expecting to find changes. We didn't find them, so this is a
 
2477
                # hard error
 
2478
                is_error = True
 
2479
        if is_error:
 
2480
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
 
2481
            raise exc_class, exc_value, exc_traceback
 
2482
 
 
2483
 
2937
2484