~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

Merge sftp-leaks into catch-them-all

Show diffs side-by-side

added added

removed removed

Lines of Context:
206
206
            yield record
207
207
 
208
208
 
 
209
class _MPDiffGenerator(object):
 
210
    """Pull out the functionality for generating mp_diffs."""
 
211
 
 
212
    def __init__(self, vf, keys):
 
213
        self.vf = vf
 
214
        # This is the order the keys were requested in
 
215
        self.ordered_keys = tuple(keys)
 
216
        # keys + their parents, what we need to compute the diffs
 
217
        self.needed_keys = ()
 
218
        # Map from key: mp_diff
 
219
        self.diffs = {}
 
220
        # Map from key: parents_needed (may have ghosts)
 
221
        self.parent_map = {}
 
222
        # Parents that aren't present
 
223
        self.ghost_parents = ()
 
224
        # Map from parent_key => number of children for this text
 
225
        self.refcounts = {}
 
226
        # Content chunks that are cached while we still need them
 
227
        self.chunks = {}
 
228
 
 
229
    def _find_needed_keys(self):
 
230
        """Find the set of keys we need to request.
 
231
 
 
232
        This includes all the original keys passed in, and the non-ghost
 
233
        parents of those keys.
 
234
 
 
235
        :return: (needed_keys, refcounts)
 
236
            needed_keys is the set of all texts we need to extract
 
237
            refcounts is a dict of {key: num_children} letting us know when we
 
238
                no longer need to cache a given parent text
 
239
        """
 
240
        # All the keys and their parents
 
241
        needed_keys = set(self.ordered_keys)
 
242
        parent_map = self.vf.get_parent_map(needed_keys)
 
243
        self.parent_map = parent_map
 
244
        # TODO: Should we be using a different construct here? I think this
 
245
        #       uses difference_update internally, and we expect the result to
 
246
        #       be tiny
 
247
        missing_keys = needed_keys.difference(parent_map)
 
248
        if missing_keys:
 
249
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
250
        # Parents that might be missing. They are allowed to be ghosts, but we
 
251
        # should check for them
 
252
        refcounts = {}
 
253
        setdefault = refcounts.setdefault
 
254
        just_parents = set()
 
255
        for child_key, parent_keys in parent_map.iteritems():
 
256
            if not parent_keys:
 
257
                # parent_keys may be None if a given VersionedFile claims to
 
258
                # not support graph operations.
 
259
                continue
 
260
            just_parents.update(parent_keys)
 
261
            needed_keys.update(parent_keys)
 
262
            for p in parent_keys:
 
263
                refcounts[p] = setdefault(p, 0) + 1
 
264
        just_parents.difference_update(parent_map)
 
265
        # Remove any parents that are actually ghosts from the needed set
 
266
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
267
        self.ghost_parents = just_parents.difference(self.present_parents)
 
268
        needed_keys.difference_update(self.ghost_parents)
 
269
        self.needed_keys = needed_keys
 
270
        self.refcounts = refcounts
 
271
        return needed_keys, refcounts
 
272
 
 
273
    def _compute_diff(self, key, parent_lines, lines):
 
274
        """Compute a single mp_diff, and store it in self._diffs"""
 
275
        if len(parent_lines) > 0:
 
276
            # XXX: _extract_blocks is not usefully defined anywhere...
 
277
            #      It was meant to extract the left-parent diff without
 
278
            #      having to recompute it for Knit content (pack-0.92,
 
279
            #      etc). That seems to have regressed somewhere
 
280
            left_parent_blocks = self.vf._extract_blocks(key,
 
281
                parent_lines[0], lines)
 
282
        else:
 
283
            left_parent_blocks = None
 
284
        diff = multiparent.MultiParent.from_lines(lines,
 
285
                    parent_lines, left_parent_blocks)
 
286
        self.diffs[key] = diff
 
287
 
 
288
    def _process_one_record(self, key, this_chunks):
 
289
        parent_keys = None
 
290
        if key in self.parent_map:
 
291
            # This record should be ready to diff, since we requested
 
292
            # content in 'topological' order
 
293
            parent_keys = self.parent_map.pop(key)
 
294
            # If a VersionedFile claims 'no-graph' support, then it may return
 
295
            # None for any parent request, so we replace it with an empty tuple
 
296
            if parent_keys is None:
 
297
                parent_keys = ()
 
298
            parent_lines = []
 
299
            for p in parent_keys:
 
300
                # Alternatively we could check p not in self.needed_keys, but
 
301
                # ghost_parents should be tiny versus huge
 
302
                if p in self.ghost_parents:
 
303
                    continue
 
304
                refcount = self.refcounts[p]
 
305
                if refcount == 1: # Last child reference
 
306
                    self.refcounts.pop(p)
 
307
                    parent_chunks = self.chunks.pop(p)
 
308
                else:
 
309
                    self.refcounts[p] = refcount - 1
 
310
                    parent_chunks = self.chunks[p]
 
311
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
312
                # TODO: Should we cache the line form? We did the
 
313
                #       computation to get it, but storing it this way will
 
314
                #       be less memory efficient...
 
315
                parent_lines.append(p_lines)
 
316
                del p_lines
 
317
            lines = osutils.chunks_to_lines(this_chunks)
 
318
            # Since we needed the lines, we'll go ahead and cache them this way
 
319
            this_chunks = lines
 
320
            self._compute_diff(key, parent_lines, lines)
 
321
            del lines
 
322
        # Is this content required for any more children?
 
323
        if key in self.refcounts:
 
324
            self.chunks[key] = this_chunks
 
325
 
 
326
    def _extract_diffs(self):
 
327
        needed_keys, refcounts = self._find_needed_keys()
 
328
        for record in self.vf.get_record_stream(needed_keys,
 
329
                                                'topological', True):
 
330
            if record.storage_kind == 'absent':
 
331
                raise errors.RevisionNotPresent(record.key, self.vf)
 
332
            self._process_one_record(record.key,
 
333
                                     record.get_bytes_as('chunked'))
 
334
        
 
335
    def compute_diffs(self):
 
336
        self._extract_diffs()
 
337
        dpop = self.diffs.pop
 
338
        return [dpop(k) for k in self.ordered_keys]
 
339
 
 
340
 
209
341
class VersionedFile(object):
210
342
    """Versioned text file storage.
211
343
 
348
480
 
349
481
    def make_mpdiffs(self, version_ids):
350
482
        """Create multiparent diffs for specified versions."""
 
483
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
484
        #      is a list of strings, not keys. And while self.get_record_stream
 
485
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
486
        #      strings... *sigh*
351
487
        knit_versions = set()
352
488
        knit_versions.update(version_ids)
353
489
        parent_map = self.get_parent_map(version_ids)
1047
1183
 
1048
1184
    def make_mpdiffs(self, keys):
1049
1185
        """Create multiparent diffs for specified keys."""
1050
 
        keys_order = tuple(keys)
1051
 
        keys = frozenset(keys)
1052
 
        knit_keys = set(keys)
1053
 
        parent_map = self.get_parent_map(keys)
1054
 
        for parent_keys in parent_map.itervalues():
1055
 
            if parent_keys:
1056
 
                knit_keys.update(parent_keys)
1057
 
        missing_keys = keys - set(parent_map)
1058
 
        if missing_keys:
1059
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
1060
 
        # We need to filter out ghosts, because we can't diff against them.
1061
 
        maybe_ghosts = knit_keys - keys
1062
 
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
1063
 
        knit_keys.difference_update(ghosts)
1064
 
        lines = {}
1065
 
        chunks_to_lines = osutils.chunks_to_lines
1066
 
        for record in self.get_record_stream(knit_keys, 'topological', True):
1067
 
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
1068
 
            # line_block_dict = {}
1069
 
            # for parent, blocks in record.extract_line_blocks():
1070
 
            #   line_blocks[parent] = blocks
1071
 
            # line_blocks[record.key] = line_block_dict
1072
 
        diffs = []
1073
 
        for key in keys_order:
1074
 
            target = lines[key]
1075
 
            parents = parent_map[key] or []
1076
 
            # Note that filtering knit_keys can lead to a parent difference
1077
 
            # between the creation and the application of the mpdiff.
1078
 
            parent_lines = [lines[p] for p in parents if p in knit_keys]
1079
 
            if len(parent_lines) > 0:
1080
 
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
1081
 
                    target)
1082
 
            else:
1083
 
                left_parent_blocks = None
1084
 
            diffs.append(multiparent.MultiParent.from_lines(target,
1085
 
                parent_lines, left_parent_blocks))
1086
 
        return diffs
 
1186
        generator = _MPDiffGenerator(self, keys)
 
1187
        return generator.compute_diffs()
 
1188
 
 
1189
    def get_annotator(self):
 
1190
        return annotate.Annotator(self)
1087
1191
 
1088
1192
    missing_keys = index._missing_keys_from_parent_map
1089
1193
 
1159
1263
            result.append((prefix + (origin,), line))
1160
1264
        return result
1161
1265
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
1266
    def check(self, progress_bar=None, keys=None):
1166
1267
        """See VersionedFiles.check()."""
1167
1268
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1624
1725
                yield (l, key)
1625
1726
 
1626
1727
 
 
1728
class NoDupeAddLinesDecorator(object):
 
1729
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1730
    is already present.
 
1731
    """
 
1732
 
 
1733
    def __init__(self, store):
 
1734
        self._store = store
 
1735
 
 
1736
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1737
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1738
            check_content=True):
 
1739
        """See VersionedFiles.add_lines.
 
1740
        
 
1741
        This implementation may return None as the third element of the return
 
1742
        value when the original store wouldn't.
 
1743
        """
 
1744
        if nostore_sha:
 
1745
            raise NotImplementedError(
 
1746
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1747
                "nostore_sha behaviour.")
 
1748
        if key[-1] is None:
 
1749
            sha1 = osutils.sha_strings(lines)
 
1750
            key = ("sha1:" + sha1,)
 
1751
        else:
 
1752
            sha1 = None
 
1753
        if key in self._store.get_parent_map([key]):
 
1754
            # This key has already been inserted, so don't do it again.
 
1755
            if sha1 is None:
 
1756
                sha1 = osutils.sha_strings(lines)
 
1757
            return sha1, sum(map(len, lines)), None
 
1758
        return self._store.add_lines(key, parents, lines,
 
1759
                parent_texts=parent_texts,
 
1760
                left_matching_blocks=left_matching_blocks,
 
1761
                nostore_sha=nostore_sha, random_id=random_id,
 
1762
                check_content=check_content)
 
1763
 
 
1764
    def __getattr__(self, name):
 
1765
        return getattr(self._store, name)
 
1766
 
 
1767
 
1627
1768
def network_bytes_to_kind_and_offset(network_bytes):
1628
1769
    """Strip of a record kind from the front of network_bytes.
1629
1770