~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-20 15:06:17 UTC
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110420150617-i41caxgemg32tq1r
Start adding tests that _worth_saving_limit works as expected.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
78
76
    )
79
77
 
80
78
from bzrlib.repofmt import pack_repo
81
 
from bzrlib.i18n import gettext
82
79
""")
83
80
from bzrlib import (
84
81
    annotate,
106
103
    ConstantMapper,
107
104
    ContentFactory,
108
105
    sort_groupcompress,
109
 
    VersionedFilesWithFallbacks,
 
106
    VersionedFiles,
110
107
    )
111
108
 
112
109
 
411
408
class KnitContent(object):
412
409
    """Content of a knit version to which deltas can be applied.
413
410
 
414
 
    This is always stored in memory as a list of lines with \\n at the end,
 
411
    This is always stored in memory as a list of lines with \n at the end,
415
412
    plus a flag saying if the final ending is really there or not, because that
416
413
    corresponds to the on-disk knit representation.
417
414
    """
848
845
                in all_build_index_memos.itervalues()])
849
846
 
850
847
 
851
 
class KnitVersionedFiles(VersionedFilesWithFallbacks):
 
848
class KnitVersionedFiles(VersionedFiles):
852
849
    """Storage for many versioned files using knit compression.
853
850
 
854
851
    Backend storage is managed by indices and data objects.
890
887
            self._index,
891
888
            self._access)
892
889
 
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
890
    def add_fallback_versioned_files(self, a_versioned_files):
900
891
        """Add a source of texts for texts not present in this knit.
901
892
 
1158
1149
 
1159
1150
        A dict of key to (record_details, index_memo, next, parents) is
1160
1151
        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.
 
1152
        method is the way referenced data should be applied.
 
1153
        index_memo is the handle to pass to the data access to actually get the
 
1154
            data
 
1155
        next is the build-parent of the version, or None for fulltexts.
 
1156
        parents is the version_ids of the parents of this version
 
1157
 
 
1158
        :param allow_missing: If True do not raise an error on a missing component,
 
1159
            just ignore it.
1170
1160
        """
1171
1161
        component_data = {}
1172
1162
        pending_components = keys
1198
1188
        generator = _VFContentMapGenerator(self, [key])
1199
1189
        return generator._get_content(key)
1200
1190
 
 
1191
    def get_known_graph_ancestry(self, keys):
 
1192
        """Get a KnownGraph instance with the ancestry of keys."""
 
1193
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1194
        for fallback in self._transitive_fallbacks():
 
1195
            if not missing_keys:
 
1196
                break
 
1197
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1198
                                                missing_keys)
 
1199
            parent_map.update(f_parent_map)
 
1200
            missing_keys = f_missing_keys
 
1201
        kg = _mod_graph.KnownGraph(parent_map)
 
1202
        return kg
 
1203
 
1201
1204
    def get_parent_map(self, keys):
1202
1205
        """Get a map of the graph parents of keys.
1203
1206
 
1234
1237
        """Produce a dictionary of knit records.
1235
1238
 
1236
1239
        :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.
 
1240
            record
 
1241
                data returned from read_records (a KnitContentobject)
 
1242
            record_details
 
1243
                opaque information to pass to parse_record
 
1244
            digest
 
1245
                SHA1 digest of the full text after all steps are done
 
1246
            next
 
1247
                build-parent of the version, i.e. the leftmost ancestor.
1242
1248
                Will be None if the record is not a delta.
1243
 
 
1244
1249
        :param keys: The keys to build a map for
1245
1250
        :param allow_missing: If some records are missing, rather than
1246
1251
            error, just return the data that could be generated.
1763
1768
                        key_records.append((key, details[0]))
1764
1769
                records_iter = enumerate(self._read_records_iter(key_records))
1765
1770
                for (key_idx, (key, data, sha_value)) in records_iter:
1766
 
                    pb.update(gettext('Walking content'), key_idx, total)
 
1771
                    pb.update('Walking content', key_idx, total)
1767
1772
                    compression_parent = build_details[key][1]
1768
1773
                    if compression_parent is None:
1769
1774
                        # fulltext
1799
1804
                source_keys.add(key)
1800
1805
                yield line, key
1801
1806
            keys.difference_update(source_keys)
1802
 
        pb.update(gettext('Walking content'), total, total)
 
1807
        pb.update('Walking content', total, total)
1803
1808
 
1804
1809
    def _make_line_delta(self, delta_seq, new_content):
1805
1810
        """Generate a line delta from delta_seq and new_content."""
1913
1918
        The result will be returned in whatever is the fastest to read.
1914
1919
        Not by the order requested. Also, multiple requests for the same
1915
1920
        record will only yield 1 response.
1916
 
 
1917
1921
        :param records: A list of (key, access_memo) entries
1918
1922
        :return: Yields (key, contents, digest) in the order
1919
1923
                 read, not the order requested
1977
1981
        :param key: The key of the record. Currently keys are always serialised
1978
1982
            using just the trailing component.
1979
1983
        :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.
 
1984
            instance, if lines is a list of 1000 bytestrings each ending in \n,
 
1985
            dense_lines may be a list with one line in it, containing all the
 
1986
            1000's lines and their \n's. Using dense_lines if it is already
 
1987
            known is a win because the string join to create bytes in this
 
1988
            function spends less time resizing the final string.
1985
1989
        :return: (len, a StringIO instance with the raw data ready to read.)
1986
1990
        """
1987
1991
        chunks = ["version %s %d %s\n" % (key[-1], len(lines), digest)]
3212
3216
                yield data
3213
3217
 
3214
3218
 
 
3219
class _DirectPackAccess(object):
 
3220
    """Access to data in one or more packs with less translation."""
 
3221
 
 
3222
    def __init__(self, index_to_packs, reload_func=None, flush_func=None):
 
3223
        """Create a _DirectPackAccess object.
 
3224
 
 
3225
        :param index_to_packs: A dict mapping index objects to the transport
 
3226
            and file names for obtaining data.
 
3227
        :param reload_func: A function to call if we determine that the pack
 
3228
            files have moved and we need to reload our caches. See
 
3229
            bzrlib.repo_fmt.pack_repo.AggregateIndex for more details.
 
3230
        """
 
3231
        self._container_writer = None
 
3232
        self._write_index = None
 
3233
        self._indices = index_to_packs
 
3234
        self._reload_func = reload_func
 
3235
        self._flush_func = flush_func
 
3236
 
 
3237
    def add_raw_records(self, key_sizes, raw_data):
 
3238
        """Add raw knit bytes to a storage area.
 
3239
 
 
3240
        The data is spooled to the container writer in one bytes-record per
 
3241
        raw data item.
 
3242
 
 
3243
        :param sizes: An iterable of tuples containing the key and size of each
 
3244
            raw data segment.
 
3245
        :param raw_data: A bytestring containing the data.
 
3246
        :return: A list of memos to retrieve the record later. Each memo is an
 
3247
            opaque index memo. For _DirectPackAccess the memo is (index, pos,
 
3248
            length), where the index field is the write_index object supplied
 
3249
            to the PackAccess object.
 
3250
        """
 
3251
        if type(raw_data) is not str:
 
3252
            raise AssertionError(
 
3253
                'data must be plain bytes was %s' % type(raw_data))
 
3254
        result = []
 
3255
        offset = 0
 
3256
        for key, size in key_sizes:
 
3257
            p_offset, p_length = self._container_writer.add_bytes_record(
 
3258
                raw_data[offset:offset+size], [])
 
3259
            offset += size
 
3260
            result.append((self._write_index, p_offset, p_length))
 
3261
        return result
 
3262
 
 
3263
    def flush(self):
 
3264
        """Flush pending writes on this access object.
 
3265
 
 
3266
        This will flush any buffered writes to a NewPack.
 
3267
        """
 
3268
        if self._flush_func is not None:
 
3269
            self._flush_func()
 
3270
            
 
3271
    def get_raw_records(self, memos_for_retrieval):
 
3272
        """Get the raw bytes for a records.
 
3273
 
 
3274
        :param memos_for_retrieval: An iterable containing the (index, pos,
 
3275
            length) memo for retrieving the bytes. The Pack access method
 
3276
            looks up the pack to use for a given record in its index_to_pack
 
3277
            map.
 
3278
        :return: An iterator over the bytes of the records.
 
3279
        """
 
3280
        # first pass, group into same-index requests
 
3281
        request_lists = []
 
3282
        current_index = None
 
3283
        for (index, offset, length) in memos_for_retrieval:
 
3284
            if current_index == index:
 
3285
                current_list.append((offset, length))
 
3286
            else:
 
3287
                if current_index is not None:
 
3288
                    request_lists.append((current_index, current_list))
 
3289
                current_index = index
 
3290
                current_list = [(offset, length)]
 
3291
        # handle the last entry
 
3292
        if current_index is not None:
 
3293
            request_lists.append((current_index, current_list))
 
3294
        for index, offsets in request_lists:
 
3295
            try:
 
3296
                transport, path = self._indices[index]
 
3297
            except KeyError:
 
3298
                # A KeyError here indicates that someone has triggered an index
 
3299
                # reload, and this index has gone missing, we need to start
 
3300
                # over.
 
3301
                if self._reload_func is None:
 
3302
                    # If we don't have a _reload_func there is nothing that can
 
3303
                    # be done
 
3304
                    raise
 
3305
                raise errors.RetryWithNewPacks(index,
 
3306
                                               reload_occurred=True,
 
3307
                                               exc_info=sys.exc_info())
 
3308
            try:
 
3309
                reader = pack.make_readv_reader(transport, path, offsets)
 
3310
                for names, read_func in reader.iter_records():
 
3311
                    yield read_func(None)
 
3312
            except errors.NoSuchFile:
 
3313
                # A NoSuchFile error indicates that a pack file has gone
 
3314
                # missing on disk, we need to trigger a reload, and start over.
 
3315
                if self._reload_func is None:
 
3316
                    raise
 
3317
                raise errors.RetryWithNewPacks(transport.abspath(path),
 
3318
                                               reload_occurred=False,
 
3319
                                               exc_info=sys.exc_info())
 
3320
 
 
3321
    def set_writer(self, writer, index, transport_packname):
 
3322
        """Set a writer to use for adding data."""
 
3323
        if index is not None:
 
3324
            self._indices[index] = transport_packname
 
3325
        self._container_writer = writer
 
3326
        self._write_index = index
 
3327
 
 
3328
    def reload_or_raise(self, retry_exc):
 
3329
        """Try calling the reload function, or re-raise the original exception.
 
3330
 
 
3331
        This should be called after _DirectPackAccess raises a
 
3332
        RetryWithNewPacks exception. This function will handle the common logic
 
3333
        of determining when the error is fatal versus being temporary.
 
3334
        It will also make sure that the original exception is raised, rather
 
3335
        than the RetryWithNewPacks exception.
 
3336
 
 
3337
        If this function returns, then the calling function should retry
 
3338
        whatever operation was being performed. Otherwise an exception will
 
3339
        be raised.
 
3340
 
 
3341
        :param retry_exc: A RetryWithNewPacks exception.
 
3342
        """
 
3343
        is_error = False
 
3344
        if self._reload_func is None:
 
3345
            is_error = True
 
3346
        elif not self._reload_func():
 
3347
            # The reload claimed that nothing changed
 
3348
            if not retry_exc.reload_occurred:
 
3349
                # If there wasn't an earlier reload, then we really were
 
3350
                # expecting to find changes. We didn't find them, so this is a
 
3351
                # hard error
 
3352
                is_error = True
 
3353
        if is_error:
 
3354
            exc_class, exc_value, exc_traceback = retry_exc.exc_info
 
3355
            raise exc_class, exc_value, exc_traceback
 
3356
 
 
3357
 
3215
3358
def annotate_knit(knit, revision_id):
3216
3359
    """Annotate a knit with no cached annotations.
3217
3360
 
3327
3470
                for idx, (sub_key, text, num_lines) in enumerate(
3328
3471
                                                self._extract_texts(records)):
3329
3472
                    if pb is not None:
3330
 
                        pb.update(gettext('annotating'), idx, len(records))
 
3473
                        pb.update('annotating', idx, len(records))
3331
3474
                    yield sub_key, text, num_lines
3332
3475
                for sub_key in ann_keys:
3333
3476
                    text = self._text_cache[sub_key]