~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Patch Queue Manager
  • Date: 2015-10-05 13:45:00 UTC
  • mfrom: (6603.3.1 bts794146)
  • Revision ID: pqm@pqm.ubuntu.com-20151005134500-v244rho557tv0ukd
(vila) Resolve Bug #1480015: Test failure: hexify removed from paramiko
 (Andrew Starr-Bochicchio) (Andrew Starr-Bochicchio)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 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
22
from cStringIO import StringIO
24
23
import os
27
26
 
28
27
from bzrlib.lazy_import import lazy_import
29
28
lazy_import(globals(), """
30
 
import urllib
31
 
 
32
29
from bzrlib import (
 
30
    annotate,
 
31
    bencode,
33
32
    errors,
 
33
    graph as _mod_graph,
34
34
    groupcompress,
35
35
    index,
36
36
    knit,
38
38
    multiparent,
39
39
    tsort,
40
40
    revision,
41
 
    ui,
 
41
    urlutils,
42
42
    )
43
 
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
44
 
from bzrlib.transport.memory import MemoryTransport
45
43
""")
46
 
from bzrlib.inter import InterObject
47
44
from bzrlib.registry import Registry
48
 
from bzrlib.symbol_versioning import *
49
45
from bzrlib.textmerge import TextMerge
50
 
from bzrlib.util import bencode
51
46
 
52
47
 
53
48
adapter_registry = Registry()
174
169
        self.key = key
175
170
        self.parents = None
176
171
 
 
172
    def get_bytes_as(self, storage_kind):
 
173
        raise ValueError('A request was made for key: %s, but that'
 
174
                         ' content is not available, and the calling'
 
175
                         ' code does not handle if it is missing.'
 
176
                         % (self.key,))
 
177
 
177
178
 
178
179
class AdapterFactory(ContentFactory):
179
180
    """A content factory to adapt between key prefix's."""
199
200
            yield record
200
201
 
201
202
 
 
203
class _MPDiffGenerator(object):
 
204
    """Pull out the functionality for generating mp_diffs."""
 
205
 
 
206
    def __init__(self, vf, keys):
 
207
        self.vf = vf
 
208
        # This is the order the keys were requested in
 
209
        self.ordered_keys = tuple(keys)
 
210
        # keys + their parents, what we need to compute the diffs
 
211
        self.needed_keys = ()
 
212
        # Map from key: mp_diff
 
213
        self.diffs = {}
 
214
        # Map from key: parents_needed (may have ghosts)
 
215
        self.parent_map = {}
 
216
        # Parents that aren't present
 
217
        self.ghost_parents = ()
 
218
        # Map from parent_key => number of children for this text
 
219
        self.refcounts = {}
 
220
        # Content chunks that are cached while we still need them
 
221
        self.chunks = {}
 
222
 
 
223
    def _find_needed_keys(self):
 
224
        """Find the set of keys we need to request.
 
225
 
 
226
        This includes all the original keys passed in, and the non-ghost
 
227
        parents of those keys.
 
228
 
 
229
        :return: (needed_keys, refcounts)
 
230
            needed_keys is the set of all texts we need to extract
 
231
            refcounts is a dict of {key: num_children} letting us know when we
 
232
                no longer need to cache a given parent text
 
233
        """
 
234
        # All the keys and their parents
 
235
        needed_keys = set(self.ordered_keys)
 
236
        parent_map = self.vf.get_parent_map(needed_keys)
 
237
        self.parent_map = parent_map
 
238
        # TODO: Should we be using a different construct here? I think this
 
239
        #       uses difference_update internally, and we expect the result to
 
240
        #       be tiny
 
241
        missing_keys = needed_keys.difference(parent_map)
 
242
        if missing_keys:
 
243
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
 
244
        # Parents that might be missing. They are allowed to be ghosts, but we
 
245
        # should check for them
 
246
        refcounts = {}
 
247
        setdefault = refcounts.setdefault
 
248
        just_parents = set()
 
249
        for child_key, parent_keys in parent_map.iteritems():
 
250
            if not parent_keys:
 
251
                # parent_keys may be None if a given VersionedFile claims to
 
252
                # not support graph operations.
 
253
                continue
 
254
            just_parents.update(parent_keys)
 
255
            needed_keys.update(parent_keys)
 
256
            for p in parent_keys:
 
257
                refcounts[p] = setdefault(p, 0) + 1
 
258
        just_parents.difference_update(parent_map)
 
259
        # Remove any parents that are actually ghosts from the needed set
 
260
        self.present_parents = set(self.vf.get_parent_map(just_parents))
 
261
        self.ghost_parents = just_parents.difference(self.present_parents)
 
262
        needed_keys.difference_update(self.ghost_parents)
 
263
        self.needed_keys = needed_keys
 
264
        self.refcounts = refcounts
 
265
        return needed_keys, refcounts
 
266
 
 
267
    def _compute_diff(self, key, parent_lines, lines):
 
268
        """Compute a single mp_diff, and store it in self._diffs"""
 
269
        if len(parent_lines) > 0:
 
270
            # XXX: _extract_blocks is not usefully defined anywhere...
 
271
            #      It was meant to extract the left-parent diff without
 
272
            #      having to recompute it for Knit content (pack-0.92,
 
273
            #      etc). That seems to have regressed somewhere
 
274
            left_parent_blocks = self.vf._extract_blocks(key,
 
275
                parent_lines[0], lines)
 
276
        else:
 
277
            left_parent_blocks = None
 
278
        diff = multiparent.MultiParent.from_lines(lines,
 
279
                    parent_lines, left_parent_blocks)
 
280
        self.diffs[key] = diff
 
281
 
 
282
    def _process_one_record(self, key, this_chunks):
 
283
        parent_keys = None
 
284
        if key in self.parent_map:
 
285
            # This record should be ready to diff, since we requested
 
286
            # content in 'topological' order
 
287
            parent_keys = self.parent_map.pop(key)
 
288
            # If a VersionedFile claims 'no-graph' support, then it may return
 
289
            # None for any parent request, so we replace it with an empty tuple
 
290
            if parent_keys is None:
 
291
                parent_keys = ()
 
292
            parent_lines = []
 
293
            for p in parent_keys:
 
294
                # Alternatively we could check p not in self.needed_keys, but
 
295
                # ghost_parents should be tiny versus huge
 
296
                if p in self.ghost_parents:
 
297
                    continue
 
298
                refcount = self.refcounts[p]
 
299
                if refcount == 1: # Last child reference
 
300
                    self.refcounts.pop(p)
 
301
                    parent_chunks = self.chunks.pop(p)
 
302
                else:
 
303
                    self.refcounts[p] = refcount - 1
 
304
                    parent_chunks = self.chunks[p]
 
305
                p_lines = osutils.chunks_to_lines(parent_chunks)
 
306
                # TODO: Should we cache the line form? We did the
 
307
                #       computation to get it, but storing it this way will
 
308
                #       be less memory efficient...
 
309
                parent_lines.append(p_lines)
 
310
                del p_lines
 
311
            lines = osutils.chunks_to_lines(this_chunks)
 
312
            # Since we needed the lines, we'll go ahead and cache them this way
 
313
            this_chunks = lines
 
314
            self._compute_diff(key, parent_lines, lines)
 
315
            del lines
 
316
        # Is this content required for any more children?
 
317
        if key in self.refcounts:
 
318
            self.chunks[key] = this_chunks
 
319
 
 
320
    def _extract_diffs(self):
 
321
        needed_keys, refcounts = self._find_needed_keys()
 
322
        for record in self.vf.get_record_stream(needed_keys,
 
323
                                                'topological', True):
 
324
            if record.storage_kind == 'absent':
 
325
                raise errors.RevisionNotPresent(record.key, self.vf)
 
326
            self._process_one_record(record.key,
 
327
                                     record.get_bytes_as('chunked'))
 
328
        
 
329
    def compute_diffs(self):
 
330
        self._extract_diffs()
 
331
        dpop = self.diffs.pop
 
332
        return [dpop(k) for k in self.ordered_keys]
 
333
 
 
334
 
202
335
class VersionedFile(object):
203
336
    """Versioned text file storage.
204
337
 
341
474
 
342
475
    def make_mpdiffs(self, version_ids):
343
476
        """Create multiparent diffs for specified versions."""
 
477
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
 
478
        #      is a list of strings, not keys. And while self.get_record_stream
 
479
        #      is supported, it takes *keys*, while self.get_parent_map() takes
 
480
        #      strings... *sigh*
344
481
        knit_versions = set()
345
482
        knit_versions.update(version_ids)
346
483
        parent_map = self.get_parent_map(version_ids)
685
822
 
686
823
    def map(self, key):
687
824
        """See KeyMapper.map()."""
688
 
        return urllib.quote(self._map(key))
 
825
        return urlutils.quote(self._map(key))
689
826
 
690
827
    def unmap(self, partition_id):
691
828
        """See KeyMapper.unmap()."""
692
 
        return self._unmap(urllib.unquote(partition_id))
 
829
        return self._unmap(urlutils.unquote(partition_id))
693
830
 
694
831
 
695
832
class PrefixMapper(URLEscapeMapper):
742
879
    def _escape(self, prefix):
743
880
        """Turn a key element into a filesystem safe string.
744
881
 
745
 
        This is similar to a plain urllib.quote, except
 
882
        This is similar to a plain urlutils.quote, except
746
883
        it uses specific safe characters, so that it doesn't
747
884
        have to translate a lot of valid file ids.
748
885
        """
755
892
 
756
893
    def _unescape(self, basename):
757
894
        """Escaped names are easily unescaped by urlutils."""
758
 
        return urllib.unquote(basename)
 
895
        return urlutils.unquote(basename)
759
896
 
760
897
 
761
898
def make_versioned_files_factory(versioned_file_factory, mapper):
788
925
 
789
926
    The use of tuples allows a single code base to support several different
790
927
    uses with only the mapping logic changing from instance to instance.
 
928
 
 
929
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
 
930
        this is a list of other VersionedFiles immediately underneath this
 
931
        one.  They may in turn each have further fallbacks.
791
932
    """
792
933
 
793
934
    def add_lines(self, key, parents, lines, parent_texts=None,
829
970
        """
830
971
        raise NotImplementedError(self.add_lines)
831
972
 
 
973
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
974
        """Add a text to the store.
 
975
 
 
976
        This is a private function for use by VersionedFileCommitBuilder.
 
977
 
 
978
        :param key: The key tuple of the text to add. If the last element is
 
979
            None, a CHK string will be generated during the addition.
 
980
        :param parents: The parents key tuples of the text to add.
 
981
        :param text: A string containing the text to be committed.
 
982
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
983
            the versioned file if the digest of the lines matches this.
 
984
        :param random_id: If True a random id has been selected rather than
 
985
            an id determined by some deterministic process such as a converter
 
986
            from a foreign VCS. When True the backend may choose not to check
 
987
            for uniqueness of the resulting key within the versioned file, so
 
988
            this should only be done when the result is expected to be unique
 
989
            anyway.
 
990
        :param check_content: If True, the lines supplied are verified to be
 
991
            bytestrings that are correctly formed lines.
 
992
        :return: The text sha1, the number of bytes in the text, and an opaque
 
993
                 representation of the inserted version which can be provided
 
994
                 back to future _add_text calls in the parent_texts dictionary.
 
995
        """
 
996
        # The default implementation just thunks over to .add_lines(),
 
997
        # inefficient, but it works.
 
998
        return self.add_lines(key, parents, osutils.split_lines(text),
 
999
                              nostore_sha=nostore_sha,
 
1000
                              random_id=random_id,
 
1001
                              check_content=True)
 
1002
 
832
1003
    def add_mpdiffs(self, records):
833
1004
        """Add mpdiffs to this VersionedFile.
834
1005
 
876
1047
        raise NotImplementedError(self.annotate)
877
1048
 
878
1049
    def check(self, progress_bar=None):
879
 
        """Check this object for integrity."""
 
1050
        """Check this object for integrity.
 
1051
        
 
1052
        :param progress_bar: A progress bar to output as the check progresses.
 
1053
        :param keys: Specific keys within the VersionedFiles to check. When
 
1054
            this parameter is not None, check() becomes a generator as per
 
1055
            get_record_stream. The difference to get_record_stream is that
 
1056
            more or deeper checks will be performed.
 
1057
        :return: None, or if keys was supplied a generator as per
 
1058
            get_record_stream.
 
1059
        """
880
1060
        raise NotImplementedError(self.check)
881
1061
 
882
1062
    @staticmethod
883
1063
    def check_not_reserved_id(version_id):
884
1064
        revision.check_not_reserved_id(version_id)
885
1065
 
 
1066
    def clear_cache(self):
 
1067
        """Clear whatever caches this VersionedFile holds.
 
1068
 
 
1069
        This is generally called after an operation has been performed, when we
 
1070
        don't expect to be using this versioned file again soon.
 
1071
        """
 
1072
 
886
1073
    def _check_lines_not_unicode(self, lines):
887
1074
        """Check that lines being added to a versioned file are not unicode."""
888
1075
        for line in lines:
895
1082
            if '\n' in line[:-1]:
896
1083
                raise errors.BzrBadParameterContainsNewline("lines")
897
1084
 
 
1085
    def get_known_graph_ancestry(self, keys):
 
1086
        """Get a KnownGraph instance with the ancestry of keys."""
 
1087
        # most basic implementation is a loop around get_parent_map
 
1088
        pending = set(keys)
 
1089
        parent_map = {}
 
1090
        while pending:
 
1091
            this_parent_map = self.get_parent_map(pending)
 
1092
            parent_map.update(this_parent_map)
 
1093
            pending = set()
 
1094
            map(pending.update, this_parent_map.itervalues())
 
1095
            pending = pending.difference(parent_map)
 
1096
        kg = _mod_graph.KnownGraph(parent_map)
 
1097
        return kg
 
1098
 
898
1099
    def get_parent_map(self, keys):
899
1100
        """Get a map of the parents of keys.
900
1101
 
980
1181
 
981
1182
    def make_mpdiffs(self, keys):
982
1183
        """Create multiparent diffs for specified keys."""
983
 
        keys_order = tuple(keys)
984
 
        keys = frozenset(keys)
985
 
        knit_keys = set(keys)
986
 
        parent_map = self.get_parent_map(keys)
987
 
        for parent_keys in parent_map.itervalues():
988
 
            if parent_keys:
989
 
                knit_keys.update(parent_keys)
990
 
        missing_keys = keys - set(parent_map)
991
 
        if missing_keys:
992
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
993
 
        # We need to filter out ghosts, because we can't diff against them.
994
 
        maybe_ghosts = knit_keys - keys
995
 
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
996
 
        knit_keys.difference_update(ghosts)
997
 
        lines = {}
998
 
        chunks_to_lines = osutils.chunks_to_lines
999
 
        for record in self.get_record_stream(knit_keys, 'topological', True):
1000
 
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
1001
 
            # line_block_dict = {}
1002
 
            # for parent, blocks in record.extract_line_blocks():
1003
 
            #   line_blocks[parent] = blocks
1004
 
            # line_blocks[record.key] = line_block_dict
1005
 
        diffs = []
1006
 
        for key in keys_order:
1007
 
            target = lines[key]
1008
 
            parents = parent_map[key] or []
1009
 
            # Note that filtering knit_keys can lead to a parent difference
1010
 
            # between the creation and the application of the mpdiff.
1011
 
            parent_lines = [lines[p] for p in parents if p in knit_keys]
1012
 
            if len(parent_lines) > 0:
1013
 
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
1014
 
                    target)
1015
 
            else:
1016
 
                left_parent_blocks = None
1017
 
            diffs.append(multiparent.MultiParent.from_lines(target,
1018
 
                parent_lines, left_parent_blocks))
1019
 
        return diffs
 
1184
        generator = _MPDiffGenerator(self, keys)
 
1185
        return generator.compute_diffs()
 
1186
 
 
1187
    def get_annotator(self):
 
1188
        return annotate.Annotator(self)
1020
1189
 
1021
1190
    missing_keys = index._missing_keys_from_parent_map
1022
1191
 
1023
1192
    def _extract_blocks(self, version_id, source, target):
1024
1193
        return None
1025
1194
 
 
1195
    def _transitive_fallbacks(self):
 
1196
        """Return the whole stack of fallback versionedfiles.
 
1197
 
 
1198
        This VersionedFiles may have a list of fallbacks, but it doesn't
 
1199
        necessarily know about the whole stack going down, and it can't know
 
1200
        at open time because they may change after the objects are opened.
 
1201
        """
 
1202
        all_fallbacks = []
 
1203
        for a_vfs in self._immediate_fallback_vfs:
 
1204
            all_fallbacks.append(a_vfs)
 
1205
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
 
1206
        return all_fallbacks
 
1207
 
1026
1208
 
1027
1209
class ThunkedVersionedFiles(VersionedFiles):
1028
1210
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1092
1274
            result.append((prefix + (origin,), line))
1093
1275
        return result
1094
1276
 
1095
 
    def check(self, progress_bar=None):
 
1277
    def check(self, progress_bar=None, keys=None):
1096
1278
        """See VersionedFiles.check()."""
 
1279
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
 
1280
        # this is tolerable. Ideally we'd pass keys down to check() and 
 
1281
        # have the older VersiondFile interface updated too.
1097
1282
        for prefix, vf in self._iter_all_components():
1098
1283
            vf.check()
 
1284
        if keys is not None:
 
1285
            return self.get_record_stream(keys, 'unordered', True)
1099
1286
 
1100
1287
    def get_parent_map(self, keys):
1101
1288
        """Get a map of the parents of keys.
1236
1423
        return result
1237
1424
 
1238
1425
 
 
1426
class VersionedFilesWithFallbacks(VersionedFiles):
 
1427
 
 
1428
    def without_fallbacks(self):
 
1429
        """Return a clone of this object without any fallbacks configured."""
 
1430
        raise NotImplementedError(self.without_fallbacks)
 
1431
 
 
1432
    def add_fallback_versioned_files(self, a_versioned_files):
 
1433
        """Add a source of texts for texts not present in this knit.
 
1434
 
 
1435
        :param a_versioned_files: A VersionedFiles object.
 
1436
        """
 
1437
        raise NotImplementedError(self.add_fallback_versioned_files)
 
1438
 
 
1439
    def get_known_graph_ancestry(self, keys):
 
1440
        """Get a KnownGraph instance with the ancestry of keys."""
 
1441
        parent_map, missing_keys = self._index.find_ancestry(keys)
 
1442
        for fallback in self._transitive_fallbacks():
 
1443
            if not missing_keys:
 
1444
                break
 
1445
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
 
1446
                                                missing_keys)
 
1447
            parent_map.update(f_parent_map)
 
1448
            missing_keys = f_missing_keys
 
1449
        kg = _mod_graph.KnownGraph(parent_map)
 
1450
        return kg
 
1451
 
 
1452
 
1239
1453
class _PlanMergeVersionedFile(VersionedFiles):
1240
1454
    """A VersionedFile for uncommitted and committed texts.
1241
1455
 
1262
1476
        # line data for locally held keys.
1263
1477
        self._lines = {}
1264
1478
        # key lookup providers
1265
 
        self._providers = [DictParentsProvider(self._parents)]
 
1479
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
1266
1480
 
1267
1481
    def plan_merge(self, ver_a, ver_b, base=None):
1268
1482
        """See VersionedFile.plan_merge"""
1275
1489
 
1276
1490
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1277
1491
        from bzrlib.merge import _PlanLCAMerge
1278
 
        graph = Graph(self)
 
1492
        graph = _mod_graph.Graph(self)
1279
1493
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1280
1494
        if base is None:
1281
1495
            return new_plan
1333
1547
            result[revision.NULL_REVISION] = ()
1334
1548
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1335
1549
        result.update(
1336
 
            _StackedParentsProvider(self._providers).get_parent_map(keys))
 
1550
            _mod_graph.StackedParentsProvider(
 
1551
                self._providers).get_parent_map(keys))
1337
1552
        for key, parents in result.iteritems():
1338
1553
            if parents == ():
1339
1554
                result[key] = (revision.NULL_REVISION,)
1350
1565
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1351
1566
                 b_marker=TextMerge.B_MARKER):
1352
1567
        TextMerge.__init__(self, a_marker, b_marker)
1353
 
        self.plan = plan
 
1568
        self.plan = list(plan)
1354
1569
 
1355
1570
    def _merge_struct(self):
1356
1571
        lines_a = []
1403
1618
            elif state == 'conflicted-b':
1404
1619
                ch_b = ch_a = True
1405
1620
                lines_b.append(line)
 
1621
            elif state == 'killed-both':
 
1622
                # This counts as a change, even though there is no associated
 
1623
                # line
 
1624
                ch_b = ch_a = True
1406
1625
            else:
1407
1626
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1408
 
                        'killed-base', 'killed-both'):
 
1627
                        'killed-base'):
1409
1628
                    raise AssertionError(state)
1410
1629
        for struct in outstanding_struct():
1411
1630
            yield struct
1412
1631
 
 
1632
    def base_from_plan(self):
 
1633
        """Construct a BASE file from the plan text."""
 
1634
        base_lines = []
 
1635
        for state, line in self.plan:
 
1636
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
 
1637
                # If unchanged, then this line is straight from base. If a or b
 
1638
                # or both killed the line, then it *used* to be in base.
 
1639
                base_lines.append(line)
 
1640
            else:
 
1641
                if state not in ('killed-base', 'irrelevant',
 
1642
                                 'ghost-a', 'ghost-b',
 
1643
                                 'new-a', 'new-b',
 
1644
                                 'conflicted-a', 'conflicted-b'):
 
1645
                    # killed-base, irrelevant means it doesn't apply
 
1646
                    # ghost-a/ghost-b are harder to say for sure, but they
 
1647
                    # aren't in the 'inc_c' which means they aren't in the
 
1648
                    # shared base of a & b. So we don't include them.  And
 
1649
                    # obviously if the line is newly inserted, it isn't in base
 
1650
 
 
1651
                    # If 'conflicted-a' or b, then it is new vs one base, but
 
1652
                    # old versus another base. However, if we make it present
 
1653
                    # in the base, it will be deleted from the target, and it
 
1654
                    # seems better to get a line doubled in the merge result,
 
1655
                    # rather than have it deleted entirely.
 
1656
                    # Example, each node is the 'text' at that point:
 
1657
                    #           MN
 
1658
                    #          /   \
 
1659
                    #        MaN   MbN
 
1660
                    #         |  X  |
 
1661
                    #        MabN MbaN
 
1662
                    #          \   /
 
1663
                    #           ???
 
1664
                    # There was a criss-cross conflict merge. Both sides
 
1665
                    # include the other, but put themselves first.
 
1666
                    # Weave marks this as a 'clean' merge, picking OTHER over
 
1667
                    # THIS. (Though the details depend on order inserted into
 
1668
                    # weave, etc.)
 
1669
                    # LCA generates a plan:
 
1670
                    # [('unchanged', M),
 
1671
                    #  ('conflicted-b', b),
 
1672
                    #  ('unchanged', a),
 
1673
                    #  ('conflicted-a', b),
 
1674
                    #  ('unchanged', N)]
 
1675
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
 
1676
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
 
1677
                    # removes one 'b', and BASE vs OTHER removes the other)
 
1678
                    # If you include neither, 3-way creates a clean "MbabN" as
 
1679
                    # THIS adds one 'b', and OTHER does too.
 
1680
                    # It seems that having the line 2 times is better than
 
1681
                    # having it omitted. (Easier to manually delete than notice
 
1682
                    # it needs to be added.)
 
1683
                    raise AssertionError('Unknown state: %s' % (state,))
 
1684
        return base_lines
 
1685
 
1413
1686
 
1414
1687
class WeaveMerge(PlanWeaveMerge):
1415
1688
    """Weave merge that takes a VersionedFile and two versions as its input."""
1491
1764
                yield (l, key)
1492
1765
 
1493
1766
 
 
1767
class NoDupeAddLinesDecorator(object):
 
1768
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
 
1769
    is already present.
 
1770
    """
 
1771
 
 
1772
    def __init__(self, store):
 
1773
        self._store = store
 
1774
 
 
1775
    def add_lines(self, key, parents, lines, parent_texts=None,
 
1776
            left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1777
            check_content=True):
 
1778
        """See VersionedFiles.add_lines.
 
1779
        
 
1780
        This implementation may return None as the third element of the return
 
1781
        value when the original store wouldn't.
 
1782
        """
 
1783
        if nostore_sha:
 
1784
            raise NotImplementedError(
 
1785
                "NoDupeAddLinesDecorator.add_lines does not implement the "
 
1786
                "nostore_sha behaviour.")
 
1787
        if key[-1] is None:
 
1788
            sha1 = osutils.sha_strings(lines)
 
1789
            key = ("sha1:" + sha1,)
 
1790
        else:
 
1791
            sha1 = None
 
1792
        if key in self._store.get_parent_map([key]):
 
1793
            # This key has already been inserted, so don't do it again.
 
1794
            if sha1 is None:
 
1795
                sha1 = osutils.sha_strings(lines)
 
1796
            return sha1, sum(map(len, lines)), None
 
1797
        return self._store.add_lines(key, parents, lines,
 
1798
                parent_texts=parent_texts,
 
1799
                left_matching_blocks=left_matching_blocks,
 
1800
                nostore_sha=nostore_sha, random_id=random_id,
 
1801
                check_content=check_content)
 
1802
 
 
1803
    def __getattr__(self, name):
 
1804
        return getattr(self._store, name)
 
1805
 
 
1806
 
1494
1807
def network_bytes_to_kind_and_offset(network_bytes):
1495
1808
    """Strip of a record kind from the front of network_bytes.
1496
1809
 
1513
1826
            record.get_bytes_as(record.storage_kind) call.
1514
1827
        """
1515
1828
        self._bytes_iterator = bytes_iterator
1516
 
        self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
1517
 
            'knit-delta-gz':knit.knit_network_to_record,
1518
 
            'knit-annotated-ft-gz':knit.knit_network_to_record,
1519
 
            'knit-annotated-delta-gz':knit.knit_network_to_record,
1520
 
            'knit-delta-closure':knit.knit_delta_closure_to_records,
1521
 
            'fulltext':fulltext_network_to_record,
1522
 
            'groupcompress-block':groupcompress.network_block_to_records,
 
1829
        self._kind_factory = {
 
1830
            'fulltext': fulltext_network_to_record,
 
1831
            'groupcompress-block': groupcompress.network_block_to_records,
 
1832
            'knit-ft-gz': knit.knit_network_to_record,
 
1833
            'knit-delta-gz': knit.knit_network_to_record,
 
1834
            'knit-annotated-ft-gz': knit.knit_network_to_record,
 
1835
            'knit-annotated-delta-gz': knit.knit_network_to_record,
 
1836
            'knit-delta-closure': knit.knit_delta_closure_to_records,
1523
1837
            }
1524
1838
 
1525
1839
    def read(self):
1586
1900
    for prefix in sorted(per_prefix_map):
1587
1901
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1588
1902
    return present_keys
 
1903
 
 
1904
 
 
1905
class _KeyRefs(object):
 
1906
 
 
1907
    def __init__(self, track_new_keys=False):
 
1908
        # dict mapping 'key' to 'set of keys referring to that key'
 
1909
        self.refs = {}
 
1910
        if track_new_keys:
 
1911
            # set remembering all new keys
 
1912
            self.new_keys = set()
 
1913
        else:
 
1914
            self.new_keys = None
 
1915
 
 
1916
    def clear(self):
 
1917
        if self.refs:
 
1918
            self.refs.clear()
 
1919
        if self.new_keys:
 
1920
            self.new_keys.clear()
 
1921
 
 
1922
    def add_references(self, key, refs):
 
1923
        # Record the new references
 
1924
        for referenced in refs:
 
1925
            try:
 
1926
                needed_by = self.refs[referenced]
 
1927
            except KeyError:
 
1928
                needed_by = self.refs[referenced] = set()
 
1929
            needed_by.add(key)
 
1930
        # Discard references satisfied by the new key
 
1931
        self.add_key(key)
 
1932
 
 
1933
    def get_new_keys(self):
 
1934
        return self.new_keys
 
1935
    
 
1936
    def get_unsatisfied_refs(self):
 
1937
        return self.refs.iterkeys()
 
1938
 
 
1939
    def _satisfy_refs_for_key(self, key):
 
1940
        try:
 
1941
            del self.refs[key]
 
1942
        except KeyError:
 
1943
            # No keys depended on this key.  That's ok.
 
1944
            pass
 
1945
 
 
1946
    def add_key(self, key):
 
1947
        # satisfy refs for key, and remember that we've seen this key.
 
1948
        self._satisfy_refs_for_key(key)
 
1949
        if self.new_keys is not None:
 
1950
            self.new_keys.add(key)
 
1951
 
 
1952
    def satisfy_refs_for_keys(self, keys):
 
1953
        for key in keys:
 
1954
            self._satisfy_refs_for_key(key)
 
1955
 
 
1956
    def get_referrers(self):
 
1957
        result = set()
 
1958
        for referrers in self.refs.itervalues():
 
1959
            result.update(referrers)
 
1960
        return result
 
1961
 
 
1962
 
 
1963