179
183
            self.missing_keys.add(key)
 
182
 
class CallableToParentsProviderAdapter(object):
 
183
 
    """A parents provider that adapts any callable to the parents provider API.
 
185
 
    i.e. it accepts calls to self.get_parent_map and relays them to the
 
186
 
    callable it was constructed with.
 
189
 
    def __init__(self, a_callable):
 
190
 
        self.callable = a_callable
 
193
 
        return "%s(%r)" % (self.__class__.__name__, self.callable)
 
195
 
    def get_parent_map(self, keys):
 
196
 
        return self.callable(keys)
 
199
186
class Graph(object):
 
200
187
    """Provide incremental access to revision graphs.
 
 
272
258
        right = searchers[1].seen
 
273
259
        return (left.difference(right), right.difference(left))
 
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(
 
279
 
        graph = Graph(DictParentsProvider(child_map))
 
280
 
        searcher = graph._make_breadth_first_searcher([old_key])
 
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)
 
295
 
    def get_child_map(self, keys):
 
296
 
        """Get a mapping from parents to children of the specified keys.
 
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.
 
302
 
        parent_map = self._parents_provider.get_parent_map(keys)
 
304
 
        for child, parents in sorted(parent_map.items()):
 
305
 
            for parent in parents:
 
306
 
                parent_child.setdefault(parent, []).append(child)
 
309
261
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
 
310
262
        """Find the left-hand distance to the NULL_REVISION.
 
 
910
862
                stop.add(parent_id)
 
913
 
    def find_lefthand_merger(self, merged_key, tip_key):
 
914
 
        """Find the first lefthand ancestor of tip_key that merged merged_key.
 
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
 
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.
 
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
 
933
865
    def find_unique_lca(self, left_revision, right_revision,
 
934
866
                        count_steps=False):
 
935
867
        """Find a unique LCA.
 
 
1550
1463
            return revs, ghosts
 
1553
 
class AbstractSearchResult(object):
 
1554
 
    """The result of a search, describing a set of keys.
 
1556
 
    Search results are typically used as the 'fetch_spec' parameter when
 
1559
 
    :seealso: AbstractSearch
 
1562
 
    def get_recipe(self):
 
1563
 
        """Return a recipe that can be used to replay this search.
 
1565
 
        The recipe allows reconstruction of the same results at a later date.
 
1567
 
        :return: A tuple of `(search_kind_str, *details)`.  The details vary by
 
1568
 
            kind of search result.
 
1570
 
        raise NotImplementedError(self.get_recipe)
 
1572
 
    def get_network_struct(self):
 
1573
 
        """Return a tuple that can be transmitted via the HPSS protocol."""
 
1574
 
        raise NotImplementedError(self.get_network_struct)
 
1577
 
        """Return the keys found in this search.
 
1579
 
        :return: A set of keys.
 
1581
 
        raise NotImplementedError(self.get_keys)
 
1584
 
        """Return false if the search lists 1 or more revisions."""
 
1585
 
        raise NotImplementedError(self.is_empty)
 
1587
 
    def refine(self, seen, referenced):
 
1588
 
        """Create a new search by refining this search.
 
1590
 
        :param seen: Revisions that have been satisfied.
 
1591
 
        :param referenced: Revision references observed while satisfying some
 
1593
 
        :return: A search result.
 
1595
 
        raise NotImplementedError(self.refine)
 
1598
 
class AbstractSearch(object):
 
1599
 
    """A search that can be executed, producing a search result.
 
1601
 
    :seealso: AbstractSearchResult
 
1605
 
        """Construct a network-ready search result from this search description.
 
1607
 
        This may take some time to search repositories, etc.
 
1609
 
        :return: A search result (an object that implements
 
1610
 
            AbstractSearchResult's API).
 
1612
 
        raise NotImplementedError(self.execute)
 
1615
 
class SearchResult(AbstractSearchResult):
 
 
1466
class SearchResult(object):
 
1616
1467
    """The result of a breadth first search.
 
1618
1469
    A SearchResult provides the ability to reconstruct the search or access a
 
 
1633
1484
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1634
1485
        self._keys = frozenset(keys)
 
1637
 
        kind, start_keys, exclude_keys, key_count = self._recipe
 
1638
 
        if len(start_keys) > 5:
 
1639
 
            start_keys_repr = repr(list(start_keys)[:5])[:-1] + ', ...]'
 
1641
 
            start_keys_repr = repr(start_keys)
 
1642
 
        if len(exclude_keys) > 5:
 
1643
 
            exclude_keys_repr = repr(list(exclude_keys)[:5])[:-1] + ', ...]'
 
1645
 
            exclude_keys_repr = repr(exclude_keys)
 
1646
 
        return '<%s %s:(%s, %s, %d)>' % (self.__class__.__name__,
 
1647
 
            kind, start_keys_repr, exclude_keys_repr, key_count)
 
1649
1487
    def get_recipe(self):
 
1650
1488
        """Return a recipe that can be used to replay this search.
 
 
1788
1606
        return PendingAncestryResult(referenced - seen, self.repo)
 
1791
 
class EmptySearchResult(AbstractSearchResult):
 
1792
 
    """An empty search result."""
 
1798
 
class EverythingResult(AbstractSearchResult):
 
1799
 
    """A search result that simply requests everything in the repository."""
 
1801
 
    def __init__(self, repo):
 
1805
 
        return '%s(%r)' % (self.__class__.__name__, self._repo)
 
1807
 
    def get_recipe(self):
 
1808
 
        raise NotImplementedError(self.get_recipe)
 
1810
 
    def get_network_struct(self):
 
1811
 
        return ('everything',)
 
1814
 
        if 'evil' in debug.debug_flags:
 
1815
 
            from bzrlib import remote
 
1816
 
            if isinstance(self._repo, remote.RemoteRepository):
 
1817
 
                # warn developers (not users) not to do this
 
1818
 
                trace.mutter_callsite(
 
1819
 
                    2, "EverythingResult(RemoteRepository).get_keys() is slow.")
 
1820
 
        return self._repo.all_revision_ids()
 
1823
 
        # It's ok for this to wrongly return False: the worst that can happen
 
1824
 
        # is that RemoteStreamSource will initiate a get_stream on an empty
 
1825
 
        # repository.  And almost all repositories are non-empty.
 
1828
 
    def refine(self, seen, referenced):
 
1829
 
        heads = set(self._repo.all_revision_ids())
 
1830
 
        heads.difference_update(seen)
 
1831
 
        heads.update(referenced)
 
1832
 
        return PendingAncestryResult(heads, self._repo)
 
1835
 
class EverythingNotInOther(AbstractSearch):
 
1836
 
    """Find all revisions in that are in one repo but not the other."""
 
1838
 
    def __init__(self, to_repo, from_repo, find_ghosts=False):
 
1839
 
        self.to_repo = to_repo
 
1840
 
        self.from_repo = from_repo
 
1841
 
        self.find_ghosts = find_ghosts
 
1844
 
        return self.to_repo.search_missing_revision_ids(
 
1845
 
            self.from_repo, find_ghosts=self.find_ghosts)
 
1848
 
class NotInOtherForRevs(AbstractSearch):
 
1849
 
    """Find all revisions missing in one repo for a some specific heads."""
 
1851
 
    def __init__(self, to_repo, from_repo, required_ids, if_present_ids=None,
 
1852
 
            find_ghosts=False, limit=None):
 
1855
 
        :param required_ids: revision IDs of heads that must be found, or else
 
1856
 
            the search will fail with NoSuchRevision.  All revisions in their
 
1857
 
            ancestry not already in the other repository will be included in
 
1859
 
        :param if_present_ids: revision IDs of heads that may be absent in the
 
1860
 
            source repository.  If present, then their ancestry not already
 
1861
 
            found in other will be included in the search result.
 
1862
 
        :param limit: maximum number of revisions to fetch
 
1864
 
        self.to_repo = to_repo
 
1865
 
        self.from_repo = from_repo
 
1866
 
        self.find_ghosts = find_ghosts
 
1867
 
        self.required_ids = required_ids
 
1868
 
        self.if_present_ids = if_present_ids
 
1872
 
        if len(self.required_ids) > 5:
 
1873
 
            reqd_revs_repr = repr(list(self.required_ids)[:5])[:-1] + ', ...]'
 
1875
 
            reqd_revs_repr = repr(self.required_ids)
 
1876
 
        if self.if_present_ids and len(self.if_present_ids) > 5:
 
1877
 
            ifp_revs_repr = repr(list(self.if_present_ids)[:5])[:-1] + ', ...]'
 
1879
 
            ifp_revs_repr = repr(self.if_present_ids)
 
1881
 
        return ("<%s from:%r to:%r find_ghosts:%r req'd:%r if-present:%r"
 
1883
 
                self.__class__.__name__, self.from_repo, self.to_repo,
 
1884
 
                self.find_ghosts, reqd_revs_repr, ifp_revs_repr,
 
1888
 
        return self.to_repo.search_missing_revision_ids(
 
1889
 
            self.from_repo, revision_ids=self.required_ids,
 
1890
 
            if_present_ids=self.if_present_ids, find_ghosts=self.find_ghosts,
 
1894
1609
def collapse_linear_regions(parent_map):
 
1895
1610
    """Collapse regions of the graph that are 'linear'.
 
 
1980
1695
        return set([h[0] for h in head_keys])
 
1982
1697
    def merge_sort(self, tip_revision):
 
1983
 
        nodes = self._graph.merge_sort((tip_revision,))
 
1985
 
            node.key = node.key[0]
 
1988
 
    def add_node(self, revision, parents):
 
1989
 
        self._graph.add_node((revision,), [(p,) for p in parents])
 
 
1698
        return self._graph.merge_sort((tip_revision,))
 
1992
1701
_counters = [0,0,0,0,0,0,0]