~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2010-12-20 02:23:31 UTC
  • mto: This revision was merged to the branch mainline in revision 5577.
  • Revision ID: jelmer@samba.org-20101220022331-hbm91o1ps9gjrlns
Add testr magic for bzr selftest --list

Show diffs side-by-side

added added

removed removed

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