/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Jelmer Vernooij
  • Date: 2011-12-18 15:43:47 UTC
  • mto: This revision was merged to the branch mainline in revision 6383.
  • Revision ID: jelmer@samba.org-20111218154347-d42sxp2qzn36uo2r
Add urlutils.quote / urlutils.unquote.

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
27
24
 
28
25
from bzrlib.lazy_import import lazy_import
29
26
lazy_import(globals(), """
30
 
import urllib
31
 
 
32
27
from bzrlib import (
33
28
    annotate,
 
29
    bencode,
34
30
    errors,
35
31
    graph as _mod_graph,
36
32
    groupcompress,
40
36
    multiparent,
41
37
    tsort,
42
38
    revision,
43
 
    ui,
 
39
    urlutils,
44
40
    )
45
 
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
46
 
from bzrlib.transport.memory import MemoryTransport
47
41
""")
48
42
from bzrlib.registry import Registry
49
 
from bzrlib.symbol_versioning import *
50
43
from bzrlib.textmerge import TextMerge
51
 
from bzrlib import bencode
52
44
 
53
45
 
54
46
adapter_registry = Registry()
206
198
            yield record
207
199
 
208
200
 
 
201
class _MPDiffGenerator(object):
 
202
    """Pull out the functionality for generating mp_diffs."""
 
203
 
 
204
    def __init__(self, vf, keys):
 
205
        self.vf = vf
 
206
        # This is the order the keys were requested in
 
207
        self.ordered_keys = tuple(keys)
 
208
        # keys + their parents, what we need to compute the diffs
 
209
        self.needed_keys = ()
 
210
        # Map from key: mp_diff
 
211
        self.diffs = {}
 
212
        # Map from key: parents_needed (may have ghosts)
 
213
        self.parent_map = {}
 
214
        # Parents that aren't present
 
215
        self.ghost_parents = ()
 
216
        # Map from parent_key => number of children for this text
 
217
        self.refcounts = {}
 
218
        # Content chunks that are cached while we still need them
 
219
        self.chunks = {}
 
220
 
 
221
    def _find_needed_keys(self):
 
222
        """Find the set of keys we need to request.
 
223
 
 
224
        This includes all the original keys passed in, and the non-ghost
 
225
        parents of those keys.
 
226
 
 
227
        :return: (needed_keys, refcounts)
 
228
            needed_keys is the set of all texts we need to extract
 
229
            refcounts is a dict of {key: num_children} letting us know when we
 
230
                no longer need to cache a given parent text
 
231
        """
 
232
        # All the keys and their parents
 
233
        needed_keys = set(self.ordered_keys)
 
234
        parent_map = self.vf.get_parent_map(needed_keys)
 
235
        self.parent_map = parent_map
 
236
        # TODO: Should we be using a different construct here? I think this
 
237
        #       uses difference_update internally, and we expect the result to
 
238
        #       be tiny
 
239
        missing_keys = needed_keys.difference(parent_map)
 
240
        if missing_keys:
 
241
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
242
        # Parents that might be missing. They are allowed to be ghosts, but we
 
243
        # should check for them
 
244
        refcounts = {}
 
245
        setdefault = refcounts.setdefault
 
246
        just_parents = set()
 
247
        for child_key, parent_keys in parent_map.iteritems():
 
248
            if not parent_keys:
 
249
                # parent_keys may be None if a given VersionedFile claims to
 
250
                # not support graph operations.
 
251
                continue
 
252
            just_parents.update(parent_keys)
 
253
            needed_keys.update(parent_keys)
 
254
            for p in parent_keys:
 
255
                refcounts[p] = setdefault(p, 0) + 1
 
256
        just_parents.difference_update(parent_map)
 
257
        # Remove any parents that are actually ghosts from the needed set
 
258
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
259
        self.ghost_parents = just_parents.difference(self.present_parents)
 
260
        needed_keys.difference_update(self.ghost_parents)
 
261
        self.needed_keys = needed_keys
 
262
        self.refcounts = refcounts
 
263
        return needed_keys, refcounts
 
264
 
 
265
    def _compute_diff(self, key, parent_lines, lines):
 
266
        """Compute a single mp_diff, and store it in self._diffs"""
 
267
        if len(parent_lines) > 0:
 
268
            # XXX: _extract_blocks is not usefully defined anywhere...
 
269
            #      It was meant to extract the left-parent diff without
 
270
            #      having to recompute it for Knit content (pack-0.92,
 
271
            #      etc). That seems to have regressed somewhere
 
272
            left_parent_blocks = self.vf._extract_blocks(key,
 
273
                parent_lines[0], lines)
 
274
        else:
 
275
            left_parent_blocks = None
 
276
        diff = multiparent.MultiParent.from_lines(lines,
 
277
                    parent_lines, left_parent_blocks)
 
278
        self.diffs[key] = diff
 
279
 
 
280
    def _process_one_record(self, key, this_chunks):
 
281
        parent_keys = None
 
282
        if key in self.parent_map:
 
283
            # This record should be ready to diff, since we requested
 
284
            # content in 'topological' order
 
285
            parent_keys = self.parent_map.pop(key)
 
286
            # If a VersionedFile claims 'no-graph' support, then it may return
 
287
            # None for any parent request, so we replace it with an empty tuple
 
288
            if parent_keys is None:
 
289
                parent_keys = ()
 
290
            parent_lines = []
 
291
            for p in parent_keys:
 
292
                # Alternatively we could check p not in self.needed_keys, but
 
293
                # ghost_parents should be tiny versus huge
 
294
                if p in self.ghost_parents:
 
295
                    continue
 
296
                refcount = self.refcounts[p]
 
297
                if refcount == 1: # Last child reference
 
298
                    self.refcounts.pop(p)
 
299
                    parent_chunks = self.chunks.pop(p)
 
300
                else:
 
301
                    self.refcounts[p] = refcount - 1
 
302
                    parent_chunks = self.chunks[p]
 
303
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
304
                # TODO: Should we cache the line form? We did the
 
305
                #       computation to get it, but storing it this way will
 
306
                #       be less memory efficient...
 
307
                parent_lines.append(p_lines)
 
308
                del p_lines
 
309
            lines = osutils.chunks_to_lines(this_chunks)
 
310
            # Since we needed the lines, we'll go ahead and cache them this way
 
311
            this_chunks = lines
 
312
            self._compute_diff(key, parent_lines, lines)
 
313
            del lines
 
314
        # Is this content required for any more children?
 
315
        if key in self.refcounts:
 
316
            self.chunks[key] = this_chunks
 
317
 
 
318
    def _extract_diffs(self):
 
319
        needed_keys, refcounts = self._find_needed_keys()
 
320
        for record in self.vf.get_record_stream(needed_keys,
 
321
                                                'topological', True):
 
322
            if record.storage_kind == 'absent':
 
323
                raise errors.RevisionNotPresent(record.key, self.vf)
 
324
            self._process_one_record(record.key,
 
325
                                     record.get_bytes_as('chunked'))
 
326
        
 
327
    def compute_diffs(self):
 
328
        self._extract_diffs()
 
329
        dpop = self.diffs.pop
 
330
        return [dpop(k) for k in self.ordered_keys]
 
331
 
 
332
 
209
333
class VersionedFile(object):
210
334
    """Versioned text file storage.
211
335
 
348
472
 
349
473
    def make_mpdiffs(self, version_ids):
350
474
        """Create multiparent diffs for specified versions."""
 
475
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
476
        #      is a list of strings, not keys. And while self.get_record_stream
 
477
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
478
        #      strings... *sigh*
351
479
        knit_versions = set()
352
480
        knit_versions.update(version_ids)
353
481
        parent_map = self.get_parent_map(version_ids)
692
820
 
693
821
    def map(self, key):
694
822
        """See KeyMapper.map()."""
695
 
        return urllib.quote(self._map(key))
 
823
        return urlutils.quote(self._map(key))
696
824
 
697
825
    def unmap(self, partition_id):
698
826
        """See KeyMapper.unmap()."""
699
 
        return self._unmap(urllib.unquote(partition_id))
 
827
        return self._unmap(urlutils.unquote(partition_id))
700
828
 
701
829
 
702
830
class PrefixMapper(URLEscapeMapper):
749
877
    def _escape(self, prefix):
750
878
        """Turn a key element into a filesystem safe string.
751
879
 
752
 
        This is similar to a plain urllib.quote, except
 
880
        This is similar to a plain urlutils.quote, except
753
881
        it uses specific safe characters, so that it doesn't
754
882
        have to translate a lot of valid file ids.
755
883
        """
762
890
 
763
891
    def _unescape(self, basename):
764
892
        """Escaped names are easily unescaped by urlutils."""
765
 
        return urllib.unquote(basename)
 
893
        return urlutils.unquote(basename)
766
894
 
767
895
 
768
896
def make_versioned_files_factory(versioned_file_factory, mapper):
795
923
 
796
924
    The use of tuples allows a single code base to support several different
797
925
    uses with only the mapping logic changing from instance to instance.
 
926
 
 
927
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
 
928
        this is a list of other VersionedFiles immediately underneath this
 
929
        one.  They may in turn each have further fallbacks.
798
930
    """
799
931
 
800
932
    def add_lines(self, key, parents, lines, parent_texts=None,
839
971
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
840
972
        """Add a text to the store.
841
973
 
842
 
        This is a private function for use by CommitBuilder.
 
974
        This is a private function for use by VersionedFileCommitBuilder.
843
975
 
844
976
        :param key: The key tuple of the text to add. If the last element is
845
977
            None, a CHK string will be generated during the addition.
1047
1179
 
1048
1180
    def make_mpdiffs(self, keys):
1049
1181
        """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
 
1182
        generator = _MPDiffGenerator(self, keys)
 
1183
        return generator.compute_diffs()
 
1184
 
 
1185
    def get_annotator(self):
 
1186
        return annotate.Annotator(self)
1087
1187
 
1088
1188
    missing_keys = index._missing_keys_from_parent_map
1089
1189
 
1090
1190
    def _extract_blocks(self, version_id, source, target):
1091
1191
        return None
1092
1192
 
 
1193
    def _transitive_fallbacks(self):
 
1194
        """Return the whole stack of fallback versionedfiles.
 
1195
 
 
1196
        This VersionedFiles may have a list of fallbacks, but it doesn't
 
1197
        necessarily know about the whole stack going down, and it can't know
 
1198
        at open time because they may change after the objects are opened.
 
1199
        """
 
1200
        all_fallbacks = []
 
1201
        for a_vfs in self._immediate_fallback_vfs:
 
1202
            all_fallbacks.append(a_vfs)
 
1203
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
 
1204
        return all_fallbacks
 
1205
 
1093
1206
 
1094
1207
class ThunkedVersionedFiles(VersionedFiles):
1095
1208
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1159
1272
            result.append((prefix + (origin,), line))
1160
1273
        return result
1161
1274
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
1275
    def check(self, progress_bar=None, keys=None):
1166
1276
        """See VersionedFiles.check()."""
1167
1277
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1311
1421
        return result
1312
1422
 
1313
1423
 
 
1424
class VersionedFilesWithFallbacks(VersionedFiles):
 
1425
 
 
1426
    def without_fallbacks(self):
 
1427
        """Return a clone of this object without any fallbacks configured."""
 
1428
        raise NotImplementedError(self.without_fallbacks)
 
1429
 
 
1430
    def add_fallback_versioned_files(self, a_versioned_files):
 
1431
        """Add a source of texts for texts not present in this knit.
 
1432
 
 
1433
        :param a_versioned_files: A VersionedFiles object.
 
1434
        """
 
1435
        raise NotImplementedError(self.add_fallback_versioned_files)
 
1436
 
 
1437
    def get_known_graph_ancestry(self, keys):
 
1438
        """Get a KnownGraph instance with the ancestry of keys."""
 
1439
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1440
        for fallback in self._transitive_fallbacks():
 
1441
            if not missing_keys:
 
1442
                break
 
1443
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1444
                                                missing_keys)
 
1445
            parent_map.update(f_parent_map)
 
1446
            missing_keys = f_missing_keys
 
1447
        kg = _mod_graph.KnownGraph(parent_map)
 
1448
        return kg
 
1449
 
 
1450
 
1314
1451
class _PlanMergeVersionedFile(VersionedFiles):
1315
1452
    """A VersionedFile for uncommitted and committed texts.
1316
1453
 
1337
1474
        # line data for locally held keys.
1338
1475
        self._lines = {}
1339
1476
        # key lookup providers
1340
 
        self._providers = [DictParentsProvider(self._parents)]
 
1477
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
1341
1478
 
1342
1479
    def plan_merge(self, ver_a, ver_b, base=None):
1343
1480
        """See VersionedFile.plan_merge"""
1350
1487
 
1351
1488
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1352
1489
        from bzrlib.merge import _PlanLCAMerge
1353
 
        graph = Graph(self)
 
1490
        graph = _mod_graph.Graph(self)
1354
1491
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1355
1492
        if base is None:
1356
1493
            return new_plan
1408
1545
            result[revision.NULL_REVISION] = ()
1409
1546
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1410
1547
        result.update(
1411
 
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1548
            _mod_graph.StackedParentsProvider(
 
1549
                self._providers).get_parent_map(keys))
1412
1550
        for key, parents in result.iteritems():
1413
1551
            if parents == ():
1414
1552
                result[key] = (revision.NULL_REVISION,)
1624
1762
                yield (l, key)
1625
1763
 
1626
1764
 
 
1765
class NoDupeAddLinesDecorator(object):
 
1766
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1767
    is already present.
 
1768
    """
 
1769
 
 
1770
    def __init__(self, store):
 
1771
        self._store = store
 
1772
 
 
1773
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1774
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1775
            check_content=True):
 
1776
        """See VersionedFiles.add_lines.
 
1777
        
 
1778
        This implementation may return None as the third element of the return
 
1779
        value when the original store wouldn't.
 
1780
        """
 
1781
        if nostore_sha:
 
1782
            raise NotImplementedError(
 
1783
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1784
                "nostore_sha behaviour.")
 
1785
        if key[-1] is None:
 
1786
            sha1 = osutils.sha_strings(lines)
 
1787
            key = ("sha1:" + sha1,)
 
1788
        else:
 
1789
            sha1 = None
 
1790
        if key in self._store.get_parent_map([key]):
 
1791
            # This key has already been inserted, so don't do it again.
 
1792
            if sha1 is None:
 
1793
                sha1 = osutils.sha_strings(lines)
 
1794
            return sha1, sum(map(len, lines)), None
 
1795
        return self._store.add_lines(key, parents, lines,
 
1796
                parent_texts=parent_texts,
 
1797
                left_matching_blocks=left_matching_blocks,
 
1798
                nostore_sha=nostore_sha, random_id=random_id,
 
1799
                check_content=check_content)
 
1800
 
 
1801
    def __getattr__(self, name):
 
1802
        return getattr(self._store, name)
 
1803
 
 
1804
 
1627
1805
def network_bytes_to_kind_and_offset(network_bytes):
1628
1806
    """Strip of a record kind from the front of network_bytes.
1629
1807
 
1720
1898
    for prefix in sorted(per_prefix_map):
1721
1899
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1722
1900
    return present_keys
 
1901
 
 
1902
 
 
1903
class _KeyRefs(object):
 
1904
 
 
1905
    def __init__(self, track_new_keys=False):
 
1906
        # dict mapping 'key' to 'set of keys referring to that key'
 
1907
        self.refs = {}
 
1908
        if track_new_keys:
 
1909
            # set remembering all new keys
 
1910
            self.new_keys = set()
 
1911
        else:
 
1912
            self.new_keys = None
 
1913
 
 
1914
    def clear(self):
 
1915
        if self.refs:
 
1916
            self.refs.clear()
 
1917
        if self.new_keys:
 
1918
            self.new_keys.clear()
 
1919
 
 
1920
    def add_references(self, key, refs):
 
1921
        # Record the new references
 
1922
        for referenced in refs:
 
1923
            try:
 
1924
                needed_by = self.refs[referenced]
 
1925
            except KeyError:
 
1926
                needed_by = self.refs[referenced] = set()
 
1927
            needed_by.add(key)
 
1928
        # Discard references satisfied by the new key
 
1929
        self.add_key(key)
 
1930
 
 
1931
    def get_new_keys(self):
 
1932
        return self.new_keys
 
1933
    
 
1934
    def get_unsatisfied_refs(self):
 
1935
        return self.refs.iterkeys()
 
1936
 
 
1937
    def _satisfy_refs_for_key(self, key):
 
1938
        try:
 
1939
            del self.refs[key]
 
1940
        except KeyError:
 
1941
            # No keys depended on this key.  That's ok.
 
1942
            pass
 
1943
 
 
1944
    def add_key(self, key):
 
1945
        # satisfy refs for key, and remember that we've seen this key.
 
1946
        self._satisfy_refs_for_key(key)
 
1947
        if self.new_keys is not None:
 
1948
            self.new_keys.add(key)
 
1949
 
 
1950
    def satisfy_refs_for_keys(self, keys):
 
1951
        for key in keys:
 
1952
            self._satisfy_refs_for_key(key)
 
1953
 
 
1954
    def get_referrers(self):
 
1955
        result = set()
 
1956
        for referrers in self.refs.itervalues():
 
1957
            result.update(referrers)
 
1958
        return result
 
1959
 
 
1960
 
 
1961