/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 breezy/versionedfile.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2017-06-02 11:26:27 UTC
  • mfrom: (6621.27.5 1089352-sni-support)
  • Revision ID: breezy.the.bot@gmail.com-20170602112627-jbvjcm9czx7gt3gb
Add SNI support.

Merged from https://code.launchpad.net/~jelmer/brz/sni-support/+merge/324979

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
19
16
 
20
17
"""Versioned text file storage api."""
21
18
 
 
19
from __future__ import absolute_import
 
20
 
22
21
from copy import copy
23
 
from cStringIO import StringIO
 
22
import itertools
24
23
import os
25
24
import struct
26
25
from zlib import adler32
27
26
 
28
 
from bzrlib.lazy_import import lazy_import
 
27
from .lazy_import import lazy_import
29
28
lazy_import(globals(), """
30
 
import urllib
31
 
 
32
 
from bzrlib import (
 
29
from breezy 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,
 
41
    urlutils,
44
42
    )
45
 
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
46
 
from bzrlib.transport.memory import MemoryTransport
47
43
""")
48
 
from bzrlib.registry import Registry
49
 
from bzrlib.symbol_versioning import *
50
 
from bzrlib.textmerge import TextMerge
51
 
from bzrlib import bencode
 
44
from .registry import Registry
 
45
from .sixish import (
 
46
    BytesIO,
 
47
    zip,
 
48
    )
 
49
from .textmerge import TextMerge
52
50
 
53
51
 
54
52
adapter_registry = Registry()
55
 
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'bzrlib.knit',
 
53
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'breezy.knit',
56
54
    'DeltaPlainToFullText')
57
 
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'bzrlib.knit',
 
55
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'breezy.knit',
58
56
    'FTPlainToFullText')
59
57
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
60
 
    'bzrlib.knit', 'DeltaAnnotatedToUnannotated')
 
58
    'breezy.knit', 'DeltaAnnotatedToUnannotated')
61
59
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
62
 
    'bzrlib.knit', 'DeltaAnnotatedToFullText')
 
60
    'breezy.knit', 'DeltaAnnotatedToFullText')
63
61
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
64
 
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
 
62
    'breezy.knit', 'FTAnnotatedToUnannotated')
65
63
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
66
 
    'bzrlib.knit', 'FTAnnotatedToFullText')
 
64
    'breezy.knit', 'FTAnnotatedToFullText')
67
65
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
68
 
#     'bzrlib.knit', 'FTAnnotatedToChunked')
 
66
#     'breezy.knit', 'FTAnnotatedToChunked')
69
67
 
70
68
 
71
69
class ContentFactory(object):
206
204
            yield record
207
205
 
208
206
 
 
207
class _MPDiffGenerator(object):
 
208
    """Pull out the functionality for generating mp_diffs."""
 
209
 
 
210
    def __init__(self, vf, keys):
 
211
        self.vf = vf
 
212
        # This is the order the keys were requested in
 
213
        self.ordered_keys = tuple(keys)
 
214
        # keys + their parents, what we need to compute the diffs
 
215
        self.needed_keys = ()
 
216
        # Map from key: mp_diff
 
217
        self.diffs = {}
 
218
        # Map from key: parents_needed (may have ghosts)
 
219
        self.parent_map = {}
 
220
        # Parents that aren't present
 
221
        self.ghost_parents = ()
 
222
        # Map from parent_key => number of children for this text
 
223
        self.refcounts = {}
 
224
        # Content chunks that are cached while we still need them
 
225
        self.chunks = {}
 
226
 
 
227
    def _find_needed_keys(self):
 
228
        """Find the set of keys we need to request.
 
229
 
 
230
        This includes all the original keys passed in, and the non-ghost
 
231
        parents of those keys.
 
232
 
 
233
        :return: (needed_keys, refcounts)
 
234
            needed_keys is the set of all texts we need to extract
 
235
            refcounts is a dict of {key: num_children} letting us know when we
 
236
                no longer need to cache a given parent text
 
237
        """
 
238
        # All the keys and their parents
 
239
        needed_keys = set(self.ordered_keys)
 
240
        parent_map = self.vf.get_parent_map(needed_keys)
 
241
        self.parent_map = parent_map
 
242
        # TODO: Should we be using a different construct here? I think this
 
243
        #       uses difference_update internally, and we expect the result to
 
244
        #       be tiny
 
245
        missing_keys = needed_keys.difference(parent_map)
 
246
        if missing_keys:
 
247
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
248
        # Parents that might be missing. They are allowed to be ghosts, but we
 
249
        # should check for them
 
250
        refcounts = {}
 
251
        setdefault = refcounts.setdefault
 
252
        just_parents = set()
 
253
        for child_key, parent_keys in parent_map.iteritems():
 
254
            if not parent_keys:
 
255
                # parent_keys may be None if a given VersionedFile claims to
 
256
                # not support graph operations.
 
257
                continue
 
258
            just_parents.update(parent_keys)
 
259
            needed_keys.update(parent_keys)
 
260
            for p in parent_keys:
 
261
                refcounts[p] = setdefault(p, 0) + 1
 
262
        just_parents.difference_update(parent_map)
 
263
        # Remove any parents that are actually ghosts from the needed set
 
264
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
265
        self.ghost_parents = just_parents.difference(self.present_parents)
 
266
        needed_keys.difference_update(self.ghost_parents)
 
267
        self.needed_keys = needed_keys
 
268
        self.refcounts = refcounts
 
269
        return needed_keys, refcounts
 
270
 
 
271
    def _compute_diff(self, key, parent_lines, lines):
 
272
        """Compute a single mp_diff, and store it in self._diffs"""
 
273
        if len(parent_lines) > 0:
 
274
            # XXX: _extract_blocks is not usefully defined anywhere...
 
275
            #      It was meant to extract the left-parent diff without
 
276
            #      having to recompute it for Knit content (pack-0.92,
 
277
            #      etc). That seems to have regressed somewhere
 
278
            left_parent_blocks = self.vf._extract_blocks(key,
 
279
                parent_lines[0], lines)
 
280
        else:
 
281
            left_parent_blocks = None
 
282
        diff = multiparent.MultiParent.from_lines(lines,
 
283
                    parent_lines, left_parent_blocks)
 
284
        self.diffs[key] = diff
 
285
 
 
286
    def _process_one_record(self, key, this_chunks):
 
287
        parent_keys = None
 
288
        if key in self.parent_map:
 
289
            # This record should be ready to diff, since we requested
 
290
            # content in 'topological' order
 
291
            parent_keys = self.parent_map.pop(key)
 
292
            # If a VersionedFile claims 'no-graph' support, then it may return
 
293
            # None for any parent request, so we replace it with an empty tuple
 
294
            if parent_keys is None:
 
295
                parent_keys = ()
 
296
            parent_lines = []
 
297
            for p in parent_keys:
 
298
                # Alternatively we could check p not in self.needed_keys, but
 
299
                # ghost_parents should be tiny versus huge
 
300
                if p in self.ghost_parents:
 
301
                    continue
 
302
                refcount = self.refcounts[p]
 
303
                if refcount == 1: # Last child reference
 
304
                    self.refcounts.pop(p)
 
305
                    parent_chunks = self.chunks.pop(p)
 
306
                else:
 
307
                    self.refcounts[p] = refcount - 1
 
308
                    parent_chunks = self.chunks[p]
 
309
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
310
                # TODO: Should we cache the line form? We did the
 
311
                #       computation to get it, but storing it this way will
 
312
                #       be less memory efficient...
 
313
                parent_lines.append(p_lines)
 
314
                del p_lines
 
315
            lines = osutils.chunks_to_lines(this_chunks)
 
316
            # Since we needed the lines, we'll go ahead and cache them this way
 
317
            this_chunks = lines
 
318
            self._compute_diff(key, parent_lines, lines)
 
319
            del lines
 
320
        # Is this content required for any more children?
 
321
        if key in self.refcounts:
 
322
            self.chunks[key] = this_chunks
 
323
 
 
324
    def _extract_diffs(self):
 
325
        needed_keys, refcounts = self._find_needed_keys()
 
326
        for record in self.vf.get_record_stream(needed_keys,
 
327
                                                'topological', True):
 
328
            if record.storage_kind == 'absent':
 
329
                raise errors.RevisionNotPresent(record.key, self.vf)
 
330
            self._process_one_record(record.key,
 
331
                                     record.get_bytes_as('chunked'))
 
332
        
 
333
    def compute_diffs(self):
 
334
        self._extract_diffs()
 
335
        dpop = self.diffs.pop
 
336
        return [dpop(k) for k in self.ordered_keys]
 
337
 
 
338
 
209
339
class VersionedFile(object):
210
340
    """Versioned text file storage.
211
341
 
348
478
 
349
479
    def make_mpdiffs(self, version_ids):
350
480
        """Create multiparent diffs for specified versions."""
 
481
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
482
        #      is a list of strings, not keys. And while self.get_record_stream
 
483
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
484
        #      strings... *sigh*
351
485
        knit_versions = set()
352
486
        knit_versions.update(version_ids)
353
487
        parent_map = self.get_parent_map(version_ids)
403
537
                                  if not mpvf.has_version(p))
404
538
        present_parents = set(self.get_parent_map(needed_parents).keys())
405
539
        for parent_id, lines in zip(present_parents,
406
 
                                 self._get_lf_split_line_list(present_parents)):
 
540
                self._get_lf_split_line_list(present_parents)):
407
541
            mpvf.add_version(lines, parent_id, [])
408
 
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
409
 
            zip(records, mpvf.get_line_list(versions)):
 
542
        for (version, parent_ids, expected_sha1, mpdiff), lines in zip(
 
543
                records, mpvf.get_line_list(versions)):
410
544
            if len(parent_ids) == 1:
411
545
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
412
546
                    mpvf.get_diff(parent_ids[0]).num_lines()))
454
588
        raise NotImplementedError(self.get_lines)
455
589
 
456
590
    def _get_lf_split_line_list(self, version_ids):
457
 
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
 
591
        return [BytesIO(t).readlines() for t in self.get_texts(version_ids)]
458
592
 
459
593
    def get_ancestry(self, version_ids, topo_sorted=True):
460
594
        """Return a list of all ancestors of given version(s). This
692
826
 
693
827
    def map(self, key):
694
828
        """See KeyMapper.map()."""
695
 
        return urllib.quote(self._map(key))
 
829
        return urlutils.quote(self._map(key))
696
830
 
697
831
    def unmap(self, partition_id):
698
832
        """See KeyMapper.unmap()."""
699
 
        return self._unmap(urllib.unquote(partition_id))
 
833
        return self._unmap(urlutils.unquote(partition_id))
700
834
 
701
835
 
702
836
class PrefixMapper(URLEscapeMapper):
749
883
    def _escape(self, prefix):
750
884
        """Turn a key element into a filesystem safe string.
751
885
 
752
 
        This is similar to a plain urllib.quote, except
 
886
        This is similar to a plain urlutils.quote, except
753
887
        it uses specific safe characters, so that it doesn't
754
888
        have to translate a lot of valid file ids.
755
889
        """
762
896
 
763
897
    def _unescape(self, basename):
764
898
        """Escaped names are easily unescaped by urlutils."""
765
 
        return urllib.unquote(basename)
 
899
        return urlutils.unquote(basename)
766
900
 
767
901
 
768
902
def make_versioned_files_factory(versioned_file_factory, mapper):
789
923
 
790
924
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
791
925
    may have a different length key-size, but that size will be constant for
792
 
    all texts added to or retrieved from it. For instance, bzrlib uses
 
926
    all texts added to or retrieved from it. For instance, breezy uses
793
927
    instances with a key-size of 2 for storing user files in a repository, with
794
928
    the first element the fileid, and the second the version of that file.
795
929
 
796
930
    The use of tuples allows a single code base to support several different
797
931
    uses with only the mapping logic changing from instance to instance.
 
932
 
 
933
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
 
934
        this is a list of other VersionedFiles immediately underneath this
 
935
        one.  They may in turn each have further fallbacks.
798
936
    """
799
937
 
800
938
    def add_lines(self, key, parents, lines, parent_texts=None,
839
977
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
840
978
        """Add a text to the store.
841
979
 
842
 
        This is a private function for use by CommitBuilder.
 
980
        This is a private function for use by VersionedFileCommitBuilder.
843
981
 
844
982
        :param key: The key tuple of the text to add. If the last element is
845
983
            None, a CHK string will be generated during the addition.
891
1029
                continue
892
1030
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
893
1031
                record.key, [])
894
 
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
895
 
            zip(records, mpvf.get_line_list(versions)):
 
1032
        for (key, parent_keys, expected_sha1, mpdiff), lines in zip(
 
1033
                records, mpvf.get_line_list(versions)):
896
1034
            if len(parent_keys) == 1:
897
1035
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
898
1036
                    mpvf.get_diff(parent_keys[0]).num_lines()))
956
1094
        while pending:
957
1095
            this_parent_map = self.get_parent_map(pending)
958
1096
            parent_map.update(this_parent_map)
959
 
            pending = set()
960
 
            map(pending.update, this_parent_map.itervalues())
961
 
            pending = pending.difference(parent_map)
 
1097
            pending = set(itertools.chain.from_iterable(
 
1098
                this_parent_map.itervalues()))
 
1099
            pending.difference_update(parent_map)
962
1100
        kg = _mod_graph.KnownGraph(parent_map)
963
1101
        return kg
964
1102
 
995
1133
        """
996
1134
        raise NotImplementedError(self.get_sha1s)
997
1135
 
998
 
    has_key = index._has_key_from_parent_map
 
1136
    __contains__ = index._has_key_from_parent_map
999
1137
 
1000
1138
    def get_missing_compression_parent_keys(self):
1001
1139
        """Return an iterable of keys of missing compression parents.
1047
1185
 
1048
1186
    def make_mpdiffs(self, keys):
1049
1187
        """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
 
1188
        generator = _MPDiffGenerator(self, keys)
 
1189
        return generator.compute_diffs()
 
1190
 
 
1191
    def get_annotator(self):
 
1192
        return annotate.Annotator(self)
1087
1193
 
1088
1194
    missing_keys = index._missing_keys_from_parent_map
1089
1195
 
1090
1196
    def _extract_blocks(self, version_id, source, target):
1091
1197
        return None
1092
1198
 
 
1199
    def _transitive_fallbacks(self):
 
1200
        """Return the whole stack of fallback versionedfiles.
 
1201
 
 
1202
        This VersionedFiles may have a list of fallbacks, but it doesn't
 
1203
        necessarily know about the whole stack going down, and it can't know
 
1204
        at open time because they may change after the objects are opened.
 
1205
        """
 
1206
        all_fallbacks = []
 
1207
        for a_vfs in self._immediate_fallback_vfs:
 
1208
            all_fallbacks.append(a_vfs)
 
1209
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
 
1210
        return all_fallbacks
 
1211
 
1093
1212
 
1094
1213
class ThunkedVersionedFiles(VersionedFiles):
1095
1214
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1159
1278
            result.append((prefix + (origin,), line))
1160
1279
        return result
1161
1280
 
1162
 
    def get_annotator(self):
1163
 
        return annotate.Annotator(self)
1164
 
 
1165
1281
    def check(self, progress_bar=None, keys=None):
1166
1282
        """See VersionedFiles.check()."""
1167
1283
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1204
1320
            prefix_keys.append(key[-1])
1205
1321
        return result
1206
1322
 
1207
 
    def _get_all_prefixes(self):
 
1323
    def _iter_all_prefixes(self):
1208
1324
        # Identify all key prefixes.
1209
1325
        # XXX: A bit hacky, needs polish.
1210
 
        if type(self._mapper) == ConstantMapper:
 
1326
        if isinstance(self._mapper, ConstantMapper):
1211
1327
            paths = [self._mapper.map(())]
1212
1328
            prefixes = [()]
1213
1329
        else:
1299
1415
                yield line, prefix + (version,)
1300
1416
 
1301
1417
    def _iter_all_components(self):
1302
 
        for path, prefix in self._get_all_prefixes():
 
1418
        for path, prefix in self._iter_all_prefixes():
1303
1419
            yield prefix, self._get_vf(path)
1304
1420
 
1305
1421
    def keys(self):
1311
1427
        return result
1312
1428
 
1313
1429
 
 
1430
class VersionedFilesWithFallbacks(VersionedFiles):
 
1431
 
 
1432
    def without_fallbacks(self):
 
1433
        """Return a clone of this object without any fallbacks configured."""
 
1434
        raise NotImplementedError(self.without_fallbacks)
 
1435
 
 
1436
    def add_fallback_versioned_files(self, a_versioned_files):
 
1437
        """Add a source of texts for texts not present in this knit.
 
1438
 
 
1439
        :param a_versioned_files: A VersionedFiles object.
 
1440
        """
 
1441
        raise NotImplementedError(self.add_fallback_versioned_files)
 
1442
 
 
1443
    def get_known_graph_ancestry(self, keys):
 
1444
        """Get a KnownGraph instance with the ancestry of keys."""
 
1445
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1446
        for fallback in self._transitive_fallbacks():
 
1447
            if not missing_keys:
 
1448
                break
 
1449
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1450
                                                missing_keys)
 
1451
            parent_map.update(f_parent_map)
 
1452
            missing_keys = f_missing_keys
 
1453
        kg = _mod_graph.KnownGraph(parent_map)
 
1454
        return kg
 
1455
 
 
1456
 
1314
1457
class _PlanMergeVersionedFile(VersionedFiles):
1315
1458
    """A VersionedFile for uncommitted and committed texts.
1316
1459
 
1337
1480
        # line data for locally held keys.
1338
1481
        self._lines = {}
1339
1482
        # key lookup providers
1340
 
        self._providers = [DictParentsProvider(self._parents)]
 
1483
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
1341
1484
 
1342
1485
    def plan_merge(self, ver_a, ver_b, base=None):
1343
1486
        """See VersionedFile.plan_merge"""
1344
 
        from bzrlib.merge import _PlanMerge
 
1487
        from .merge import _PlanMerge
1345
1488
        if base is None:
1346
1489
            return _PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge()
1347
1490
        old_plan = list(_PlanMerge(ver_a, base, self, (self._file_id,)).plan_merge())
1349
1492
        return _PlanMerge._subtract_plans(old_plan, new_plan)
1350
1493
 
1351
1494
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1352
 
        from bzrlib.merge import _PlanLCAMerge
1353
 
        graph = Graph(self)
 
1495
        from .merge import _PlanLCAMerge
 
1496
        graph = _mod_graph.Graph(self)
1354
1497
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1355
1498
        if base is None:
1356
1499
            return new_plan
1363
1506
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
1364
1507
        are permitted.  Only reserved ids are permitted.
1365
1508
        """
1366
 
        if type(key) is not tuple:
 
1509
        if not isinstance(key, tuple):
1367
1510
            raise TypeError(key)
1368
1511
        if not revision.is_reserved_id(key[-1]):
1369
1512
            raise ValueError('Only reserved ids may be used')
1408
1551
            result[revision.NULL_REVISION] = ()
1409
1552
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1410
1553
        result.update(
1411
 
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1554
            _mod_graph.StackedParentsProvider(
 
1555
                self._providers).get_parent_map(keys))
1412
1556
        for key, parents in result.iteritems():
1413
1557
            if parents == ():
1414
1558
                result[key] = (revision.NULL_REVISION,)
1624
1768
                yield (l, key)
1625
1769
 
1626
1770
 
 
1771
class NoDupeAddLinesDecorator(object):
 
1772
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1773
    is already present.
 
1774
    """
 
1775
 
 
1776
    def __init__(self, store):
 
1777
        self._store = store
 
1778
 
 
1779
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1780
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1781
            check_content=True):
 
1782
        """See VersionedFiles.add_lines.
 
1783
        
 
1784
        This implementation may return None as the third element of the return
 
1785
        value when the original store wouldn't.
 
1786
        """
 
1787
        if nostore_sha:
 
1788
            raise NotImplementedError(
 
1789
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1790
                "nostore_sha behaviour.")
 
1791
        if key[-1] is None:
 
1792
            sha1 = osutils.sha_strings(lines)
 
1793
            key = ("sha1:" + sha1,)
 
1794
        else:
 
1795
            sha1 = None
 
1796
        if key in self._store.get_parent_map([key]):
 
1797
            # This key has already been inserted, so don't do it again.
 
1798
            if sha1 is None:
 
1799
                sha1 = osutils.sha_strings(lines)
 
1800
            return sha1, sum(map(len, lines)), None
 
1801
        return self._store.add_lines(key, parents, lines,
 
1802
                parent_texts=parent_texts,
 
1803
                left_matching_blocks=left_matching_blocks,
 
1804
                nostore_sha=nostore_sha, random_id=random_id,
 
1805
                check_content=check_content)
 
1806
 
 
1807
    def __getattr__(self, name):
 
1808
        return getattr(self._store, name)
 
1809
 
 
1810
 
1627
1811
def network_bytes_to_kind_and_offset(network_bytes):
1628
1812
    """Strip of a record kind from the front of network_bytes.
1629
1813
 
1720
1904
    for prefix in sorted(per_prefix_map):
1721
1905
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1722
1906
    return present_keys
 
1907
 
 
1908
 
 
1909
class _KeyRefs(object):
 
1910
 
 
1911
    def __init__(self, track_new_keys=False):
 
1912
        # dict mapping 'key' to 'set of keys referring to that key'
 
1913
        self.refs = {}
 
1914
        if track_new_keys:
 
1915
            # set remembering all new keys
 
1916
            self.new_keys = set()
 
1917
        else:
 
1918
            self.new_keys = None
 
1919
 
 
1920
    def clear(self):
 
1921
        if self.refs:
 
1922
            self.refs.clear()
 
1923
        if self.new_keys:
 
1924
            self.new_keys.clear()
 
1925
 
 
1926
    def add_references(self, key, refs):
 
1927
        # Record the new references
 
1928
        for referenced in refs:
 
1929
            try:
 
1930
                needed_by = self.refs[referenced]
 
1931
            except KeyError:
 
1932
                needed_by = self.refs[referenced] = set()
 
1933
            needed_by.add(key)
 
1934
        # Discard references satisfied by the new key
 
1935
        self.add_key(key)
 
1936
 
 
1937
    def get_new_keys(self):
 
1938
        return self.new_keys
 
1939
    
 
1940
    def get_unsatisfied_refs(self):
 
1941
        return self.refs.iterkeys()
 
1942
 
 
1943
    def _satisfy_refs_for_key(self, key):
 
1944
        try:
 
1945
            del self.refs[key]
 
1946
        except KeyError:
 
1947
            # No keys depended on this key.  That's ok.
 
1948
            pass
 
1949
 
 
1950
    def add_key(self, key):
 
1951
        # satisfy refs for key, and remember that we've seen this key.
 
1952
        self._satisfy_refs_for_key(key)
 
1953
        if self.new_keys is not None:
 
1954
            self.new_keys.add(key)
 
1955
 
 
1956
    def satisfy_refs_for_keys(self, keys):
 
1957
        for key in keys:
 
1958
            self._satisfy_refs_for_key(key)
 
1959
 
 
1960
    def get_referrers(self):
 
1961
        result = set()
 
1962
        for referrers in self.refs.itervalues():
 
1963
            result.update(referrers)
 
1964
        return result
 
1965
 
 
1966
 
 
1967