~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Haw Loeung (hloeung)
  • Date: 2012-07-24 12:53:36 UTC
  • mfrom: (6541 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6542.
  • Revision ID: haw.loeung@canonical.com-20120724125336-r3wzxm02lyec7jm6
[hloeung] Merged with upstream resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 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
 
17
19
import time
18
20
 
19
21
from bzrlib import (
23
25
    revision,
24
26
    trace,
25
27
    )
26
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
27
28
 
28
29
STEP_UNIQUE_SEARCHER_EVERY = 5
29
30
 
59
60
    def __repr__(self):
60
61
        return 'DictParentsProvider(%r)' % self.ancestry
61
62
 
 
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
 
62
68
    def get_parent_map(self, keys):
63
69
        """See StackedParentsProvider.get_parent_map"""
64
70
        ancestry = self.ancestry
65
 
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
 
71
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
66
72
 
67
 
@deprecated_function(deprecated_in((1, 16, 0)))
68
 
def _StackedParentsProvider(*args, **kwargs):
69
 
    return StackedParentsProvider(*args, **kwargs)
70
73
 
71
74
class StackedParentsProvider(object):
72
75
    """A parents provider which stacks (or unions) multiple providers.
73
 
    
 
76
 
74
77
    The providers are queries in the order of the provided parent_providers.
75
78
    """
76
 
    
 
79
 
77
80
    def __init__(self, parent_providers):
78
81
        self._parent_providers = parent_providers
79
82
 
95
98
        """
96
99
        found = {}
97
100
        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
98
117
        for parents_provider in self._parent_providers:
99
118
            new_found = parents_provider.get_parent_map(remaining)
100
119
            found.update(new_found)
154
173
            return None
155
174
        return dict(self._cache)
156
175
 
 
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
 
157
187
    def get_parent_map(self, keys):
158
188
        """See StackedParentsProvider.get_parent_map."""
159
189
        cache = self._cache
183
213
            self.missing_keys.add(key)
184
214
 
185
215
 
 
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
 
186
233
class Graph(object):
187
234
    """Provide incremental access to revision graphs.
188
235
 
237
284
        common ancestor of all border ancestors, because this shows that it
238
285
        cannot be a descendant of any border ancestor.
239
286
 
240
 
        The scaling of this operation should be proportional to
 
287
        The scaling of this operation should be proportional to:
 
288
 
241
289
        1. The number of uncommon ancestors
242
290
        2. The number of border ancestors
243
291
        3. The length of the shortest path between a border ancestor and an
375
423
 
376
424
        :param unique_revision: The revision_id whose ancestry we are
377
425
            interested in.
378
 
            XXX: Would this API be better if we allowed multiple revisions on
379
 
                 to be searched here?
 
426
            (XXX: Would this API be better if we allowed multiple revisions on
 
427
            to be searched here?)
380
428
        :param common_revisions: Revision_ids of ancestries to exclude.
381
429
        :return: A set of revisions in the ancestry of unique_revision
382
430
        """
1300
1348
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
1301
1349
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
1302
1350
 
1303
 
    def get_result(self):
1304
 
        """Get a SearchResult for the current state of this searcher.
 
1351
    def get_state(self):
 
1352
        """Get the current state of this searcher.
1305
1353
 
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.
 
1354
        :return: Tuple with started keys, excludes and included keys
1309
1355
        """
1310
1356
        if self._returning == 'next':
1311
1357
            # We have to know the current nodes children to be able to list the
1322
1368
            next_query = self._next_query
1323
1369
        excludes = self._stopped_keys.union(next_query)
1324
1370
        included_keys = self.seen.difference(excludes)
1325
 
        return SearchResult(self._started_keys, excludes, len(included_keys),
 
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),
1326
1383
            included_keys)
1327
1384
 
1328
1385
    def step(self):
1405
1462
        parents_of_found = set()
1406
1463
        # revisions may contain nodes that point to other nodes in revisions:
1407
1464
        # we want to filter them out.
1408
 
        self.seen.update(revisions)
 
1465
        seen = self.seen
 
1466
        seen.update(revisions)
1409
1467
        parent_map = self._parents_provider.get_parent_map(revisions)
1410
1468
        found_revisions.update(parent_map)
1411
1469
        for rev_id, parents in parent_map.iteritems():
1412
1470
            if parents is None:
1413
1471
                continue
1414
 
            new_found_parents = [p for p in parents if p not in self.seen]
 
1472
            new_found_parents = [p for p in parents if p not in seen]
1415
1473
            if new_found_parents:
1416
1474
                # Calling set.update() with an empty generator is actually
1417
1475
                # rather expensive.
1536
1594
            return revs, ghosts
1537
1595
 
1538
1596
 
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)
 
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
1680
1610
 
1681
1611
 
1682
1612
def collapse_linear_regions(parent_map):
1768
1698
        return set([h[0] for h in head_keys])
1769
1699
 
1770
1700
    def merge_sort(self, tip_revision):
1771
 
        return self._graph.merge_sort((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])
1772
1708
 
1773
1709
 
1774
1710
_counters = [0,0,0,0,0,0,0]