~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Vincent Ladeuil
  • Date: 2010-12-07 10:16:53 UTC
  • mto: (5575.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 5576.
  • Revision ID: v.ladeuil+lp@free.fr-20101207101653-20iiufih26buvmy3
Use assertLength as it provides a better ouput to debug tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import time
20
18
 
21
19
from bzrlib import (
25
23
    revision,
26
24
    trace,
27
25
    )
 
26
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
28
27
 
29
28
STEP_UNIQUE_SEARCHER_EVERY = 5
30
29
 
60
59
    def __repr__(self):
61
60
        return 'DictParentsProvider(%r)' % self.ancestry
62
61
 
63
 
    # Note: DictParentsProvider does not implement get_cached_parent_map
64
 
    #       Arguably, the data is clearly cached in memory. However, this class
65
 
    #       is mostly used for testing, and it keeps the tests clean to not
66
 
    #       change it.
67
 
 
68
62
    def get_parent_map(self, keys):
69
63
        """See StackedParentsProvider.get_parent_map"""
70
64
        ancestry = self.ancestry
71
 
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
 
65
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
72
66
 
 
67
@deprecated_function(deprecated_in((1, 16, 0)))
 
68
def _StackedParentsProvider(*args, **kwargs):
 
69
    return StackedParentsProvider(*args, **kwargs)
73
70
 
74
71
class StackedParentsProvider(object):
75
72
    """A parents provider which stacks (or unions) multiple providers.
76
 
 
 
73
    
77
74
    The providers are queries in the order of the provided parent_providers.
78
75
    """
79
 
 
 
76
    
80
77
    def __init__(self, parent_providers):
81
78
        self._parent_providers = parent_providers
82
79
 
98
95
        """
99
96
        found = {}
100
97
        remaining = set(keys)
101
 
        # This adds getattr() overhead to each get_parent_map call. However,
102
 
        # this is StackedParentsProvider, which means we're dealing with I/O
103
 
        # (either local indexes, or remote RPCs), so CPU overhead should be
104
 
        # minimal.
105
 
        for parents_provider in self._parent_providers:
106
 
            get_cached = getattr(parents_provider, 'get_cached_parent_map',
107
 
                                 None)
108
 
            if get_cached is None:
109
 
                continue
110
 
            new_found = get_cached(remaining)
111
 
            found.update(new_found)
112
 
            remaining.difference_update(new_found)
113
 
            if not remaining:
114
 
                break
115
 
        if not remaining:
116
 
            return found
117
98
        for parents_provider in self._parent_providers:
118
99
            new_found = parents_provider.get_parent_map(remaining)
119
100
            found.update(new_found)
173
154
            return None
174
155
        return dict(self._cache)
175
156
 
176
 
    def get_cached_parent_map(self, keys):
177
 
        """Return items from the cache.
178
 
 
179
 
        This returns the same info as get_parent_map, but explicitly does not
180
 
        invoke the supplied ParentsProvider to search for uncached values.
181
 
        """
182
 
        cache = self._cache
183
 
        if cache is None:
184
 
            return {}
185
 
        return dict([(key, cache[key]) for key in keys if key in cache])
186
 
 
187
157
    def get_parent_map(self, keys):
188
158
        """See StackedParentsProvider.get_parent_map."""
189
159
        cache = self._cache
213
183
            self.missing_keys.add(key)
214
184
 
215
185
 
216
 
class CallableToParentsProviderAdapter(object):
217
 
    """A parents provider that adapts any callable to the parents provider API.
218
 
 
219
 
    i.e. it accepts calls to self.get_parent_map and relays them to the
220
 
    callable it was constructed with.
221
 
    """
222
 
 
223
 
    def __init__(self, a_callable):
224
 
        self.callable = a_callable
225
 
 
226
 
    def __repr__(self):
227
 
        return "%s(%r)" % (self.__class__.__name__, self.callable)
228
 
 
229
 
    def get_parent_map(self, keys):
230
 
        return self.callable(keys)
231
 
 
232
 
 
233
186
class Graph(object):
234
187
    """Provide incremental access to revision graphs.
235
188
 
284
237
        common ancestor of all border ancestors, because this shows that it
285
238
        cannot be a descendant of any border ancestor.
286
239
 
287
 
        The scaling of this operation should be proportional to:
288
 
 
 
240
        The scaling of this operation should be proportional to
289
241
        1. The number of uncommon ancestors
290
242
        2. The number of border ancestors
291
243
        3. The length of the shortest path between a border ancestor and an
423
375
 
424
376
        :param unique_revision: The revision_id whose ancestry we are
425
377
            interested in.
426
 
            (XXX: Would this API be better if we allowed multiple revisions on
427
 
            to be searched here?)
 
378
            XXX: Would this API be better if we allowed multiple revisions on
 
379
                 to be searched here?
428
380
        :param common_revisions: Revision_ids of ancestries to exclude.
429
381
        :return: A set of revisions in the ancestry of unique_revision
430
382
        """
1348
1300
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
1349
1301
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
1350
1302
 
1351
 
    def get_state(self):
1352
 
        """Get the current state of this searcher.
 
1303
    def get_result(self):
 
1304
        """Get a SearchResult for the current state of this searcher.
1353
1305
 
1354
 
        :return: Tuple with started keys, excludes and included keys
 
1306
        :return: A SearchResult for this search so far. The SearchResult is
 
1307
            static - the search can be advanced and the search result will not
 
1308
            be invalidated or altered.
1355
1309
        """
1356
1310
        if self._returning == 'next':
1357
1311
            # We have to know the current nodes children to be able to list the
1368
1322
            next_query = self._next_query
1369
1323
        excludes = self._stopped_keys.union(next_query)
1370
1324
        included_keys = self.seen.difference(excludes)
1371
 
        return self._started_keys, excludes, included_keys
1372
 
 
1373
 
    def _get_result(self):
1374
 
        """Get a SearchResult for the current state of this searcher.
1375
 
 
1376
 
        :return: A SearchResult for this search so far. The SearchResult is
1377
 
            static - the search can be advanced and the search result will not
1378
 
            be invalidated or altered.
1379
 
        """
1380
 
        from bzrlib.vf_search import SearchResult
1381
 
        (started_keys, excludes, included_keys) = self.get_state()
1382
 
        return SearchResult(started_keys, excludes, len(included_keys),
 
1325
        return SearchResult(self._started_keys, excludes, len(included_keys),
1383
1326
            included_keys)
1384
1327
 
1385
1328
    def step(self):
1462
1405
        parents_of_found = set()
1463
1406
        # revisions may contain nodes that point to other nodes in revisions:
1464
1407
        # we want to filter them out.
1465
 
        seen = self.seen
1466
 
        seen.update(revisions)
 
1408
        self.seen.update(revisions)
1467
1409
        parent_map = self._parents_provider.get_parent_map(revisions)
1468
1410
        found_revisions.update(parent_map)
1469
1411
        for rev_id, parents in parent_map.iteritems():
1470
1412
            if parents is None:
1471
1413
                continue
1472
 
            new_found_parents = [p for p in parents if p not in seen]
 
1414
            new_found_parents = [p for p in parents if p not in self.seen]
1473
1415
            if new_found_parents:
1474
1416
                # Calling set.update() with an empty generator is actually
1475
1417
                # rather expensive.
1594
1536
            return revs, ghosts
1595
1537
 
1596
1538
 
1597
 
def invert_parent_map(parent_map):
1598
 
    """Given a map from child => parents, create a map of parent=>children"""
1599
 
    child_map = {}
1600
 
    for child, parents in parent_map.iteritems():
1601
 
        for p in parents:
1602
 
            # Any given parent is likely to have only a small handful
1603
 
            # of children, many will have only one. So we avoid mem overhead of
1604
 
            # a list, in exchange for extra copying of tuples
1605
 
            if p not in child_map:
1606
 
                child_map[p] = (child,)
1607
 
            else:
1608
 
                child_map[p] = child_map[p] + (child,)
1609
 
    return child_map
 
1539
class SearchResult(object):
 
1540
    """The result of a breadth first search.
 
1541
 
 
1542
    A SearchResult provides the ability to reconstruct the search or access a
 
1543
    set of the keys the search found.
 
1544
    """
 
1545
 
 
1546
    def __init__(self, start_keys, exclude_keys, key_count, keys):
 
1547
        """Create a SearchResult.
 
1548
 
 
1549
        :param start_keys: The keys the search started at.
 
1550
        :param exclude_keys: The keys the search excludes.
 
1551
        :param key_count: The total number of keys (from start to but not
 
1552
            including exclude).
 
1553
        :param keys: The keys the search found. Note that in future we may get
 
1554
            a SearchResult from a smart server, in which case the keys list is
 
1555
            not necessarily immediately available.
 
1556
        """
 
1557
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1558
        self._keys = frozenset(keys)
 
1559
 
 
1560
    def get_recipe(self):
 
1561
        """Return a recipe that can be used to replay this search.
 
1562
 
 
1563
        The recipe allows reconstruction of the same results at a later date
 
1564
        without knowing all the found keys. The essential elements are a list
 
1565
        of keys to start and to stop at. In order to give reproducible
 
1566
        results when ghosts are encountered by a search they are automatically
 
1567
        added to the exclude list (or else ghost filling may alter the
 
1568
        results).
 
1569
 
 
1570
        :return: A tuple ('search', start_keys_set, exclude_keys_set,
 
1571
            revision_count). To recreate the results of this search, create a
 
1572
            breadth first searcher on the same graph starting at start_keys.
 
1573
            Then call next() (or next_with_ghosts()) repeatedly, and on every
 
1574
            result, call stop_searching_any on any keys from the exclude_keys
 
1575
            set. The revision_count value acts as a trivial cross-check - the
 
1576
            found revisions of the new search should have as many elements as
 
1577
            revision_count. If it does not, then additional revisions have been
 
1578
            ghosted since the search was executed the first time and the second
 
1579
            time.
 
1580
        """
 
1581
        return self._recipe
 
1582
 
 
1583
    def get_keys(self):
 
1584
        """Return the keys found in this search.
 
1585
 
 
1586
        :return: A set of keys.
 
1587
        """
 
1588
        return self._keys
 
1589
 
 
1590
    def is_empty(self):
 
1591
        """Return false if the search lists 1 or more revisions."""
 
1592
        return self._recipe[3] == 0
 
1593
 
 
1594
    def refine(self, seen, referenced):
 
1595
        """Create a new search by refining this search.
 
1596
 
 
1597
        :param seen: Revisions that have been satisfied.
 
1598
        :param referenced: Revision references observed while satisfying some
 
1599
            of this search.
 
1600
        """
 
1601
        start = self._recipe[1]
 
1602
        exclude = self._recipe[2]
 
1603
        count = self._recipe[3]
 
1604
        keys = self.get_keys()
 
1605
        # New heads = referenced + old heads - seen things - exclude
 
1606
        pending_refs = set(referenced)
 
1607
        pending_refs.update(start)
 
1608
        pending_refs.difference_update(seen)
 
1609
        pending_refs.difference_update(exclude)
 
1610
        # New exclude = old exclude + satisfied heads
 
1611
        seen_heads = start.intersection(seen)
 
1612
        exclude.update(seen_heads)
 
1613
        # keys gets seen removed
 
1614
        keys = keys - seen
 
1615
        # length is reduced by len(seen)
 
1616
        count -= len(seen)
 
1617
        return SearchResult(pending_refs, exclude, count, keys)
 
1618
 
 
1619
 
 
1620
class PendingAncestryResult(object):
 
1621
    """A search result that will reconstruct the ancestry for some graph heads.
 
1622
 
 
1623
    Unlike SearchResult, this doesn't hold the complete search result in
 
1624
    memory, it just holds a description of how to generate it.
 
1625
    """
 
1626
 
 
1627
    def __init__(self, heads, repo):
 
1628
        """Constructor.
 
1629
 
 
1630
        :param heads: an iterable of graph heads.
 
1631
        :param repo: a repository to use to generate the ancestry for the given
 
1632
            heads.
 
1633
        """
 
1634
        self.heads = frozenset(heads)
 
1635
        self.repo = repo
 
1636
 
 
1637
    def get_recipe(self):
 
1638
        """Return a recipe that can be used to replay this search.
 
1639
 
 
1640
        The recipe allows reconstruction of the same results at a later date.
 
1641
 
 
1642
        :seealso SearchResult.get_recipe:
 
1643
 
 
1644
        :return: A tuple ('proxy-search', start_keys_set, set(), -1)
 
1645
            To recreate this result, create a PendingAncestryResult with the
 
1646
            start_keys_set.
 
1647
        """
 
1648
        return ('proxy-search', self.heads, set(), -1)
 
1649
 
 
1650
    def get_keys(self):
 
1651
        """See SearchResult.get_keys.
 
1652
 
 
1653
        Returns all the keys for the ancestry of the heads, excluding
 
1654
        NULL_REVISION.
 
1655
        """
 
1656
        return self._get_keys(self.repo.get_graph())
 
1657
 
 
1658
    def _get_keys(self, graph):
 
1659
        NULL_REVISION = revision.NULL_REVISION
 
1660
        keys = [key for (key, parents) in graph.iter_ancestry(self.heads)
 
1661
                if key != NULL_REVISION and parents is not None]
 
1662
        return keys
 
1663
 
 
1664
    def is_empty(self):
 
1665
        """Return false if the search lists 1 or more revisions."""
 
1666
        if revision.NULL_REVISION in self.heads:
 
1667
            return len(self.heads) == 1
 
1668
        else:
 
1669
            return len(self.heads) == 0
 
1670
 
 
1671
    def refine(self, seen, referenced):
 
1672
        """Create a new search by refining this search.
 
1673
 
 
1674
        :param seen: Revisions that have been satisfied.
 
1675
        :param referenced: Revision references observed while satisfying some
 
1676
            of this search.
 
1677
        """
 
1678
        referenced = self.heads.union(referenced)
 
1679
        return PendingAncestryResult(referenced - seen, self.repo)
1610
1680
 
1611
1681
 
1612
1682
def collapse_linear_regions(parent_map):
1698
1768
        return set([h[0] for h in head_keys])
1699
1769
 
1700
1770
    def merge_sort(self, tip_revision):
1701
 
        nodes = self._graph.merge_sort((tip_revision,))
1702
 
        for node in nodes:
1703
 
            node.key = node.key[0]
1704
 
        return nodes
1705
 
 
1706
 
    def add_node(self, revision, parents):
1707
 
        self._graph.add_node((revision,), [(p,) for p in parents])
 
1771
        return self._graph.merge_sort((tip_revision,))
1708
1772
 
1709
1773
 
1710
1774
_counters = [0,0,0,0,0,0,0]