/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-26 09:54:04 UTC
  • mfrom: (6091.2.2 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110826095404-1b0s2cpvcf97ox0z
(vila) Fix meliae test feature definitions. (Max Bowsher)

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
23
23
    revision,
24
24
    trace,
25
25
    )
26
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
27
26
 
28
27
STEP_UNIQUE_SEARCHER_EVERY = 5
29
28
 
62
61
    def get_parent_map(self, keys):
63
62
        """See StackedParentsProvider.get_parent_map"""
64
63
        ancestry = self.ancestry
65
 
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
 
64
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
66
65
 
67
 
@deprecated_function(deprecated_in((1, 16, 0)))
68
 
def _StackedParentsProvider(*args, **kwargs):
69
 
    return StackedParentsProvider(*args, **kwargs)
70
66
 
71
67
class StackedParentsProvider(object):
72
68
    """A parents provider which stacks (or unions) multiple providers.
183
179
            self.missing_keys.add(key)
184
180
 
185
181
 
 
182
class CallableToParentsProviderAdapter(object):
 
183
    """A parents provider that adapts any callable to the parents provider API.
 
184
 
 
185
    i.e. it accepts calls to self.get_parent_map and relays them to the
 
186
    callable it was constructed with.
 
187
    """
 
188
 
 
189
    def __init__(self, a_callable):
 
190
        self.callable = a_callable
 
191
 
 
192
    def __repr__(self):
 
193
        return "%s(%r)" % (self.__class__.__name__, self.callable)
 
194
 
 
195
    def get_parent_map(self, keys):
 
196
        return self.callable(keys)
 
197
 
 
198
 
186
199
class Graph(object):
187
200
    """Provide incremental access to revision graphs.
188
201
 
237
250
        common ancestor of all border ancestors, because this shows that it
238
251
        cannot be a descendant of any border ancestor.
239
252
 
240
 
        The scaling of this operation should be proportional to
 
253
        The scaling of this operation should be proportional to:
 
254
 
241
255
        1. The number of uncommon ancestors
242
256
        2. The number of border ancestors
243
257
        3. The length of the shortest path between a border ancestor and an
258
272
        right = searchers[1].seen
259
273
        return (left.difference(right), right.difference(left))
260
274
 
 
275
    def find_descendants(self, old_key, new_key):
 
276
        """Find descendants of old_key that are ancestors of new_key."""
 
277
        child_map = self.get_child_map(self._find_descendant_ancestors(
 
278
            old_key, new_key))
 
279
        graph = Graph(DictParentsProvider(child_map))
 
280
        searcher = graph._make_breadth_first_searcher([old_key])
 
281
        list(searcher)
 
282
        return searcher.seen
 
283
 
 
284
    def _find_descendant_ancestors(self, old_key, new_key):
 
285
        """Find ancestors of new_key that may be descendants of old_key."""
 
286
        stop = self._make_breadth_first_searcher([old_key])
 
287
        descendants = self._make_breadth_first_searcher([new_key])
 
288
        for revisions in descendants:
 
289
            old_stop = stop.seen.intersection(revisions)
 
290
            descendants.stop_searching_any(old_stop)
 
291
            seen_stop = descendants.find_seen_ancestors(stop.step())
 
292
            descendants.stop_searching_any(seen_stop)
 
293
        return descendants.seen.difference(stop.seen)
 
294
 
 
295
    def get_child_map(self, keys):
 
296
        """Get a mapping from parents to children of the specified keys.
 
297
 
 
298
        This is simply the inversion of get_parent_map.  Only supplied keys
 
299
        will be discovered as children.
 
300
        :return: a dict of key:child_list for keys.
 
301
        """
 
302
        parent_map = self._parents_provider.get_parent_map(keys)
 
303
        parent_child = {}
 
304
        for child, parents in sorted(parent_map.items()):
 
305
            for parent in parents:
 
306
                parent_child.setdefault(parent, []).append(child)
 
307
        return parent_child
 
308
 
261
309
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
262
310
        """Find the left-hand distance to the NULL_REVISION.
263
311
 
341
389
 
342
390
        :param unique_revision: The revision_id whose ancestry we are
343
391
            interested in.
344
 
            XXX: Would this API be better if we allowed multiple revisions on
345
 
                 to be searched here?
 
392
            (XXX: Would this API be better if we allowed multiple revisions on
 
393
            to be searched here?)
346
394
        :param common_revisions: Revision_ids of ancestries to exclude.
347
395
        :return: A set of revisions in the ancestry of unique_revision
348
396
        """
862
910
                stop.add(parent_id)
863
911
        return found
864
912
 
 
913
    def find_lefthand_merger(self, merged_key, tip_key):
 
914
        """Find the first lefthand ancestor of tip_key that merged merged_key.
 
915
 
 
916
        We do this by first finding the descendants of merged_key, then
 
917
        walking through the lefthand ancestry of tip_key until we find a key
 
918
        that doesn't descend from merged_key.  Its child is the key that
 
919
        merged merged_key.
 
920
 
 
921
        :return: The first lefthand ancestor of tip_key to merge merged_key.
 
922
            merged_key if it is a lefthand ancestor of tip_key.
 
923
            None if no ancestor of tip_key merged merged_key.
 
924
        """
 
925
        descendants = self.find_descendants(merged_key, tip_key)
 
926
        candidate_iterator = self.iter_lefthand_ancestry(tip_key)
 
927
        last_candidate = None
 
928
        for candidate in candidate_iterator:
 
929
            if candidate not in descendants:
 
930
                return last_candidate
 
931
            last_candidate = candidate
 
932
 
865
933
    def find_unique_lca(self, left_revision, right_revision,
866
934
                        count_steps=False):
867
935
        """Find a unique LCA.
919
987
                yield (ghost, None)
920
988
            pending = next_pending
921
989
 
 
990
    def iter_lefthand_ancestry(self, start_key, stop_keys=None):
 
991
        if stop_keys is None:
 
992
            stop_keys = ()
 
993
        next_key = start_key
 
994
        def get_parents(key):
 
995
            try:
 
996
                return self._parents_provider.get_parent_map([key])[key]
 
997
            except KeyError:
 
998
                raise errors.RevisionNotPresent(next_key, self)
 
999
        while True:
 
1000
            if next_key in stop_keys:
 
1001
                return
 
1002
            parents = get_parents(next_key)
 
1003
            yield next_key
 
1004
            if len(parents) == 0:
 
1005
                return
 
1006
            else:
 
1007
                next_key = parents[0]
 
1008
 
922
1009
    def iter_topo_order(self, revisions):
923
1010
        """Iterate through the input revisions in topological order.
924
1011
 
1332
1419
        parents_of_found = set()
1333
1420
        # revisions may contain nodes that point to other nodes in revisions:
1334
1421
        # we want to filter them out.
1335
 
        self.seen.update(revisions)
 
1422
        seen = self.seen
 
1423
        seen.update(revisions)
1336
1424
        parent_map = self._parents_provider.get_parent_map(revisions)
1337
1425
        found_revisions.update(parent_map)
1338
1426
        for rev_id, parents in parent_map.iteritems():
1339
1427
            if parents is None:
1340
1428
                continue
1341
 
            new_found_parents = [p for p in parents if p not in self.seen]
 
1429
            new_found_parents = [p for p in parents if p not in seen]
1342
1430
            if new_found_parents:
1343
1431
                # Calling set.update() with an empty generator is actually
1344
1432
                # rather expensive.
1463
1551
            return revs, ghosts
1464
1552
 
1465
1553
 
1466
 
class SearchResult(object):
 
1554
class AbstractSearchResult(object):
 
1555
    """The result of a search, describing a set of keys.
 
1556
    
 
1557
    Search results are typically used as the 'fetch_spec' parameter when
 
1558
    fetching revisions.
 
1559
 
 
1560
    :seealso: AbstractSearch
 
1561
    """
 
1562
 
 
1563
    def get_recipe(self):
 
1564
        """Return a recipe that can be used to replay this search.
 
1565
 
 
1566
        The recipe allows reconstruction of the same results at a later date.
 
1567
 
 
1568
        :return: A tuple of `(search_kind_str, *details)`.  The details vary by
 
1569
            kind of search result.
 
1570
        """
 
1571
        raise NotImplementedError(self.get_recipe)
 
1572
 
 
1573
    def get_network_struct(self):
 
1574
        """Return a tuple that can be transmitted via the HPSS protocol."""
 
1575
        raise NotImplementedError(self.get_network_struct)
 
1576
 
 
1577
    def get_keys(self):
 
1578
        """Return the keys found in this search.
 
1579
 
 
1580
        :return: A set of keys.
 
1581
        """
 
1582
        raise NotImplementedError(self.get_keys)
 
1583
 
 
1584
    def is_empty(self):
 
1585
        """Return false if the search lists 1 or more revisions."""
 
1586
        raise NotImplementedError(self.is_empty)
 
1587
 
 
1588
    def refine(self, seen, referenced):
 
1589
        """Create a new search by refining this search.
 
1590
 
 
1591
        :param seen: Revisions that have been satisfied.
 
1592
        :param referenced: Revision references observed while satisfying some
 
1593
            of this search.
 
1594
        :return: A search result.
 
1595
        """
 
1596
        raise NotImplementedError(self.refine)
 
1597
 
 
1598
 
 
1599
class AbstractSearch(object):
 
1600
    """A search that can be executed, producing a search result.
 
1601
 
 
1602
    :seealso: AbstractSearchResult
 
1603
    """
 
1604
 
 
1605
    def execute(self):
 
1606
        """Construct a network-ready search result from this search description.
 
1607
 
 
1608
        This may take some time to search repositories, etc.
 
1609
 
 
1610
        :return: A search result (an object that implements
 
1611
            AbstractSearchResult's API).
 
1612
        """
 
1613
        raise NotImplementedError(self.execute)
 
1614
 
 
1615
 
 
1616
class SearchResult(AbstractSearchResult):
1467
1617
    """The result of a breadth first search.
1468
1618
 
1469
1619
    A SearchResult provides the ability to reconstruct the search or access a
1484
1634
        self._recipe = ('search', start_keys, exclude_keys, key_count)
1485
1635
        self._keys = frozenset(keys)
1486
1636
 
 
1637
    def __repr__(self):
 
1638
        kind, start_keys, exclude_keys, key_count = self._recipe
 
1639
        if len(start_keys) > 5:
 
1640
            start_keys_repr = repr(list(start_keys)[:5])[:-1] + ', ...]'
 
1641
        else:
 
1642
            start_keys_repr = repr(start_keys)
 
1643
        if len(exclude_keys) > 5:
 
1644
            exclude_keys_repr = repr(list(exclude_keys)[:5])[:-1] + ', ...]'
 
1645
        else:
 
1646
            exclude_keys_repr = repr(exclude_keys)
 
1647
        return '<%s %s:(%s, %s, %d)>' % (self.__class__.__name__,
 
1648
            kind, start_keys_repr, exclude_keys_repr, key_count)
 
1649
 
1487
1650
    def get_recipe(self):
1488
1651
        """Return a recipe that can be used to replay this search.
1489
1652
 
1507
1670
        """
1508
1671
        return self._recipe
1509
1672
 
 
1673
    def get_network_struct(self):
 
1674
        start_keys = ' '.join(self._recipe[1])
 
1675
        stop_keys = ' '.join(self._recipe[2])
 
1676
        count = str(self._recipe[3])
 
1677
        return (self._recipe[0], '\n'.join((start_keys, stop_keys, count)))
 
1678
 
1510
1679
    def get_keys(self):
1511
1680
        """Return the keys found in this search.
1512
1681
 
1544
1713
        return SearchResult(pending_refs, exclude, count, keys)
1545
1714
 
1546
1715
 
1547
 
class PendingAncestryResult(object):
 
1716
class PendingAncestryResult(AbstractSearchResult):
1548
1717
    """A search result that will reconstruct the ancestry for some graph heads.
1549
1718
 
1550
1719
    Unlike SearchResult, this doesn't hold the complete search result in
1561
1730
        self.heads = frozenset(heads)
1562
1731
        self.repo = repo
1563
1732
 
 
1733
    def __repr__(self):
 
1734
        if len(self.heads) > 5:
 
1735
            heads_repr = repr(list(self.heads)[:5])[:-1]
 
1736
            heads_repr += ', <%d more>...]' % (len(self.heads) - 5,)
 
1737
        else:
 
1738
            heads_repr = repr(self.heads)
 
1739
        return '<%s heads:%s repo:%r>' % (
 
1740
            self.__class__.__name__, heads_repr, self.repo)
 
1741
 
1564
1742
    def get_recipe(self):
1565
1743
        """Return a recipe that can be used to replay this search.
1566
1744
 
1574
1752
        """
1575
1753
        return ('proxy-search', self.heads, set(), -1)
1576
1754
 
 
1755
    def get_network_struct(self):
 
1756
        parts = ['ancestry-of']
 
1757
        parts.extend(self.heads)
 
1758
        return parts
 
1759
 
1577
1760
    def get_keys(self):
1578
1761
        """See SearchResult.get_keys.
1579
1762
 
1606
1789
        return PendingAncestryResult(referenced - seen, self.repo)
1607
1790
 
1608
1791
 
 
1792
class EmptySearchResult(AbstractSearchResult):
 
1793
    """An empty search result."""
 
1794
 
 
1795
    def is_empty(self):
 
1796
        return True
 
1797
    
 
1798
 
 
1799
class EverythingResult(AbstractSearchResult):
 
1800
    """A search result that simply requests everything in the repository."""
 
1801
 
 
1802
    def __init__(self, repo):
 
1803
        self._repo = repo
 
1804
 
 
1805
    def __repr__(self):
 
1806
        return '%s(%r)' % (self.__class__.__name__, self._repo)
 
1807
 
 
1808
    def get_recipe(self):
 
1809
        raise NotImplementedError(self.get_recipe)
 
1810
 
 
1811
    def get_network_struct(self):
 
1812
        return ('everything',)
 
1813
 
 
1814
    def get_keys(self):
 
1815
        if 'evil' in debug.debug_flags:
 
1816
            from bzrlib import remote
 
1817
            if isinstance(self._repo, remote.RemoteRepository):
 
1818
                # warn developers (not users) not to do this
 
1819
                trace.mutter_callsite(
 
1820
                    2, "EverythingResult(RemoteRepository).get_keys() is slow.")
 
1821
        return self._repo.all_revision_ids()
 
1822
 
 
1823
    def is_empty(self):
 
1824
        # It's ok for this to wrongly return False: the worst that can happen
 
1825
        # is that RemoteStreamSource will initiate a get_stream on an empty
 
1826
        # repository.  And almost all repositories are non-empty.
 
1827
        return False
 
1828
 
 
1829
    def refine(self, seen, referenced):
 
1830
        heads = set(self._repo.all_revision_ids())
 
1831
        heads.difference_update(seen)
 
1832
        heads.update(referenced)
 
1833
        return PendingAncestryResult(heads, self._repo)
 
1834
 
 
1835
 
 
1836
class EverythingNotInOther(AbstractSearch):
 
1837
    """Find all revisions in that are in one repo but not the other."""
 
1838
 
 
1839
    def __init__(self, to_repo, from_repo, find_ghosts=False):
 
1840
        self.to_repo = to_repo
 
1841
        self.from_repo = from_repo
 
1842
        self.find_ghosts = find_ghosts
 
1843
 
 
1844
    def execute(self):
 
1845
        return self.to_repo.search_missing_revision_ids(
 
1846
            self.from_repo, find_ghosts=self.find_ghosts)
 
1847
 
 
1848
 
 
1849
class NotInOtherForRevs(AbstractSearch):
 
1850
    """Find all revisions missing in one repo for a some specific heads."""
 
1851
 
 
1852
    def __init__(self, to_repo, from_repo, required_ids, if_present_ids=None,
 
1853
            find_ghosts=False, limit=None):
 
1854
        """Constructor.
 
1855
 
 
1856
        :param required_ids: revision IDs of heads that must be found, or else
 
1857
            the search will fail with NoSuchRevision.  All revisions in their
 
1858
            ancestry not already in the other repository will be included in
 
1859
            the search result.
 
1860
        :param if_present_ids: revision IDs of heads that may be absent in the
 
1861
            source repository.  If present, then their ancestry not already
 
1862
            found in other will be included in the search result.
 
1863
        :param limit: maximum number of revisions to fetch
 
1864
        """
 
1865
        self.to_repo = to_repo
 
1866
        self.from_repo = from_repo
 
1867
        self.find_ghosts = find_ghosts
 
1868
        self.required_ids = required_ids
 
1869
        self.if_present_ids = if_present_ids
 
1870
        self.limit = limit
 
1871
 
 
1872
    def __repr__(self):
 
1873
        if len(self.required_ids) > 5:
 
1874
            reqd_revs_repr = repr(list(self.required_ids)[:5])[:-1] + ', ...]'
 
1875
        else:
 
1876
            reqd_revs_repr = repr(self.required_ids)
 
1877
        if self.if_present_ids and len(self.if_present_ids) > 5:
 
1878
            ifp_revs_repr = repr(list(self.if_present_ids)[:5])[:-1] + ', ...]'
 
1879
        else:
 
1880
            ifp_revs_repr = repr(self.if_present_ids)
 
1881
 
 
1882
        return ("<%s from:%r to:%r find_ghosts:%r req'd:%r if-present:%r"
 
1883
                "limit:%r>") % (
 
1884
                self.__class__.__name__, self.from_repo, self.to_repo,
 
1885
                self.find_ghosts, reqd_revs_repr, ifp_revs_repr,
 
1886
                self.limit)
 
1887
 
 
1888
    def execute(self):
 
1889
        return self.to_repo.search_missing_revision_ids(
 
1890
            self.from_repo, revision_ids=self.required_ids,
 
1891
            if_present_ids=self.if_present_ids, find_ghosts=self.find_ghosts,
 
1892
            limit=self.limit)
 
1893
 
 
1894
 
 
1895
def invert_parent_map(parent_map):
 
1896
    """Given a map from child => parents, create a map of parent=>children"""
 
1897
    child_map = {}
 
1898
    for child, parents in parent_map.iteritems():
 
1899
        for p in parents:
 
1900
            # Any given parent is likely to have only a small handful
 
1901
            # of children, many will have only one. So we avoid mem overhead of
 
1902
            # a list, in exchange for extra copying of tuples
 
1903
            if p not in child_map:
 
1904
                child_map[p] = (child,)
 
1905
            else:
 
1906
                child_map[p] = child_map[p] + (child,)
 
1907
    return child_map
 
1908
 
 
1909
 
 
1910
def _find_possible_heads(parent_map, tip_keys, depth):
 
1911
    """Walk backwards (towards children) through the parent_map.
 
1912
 
 
1913
    This finds 'heads' that will hopefully succinctly describe our search
 
1914
    graph.
 
1915
    """
 
1916
    child_map = invert_parent_map(parent_map)
 
1917
    heads = set()
 
1918
    current_roots = tip_keys
 
1919
    walked = set(current_roots)
 
1920
    while current_roots and depth > 0:
 
1921
        depth -= 1
 
1922
        children = set()
 
1923
        children_update = children.update
 
1924
        for p in current_roots:
 
1925
            # Is it better to pre- or post- filter the children?
 
1926
            try:
 
1927
                children_update(child_map[p])
 
1928
            except KeyError:
 
1929
                heads.add(p)
 
1930
        # If we've seen a key before, we don't want to walk it again. Note that
 
1931
        # 'children' stays relatively small while 'walked' grows large. So
 
1932
        # don't use 'difference_update' here which has to walk all of 'walked'.
 
1933
        # '.difference' is smart enough to walk only children and compare it to
 
1934
        # walked.
 
1935
        children = children.difference(walked)
 
1936
        walked.update(children)
 
1937
        current_roots = children
 
1938
    if current_roots:
 
1939
        # We walked to the end of depth, so these are the new tips.
 
1940
        heads.update(current_roots)
 
1941
    return heads
 
1942
 
 
1943
 
 
1944
def _run_search(parent_map, heads, exclude_keys):
 
1945
    """Given a parent map, run a _BreadthFirstSearcher on it.
 
1946
 
 
1947
    Start at heads, walk until you hit exclude_keys. As a further improvement,
 
1948
    watch for any heads that you encounter while walking, which means they were
 
1949
    not heads of the search.
 
1950
 
 
1951
    This is mostly used to generate a succinct recipe for how to walk through
 
1952
    most of parent_map.
 
1953
 
 
1954
    :return: (_BreadthFirstSearcher, set(heads_encountered_by_walking))
 
1955
    """
 
1956
    g = Graph(DictParentsProvider(parent_map))
 
1957
    s = g._make_breadth_first_searcher(heads)
 
1958
    found_heads = set()
 
1959
    while True:
 
1960
        try:
 
1961
            next_revs = s.next()
 
1962
        except StopIteration:
 
1963
            break
 
1964
        for parents in s._current_parents.itervalues():
 
1965
            f_heads = heads.intersection(parents)
 
1966
            if f_heads:
 
1967
                found_heads.update(f_heads)
 
1968
        stop_keys = exclude_keys.intersection(next_revs)
 
1969
        if stop_keys:
 
1970
            s.stop_searching_any(stop_keys)
 
1971
    for parents in s._current_parents.itervalues():
 
1972
        f_heads = heads.intersection(parents)
 
1973
        if f_heads:
 
1974
            found_heads.update(f_heads)
 
1975
    return s, found_heads
 
1976
 
 
1977
 
 
1978
def limited_search_result_from_parent_map(parent_map, missing_keys, tip_keys,
 
1979
                                          depth):
 
1980
    """Transform a parent_map that is searching 'tip_keys' into an
 
1981
    approximate SearchResult.
 
1982
 
 
1983
    We should be able to generate a SearchResult from a given set of starting
 
1984
    keys, that covers a subset of parent_map that has the last step pointing at
 
1985
    tip_keys. This is to handle the case that really-long-searches shouldn't be
 
1986
    started from scratch on each get_parent_map request, but we *do* want to
 
1987
    filter out some of the keys that we've already seen, so we don't get
 
1988
    information that we already know about on every request.
 
1989
 
 
1990
    The server will validate the search (that starting at start_keys and
 
1991
    stopping at stop_keys yields the exact key_count), so we have to be careful
 
1992
    to give an exact recipe.
 
1993
 
 
1994
    Basic algorithm is:
 
1995
        1) Invert parent_map to get child_map (todo: have it cached and pass it
 
1996
           in)
 
1997
        2) Starting at tip_keys, walk towards children for 'depth' steps.
 
1998
        3) At that point, we have the 'start' keys.
 
1999
        4) Start walking parent_map from 'start' keys, counting how many keys
 
2000
           are seen, and generating stop_keys for anything that would walk
 
2001
           outside of the parent_map.
 
2002
 
 
2003
    :param parent_map: A map from {child_id: (parent_ids,)}
 
2004
    :param missing_keys: parent_ids that we know are unavailable
 
2005
    :param tip_keys: the revision_ids that we are searching
 
2006
    :param depth: How far back to walk.
 
2007
    """
 
2008
    if not parent_map:
 
2009
        # No search to send, because we haven't done any searching yet.
 
2010
        return [], [], 0
 
2011
    heads = _find_possible_heads(parent_map, tip_keys, depth)
 
2012
    s, found_heads = _run_search(parent_map, heads, set(tip_keys))
 
2013
    _, start_keys, exclude_keys, key_count = s.get_result().get_recipe()
 
2014
    if found_heads:
 
2015
        # Anything in found_heads are redundant start_keys, we hit them while
 
2016
        # walking, so we can exclude them from the start list.
 
2017
        start_keys = set(start_keys).difference(found_heads)
 
2018
    return start_keys, exclude_keys, key_count
 
2019
 
 
2020
 
 
2021
def search_result_from_parent_map(parent_map, missing_keys):
 
2022
    """Transform a parent_map into SearchResult information."""
 
2023
    if not parent_map:
 
2024
        # parent_map is empty or None, simple search result
 
2025
        return [], [], 0
 
2026
    # start_set is all the keys in the cache
 
2027
    start_set = set(parent_map)
 
2028
    # result set is all the references to keys in the cache
 
2029
    result_parents = set()
 
2030
    for parents in parent_map.itervalues():
 
2031
        result_parents.update(parents)
 
2032
    stop_keys = result_parents.difference(start_set)
 
2033
    # We don't need to send ghosts back to the server as a position to
 
2034
    # stop either.
 
2035
    stop_keys.difference_update(missing_keys)
 
2036
    key_count = len(parent_map)
 
2037
    if (revision.NULL_REVISION in result_parents
 
2038
        and revision.NULL_REVISION in missing_keys):
 
2039
        # If we pruned NULL_REVISION from the stop_keys because it's also
 
2040
        # in our cache of "missing" keys we need to increment our key count
 
2041
        # by 1, because the reconsitituted SearchResult on the server will
 
2042
        # still consider NULL_REVISION to be an included key.
 
2043
        key_count += 1
 
2044
    included_keys = start_set.intersection(result_parents)
 
2045
    start_set.difference_update(included_keys)
 
2046
    return start_set, stop_keys, key_count
 
2047
 
 
2048
 
1609
2049
def collapse_linear_regions(parent_map):
1610
2050
    """Collapse regions of the graph that are 'linear'.
1611
2051
 
1695
2135
        return set([h[0] for h in head_keys])
1696
2136
 
1697
2137
    def merge_sort(self, tip_revision):
1698
 
        return self._graph.merge_sort((tip_revision,))
 
2138
        nodes = self._graph.merge_sort((tip_revision,))
 
2139
        for node in nodes:
 
2140
            node.key = node.key[0]
 
2141
        return nodes
 
2142
 
 
2143
    def add_node(self, revision, parents):
 
2144
        self._graph.add_node((revision,), [(p,) for p in parents])
1699
2145
 
1700
2146
 
1701
2147
_counters = [0,0,0,0,0,0,0]