~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Martin Pool
  • Date: 2009-08-14 12:08:08 UTC
  • mto: This revision was merged to the branch mainline in revision 4614.
  • Revision ID: mbp@sourcefrog.net-20090814120808-4gvx7fhg44z29cj3
Use platform(aliased=1)

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>
32
32
from bzrlib import (
33
33
    annotate,
34
34
    errors,
35
 
    graph as _mod_graph,
36
35
    groupcompress,
37
36
    index,
38
37
    knit,
45
44
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
46
45
from bzrlib.transport.memory import MemoryTransport
47
46
""")
 
47
from bzrlib.inter import InterObject
48
48
from bzrlib.registry import Registry
 
49
from bzrlib.symbol_versioning import *
49
50
from bzrlib.textmerge import TextMerge
50
51
from bzrlib import bencode
51
52
 
205
206
            yield record
206
207
 
207
208
 
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
209
class VersionedFile(object):
341
210
    """Versioned text file storage.
342
211
 
479
348
 
480
349
    def make_mpdiffs(self, version_ids):
481
350
        """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
351
        knit_versions = set()
487
352
        knit_versions.update(version_ids)
488
353
        parent_map = self.get_parent_map(version_ids)
1064
929
    def check_not_reserved_id(version_id):
1065
930
        revision.check_not_reserved_id(version_id)
1066
931
 
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
932
    def _check_lines_not_unicode(self, lines):
1075
933
        """Check that lines being added to a versioned file are not unicode."""
1076
934
        for line in lines:
1083
941
            if '\n' in line[:-1]:
1084
942
                raise errors.BzrBadParameterContainsNewline("lines")
1085
943
 
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
944
    def get_parent_map(self, keys):
1101
945
        """Get a map of the parents of keys.
1102
946
 
1182
1026
 
1183
1027
    def make_mpdiffs(self, keys):
1184
1028
        """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)
 
1029
        keys_order = tuple(keys)
 
1030
        keys = frozenset(keys)
 
1031
        knit_keys = set(keys)
 
1032
        parent_map = self.get_parent_map(keys)
 
1033
        for parent_keys in parent_map.itervalues():
 
1034
            if parent_keys:
 
1035
                knit_keys.update(parent_keys)
 
1036
        missing_keys = keys - set(parent_map)
 
1037
        if missing_keys:
 
1038
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
1039
        # We need to filter out ghosts, because we can't diff against them.
 
1040
        maybe_ghosts = knit_keys - keys
 
1041
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
1042
        knit_keys.difference_update(ghosts)
 
1043
        lines = {}
 
1044
        chunks_to_lines = osutils.chunks_to_lines
 
1045
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
1046
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
 
1047
            # line_block_dict = {}
 
1048
            # for parent, blocks in record.extract_line_blocks():
 
1049
            #   line_blocks[parent] = blocks
 
1050
            # line_blocks[record.key] = line_block_dict
 
1051
        diffs = []
 
1052
        for key in keys_order:
 
1053
            target = lines[key]
 
1054
            parents = parent_map[key] or []
 
1055
            # Note that filtering knit_keys can lead to a parent difference
 
1056
            # between the creation and the application of the mpdiff.
 
1057
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
1058
            if len(parent_lines) > 0:
 
1059
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
1060
                    target)
 
1061
            else:
 
1062
                left_parent_blocks = None
 
1063
            diffs.append(multiparent.MultiParent.from_lines(target,
 
1064
                parent_lines, left_parent_blocks))
 
1065
        return diffs
1190
1066
 
1191
1067
    missing_keys = index._missing_keys_from_parent_map
1192
1068
 
1262
1138
            result.append((prefix + (origin,), line))
1263
1139
        return result
1264
1140
 
 
1141
    def get_annotator(self):
 
1142
        return annotate.Annotator(self)
 
1143
 
1265
1144
    def check(self, progress_bar=None, keys=None):
1266
1145
        """See VersionedFiles.check()."""
1267
1146
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1525
1404
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1526
1405
                 b_marker=TextMerge.B_MARKER):
1527
1406
        TextMerge.__init__(self, a_marker, b_marker)
1528
 
        self.plan = list(plan)
 
1407
        self.plan = plan
1529
1408
 
1530
1409
    def _merge_struct(self):
1531
1410
        lines_a = []
1589
1468
        for struct in outstanding_struct():
1590
1469
            yield struct
1591
1470
 
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
1471
 
1647
1472
class WeaveMerge(PlanWeaveMerge):
1648
1473
    """Weave merge that takes a VersionedFile and two versions as its input."""
1724
1549
                yield (l, key)
1725
1550
 
1726
1551
 
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
1552
def network_bytes_to_kind_and_offset(network_bytes):
1768
1553
    """Strip of a record kind from the front of network_bytes.
1769
1554
 
1786
1571
            record.get_bytes_as(record.storage_kind) call.
1787
1572
        """
1788
1573
        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,
 
1574
        self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
 
1575
            'knit-delta-gz':knit.knit_network_to_record,
 
1576
            'knit-annotated-ft-gz':knit.knit_network_to_record,
 
1577
            'knit-annotated-delta-gz':knit.knit_network_to_record,
 
1578
            'knit-delta-closure':knit.knit_delta_closure_to_records,
 
1579
            'fulltext':fulltext_network_to_record,
 
1580
            'groupcompress-block':groupcompress.network_block_to_records,
1797
1581
            }
1798
1582
 
1799
1583
    def read(self):