~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Patch Queue Manager
  • Date: 2011-10-09 13:52:06 UTC
  • mfrom: (6202.1.3 revno-revision)
  • Revision ID: pqm@pqm.ubuntu.com-20111009135206-t3utsln6mtzv9eut
(jelmer) Add a --revision argument to 'bzr revno'. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
58
58
    def __repr__(self):
59
59
        return 'DictParentsProvider(%r)' % self.ancestry
60
60
 
 
61
    # Note: DictParentsProvider does not implement get_cached_parent_map
 
62
    #       Arguably, the data is clearly cached in memory. However, this class
 
63
    #       is mostly used for testing, and it keeps the tests clean to not
 
64
    #       change it.
 
65
 
61
66
    def get_parent_map(self, keys):
62
67
        """See StackedParentsProvider.get_parent_map"""
63
68
        ancestry = self.ancestry
64
 
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
 
69
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
65
70
 
66
71
 
67
72
class StackedParentsProvider(object):
68
73
    """A parents provider which stacks (or unions) multiple providers.
69
 
    
 
74
 
70
75
    The providers are queries in the order of the provided parent_providers.
71
76
    """
72
 
    
 
77
 
73
78
    def __init__(self, parent_providers):
74
79
        self._parent_providers = parent_providers
75
80
 
91
96
        """
92
97
        found = {}
93
98
        remaining = set(keys)
 
99
        # This adds getattr() overhead to each get_parent_map call. However,
 
100
        # this is StackedParentsProvider, which means we're dealing with I/O
 
101
        # (either local indexes, or remote RPCs), so CPU overhead should be
 
102
        # minimal.
 
103
        for parents_provider in self._parent_providers:
 
104
            get_cached = getattr(parents_provider, 'get_cached_parent_map',
 
105
                                 None)
 
106
            if get_cached is None:
 
107
                continue
 
108
            new_found = get_cached(remaining)
 
109
            found.update(new_found)
 
110
            remaining.difference_update(new_found)
 
111
            if not remaining:
 
112
                break
 
113
        if not remaining:
 
114
            return found
94
115
        for parents_provider in self._parent_providers:
95
116
            new_found = parents_provider.get_parent_map(remaining)
96
117
            found.update(new_found)
150
171
            return None
151
172
        return dict(self._cache)
152
173
 
 
174
    def get_cached_parent_map(self, keys):
 
175
        """Return items from the cache.
 
176
 
 
177
        This returns the same info as get_parent_map, but explicitly does not
 
178
        invoke the supplied ParentsProvider to search for uncached values.
 
179
        """
 
180
        cache = self._cache
 
181
        if cache is None:
 
182
            return {}
 
183
        return dict([(key, cache[key]) for key in keys if key in cache])
 
184
 
153
185
    def get_parent_map(self, keys):
154
186
        """See StackedParentsProvider.get_parent_map."""
155
187
        cache = self._cache
1419
1451
        parents_of_found = set()
1420
1452
        # revisions may contain nodes that point to other nodes in revisions:
1421
1453
        # we want to filter them out.
1422
 
        self.seen.update(revisions)
 
1454
        seen = self.seen
 
1455
        seen.update(revisions)
1423
1456
        parent_map = self._parents_provider.get_parent_map(revisions)
1424
1457
        found_revisions.update(parent_map)
1425
1458
        for rev_id, parents in parent_map.iteritems():
1426
1459
            if parents is None:
1427
1460
                continue
1428
 
            new_found_parents = [p for p in parents if p not in self.seen]
 
1461
            new_found_parents = [p for p in parents if p not in seen]
1429
1462
            if new_found_parents:
1430
1463
                # Calling set.update() with an empty generator is actually
1431
1464
                # rather expensive.
1891
1924
            limit=self.limit)
1892
1925
 
1893
1926
 
 
1927
def invert_parent_map(parent_map):
 
1928
    """Given a map from child => parents, create a map of parent=>children"""
 
1929
    child_map = {}
 
1930
    for child, parents in parent_map.iteritems():
 
1931
        for p in parents:
 
1932
            # Any given parent is likely to have only a small handful
 
1933
            # of children, many will have only one. So we avoid mem overhead of
 
1934
            # a list, in exchange for extra copying of tuples
 
1935
            if p not in child_map:
 
1936
                child_map[p] = (child,)
 
1937
            else:
 
1938
                child_map[p] = child_map[p] + (child,)
 
1939
    return child_map
 
1940
 
 
1941
 
 
1942
def _find_possible_heads(parent_map, tip_keys, depth):
 
1943
    """Walk backwards (towards children) through the parent_map.
 
1944
 
 
1945
    This finds 'heads' that will hopefully succinctly describe our search
 
1946
    graph.
 
1947
    """
 
1948
    child_map = invert_parent_map(parent_map)
 
1949
    heads = set()
 
1950
    current_roots = tip_keys
 
1951
    walked = set(current_roots)
 
1952
    while current_roots and depth > 0:
 
1953
        depth -= 1
 
1954
        children = set()
 
1955
        children_update = children.update
 
1956
        for p in current_roots:
 
1957
            # Is it better to pre- or post- filter the children?
 
1958
            try:
 
1959
                children_update(child_map[p])
 
1960
            except KeyError:
 
1961
                heads.add(p)
 
1962
        # If we've seen a key before, we don't want to walk it again. Note that
 
1963
        # 'children' stays relatively small while 'walked' grows large. So
 
1964
        # don't use 'difference_update' here which has to walk all of 'walked'.
 
1965
        # '.difference' is smart enough to walk only children and compare it to
 
1966
        # walked.
 
1967
        children = children.difference(walked)
 
1968
        walked.update(children)
 
1969
        current_roots = children
 
1970
    if current_roots:
 
1971
        # We walked to the end of depth, so these are the new tips.
 
1972
        heads.update(current_roots)
 
1973
    return heads
 
1974
 
 
1975
 
 
1976
def _run_search(parent_map, heads, exclude_keys):
 
1977
    """Given a parent map, run a _BreadthFirstSearcher on it.
 
1978
 
 
1979
    Start at heads, walk until you hit exclude_keys. As a further improvement,
 
1980
    watch for any heads that you encounter while walking, which means they were
 
1981
    not heads of the search.
 
1982
 
 
1983
    This is mostly used to generate a succinct recipe for how to walk through
 
1984
    most of parent_map.
 
1985
 
 
1986
    :return: (_BreadthFirstSearcher, set(heads_encountered_by_walking))
 
1987
    """
 
1988
    g = Graph(DictParentsProvider(parent_map))
 
1989
    s = g._make_breadth_first_searcher(heads)
 
1990
    found_heads = set()
 
1991
    while True:
 
1992
        try:
 
1993
            next_revs = s.next()
 
1994
        except StopIteration:
 
1995
            break
 
1996
        for parents in s._current_parents.itervalues():
 
1997
            f_heads = heads.intersection(parents)
 
1998
            if f_heads:
 
1999
                found_heads.update(f_heads)
 
2000
        stop_keys = exclude_keys.intersection(next_revs)
 
2001
        if stop_keys:
 
2002
            s.stop_searching_any(stop_keys)
 
2003
    for parents in s._current_parents.itervalues():
 
2004
        f_heads = heads.intersection(parents)
 
2005
        if f_heads:
 
2006
            found_heads.update(f_heads)
 
2007
    return s, found_heads
 
2008
 
 
2009
 
 
2010
def limited_search_result_from_parent_map(parent_map, missing_keys, tip_keys,
 
2011
                                          depth):
 
2012
    """Transform a parent_map that is searching 'tip_keys' into an
 
2013
    approximate SearchResult.
 
2014
 
 
2015
    We should be able to generate a SearchResult from a given set of starting
 
2016
    keys, that covers a subset of parent_map that has the last step pointing at
 
2017
    tip_keys. This is to handle the case that really-long-searches shouldn't be
 
2018
    started from scratch on each get_parent_map request, but we *do* want to
 
2019
    filter out some of the keys that we've already seen, so we don't get
 
2020
    information that we already know about on every request.
 
2021
 
 
2022
    The server will validate the search (that starting at start_keys and
 
2023
    stopping at stop_keys yields the exact key_count), so we have to be careful
 
2024
    to give an exact recipe.
 
2025
 
 
2026
    Basic algorithm is:
 
2027
        1) Invert parent_map to get child_map (todo: have it cached and pass it
 
2028
           in)
 
2029
        2) Starting at tip_keys, walk towards children for 'depth' steps.
 
2030
        3) At that point, we have the 'start' keys.
 
2031
        4) Start walking parent_map from 'start' keys, counting how many keys
 
2032
           are seen, and generating stop_keys for anything that would walk
 
2033
           outside of the parent_map.
 
2034
 
 
2035
    :param parent_map: A map from {child_id: (parent_ids,)}
 
2036
    :param missing_keys: parent_ids that we know are unavailable
 
2037
    :param tip_keys: the revision_ids that we are searching
 
2038
    :param depth: How far back to walk.
 
2039
    """
 
2040
    if not parent_map:
 
2041
        # No search to send, because we haven't done any searching yet.
 
2042
        return [], [], 0
 
2043
    heads = _find_possible_heads(parent_map, tip_keys, depth)
 
2044
    s, found_heads = _run_search(parent_map, heads, set(tip_keys))
 
2045
    _, start_keys, exclude_keys, key_count = s.get_result().get_recipe()
 
2046
    if found_heads:
 
2047
        # Anything in found_heads are redundant start_keys, we hit them while
 
2048
        # walking, so we can exclude them from the start list.
 
2049
        start_keys = set(start_keys).difference(found_heads)
 
2050
    return start_keys, exclude_keys, key_count
 
2051
 
 
2052
 
 
2053
def search_result_from_parent_map(parent_map, missing_keys):
 
2054
    """Transform a parent_map into SearchResult information."""
 
2055
    if not parent_map:
 
2056
        # parent_map is empty or None, simple search result
 
2057
        return [], [], 0
 
2058
    # start_set is all the keys in the cache
 
2059
    start_set = set(parent_map)
 
2060
    # result set is all the references to keys in the cache
 
2061
    result_parents = set()
 
2062
    for parents in parent_map.itervalues():
 
2063
        result_parents.update(parents)
 
2064
    stop_keys = result_parents.difference(start_set)
 
2065
    # We don't need to send ghosts back to the server as a position to
 
2066
    # stop either.
 
2067
    stop_keys.difference_update(missing_keys)
 
2068
    key_count = len(parent_map)
 
2069
    if (revision.NULL_REVISION in result_parents
 
2070
        and revision.NULL_REVISION in missing_keys):
 
2071
        # If we pruned NULL_REVISION from the stop_keys because it's also
 
2072
        # in our cache of "missing" keys we need to increment our key count
 
2073
        # by 1, because the reconsitituted SearchResult on the server will
 
2074
        # still consider NULL_REVISION to be an included key.
 
2075
        key_count += 1
 
2076
    included_keys = start_set.intersection(result_parents)
 
2077
    start_set.difference_update(included_keys)
 
2078
    return start_set, stop_keys, key_count
 
2079
 
 
2080
 
1894
2081
def collapse_linear_regions(parent_map):
1895
2082
    """Collapse regions of the graph that are 'linear'.
1896
2083