~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Andrew Bennetts
  • Date: 2011-02-25 08:45:27 UTC
  • mto: This revision was merged to the branch mainline in revision 5695.
  • Revision ID: andrew.bennetts@canonical.com-20110225084527-0ucp7p00d00hoqon
Add another test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
2
 
#
3
 
# Authors:
4
 
#   Johan Rydberg <jrydberg@gnu.org>
 
1
# Copyright (C) 2006-2011 Canonical Ltd
5
2
#
6
3
# This program is free software; you can redistribute it and/or modify
7
4
# it under the terms of the GNU General Public License as published by
46
43
from bzrlib.transport.memory import MemoryTransport
47
44
""")
48
45
from bzrlib.registry import Registry
49
 
from bzrlib.symbol_versioning import *
50
46
from bzrlib.textmerge import TextMerge
51
47
from bzrlib import bencode
52
48
 
206
202
            yield record
207
203
 
208
204
 
 
205
class _MPDiffGenerator(object):
 
206
    """Pull out the functionality for generating mp_diffs."""
 
207
 
 
208
    def __init__(self, vf, keys):
 
209
        self.vf = vf
 
210
        # This is the order the keys were requested in
 
211
        self.ordered_keys = tuple(keys)
 
212
        # keys + their parents, what we need to compute the diffs
 
213
        self.needed_keys = ()
 
214
        # Map from key: mp_diff
 
215
        self.diffs = {}
 
216
        # Map from key: parents_needed (may have ghosts)
 
217
        self.parent_map = {}
 
218
        # Parents that aren't present
 
219
        self.ghost_parents = ()
 
220
        # Map from parent_key => number of children for this text
 
221
        self.refcounts = {}
 
222
        # Content chunks that are cached while we still need them
 
223
        self.chunks = {}
 
224
 
 
225
    def _find_needed_keys(self):
 
226
        """Find the set of keys we need to request.
 
227
 
 
228
        This includes all the original keys passed in, and the non-ghost
 
229
        parents of those keys.
 
230
 
 
231
        :return: (needed_keys, refcounts)
 
232
            needed_keys is the set of all texts we need to extract
 
233
            refcounts is a dict of {key: num_children} letting us know when we
 
234
                no longer need to cache a given parent text
 
235
        """
 
236
        # All the keys and their parents
 
237
        needed_keys = set(self.ordered_keys)
 
238
        parent_map = self.vf.get_parent_map(needed_keys)
 
239
        self.parent_map = parent_map
 
240
        # TODO: Should we be using a different construct here? I think this
 
241
        #       uses difference_update internally, and we expect the result to
 
242
        #       be tiny
 
243
        missing_keys = needed_keys.difference(parent_map)
 
244
        if missing_keys:
 
245
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
246
        # Parents that might be missing. They are allowed to be ghosts, but we
 
247
        # should check for them
 
248
        refcounts = {}
 
249
        setdefault = refcounts.setdefault
 
250
        just_parents = set()
 
251
        for child_key, parent_keys in parent_map.iteritems():
 
252
            if not parent_keys:
 
253
                # parent_keys may be None if a given VersionedFile claims to
 
254
                # not support graph operations.
 
255
                continue
 
256
            just_parents.update(parent_keys)
 
257
            needed_keys.update(parent_keys)
 
258
            for p in parent_keys:
 
259
                refcounts[p] = setdefault(p, 0) + 1
 
260
        just_parents.difference_update(parent_map)
 
261
        # Remove any parents that are actually ghosts from the needed set
 
262
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
263
        self.ghost_parents = just_parents.difference(self.present_parents)
 
264
        needed_keys.difference_update(self.ghost_parents)
 
265
        self.needed_keys = needed_keys
 
266
        self.refcounts = refcounts
 
267
        return needed_keys, refcounts
 
268
 
 
269
    def _compute_diff(self, key, parent_lines, lines):
 
270
        """Compute a single mp_diff, and store it in self._diffs"""
 
271
        if len(parent_lines) > 0:
 
272
            # XXX: _extract_blocks is not usefully defined anywhere...
 
273
            #      It was meant to extract the left-parent diff without
 
274
            #      having to recompute it for Knit content (pack-0.92,
 
275
            #      etc). That seems to have regressed somewhere
 
276
            left_parent_blocks = self.vf._extract_blocks(key,
 
277
                parent_lines[0], lines)
 
278
        else:
 
279
            left_parent_blocks = None
 
280
        diff = multiparent.MultiParent.from_lines(lines,
 
281
                    parent_lines, left_parent_blocks)
 
282
        self.diffs[key] = diff
 
283
 
 
284
    def _process_one_record(self, key, this_chunks):
 
285
        parent_keys = None
 
286
        if key in self.parent_map:
 
287
            # This record should be ready to diff, since we requested
 
288
            # content in 'topological' order
 
289
            parent_keys = self.parent_map.pop(key)
 
290
            # If a VersionedFile claims 'no-graph' support, then it may return
 
291
            # None for any parent request, so we replace it with an empty tuple
 
292
            if parent_keys is None:
 
293
                parent_keys = ()
 
294
            parent_lines = []
 
295
            for p in parent_keys:
 
296
                # Alternatively we could check p not in self.needed_keys, but
 
297
                # ghost_parents should be tiny versus huge
 
298
                if p in self.ghost_parents:
 
299
                    continue
 
300
                refcount = self.refcounts[p]
 
301
                if refcount == 1: # Last child reference
 
302
                    self.refcounts.pop(p)
 
303
                    parent_chunks = self.chunks.pop(p)
 
304
                else:
 
305
                    self.refcounts[p] = refcount - 1
 
306
                    parent_chunks = self.chunks[p]
 
307
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
308
                # TODO: Should we cache the line form? We did the
 
309
                #       computation to get it, but storing it this way will
 
310
                #       be less memory efficient...
 
311
                parent_lines.append(p_lines)
 
312
                del p_lines
 
313
            lines = osutils.chunks_to_lines(this_chunks)
 
314
            # Since we needed the lines, we'll go ahead and cache them this way
 
315
            this_chunks = lines
 
316
            self._compute_diff(key, parent_lines, lines)
 
317
            del lines
 
318
        # Is this content required for any more children?
 
319
        if key in self.refcounts:
 
320
            self.chunks[key] = this_chunks
 
321
 
 
322
    def _extract_diffs(self):
 
323
        needed_keys, refcounts = self._find_needed_keys()
 
324
        for record in self.vf.get_record_stream(needed_keys,
 
325
                                                'topological', True):
 
326
            if record.storage_kind == 'absent':
 
327
                raise errors.RevisionNotPresent(record.key, self.vf)
 
328
            self._process_one_record(record.key,
 
329
                                     record.get_bytes_as('chunked'))
 
330
        
 
331
    def compute_diffs(self):
 
332
        self._extract_diffs()
 
333
        dpop = self.diffs.pop
 
334
        return [dpop(k) for k in self.ordered_keys]
 
335
 
 
336
 
209
337
class VersionedFile(object):
210
338
    """Versioned text file storage.
211
339
 
348
476
 
349
477
    def make_mpdiffs(self, version_ids):
350
478
        """Create multiparent diffs for specified versions."""
 
479
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
480
        #      is a list of strings, not keys. And while self.get_record_stream
 
481
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
482
        #      strings... *sigh*
351
483
        knit_versions = set()
352
484
        knit_versions.update(version_ids)
353
485
        parent_map = self.get_parent_map(version_ids)
795
927
 
796
928
    The use of tuples allows a single code base to support several different
797
929
    uses with only the mapping logic changing from instance to instance.
 
930
 
 
931
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
 
932
        this is a list of other VersionedFiles immediately underneath this
 
933
        one.  They may in turn each have further fallbacks.
798
934
    """
799
935
 
800
936
    def add_lines(self, key, parents, lines, parent_texts=None,
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
 
1090
1194
    def _extract_blocks(self, version_id, source, target):
1091
1195
        return None
1092
1196
 
 
1197
    def _transitive_fallbacks(self):
 
1198
        """Return the whole stack of fallback versionedfiles.
 
1199
 
 
1200
        This VersionedFiles may have a list of fallbacks, but it doesn't
 
1201
        necessarily know about the whole stack going down, and it can't know
 
1202
        at open time because they may change after the objects are opened.
 
1203
        """
 
1204
        all_fallbacks = []
 
1205
        for a_vfs in self._immediate_fallback_vfs:
 
1206
            all_fallbacks.append(a_vfs)
 
1207
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
 
1208
        return all_fallbacks
 
1209
 
1093
1210
 
1094
1211
class ThunkedVersionedFiles(VersionedFiles):
1095
1212
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1159
1276
            result.append((prefix + (origin,), line))
1160
1277
        return result
1161
1278
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
1279
    def check(self, progress_bar=None, keys=None):
1166
1280
        """See VersionedFiles.check()."""
1167
1281
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1624
1738
                yield (l, key)
1625
1739
 
1626
1740
 
 
1741
class NoDupeAddLinesDecorator(object):
 
1742
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1743
    is already present.
 
1744
    """
 
1745
 
 
1746
    def __init__(self, store):
 
1747
        self._store = store
 
1748
 
 
1749
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1750
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1751
            check_content=True):
 
1752
        """See VersionedFiles.add_lines.
 
1753
        
 
1754
        This implementation may return None as the third element of the return
 
1755
        value when the original store wouldn't.
 
1756
        """
 
1757
        if nostore_sha:
 
1758
            raise NotImplementedError(
 
1759
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1760
                "nostore_sha behaviour.")
 
1761
        if key[-1] is None:
 
1762
            sha1 = osutils.sha_strings(lines)
 
1763
            key = ("sha1:" + sha1,)
 
1764
        else:
 
1765
            sha1 = None
 
1766
        if key in self._store.get_parent_map([key]):
 
1767
            # This key has already been inserted, so don't do it again.
 
1768
            if sha1 is None:
 
1769
                sha1 = osutils.sha_strings(lines)
 
1770
            return sha1, sum(map(len, lines)), None
 
1771
        return self._store.add_lines(key, parents, lines,
 
1772
                parent_texts=parent_texts,
 
1773
                left_matching_blocks=left_matching_blocks,
 
1774
                nostore_sha=nostore_sha, random_id=random_id,
 
1775
                check_content=check_content)
 
1776
 
 
1777
    def __getattr__(self, name):
 
1778
        return getattr(self._store, name)
 
1779
 
 
1780
 
1627
1781
def network_bytes_to_kind_and_offset(network_bytes):
1628
1782
    """Strip of a record kind from the front of network_bytes.
1629
1783