/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: Martin
  • Date: 2017-05-24 20:47:06 UTC
  • mto: This revision was merged to the branch mainline in revision 6635.
  • Revision ID: gzlist@googlemail.com-20170524204706-jvole7jqnplsj8ts
Rename report_delta param from filter to predicate with tests and release notes

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