~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-19 17:40:59 UTC
  • mto: This revision was merged to the branch mainline in revision 4466.
  • Revision ID: john@arbash-meinel.com-20090619174059-jzowjv0d86vzjg4m
Update the python code to do the same checking around known_parent_gdfos being present.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
30
30
import urllib
31
31
 
32
32
from bzrlib import (
33
 
    annotate,
34
33
    errors,
35
 
    graph as _mod_graph,
36
34
    groupcompress,
37
35
    index,
38
36
    knit,
45
43
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
46
44
from bzrlib.transport.memory import MemoryTransport
47
45
""")
 
46
from bzrlib.inter import InterObject
48
47
from bzrlib.registry import Registry
 
48
from bzrlib.symbol_versioning import *
49
49
from bzrlib.textmerge import TextMerge
50
50
from bzrlib import bencode
51
51
 
174
174
        self.key = key
175
175
        self.parents = None
176
176
 
177
 
    def get_bytes_as(self, storage_kind):
178
 
        raise ValueError('A request was made for key: %s, but that'
179
 
                         ' content is not available, and the calling'
180
 
                         ' code does not handle if it is missing.'
181
 
                         % (self.key,))
182
 
 
183
177
 
184
178
class AdapterFactory(ContentFactory):
185
179
    """A content factory to adapt between key prefix's."""
205
199
            yield record
206
200
 
207
201
 
208
 
class _MPDiffGenerator(object):
209
 
    """Pull out the functionality for generating mp_diffs."""
210
 
 
211
 
    def __init__(self, vf, keys):
212
 
        self.vf = vf
213
 
        # This is the order the keys were requested in
214
 
        self.ordered_keys = tuple(keys)
215
 
        # keys + their parents, what we need to compute the diffs
216
 
        self.needed_keys = ()
217
 
        # Map from key: mp_diff
218
 
        self.diffs = {}
219
 
        # Map from key: parents_needed (may have ghosts)
220
 
        self.parent_map = {}
221
 
        # Parents that aren't present
222
 
        self.ghost_parents = ()
223
 
        # Map from parent_key => number of children for this text
224
 
        self.refcounts = {}
225
 
        # Content chunks that are cached while we still need them
226
 
        self.chunks = {}
227
 
 
228
 
    def _find_needed_keys(self):
229
 
        """Find the set of keys we need to request.
230
 
 
231
 
        This includes all the original keys passed in, and the non-ghost
232
 
        parents of those keys.
233
 
 
234
 
        :return: (needed_keys, refcounts)
235
 
            needed_keys is the set of all texts we need to extract
236
 
            refcounts is a dict of {key: num_children} letting us know when we
237
 
                no longer need to cache a given parent text
238
 
        """
239
 
        # All the keys and their parents
240
 
        needed_keys = set(self.ordered_keys)
241
 
        parent_map = self.vf.get_parent_map(needed_keys)
242
 
        self.parent_map = parent_map
243
 
        # TODO: Should we be using a different construct here? I think this
244
 
        #       uses difference_update internally, and we expect the result to
245
 
        #       be tiny
246
 
        missing_keys = needed_keys.difference(parent_map)
247
 
        if missing_keys:
248
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
249
 
        # Parents that might be missing. They are allowed to be ghosts, but we
250
 
        # should check for them
251
 
        refcounts = {}
252
 
        setdefault = refcounts.setdefault
253
 
        just_parents = set()
254
 
        for child_key, parent_keys in parent_map.iteritems():
255
 
            if not parent_keys:
256
 
                # parent_keys may be None if a given VersionedFile claims to
257
 
                # not support graph operations.
258
 
                continue
259
 
            just_parents.update(parent_keys)
260
 
            needed_keys.update(parent_keys)
261
 
            for p in parent_keys:
262
 
                refcounts[p] = setdefault(p, 0) + 1
263
 
        just_parents.difference_update(parent_map)
264
 
        # Remove any parents that are actually ghosts from the needed set
265
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
266
 
        self.ghost_parents = just_parents.difference(self.present_parents)
267
 
        needed_keys.difference_update(self.ghost_parents)
268
 
        self.needed_keys = needed_keys
269
 
        self.refcounts = refcounts
270
 
        return needed_keys, refcounts
271
 
 
272
 
    def _compute_diff(self, key, parent_lines, lines):
273
 
        """Compute a single mp_diff, and store it in self._diffs"""
274
 
        if len(parent_lines) > 0:
275
 
            # XXX: _extract_blocks is not usefully defined anywhere...
276
 
            #      It was meant to extract the left-parent diff without
277
 
            #      having to recompute it for Knit content (pack-0.92,
278
 
            #      etc). That seems to have regressed somewhere
279
 
            left_parent_blocks = self.vf._extract_blocks(key,
280
 
                parent_lines[0], lines)
281
 
        else:
282
 
            left_parent_blocks = None
283
 
        diff = multiparent.MultiParent.from_lines(lines,
284
 
                    parent_lines, left_parent_blocks)
285
 
        self.diffs[key] = diff
286
 
 
287
 
    def _process_one_record(self, key, this_chunks):
288
 
        parent_keys = None
289
 
        if key in self.parent_map:
290
 
            # This record should be ready to diff, since we requested
291
 
            # content in 'topological' order
292
 
            parent_keys = self.parent_map.pop(key)
293
 
            # If a VersionedFile claims 'no-graph' support, then it may return
294
 
            # None for any parent request, so we replace it with an empty tuple
295
 
            if parent_keys is None:
296
 
                parent_keys = ()
297
 
            parent_lines = []
298
 
            for p in parent_keys:
299
 
                # Alternatively we could check p not in self.needed_keys, but
300
 
                # ghost_parents should be tiny versus huge
301
 
                if p in self.ghost_parents:
302
 
                    continue
303
 
                refcount = self.refcounts[p]
304
 
                if refcount == 1: # Last child reference
305
 
                    self.refcounts.pop(p)
306
 
                    parent_chunks = self.chunks.pop(p)
307
 
                else:
308
 
                    self.refcounts[p] = refcount - 1
309
 
                    parent_chunks = self.chunks[p]
310
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
311
 
                # TODO: Should we cache the line form? We did the
312
 
                #       computation to get it, but storing it this way will
313
 
                #       be less memory efficient...
314
 
                parent_lines.append(p_lines)
315
 
                del p_lines
316
 
            lines = osutils.chunks_to_lines(this_chunks)
317
 
            # Since we needed the lines, we'll go ahead and cache them this way
318
 
            this_chunks = lines
319
 
            self._compute_diff(key, parent_lines, lines)
320
 
            del lines
321
 
        # Is this content required for any more children?
322
 
        if key in self.refcounts:
323
 
            self.chunks[key] = this_chunks
324
 
 
325
 
    def _extract_diffs(self):
326
 
        needed_keys, refcounts = self._find_needed_keys()
327
 
        for record in self.vf.get_record_stream(needed_keys,
328
 
                                                'topological', True):
329
 
            if record.storage_kind == 'absent':
330
 
                raise errors.RevisionNotPresent(record.key, self.vf)
331
 
            self._process_one_record(record.key,
332
 
                                     record.get_bytes_as('chunked'))
333
 
        
334
 
    def compute_diffs(self):
335
 
        self._extract_diffs()
336
 
        dpop = self.diffs.pop
337
 
        return [dpop(k) for k in self.ordered_keys]
338
 
 
339
 
 
340
202
class VersionedFile(object):
341
203
    """Versioned text file storage.
342
204
 
479
341
 
480
342
    def make_mpdiffs(self, version_ids):
481
343
        """Create multiparent diffs for specified versions."""
482
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
483
 
        #      is a list of strings, not keys. And while self.get_record_stream
484
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
485
 
        #      strings... *sigh*
486
344
        knit_versions = set()
487
345
        knit_versions.update(version_ids)
488
346
        parent_map = self.get_parent_map(version_ids)
971
829
        """
972
830
        raise NotImplementedError(self.add_lines)
973
831
 
974
 
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
975
 
        """Add a text to the store.
976
 
 
977
 
        This is a private function for use by CommitBuilder.
978
 
 
979
 
        :param key: The key tuple of the text to add. If the last element is
980
 
            None, a CHK string will be generated during the addition.
981
 
        :param parents: The parents key tuples of the text to add.
982
 
        :param text: A string containing the text to be committed.
983
 
        :param nostore_sha: Raise ExistingContent and do not add the lines to
984
 
            the versioned file if the digest of the lines matches this.
985
 
        :param random_id: If True a random id has been selected rather than
986
 
            an id determined by some deterministic process such as a converter
987
 
            from a foreign VCS. When True the backend may choose not to check
988
 
            for uniqueness of the resulting key within the versioned file, so
989
 
            this should only be done when the result is expected to be unique
990
 
            anyway.
991
 
        :param check_content: If True, the lines supplied are verified to be
992
 
            bytestrings that are correctly formed lines.
993
 
        :return: The text sha1, the number of bytes in the text, and an opaque
994
 
                 representation of the inserted version which can be provided
995
 
                 back to future _add_text calls in the parent_texts dictionary.
996
 
        """
997
 
        # The default implementation just thunks over to .add_lines(),
998
 
        # inefficient, but it works.
999
 
        return self.add_lines(key, parents, osutils.split_lines(text),
1000
 
                              nostore_sha=nostore_sha,
1001
 
                              random_id=random_id,
1002
 
                              check_content=True)
1003
 
 
1004
832
    def add_mpdiffs(self, records):
1005
833
        """Add mpdiffs to this VersionedFile.
1006
834
 
1048
876
        raise NotImplementedError(self.annotate)
1049
877
 
1050
878
    def check(self, progress_bar=None):
1051
 
        """Check this object for integrity.
1052
 
        
1053
 
        :param progress_bar: A progress bar to output as the check progresses.
1054
 
        :param keys: Specific keys within the VersionedFiles to check. When
1055
 
            this parameter is not None, check() becomes a generator as per
1056
 
            get_record_stream. The difference to get_record_stream is that
1057
 
            more or deeper checks will be performed.
1058
 
        :return: None, or if keys was supplied a generator as per
1059
 
            get_record_stream.
1060
 
        """
 
879
        """Check this object for integrity."""
1061
880
        raise NotImplementedError(self.check)
1062
881
 
1063
882
    @staticmethod
1064
883
    def check_not_reserved_id(version_id):
1065
884
        revision.check_not_reserved_id(version_id)
1066
885
 
1067
 
    def clear_cache(self):
1068
 
        """Clear whatever caches this VersionedFile holds.
1069
 
 
1070
 
        This is generally called after an operation has been performed, when we
1071
 
        don't expect to be using this versioned file again soon.
1072
 
        """
1073
 
 
1074
886
    def _check_lines_not_unicode(self, lines):
1075
887
        """Check that lines being added to a versioned file are not unicode."""
1076
888
        for line in lines:
1083
895
            if '\n' in line[:-1]:
1084
896
                raise errors.BzrBadParameterContainsNewline("lines")
1085
897
 
1086
 
    def get_known_graph_ancestry(self, keys):
1087
 
        """Get a KnownGraph instance with the ancestry of keys."""
1088
 
        # most basic implementation is a loop around get_parent_map
1089
 
        pending = set(keys)
1090
 
        parent_map = {}
1091
 
        while pending:
1092
 
            this_parent_map = self.get_parent_map(pending)
1093
 
            parent_map.update(this_parent_map)
1094
 
            pending = set()
1095
 
            map(pending.update, this_parent_map.itervalues())
1096
 
            pending = pending.difference(parent_map)
1097
 
        kg = _mod_graph.KnownGraph(parent_map)
1098
 
        return kg
1099
 
 
1100
898
    def get_parent_map(self, keys):
1101
899
        """Get a map of the parents of keys.
1102
900
 
1182
980
 
1183
981
    def make_mpdiffs(self, keys):
1184
982
        """Create multiparent diffs for specified keys."""
1185
 
        generator = _MPDiffGenerator(self, keys)
1186
 
        return generator.compute_diffs()
1187
 
 
1188
 
    def get_annotator(self):
1189
 
        return annotate.Annotator(self)
 
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
1190
1020
 
1191
1021
    missing_keys = index._missing_keys_from_parent_map
1192
1022
 
1262
1092
            result.append((prefix + (origin,), line))
1263
1093
        return result
1264
1094
 
1265
 
    def check(self, progress_bar=None, keys=None):
 
1095
    def check(self, progress_bar=None):
1266
1096
        """See VersionedFiles.check()."""
1267
 
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1268
 
        # this is tolerable. Ideally we'd pass keys down to check() and 
1269
 
        # have the older VersiondFile interface updated too.
1270
1097
        for prefix, vf in self._iter_all_components():
1271
1098
            vf.check()
1272
 
        if keys is not None:
1273
 
            return self.get_record_stream(keys, 'unordered', True)
1274
1099
 
1275
1100
    def get_parent_map(self, keys):
1276
1101
        """Get a map of the parents of keys.
1525
1350
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1526
1351
                 b_marker=TextMerge.B_MARKER):
1527
1352
        TextMerge.__init__(self, a_marker, b_marker)
1528
 
        self.plan = list(plan)
 
1353
        self.plan = plan
1529
1354
 
1530
1355
    def _merge_struct(self):
1531
1356
        lines_a = []
1589
1414
        for struct in outstanding_struct():
1590
1415
            yield struct
1591
1416
 
1592
 
    def base_from_plan(self):
1593
 
        """Construct a BASE file from the plan text."""
1594
 
        base_lines = []
1595
 
        for state, line in self.plan:
1596
 
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
1597
 
                # If unchanged, then this line is straight from base. If a or b
1598
 
                # or both killed the line, then it *used* to be in base.
1599
 
                base_lines.append(line)
1600
 
            else:
1601
 
                if state not in ('killed-base', 'irrelevant',
1602
 
                                 'ghost-a', 'ghost-b',
1603
 
                                 'new-a', 'new-b',
1604
 
                                 'conflicted-a', 'conflicted-b'):
1605
 
                    # killed-base, irrelevant means it doesn't apply
1606
 
                    # ghost-a/ghost-b are harder to say for sure, but they
1607
 
                    # aren't in the 'inc_c' which means they aren't in the
1608
 
                    # shared base of a & b. So we don't include them.  And
1609
 
                    # obviously if the line is newly inserted, it isn't in base
1610
 
 
1611
 
                    # If 'conflicted-a' or b, then it is new vs one base, but
1612
 
                    # old versus another base. However, if we make it present
1613
 
                    # in the base, it will be deleted from the target, and it
1614
 
                    # seems better to get a line doubled in the merge result,
1615
 
                    # rather than have it deleted entirely.
1616
 
                    # Example, each node is the 'text' at that point:
1617
 
                    #           MN
1618
 
                    #          /   \
1619
 
                    #        MaN   MbN
1620
 
                    #         |  X  |
1621
 
                    #        MabN MbaN
1622
 
                    #          \   /
1623
 
                    #           ???
1624
 
                    # There was a criss-cross conflict merge. Both sides
1625
 
                    # include the other, but put themselves first.
1626
 
                    # Weave marks this as a 'clean' merge, picking OTHER over
1627
 
                    # THIS. (Though the details depend on order inserted into
1628
 
                    # weave, etc.)
1629
 
                    # LCA generates a plan:
1630
 
                    # [('unchanged', M),
1631
 
                    #  ('conflicted-b', b),
1632
 
                    #  ('unchanged', a),
1633
 
                    #  ('conflicted-a', b),
1634
 
                    #  ('unchanged', N)]
1635
 
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
1636
 
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
1637
 
                    # removes one 'b', and BASE vs OTHER removes the other)
1638
 
                    # If you include neither, 3-way creates a clean "MbabN" as
1639
 
                    # THIS adds one 'b', and OTHER does too.
1640
 
                    # It seems that having the line 2 times is better than
1641
 
                    # having it omitted. (Easier to manually delete than notice
1642
 
                    # it needs to be added.)
1643
 
                    raise AssertionError('Unknown state: %s' % (state,))
1644
 
        return base_lines
1645
 
 
1646
1417
 
1647
1418
class WeaveMerge(PlanWeaveMerge):
1648
1419
    """Weave merge that takes a VersionedFile and two versions as its input."""
1724
1495
                yield (l, key)
1725
1496
 
1726
1497
 
1727
 
class NoDupeAddLinesDecorator(object):
1728
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1729
 
    is already present.
1730
 
    """
1731
 
 
1732
 
    def __init__(self, store):
1733
 
        self._store = store
1734
 
 
1735
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1736
 
            left_matching_blocks=None, nostore_sha=None, random_id=False,
1737
 
            check_content=True):
1738
 
        """See VersionedFiles.add_lines.
1739
 
        
1740
 
        This implementation may return None as the third element of the return
1741
 
        value when the original store wouldn't.
1742
 
        """
1743
 
        if nostore_sha:
1744
 
            raise NotImplementedError(
1745
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1746
 
                "nostore_sha behaviour.")
1747
 
        if key[-1] is None:
1748
 
            sha1 = osutils.sha_strings(lines)
1749
 
            key = ("sha1:" + sha1,)
1750
 
        else:
1751
 
            sha1 = None
1752
 
        if key in self._store.get_parent_map([key]):
1753
 
            # This key has already been inserted, so don't do it again.
1754
 
            if sha1 is None:
1755
 
                sha1 = osutils.sha_strings(lines)
1756
 
            return sha1, sum(map(len, lines)), None
1757
 
        return self._store.add_lines(key, parents, lines,
1758
 
                parent_texts=parent_texts,
1759
 
                left_matching_blocks=left_matching_blocks,
1760
 
                nostore_sha=nostore_sha, random_id=random_id,
1761
 
                check_content=check_content)
1762
 
 
1763
 
    def __getattr__(self, name):
1764
 
        return getattr(self._store, name)
1765
 
 
1766
 
 
1767
1498
def network_bytes_to_kind_and_offset(network_bytes):
1768
1499
    """Strip of a record kind from the front of network_bytes.
1769
1500
 
1786
1517
            record.get_bytes_as(record.storage_kind) call.
1787
1518
        """
1788
1519
        self._bytes_iterator = bytes_iterator
1789
 
        self._kind_factory = {
1790
 
            'fulltext': fulltext_network_to_record,
1791
 
            'groupcompress-block': groupcompress.network_block_to_records,
1792
 
            'knit-ft-gz': knit.knit_network_to_record,
1793
 
            'knit-delta-gz': knit.knit_network_to_record,
1794
 
            'knit-annotated-ft-gz': knit.knit_network_to_record,
1795
 
            'knit-annotated-delta-gz': knit.knit_network_to_record,
1796
 
            'knit-delta-closure': knit.knit_delta_closure_to_records,
 
1520
        self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
 
1521
            'knit-delta-gz':knit.knit_network_to_record,
 
1522
            'knit-annotated-ft-gz':knit.knit_network_to_record,
 
1523
            'knit-annotated-delta-gz':knit.knit_network_to_record,
 
1524
            'knit-delta-closure':knit.knit_delta_closure_to_records,
 
1525
            'fulltext':fulltext_network_to_record,
 
1526
            'groupcompress-block':groupcompress.network_block_to_records,
1797
1527
            }
1798
1528
 
1799
1529
    def read(self):