~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: Andrew Bennetts
  • Date: 2009-12-03 02:24:54 UTC
  • mfrom: (4634.101.4 2.0)
  • mto: This revision was merged to the branch mainline in revision 4857.
  • Revision ID: andrew.bennetts@canonical.com-20091203022454-m2gyhbcdqi1t7ujz
Merge lp:bzr/2.0 into lp:bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
51
51
 
52
52
"""
53
53
 
54
 
from __future__ import absolute_import
55
 
 
56
54
 
57
55
from cStringIO import StringIO
58
56
from itertools import izip
59
57
import operator
60
58
import os
 
59
import sys
61
60
 
62
61
from bzrlib.lazy_import import lazy_import
63
62
lazy_import(globals(), """
64
 
import gzip
65
 
 
66
63
from bzrlib import (
 
64
    annotate,
67
65
    debug,
68
66
    diff,
69
67
    graph as _mod_graph,
70
68
    index as _mod_index,
 
69
    lru_cache,
71
70
    pack,
72
 
    patiencediff,
 
71
    progress,
73
72
    static_tuple,
74
73
    trace,
75
74
    tsort,
76
75
    tuned_gzip,
77
 
    ui,
78
76
    )
79
 
 
80
 
from bzrlib.repofmt import pack_repo
81
 
from bzrlib.i18n import gettext
82
77
""")
83
78
from bzrlib import (
84
 
    annotate,
85
79
    errors,
86
80
    osutils,
 
81
    patiencediff,
87
82
    )
88
83
from bzrlib.errors import (
 
84
    FileExists,
89
85
    NoSuchFile,
 
86
    KnitError,
90
87
    InvalidRevisionId,
91
88
    KnitCorrupt,
92
89
    KnitHeaderError,
93
90
    RevisionNotPresent,
 
91
    RevisionAlreadyPresent,
94
92
    SHA1KnitCorrupt,
95
93
    )
96
94
from bzrlib.osutils import (
97
95
    contains_whitespace,
 
96
    contains_linebreaks,
98
97
    sha_string,
99
98
    sha_strings,
100
99
    split_lines,
101
100
    )
102
101
from bzrlib.versionedfile import (
103
 
    _KeyRefs,
104
102
    AbsentContentFactory,
105
103
    adapter_registry,
106
104
    ConstantMapper,
107
105
    ContentFactory,
 
106
    ChunkedContentFactory,
108
107
    sort_groupcompress,
109
 
    VersionedFilesWithFallbacks,
 
108
    VersionedFile,
 
109
    VersionedFiles,
110
110
    )
111
111
 
112
112
 
411
411
class KnitContent(object):
412
412
    """Content of a knit version to which deltas can be applied.
413
413
 
414
 
    This is always stored in memory as a list of lines with \\n at the end,
 
414
    This is always stored in memory as a list of lines with \n at the end,
415
415
    plus a flag saying if the final ending is really there or not, because that
416
416
    corresponds to the on-disk knit representation.
417
417
    """
804
804
        writer.begin()
805
805
        index = _KnitGraphIndex(graph_index, lambda:True, parents=parents,
806
806
            deltas=delta, add_callback=graph_index.add_nodes)
807
 
        access = pack_repo._DirectPackAccess({})
 
807
        access = _DirectPackAccess({})
808
808
        access.set_writer(writer, graph_index, (transport, 'newpack'))
809
809
        result = KnitVersionedFiles(index, access,
810
810
            max_delta_chain=max_delta_chain)
848
848
                in all_build_index_memos.itervalues()])
849
849
 
850
850
 
851
 
class KnitVersionedFiles(VersionedFilesWithFallbacks):
 
851
class KnitVersionedFiles(VersionedFiles):
852
852
    """Storage for many versioned files using knit compression.
853
853
 
854
854
    Backend storage is managed by indices and data objects.
881
881
            self._factory = KnitAnnotateFactory()
882
882
        else:
883
883
            self._factory = KnitPlainFactory()
884
 
        self._immediate_fallback_vfs = []
 
884
        self._fallback_vfs = []
885
885
        self._reload_func = reload_func
886
886
 
887
887
    def __repr__(self):
890
890
            self._index,
891
891
            self._access)
892
892
 
893
 
    def without_fallbacks(self):
894
 
        """Return a clone of this object without any fallbacks configured."""
895
 
        return KnitVersionedFiles(self._index, self._access,
896
 
            self._max_delta_chain, self._factory.annotated,
897
 
            self._reload_func)
898
 
 
899
893
    def add_fallback_versioned_files(self, a_versioned_files):
900
894
        """Add a source of texts for texts not present in this knit.
901
895
 
902
896
        :param a_versioned_files: A VersionedFiles object.
903
897
        """
904
 
        self._immediate_fallback_vfs.append(a_versioned_files)
 
898
        self._fallback_vfs.append(a_versioned_files)
905
899
 
906
900
    def add_lines(self, key, parents, lines, parent_texts=None,
907
901
        left_matching_blocks=None, nostore_sha=None, random_id=False,
1074
1068
                    raise errors.KnitCorrupt(self,
1075
1069
                        "Missing basis parent %s for %s" % (
1076
1070
                        compression_parent, key))
1077
 
        for fallback_vfs in self._immediate_fallback_vfs:
 
1071
        for fallback_vfs in self._fallback_vfs:
1078
1072
            fallback_vfs.check()
1079
1073
 
1080
1074
    def _check_add(self, key, lines, random_id, check_content):
1158
1152
 
1159
1153
        A dict of key to (record_details, index_memo, next, parents) is
1160
1154
        returned.
1161
 
 
1162
 
        * method is the way referenced data should be applied.
1163
 
        * index_memo is the handle to pass to the data access to actually get
1164
 
          the data
1165
 
        * next is the build-parent of the version, or None for fulltexts.
1166
 
        * parents is the version_ids of the parents of this version
1167
 
 
1168
 
        :param allow_missing: If True do not raise an error on a missing
1169
 
            component, just ignore it.
 
1155
        method is the way referenced data should be applied.
 
1156
        index_memo is the handle to pass to the data access to actually get the
 
1157
            data
 
1158
        next is the build-parent of the version, or None for fulltexts.
 
1159
        parents is the version_ids of the parents of this version
 
1160
 
 
1161
        :param allow_missing: If True do not raise an error on a missing component,
 
1162
            just ignore it.
1170
1163
        """
1171
1164
        component_data = {}
1172
1165
        pending_components = keys
1198
1191
        generator = _VFContentMapGenerator(self, [key])
1199
1192
        return generator._get_content(key)
1200
1193
 
 
1194
    def get_known_graph_ancestry(self, keys):
 
1195
        """Get a KnownGraph instance with the ancestry of keys."""
 
1196
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1197
        for fallback in self._fallback_vfs:
 
1198
            if not missing_keys:
 
1199
                break
 
1200
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1201
                                                missing_keys)
 
1202
            parent_map.update(f_parent_map)
 
1203
            missing_keys = f_missing_keys
 
1204
        kg = _mod_graph.KnownGraph(parent_map)
 
1205
        return kg
 
1206
 
1201
1207
    def get_parent_map(self, keys):
1202
1208
        """Get a map of the graph parents of keys.
1203
1209
 
1218
1224
            and so on.
1219
1225
        """
1220
1226
        result = {}
1221
 
        sources = [self._index] + self._immediate_fallback_vfs
 
1227
        sources = [self._index] + self._fallback_vfs
1222
1228
        source_results = []
1223
1229
        missing = set(keys)
1224
1230
        for source in sources:
1234
1240
        """Produce a dictionary of knit records.
1235
1241
 
1236
1242
        :return: {key:(record, record_details, digest, next)}
1237
 
 
1238
 
            * record: data returned from read_records (a KnitContentobject)
1239
 
            * record_details: opaque information to pass to parse_record
1240
 
            * digest: SHA1 digest of the full text after all steps are done
1241
 
            * next: build-parent of the version, i.e. the leftmost ancestor.
 
1243
            record
 
1244
                data returned from read_records (a KnitContentobject)
 
1245
            record_details
 
1246
                opaque information to pass to parse_record
 
1247
            digest
 
1248
                SHA1 digest of the full text after all steps are done
 
1249
            next
 
1250
                build-parent of the version, i.e. the leftmost ancestor.
1242
1251
                Will be None if the record is not a delta.
1243
 
 
1244
1252
        :param keys: The keys to build a map for
1245
1253
        :param allow_missing: If some records are missing, rather than
1246
1254
            error, just return the data that could be generated.
1516
1524
                        yield KnitContentFactory(key, global_map[key],
1517
1525
                            record_details, None, raw_data, self._factory.annotated, None)
1518
1526
                else:
1519
 
                    vf = self._immediate_fallback_vfs[parent_maps.index(source) - 1]
 
1527
                    vf = self._fallback_vfs[parent_maps.index(source) - 1]
1520
1528
                    for record in vf.get_record_stream(keys, ordering,
1521
1529
                        include_delta_closure):
1522
1530
                        yield record
1532
1540
            # record entry 2 is the 'digest'.
1533
1541
            result[key] = details[2]
1534
1542
        missing.difference_update(set(result))
1535
 
        for source in self._immediate_fallback_vfs:
 
1543
        for source in self._fallback_vfs:
1536
1544
            if not missing:
1537
1545
                break
1538
1546
            new_result = source.get_sha1s(missing)
1609
1617
                raise RevisionNotPresent([record.key], self)
1610
1618
            elif ((record.storage_kind in knit_types)
1611
1619
                  and (compression_parent is None
1612
 
                       or not self._immediate_fallback_vfs
 
1620
                       or not self._fallback_vfs
1613
1621
                       or self._index.has_key(compression_parent)
1614
1622
                       or not self.has_key(compression_parent))):
1615
1623
                # we can insert the knit record literally if either it has no
1747
1755
        :return: An iterator over (line, key).
1748
1756
        """
1749
1757
        if pb is None:
1750
 
            pb = ui.ui_factory.nested_progress_bar()
 
1758
            pb = progress.DummyProgress()
1751
1759
        keys = set(keys)
1752
1760
        total = len(keys)
1753
1761
        done = False
1763
1771
                        key_records.append((key, details[0]))
1764
1772
                records_iter = enumerate(self._read_records_iter(key_records))
1765
1773
                for (key_idx, (key, data, sha_value)) in records_iter:
1766
 
                    pb.update(gettext('Walking content'), key_idx, total)
 
1774
                    pb.update('Walking content', key_idx, total)
1767
1775
                    compression_parent = build_details[key][1]
1768
1776
                    if compression_parent is None:
1769
1777
                        # fulltext
1787
1795
        # vfs, and hope to find them there.  Note that if the keys are found
1788
1796
        # but had no changes or no content, the fallback may not return
1789
1797
        # anything.
1790
 
        if keys and not self._immediate_fallback_vfs:
 
1798
        if keys and not self._fallback_vfs:
1791
1799
            # XXX: strictly the second parameter is meant to be the file id
1792
1800
            # but it's not easily accessible here.
1793
1801
            raise RevisionNotPresent(keys, repr(self))
1794
 
        for source in self._immediate_fallback_vfs:
 
1802
        for source in self._fallback_vfs:
1795
1803
            if not keys:
1796
1804
                break
1797
1805
            source_keys = set()
1799
1807
                source_keys.add(key)
1800
1808
                yield line, key
1801
1809
            keys.difference_update(source_keys)
1802
 
        pb.update(gettext('Walking content'), total, total)
 
1810
        pb.update('Walking content', total, total)
1803
1811
 
1804
1812
    def _make_line_delta(self, delta_seq, new_content):
1805
1813
        """Generate a line delta from delta_seq and new_content."""
1870
1878
        :return: the header and the decompressor stream.
1871
1879
                 as (stream, header_record)
1872
1880
        """
1873
 
        df = gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
 
1881
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(raw_data))
1874
1882
        try:
1875
1883
            # Current serialise
1876
1884
            rec = self._check_header(key, df.readline())
1885
1893
        # 4168 calls in 2880 217 internal
1886
1894
        # 4168 calls to _parse_record_header in 2121
1887
1895
        # 4168 calls to readlines in 330
1888
 
        df = gzip.GzipFile(mode='rb', fileobj=StringIO(data))
 
1896
        df = tuned_gzip.GzipFile(mode='rb', fileobj=StringIO(data))
1889
1897
        try:
1890
1898
            record_contents = df.readlines()
1891
1899
        except Exception, e:
1913
1921
        The result will be returned in whatever is the fastest to read.
1914
1922
        Not by the order requested. Also, multiple requests for the same
1915
1923
        record will only yield 1 response.
1916
 
 
1917
1924
        :param records: A list of (key, access_memo) entries
1918
1925
        :return: Yields (key, contents, digest) in the order
1919
1926
                 read, not the order requested
1977
1984
        :param key: The key of the record. Currently keys are always serialised
1978
1985
            using just the trailing component.
1979
1986
        :param dense_lines: The bytes of lines but in a denser form. For
1980
 
            instance, if lines is a list of 1000 bytestrings each ending in
1981
 
            \\n, dense_lines may be a list with one line in it, containing all
1982
 
            the 1000's lines and their \\n's. Using dense_lines if it is
1983
 
            already known is a win because the string join to create bytes in
1984
 
            this function spends less time resizing the final string.
 
1987
            instance, if lines is a list of 1000 bytestrings each ending in \n,
 
1988
            dense_lines may be a list with one line in it, containing all the
 
1989
            1000's lines and their \n's. Using dense_lines if it is already
 
1990
            known is a win because the string join to create bytes in this
 
1991
            function spends less time resizing the final string.
1985
1992
        :return: (len, a StringIO instance with the raw data ready to read.)
1986
1993
        """
1987
1994
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
2007
2014
        """See VersionedFiles.keys."""
2008
2015
        if 'evil' in debug.debug_flags:
2009
2016
            trace.mutter_callsite(2, "keys scales with size of history")
2010
 
        sources = [self._index] + self._immediate_fallback_vfs
 
2017
        sources = [self._index] + self._fallback_vfs
2011
2018
        result = set()
2012
2019
        for source in sources:
2013
2020
            result.update(source.keys())
2053
2060
 
2054
2061
        missing_keys = set(nonlocal_keys)
2055
2062
        # Read from remote versioned file instances and provide to our caller.
2056
 
        for source in self.vf._immediate_fallback_vfs:
 
2063
        for source in self.vf._fallback_vfs:
2057
2064
            if not missing_keys:
2058
2065
                break
2059
2066
            # Loop over fallback repositories asking them for texts - ignore
2778
2785
        return key[:-1], key[-1]
2779
2786
 
2780
2787
 
 
2788
class _KeyRefs(object):
 
2789
 
 
2790
    def __init__(self, track_new_keys=False):
 
2791
        # dict mapping 'key' to 'set of keys referring to that key'
 
2792
        self.refs = {}
 
2793
        if track_new_keys:
 
2794
            # set remembering all new keys
 
2795
            self.new_keys = set()
 
2796
        else:
 
2797
            self.new_keys = None
 
2798
 
 
2799
    def clear(self):
 
2800
        if self.refs:
 
2801
            self.refs.clear()
 
2802
        if self.new_keys:
 
2803
            self.new_keys.clear()
 
2804
 
 
2805
    def add_references(self, key, refs):
 
2806
        # Record the new references
 
2807
        for referenced in refs:
 
2808
            try:
 
2809
                needed_by = self.refs[referenced]
 
2810
            except KeyError:
 
2811
                needed_by = self.refs[referenced] = set()
 
2812
            needed_by.add(key)
 
2813
        # Discard references satisfied by the new key
 
2814
        self.add_key(key)
 
2815
 
 
2816
    def get_new_keys(self):
 
2817
        return self.new_keys
 
2818
    
 
2819
    def get_unsatisfied_refs(self):
 
2820
        return self.refs.iterkeys()
 
2821
 
 
2822
    def _satisfy_refs_for_key(self, key):
 
2823
        try:
 
2824
            del self.refs[key]
 
2825
        except KeyError:
 
2826
            # No keys depended on this key.  That's ok.
 
2827
            pass
 
2828
 
 
2829
    def add_key(self, key):
 
2830
        # satisfy refs for key, and remember that we've seen this key.
 
2831
        self._satisfy_refs_for_key(key)
 
2832
        if self.new_keys is not None:
 
2833
            self.new_keys.add(key)
 
2834
 
 
2835
    def satisfy_refs_for_keys(self, keys):
 
2836
        for key in keys:
 
2837
            self._satisfy_refs_for_key(key)
 
2838
 
 
2839
    def get_referrers(self):
 
2840
        result = set()
 
2841
        for referrers in self.refs.itervalues():
 
2842
            result.update(referrers)
 
2843
        return result
 
2844
 
 
2845
 
2781
2846
class _KnitGraphIndex(object):
2782
2847
    """A KnitVersionedFiles index layered on GraphIndex."""
2783
2848
 
3212
3277
                yield data
3213
3278
 
3214
3279
 
 
3280
class _DirectPackAccess(object):
 
3281
    """Access to data in one or more packs with less translation."""
 
3282
 
 
3283
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
 
3284
        """Create a _DirectPackAccess object.
 
3285
 
 
3286
        :param index_to_packs: A dict mapping index objects to the transport
 
3287
            and file names for obtaining data.
 
3288
        :param reload_func: A function to call if we determine that the pack
 
3289
            files have moved and we need to reload our caches. See
 
3290
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
 
3291
        """
 
3292
        self._container_writer = None
 
3293
        self._write_index = None
 
3294
        self._indices = index_to_packs
 
3295
        self._reload_func = reload_func
 
3296
        self._flush_func = flush_func
 
3297
 
 
3298
    def add_raw_records(self, key_sizes, raw_data):
 
3299
        """Add raw knit bytes to a storage area.
 
3300
 
 
3301
        The data is spooled to the container writer in one bytes-record per
 
3302
        raw data item.
 
3303
 
 
3304
        :param sizes: An iterable of tuples containing the key and size of each
 
3305
            raw data segment.
 
3306
        :param raw_data: A bytestring containing the data.
 
3307
        :return: A list of memos to retrieve the record later. Each memo is an
 
3308
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
 
3309
            length), where the index field is the write_index object supplied
 
3310
            to the PackAccess object.
 
3311
        """
 
3312
        if type(raw_data) is not str:
 
3313
            raise AssertionError(
 
3314
                'data must be plain bytes was %s' % type(raw_data))
 
3315
        result = []
 
3316
        offset = 0
 
3317
        for key, size in key_sizes:
 
3318
            p_offset, p_length = self._container_writer.add_bytes_record(
 
3319
                raw_data[offset:offset+size], [])
 
3320
            offset += size
 
3321
            result.append((self._write_index, p_offset, p_length))
 
3322
        return result
 
3323
 
 
3324
    def flush(self):
 
3325
        """Flush pending writes on this access object.
 
3326
 
 
3327
        This will flush any buffered writes to a NewPack.
 
3328
        """
 
3329
        if self._flush_func is not None:
 
3330
            self._flush_func()
 
3331
            
 
3332
    def get_raw_records(self, memos_for_retrieval):
 
3333
        """Get the raw bytes for a records.
 
3334
 
 
3335
        :param memos_for_retrieval: An iterable containing the (index, pos,
 
3336
            length) memo for retrieving the bytes. The Pack access method
 
3337
            looks up the pack to use for a given record in its index_to_pack
 
3338
            map.
 
3339
        :return: An iterator over the bytes of the records.
 
3340
        """
 
3341
        # first pass, group into same-index requests
 
3342
        request_lists = []
 
3343
        current_index = None
 
3344
        for (index, offset, length) in memos_for_retrieval:
 
3345
            if current_index == index:
 
3346
                current_list.append((offset, length))
 
3347
            else:
 
3348
                if current_index is not None:
 
3349
                    request_lists.append((current_index, current_list))
 
3350
                current_index = index
 
3351
                current_list = [(offset, length)]
 
3352
        # handle the last entry
 
3353
        if current_index is not None:
 
3354
            request_lists.append((current_index, current_list))
 
3355
        for index, offsets in request_lists:
 
3356
            try:
 
3357
                transport, path = self._indices[index]
 
3358
            except KeyError:
 
3359
                # A KeyError here indicates that someone has triggered an index
 
3360
                # reload, and this index has gone missing, we need to start
 
3361
                # over.
 
3362
                if self._reload_func is None:
 
3363
                    # If we don't have a _reload_func there is nothing that can
 
3364
                    # be done
 
3365
                    raise
 
3366
                raise errors.RetryWithNewPacks(index,
 
3367
                                               reload_occurred=True,
 
3368
                                               exc_info=sys.exc_info())
 
3369
            try:
 
3370
                reader = pack.make_readv_reader(transport, path, offsets)
 
3371
                for names, read_func in reader.iter_records():
 
3372
                    yield read_func(None)
 
3373
            except errors.NoSuchFile:
 
3374
                # A NoSuchFile error indicates that a pack file has gone
 
3375
                # missing on disk, we need to trigger a reload, and start over.
 
3376
                if self._reload_func is None:
 
3377
                    raise
 
3378
                raise errors.RetryWithNewPacks(transport.abspath(path),
 
3379
                                               reload_occurred=False,
 
3380
                                               exc_info=sys.exc_info())
 
3381
 
 
3382
    def set_writer(self, writer, index, transport_packname):
 
3383
        """Set a writer to use for adding data."""
 
3384
        if index is not None:
 
3385
            self._indices[index] = transport_packname
 
3386
        self._container_writer = writer
 
3387
        self._write_index = index
 
3388
 
 
3389
    def reload_or_raise(self, retry_exc):
 
3390
        """Try calling the reload function, or re-raise the original exception.
 
3391
 
 
3392
        This should be called after _DirectPackAccess raises a
 
3393
        RetryWithNewPacks exception. This function will handle the common logic
 
3394
        of determining when the error is fatal versus being temporary.
 
3395
        It will also make sure that the original exception is raised, rather
 
3396
        than the RetryWithNewPacks exception.
 
3397
 
 
3398
        If this function returns, then the calling function should retry
 
3399
        whatever operation was being performed. Otherwise an exception will
 
3400
        be raised.
 
3401
 
 
3402
        :param retry_exc: A RetryWithNewPacks exception.
 
3403
        """
 
3404
        is_error = False
 
3405
        if self._reload_func is None:
 
3406
            is_error = True
 
3407
        elif not self._reload_func():
 
3408
            # The reload claimed that nothing changed
 
3409
            if not retry_exc.reload_occurred:
 
3410
                # If there wasn't an earlier reload, then we really were
 
3411
                # expecting to find changes. We didn't find them, so this is a
 
3412
                # hard error
 
3413
                is_error = True
 
3414
        if is_error:
 
3415
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
 
3416
            raise exc_class, exc_value, exc_traceback
 
3417
 
 
3418
 
 
3419
# Deprecated, use PatienceSequenceMatcher instead
 
3420
KnitSequenceMatcher = patiencediff.PatienceSequenceMatcher
 
3421
 
 
3422
 
3215
3423
def annotate_knit(knit, revision_id):
3216
3424
    """Annotate a knit with no cached annotations.
3217
3425
 
3315
3523
        return records, ann_keys
3316
3524
 
3317
3525
    def _get_needed_texts(self, key, pb=None):
3318
 
        # if True or len(self._vf._immediate_fallback_vfs) > 0:
3319
 
        if len(self._vf._immediate_fallback_vfs) > 0:
 
3526
        # if True or len(self._vf._fallback_vfs) > 0:
 
3527
        if len(self._vf._fallback_vfs) > 0:
3320
3528
            # If we have fallbacks, go to the generic path
3321
3529
            for v in annotate.Annotator._get_needed_texts(self, key, pb=pb):
3322
3530
                yield v
3327
3535
                for idx, (sub_key, text, num_lines) in enumerate(
3328
3536
                                                self._extract_texts(records)):
3329
3537
                    if pb is not None:
3330
 
                        pb.update(gettext('annotating'), idx, len(records))
 
3538
                        pb.update('annotating', idx, len(records))
3331
3539
                    yield sub_key, text, num_lines
3332
3540
                for sub_key in ann_keys:
3333
3541
                    text = self._text_cache[sub_key]