~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Martin Pool
  • Date: 2010-06-02 04:56:07 UTC
  • mto: This revision was merged to the branch mainline in revision 5279.
  • Revision ID: mbp@canonical.com-20100602045607-uhn6m12c9k5rjlxz
Change _LINUX_NL to _UNIX_NL

Show diffs side-by-side

added added

removed removed

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