~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-08-13 18:51:45 UTC
  • mfrom: (4595.5.3 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090813185145-ta4t40a5t8z05amk
(Neil Martinsen-Burrell) Include bazaar-vcs.org/BzrGivingBack in
        HACKING.txt

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
49
from bzrlib.symbol_versioning import *
50
50
from bzrlib.textmerge import TextMerge
206
206
            yield record
207
207
 
208
208
 
209
 
class _MPDiffGenerator(object):
210
 
    """Pull out the functionality for generating mp_diffs."""
211
 
 
212
 
    def __init__(self, vf, keys):
213
 
        self.vf = vf
214
 
        # This is the order the keys were requested in
215
 
        self.ordered_keys = tuple(keys)
216
 
        # keys + their parents, what we need to compute the diffs
217
 
        self.needed_keys = ()
218
 
        # Map from key: mp_diff
219
 
        self.diffs = {}
220
 
        # Map from key: parents_needed (may have ghosts)
221
 
        self.parent_map = {}
222
 
        # Parents that aren't present
223
 
        self.ghost_parents = ()
224
 
        # Map from parent_key => number of children for this text
225
 
        self.refcounts = {}
226
 
        # Content chunks that are cached while we still need them
227
 
        self.chunks = {}
228
 
 
229
 
    def _find_needed_keys(self):
230
 
        """Find the set of keys we need to request.
231
 
 
232
 
        This includes all the original keys passed in, and the non-ghost
233
 
        parents of those keys.
234
 
 
235
 
        :return: (needed_keys, refcounts)
236
 
            needed_keys is the set of all texts we need to extract
237
 
            refcounts is a dict of {key: num_children} letting us know when we
238
 
                no longer need to cache a given parent text
239
 
        """
240
 
        # All the keys and their parents
241
 
        needed_keys = set(self.ordered_keys)
242
 
        parent_map = self.vf.get_parent_map(needed_keys)
243
 
        self.parent_map = parent_map
244
 
        # TODO: Should we be using a different construct here? I think this
245
 
        #       uses difference_update internally, and we expect the result to
246
 
        #       be tiny
247
 
        missing_keys = needed_keys.difference(parent_map)
248
 
        if missing_keys:
249
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
250
 
        # Parents that might be missing. They are allowed to be ghosts, but we
251
 
        # should check for them
252
 
        refcounts = {}
253
 
        setdefault = refcounts.setdefault
254
 
        just_parents = set()
255
 
        for child_key, parent_keys in parent_map.iteritems():
256
 
            if not parent_keys:
257
 
                # parent_keys may be None if a given VersionedFile claims to
258
 
                # not support graph operations.
259
 
                continue
260
 
            just_parents.update(parent_keys)
261
 
            needed_keys.update(parent_keys)
262
 
            for p in parent_keys:
263
 
                refcounts[p] = setdefault(p, 0) + 1
264
 
        just_parents.difference_update(parent_map)
265
 
        # Remove any parents that are actually ghosts from the needed set
266
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
267
 
        self.ghost_parents = just_parents.difference(self.present_parents)
268
 
        needed_keys.difference_update(self.ghost_parents)
269
 
        self.needed_keys = needed_keys
270
 
        self.refcounts = refcounts
271
 
        return needed_keys, refcounts
272
 
 
273
 
    def _compute_diff(self, key, parent_lines, lines):
274
 
        """Compute a single mp_diff, and store it in self._diffs"""
275
 
        if len(parent_lines) > 0:
276
 
            # XXX: _extract_blocks is not usefully defined anywhere...
277
 
            #      It was meant to extract the left-parent diff without
278
 
            #      having to recompute it for Knit content (pack-0.92,
279
 
            #      etc). That seems to have regressed somewhere
280
 
            left_parent_blocks = self.vf._extract_blocks(key,
281
 
                parent_lines[0], lines)
282
 
        else:
283
 
            left_parent_blocks = None
284
 
        diff = multiparent.MultiParent.from_lines(lines,
285
 
                    parent_lines, left_parent_blocks)
286
 
        self.diffs[key] = diff
287
 
 
288
 
    def _process_one_record(self, key, this_chunks):
289
 
        parent_keys = None
290
 
        if key in self.parent_map:
291
 
            # This record should be ready to diff, since we requested
292
 
            # content in 'topological' order
293
 
            parent_keys = self.parent_map.pop(key)
294
 
            # If a VersionedFile claims 'no-graph' support, then it may return
295
 
            # None for any parent request, so we replace it with an empty tuple
296
 
            if parent_keys is None:
297
 
                parent_keys = ()
298
 
            parent_lines = []
299
 
            for p in parent_keys:
300
 
                # Alternatively we could check p not in self.needed_keys, but
301
 
                # ghost_parents should be tiny versus huge
302
 
                if p in self.ghost_parents:
303
 
                    continue
304
 
                refcount = self.refcounts[p]
305
 
                if refcount == 1: # Last child reference
306
 
                    self.refcounts.pop(p)
307
 
                    parent_chunks = self.chunks.pop(p)
308
 
                else:
309
 
                    self.refcounts[p] = refcount - 1
310
 
                    parent_chunks = self.chunks[p]
311
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
312
 
                # TODO: Should we cache the line form? We did the
313
 
                #       computation to get it, but storing it this way will
314
 
                #       be less memory efficient...
315
 
                parent_lines.append(p_lines)
316
 
                del p_lines
317
 
            lines = osutils.chunks_to_lines(this_chunks)
318
 
            # Since we needed the lines, we'll go ahead and cache them this way
319
 
            this_chunks = lines
320
 
            self._compute_diff(key, parent_lines, lines)
321
 
            del lines
322
 
        # Is this content required for any more children?
323
 
        if key in self.refcounts:
324
 
            self.chunks[key] = this_chunks
325
 
 
326
 
    def _extract_diffs(self):
327
 
        needed_keys, refcounts = self._find_needed_keys()
328
 
        for record in self.vf.get_record_stream(needed_keys,
329
 
                                                'topological', True):
330
 
            if record.storage_kind == 'absent':
331
 
                raise errors.RevisionNotPresent(record.key, self.vf)
332
 
            self._process_one_record(record.key,
333
 
                                     record.get_bytes_as('chunked'))
334
 
        
335
 
    def compute_diffs(self):
336
 
        self._extract_diffs()
337
 
        dpop = self.diffs.pop
338
 
        return [dpop(k) for k in self.ordered_keys]
339
 
 
340
 
 
341
209
class VersionedFile(object):
342
210
    """Versioned text file storage.
343
211
 
480
348
 
481
349
    def make_mpdiffs(self, version_ids):
482
350
        """Create multiparent diffs for specified versions."""
483
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
484
 
        #      is a list of strings, not keys. And while self.get_record_stream
485
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
486
 
        #      strings... *sigh*
487
351
        knit_versions = set()
488
352
        knit_versions.update(version_ids)
489
353
        parent_map = self.get_parent_map(version_ids)
1065
929
    def check_not_reserved_id(version_id):
1066
930
        revision.check_not_reserved_id(version_id)
1067
931
 
1068
 
    def clear_cache(self):
1069
 
        """Clear whatever caches this VersionedFile holds.
1070
 
 
1071
 
        This is generally called after an operation has been performed, when we
1072
 
        don't expect to be using this versioned file again soon.
1073
 
        """
1074
 
 
1075
932
    def _check_lines_not_unicode(self, lines):
1076
933
        """Check that lines being added to a versioned file are not unicode."""
1077
934
        for line in lines:
1084
941
            if '\n' in line[:-1]:
1085
942
                raise errors.BzrBadParameterContainsNewline("lines")
1086
943
 
1087
 
    def get_known_graph_ancestry(self, keys):
1088
 
        """Get a KnownGraph instance with the ancestry of keys."""
1089
 
        # most basic implementation is a loop around get_parent_map
1090
 
        pending = set(keys)
1091
 
        parent_map = {}
1092
 
        while pending:
1093
 
            this_parent_map = self.get_parent_map(pending)
1094
 
            parent_map.update(this_parent_map)
1095
 
            pending = set()
1096
 
            map(pending.update, this_parent_map.itervalues())
1097
 
            pending = pending.difference(parent_map)
1098
 
        kg = _mod_graph.KnownGraph(parent_map)
1099
 
        return kg
1100
 
 
1101
944
    def get_parent_map(self, keys):
1102
945
        """Get a map of the parents of keys.
1103
946
 
1183
1026
 
1184
1027
    def make_mpdiffs(self, keys):
1185
1028
        """Create multiparent diffs for specified keys."""
1186
 
        generator = _MPDiffGenerator(self, keys)
1187
 
        return generator.compute_diffs()
1188
 
 
1189
 
    def get_annotator(self):
1190
 
        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
1191
1066
 
1192
1067
    missing_keys = index._missing_keys_from_parent_map
1193
1068
 
1263
1138
            result.append((prefix + (origin,), line))
1264
1139
        return result
1265
1140
 
 
1141
    def get_annotator(self):
 
1142
        return annotate.Annotator(self)
 
1143
 
1266
1144
    def check(self, progress_bar=None, keys=None):
1267
1145
        """See VersionedFiles.check()."""
1268
1146
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1526
1404
    def __init__(self, plan, a_marker=TextMerge.A_MARKER,
1527
1405
                 b_marker=TextMerge.B_MARKER):
1528
1406
        TextMerge.__init__(self, a_marker, b_marker)
1529
 
        self.plan = list(plan)
 
1407
        self.plan = plan
1530
1408
 
1531
1409
    def _merge_struct(self):
1532
1410
        lines_a = []
1590
1468
        for struct in outstanding_struct():
1591
1469
            yield struct
1592
1470
 
1593
 
    def base_from_plan(self):
1594
 
        """Construct a BASE file from the plan text."""
1595
 
        base_lines = []
1596
 
        for state, line in self.plan:
1597
 
            if state in ('killed-a', 'killed-b', 'killed-both', 'unchanged'):
1598
 
                # If unchanged, then this line is straight from base. If a or b
1599
 
                # or both killed the line, then it *used* to be in base.
1600
 
                base_lines.append(line)
1601
 
            else:
1602
 
                if state not in ('killed-base', 'irrelevant',
1603
 
                                 'ghost-a', 'ghost-b',
1604
 
                                 'new-a', 'new-b',
1605
 
                                 'conflicted-a', 'conflicted-b'):
1606
 
                    # killed-base, irrelevant means it doesn't apply
1607
 
                    # ghost-a/ghost-b are harder to say for sure, but they
1608
 
                    # aren't in the 'inc_c' which means they aren't in the
1609
 
                    # shared base of a & b. So we don't include them.  And
1610
 
                    # obviously if the line is newly inserted, it isn't in base
1611
 
 
1612
 
                    # If 'conflicted-a' or b, then it is new vs one base, but
1613
 
                    # old versus another base. However, if we make it present
1614
 
                    # in the base, it will be deleted from the target, and it
1615
 
                    # seems better to get a line doubled in the merge result,
1616
 
                    # rather than have it deleted entirely.
1617
 
                    # Example, each node is the 'text' at that point:
1618
 
                    #           MN
1619
 
                    #          /   \
1620
 
                    #        MaN   MbN
1621
 
                    #         |  X  |
1622
 
                    #        MabN MbaN
1623
 
                    #          \   /
1624
 
                    #           ???
1625
 
                    # There was a criss-cross conflict merge. Both sides
1626
 
                    # include the other, but put themselves first.
1627
 
                    # Weave marks this as a 'clean' merge, picking OTHER over
1628
 
                    # THIS. (Though the details depend on order inserted into
1629
 
                    # weave, etc.)
1630
 
                    # LCA generates a plan:
1631
 
                    # [('unchanged', M),
1632
 
                    #  ('conflicted-b', b),
1633
 
                    #  ('unchanged', a),
1634
 
                    #  ('conflicted-a', b),
1635
 
                    #  ('unchanged', N)]
1636
 
                    # If you mark 'conflicted-*' as part of BASE, then a 3-way
1637
 
                    # merge tool will cleanly generate "MaN" (as BASE vs THIS
1638
 
                    # removes one 'b', and BASE vs OTHER removes the other)
1639
 
                    # If you include neither, 3-way creates a clean "MbabN" as
1640
 
                    # THIS adds one 'b', and OTHER does too.
1641
 
                    # It seems that having the line 2 times is better than
1642
 
                    # having it omitted. (Easier to manually delete than notice
1643
 
                    # it needs to be added.)
1644
 
                    raise AssertionError('Unknown state: %s' % (state,))
1645
 
        return base_lines
1646
 
 
1647
1471
 
1648
1472
class WeaveMerge(PlanWeaveMerge):
1649
1473
    """Weave merge that takes a VersionedFile and two versions as its input."""
1725
1549
                yield (l, key)
1726
1550
 
1727
1551
 
1728
 
class NoDupeAddLinesDecorator(object):
1729
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1730
 
    is already present.
1731
 
    """
1732
 
 
1733
 
    def __init__(self, store):
1734
 
        self._store = store
1735
 
 
1736
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1737
 
            left_matching_blocks=None, nostore_sha=None, random_id=False,
1738
 
            check_content=True):
1739
 
        """See VersionedFiles.add_lines.
1740
 
        
1741
 
        This implementation may return None as the third element of the return
1742
 
        value when the original store wouldn't.
1743
 
        """
1744
 
        if nostore_sha:
1745
 
            raise NotImplementedError(
1746
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1747
 
                "nostore_sha behaviour.")
1748
 
        if key[-1] is None:
1749
 
            sha1 = osutils.sha_strings(lines)
1750
 
            key = ("sha1:" + sha1,)
1751
 
        else:
1752
 
            sha1 = None
1753
 
        if key in self._store.get_parent_map([key]):
1754
 
            # This key has already been inserted, so don't do it again.
1755
 
            if sha1 is None:
1756
 
                sha1 = osutils.sha_strings(lines)
1757
 
            return sha1, sum(map(len, lines)), None
1758
 
        return self._store.add_lines(key, parents, lines,
1759
 
                parent_texts=parent_texts,
1760
 
                left_matching_blocks=left_matching_blocks,
1761
 
                nostore_sha=nostore_sha, random_id=random_id,
1762
 
                check_content=check_content)
1763
 
 
1764
 
    def __getattr__(self, name):
1765
 
        return getattr(self._store, name)
1766
 
 
1767
 
 
1768
1552
def network_bytes_to_kind_and_offset(network_bytes):
1769
1553
    """Strip of a record kind from the front of network_bytes.
1770
1554
 
1787
1571
            record.get_bytes_as(record.storage_kind) call.
1788
1572
        """
1789
1573
        self._bytes_iterator = bytes_iterator
1790
 
        self._kind_factory = {
1791
 
            'fulltext': fulltext_network_to_record,
1792
 
            'groupcompress-block': groupcompress.network_block_to_records,
1793
 
            'knit-ft-gz': knit.knit_network_to_record,
1794
 
            'knit-delta-gz': knit.knit_network_to_record,
1795
 
            'knit-annotated-ft-gz': knit.knit_network_to_record,
1796
 
            'knit-annotated-delta-gz': knit.knit_network_to_record,
1797
 
            '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,
1798
1581
            }
1799
1582
 
1800
1583
    def read(self):