~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2011-10-14 12:49:33 UTC
  • mto: This revision was merged to the branch mainline in revision 6216.
  • Revision ID: jelmer@samba.org-20111014124933-732wmdc39ebzga0b
Fix last locking issues.

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
31
28
 
32
29
from bzrlib import (
33
30
    annotate,
 
31
    bencode,
34
32
    errors,
35
33
    graph as _mod_graph,
36
34
    groupcompress,
40
38
    multiparent,
41
39
    tsort,
42
40
    revision,
43
 
    ui,
44
41
    )
45
 
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
46
 
from bzrlib.transport.memory import MemoryTransport
47
42
""")
48
43
from bzrlib.registry import Registry
49
 
from bzrlib.symbol_versioning import *
50
44
from bzrlib.textmerge import TextMerge
51
 
from bzrlib import bencode
52
45
 
53
46
 
54
47
adapter_registry = Registry()
206
199
            yield record
207
200
 
208
201
 
 
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
 
209
334
class VersionedFile(object):
210
335
    """Versioned text file storage.
211
336
 
348
473
 
349
474
    def make_mpdiffs(self, version_ids):
350
475
        """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*
351
480
        knit_versions = set()
352
481
        knit_versions.update(version_ids)
353
482
        parent_map = self.get_parent_map(version_ids)
795
924
 
796
925
    The use of tuples allows a single code base to support several different
797
926
    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.
798
931
    """
799
932
 
800
933
    def add_lines(self, key, parents, lines, parent_texts=None,
839
972
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
840
973
        """Add a text to the store.
841
974
 
842
 
        This is a private function for use by CommitBuilder.
 
975
        This is a private function for use by VersionedFileCommitBuilder.
843
976
 
844
977
        :param key: The key tuple of the text to add. If the last element is
845
978
            None, a CHK string will be generated during the addition.
1047
1180
 
1048
1181
    def make_mpdiffs(self, keys):
1049
1182
        """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
 
1183
        generator = _MPDiffGenerator(self, keys)
 
1184
        return generator.compute_diffs()
 
1185
 
 
1186
    def get_annotator(self):
 
1187
        return annotate.Annotator(self)
1087
1188
 
1088
1189
    missing_keys = index._missing_keys_from_parent_map
1089
1190
 
1090
1191
    def _extract_blocks(self, version_id, source, target):
1091
1192
        return None
1092
1193
 
 
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
 
1093
1207
 
1094
1208
class ThunkedVersionedFiles(VersionedFiles):
1095
1209
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1159
1273
            result.append((prefix + (origin,), line))
1160
1274
        return result
1161
1275
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
1276
    def check(self, progress_bar=None, keys=None):
1166
1277
        """See VersionedFiles.check()."""
1167
1278
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1311
1422
        return result
1312
1423
 
1313
1424
 
 
1425
class VersionedFilesWithFallbacks(VersionedFiles):
 
1426
 
 
1427
    def without_fallbacks(self):
 
1428
        """Return a clone of this object without any fallbacks configured."""
 
1429
        raise NotImplementedError(self.without_fallbacks)
 
1430
 
 
1431
    def add_fallback_versioned_files(self, a_versioned_files):
 
1432
        """Add a source of texts for texts not present in this knit.
 
1433
 
 
1434
        :param a_versioned_files: A VersionedFiles object.
 
1435
        """
 
1436
        raise NotImplementedError(self.add_fallback_versioned_files)
 
1437
 
 
1438
    def get_known_graph_ancestry(self, keys):
 
1439
        """Get a KnownGraph instance with the ancestry of keys."""
 
1440
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1441
        for fallback in self._transitive_fallbacks():
 
1442
            if not missing_keys:
 
1443
                break
 
1444
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1445
                                                missing_keys)
 
1446
            parent_map.update(f_parent_map)
 
1447
            missing_keys = f_missing_keys
 
1448
        kg = _mod_graph.KnownGraph(parent_map)
 
1449
        return kg
 
1450
 
 
1451
 
1314
1452
class _PlanMergeVersionedFile(VersionedFiles):
1315
1453
    """A VersionedFile for uncommitted and committed texts.
1316
1454
 
1337
1475
        # line data for locally held keys.
1338
1476
        self._lines = {}
1339
1477
        # key lookup providers
1340
 
        self._providers = [DictParentsProvider(self._parents)]
 
1478
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
1341
1479
 
1342
1480
    def plan_merge(self, ver_a, ver_b, base=None):
1343
1481
        """See VersionedFile.plan_merge"""
1350
1488
 
1351
1489
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1352
1490
        from bzrlib.merge import _PlanLCAMerge
1353
 
        graph = Graph(self)
 
1491
        graph = _mod_graph.Graph(self)
1354
1492
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1355
1493
        if base is None:
1356
1494
            return new_plan
1408
1546
            result[revision.NULL_REVISION] = ()
1409
1547
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1410
1548
        result.update(
1411
 
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1549
            _mod_graph.StackedParentsProvider(
 
1550
                self._providers).get_parent_map(keys))
1412
1551
        for key, parents in result.iteritems():
1413
1552
            if parents == ():
1414
1553
                result[key] = (revision.NULL_REVISION,)
1624
1763
                yield (l, key)
1625
1764
 
1626
1765
 
 
1766
class NoDupeAddLinesDecorator(object):
 
1767
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1768
    is already present.
 
1769
    """
 
1770
 
 
1771
    def __init__(self, store):
 
1772
        self._store = store
 
1773
 
 
1774
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1775
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1776
            check_content=True):
 
1777
        """See VersionedFiles.add_lines.
 
1778
        
 
1779
        This implementation may return None as the third element of the return
 
1780
        value when the original store wouldn't.
 
1781
        """
 
1782
        if nostore_sha:
 
1783
            raise NotImplementedError(
 
1784
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1785
                "nostore_sha behaviour.")
 
1786
        if key[-1] is None:
 
1787
            sha1 = osutils.sha_strings(lines)
 
1788
            key = ("sha1:" + sha1,)
 
1789
        else:
 
1790
            sha1 = None
 
1791
        if key in self._store.get_parent_map([key]):
 
1792
            # This key has already been inserted, so don't do it again.
 
1793
            if sha1 is None:
 
1794
                sha1 = osutils.sha_strings(lines)
 
1795
            return sha1, sum(map(len, lines)), None
 
1796
        return self._store.add_lines(key, parents, lines,
 
1797
                parent_texts=parent_texts,
 
1798
                left_matching_blocks=left_matching_blocks,
 
1799
                nostore_sha=nostore_sha, random_id=random_id,
 
1800
                check_content=check_content)
 
1801
 
 
1802
    def __getattr__(self, name):
 
1803
        return getattr(self._store, name)
 
1804
 
 
1805
 
1627
1806
def network_bytes_to_kind_and_offset(network_bytes):
1628
1807
    """Strip of a record kind from the front of network_bytes.
1629
1808
 
1720
1899
    for prefix in sorted(per_prefix_map):
1721
1900
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1722
1901
    return present_keys
 
1902
 
 
1903
 
 
1904
class _KeyRefs(object):
 
1905
 
 
1906
    def __init__(self, track_new_keys=False):
 
1907
        # dict mapping 'key' to 'set of keys referring to that key'
 
1908
        self.refs = {}
 
1909
        if track_new_keys:
 
1910
            # set remembering all new keys
 
1911
            self.new_keys = set()
 
1912
        else:
 
1913
            self.new_keys = None
 
1914
 
 
1915
    def clear(self):
 
1916
        if self.refs:
 
1917
            self.refs.clear()
 
1918
        if self.new_keys:
 
1919
            self.new_keys.clear()
 
1920
 
 
1921
    def add_references(self, key, refs):
 
1922
        # Record the new references
 
1923
        for referenced in refs:
 
1924
            try:
 
1925
                needed_by = self.refs[referenced]
 
1926
            except KeyError:
 
1927
                needed_by = self.refs[referenced] = set()
 
1928
            needed_by.add(key)
 
1929
        # Discard references satisfied by the new key
 
1930
        self.add_key(key)
 
1931
 
 
1932
    def get_new_keys(self):
 
1933
        return self.new_keys
 
1934
    
 
1935
    def get_unsatisfied_refs(self):
 
1936
        return self.refs.iterkeys()
 
1937
 
 
1938
    def _satisfy_refs_for_key(self, key):
 
1939
        try:
 
1940
            del self.refs[key]
 
1941
        except KeyError:
 
1942
            # No keys depended on this key.  That's ok.
 
1943
            pass
 
1944
 
 
1945
    def add_key(self, key):
 
1946
        # satisfy refs for key, and remember that we've seen this key.
 
1947
        self._satisfy_refs_for_key(key)
 
1948
        if self.new_keys is not None:
 
1949
            self.new_keys.add(key)
 
1950
 
 
1951
    def satisfy_refs_for_keys(self, keys):
 
1952
        for key in keys:
 
1953
            self._satisfy_refs_for_key(key)
 
1954
 
 
1955
    def get_referrers(self):
 
1956
        result = set()
 
1957
        for referrers in self.refs.itervalues():
 
1958
            result.update(referrers)
 
1959
        return result
 
1960
 
 
1961
 
 
1962