258
254
right = searchers[1].seen
259
255
return (left.difference(right), right.difference(left))
257
def find_descendants(self, old_key, new_key):
258
"""Find descendants of old_key that are ancestors of new_key."""
259
child_map = self.get_child_map(self._find_descendant_ancestors(
261
graph = Graph(DictParentsProvider(child_map))
262
searcher = graph._make_breadth_first_searcher([old_key])
266
def _find_descendant_ancestors(self, old_key, new_key):
267
"""Find ancestors of new_key that may be descendants of old_key."""
268
stop = self._make_breadth_first_searcher([old_key])
269
descendants = self._make_breadth_first_searcher([new_key])
270
for revisions in descendants:
271
old_stop = stop.seen.intersection(revisions)
272
descendants.stop_searching_any(old_stop)
273
seen_stop = descendants.find_seen_ancestors(stop.step())
274
descendants.stop_searching_any(seen_stop)
275
return descendants.seen.difference(stop.seen)
277
def get_child_map(self, keys):
278
"""Get a mapping from parents to children of the specified keys.
280
This is simply the inversion of get_parent_map. Only supplied keys
281
will be discovered as children.
282
:return: a dict of key:child_list for keys.
284
parent_map = self._parents_provider.get_parent_map(keys)
286
for child, parents in sorted(parent_map.items()):
287
for parent in parents:
288
parent_child.setdefault(parent, []).append(child)
261
291
def find_distance_to_null(self, target_revision_id, known_revision_ids):
262
292
"""Find the left-hand distance to the NULL_REVISION.
862
892
stop.add(parent_id)
895
def find_lefthand_merger(self, merged_key, tip_key):
896
"""Find the first lefthand ancestor of tip_key that merged merged_key.
898
We do this by first finding the descendants of merged_key, then
899
walking through the lefthand ancestry of tip_key until we find a key
900
that doesn't descend from merged_key. Its child is the key that
903
:return: The first lefthand ancestor of tip_key to merge merged_key.
904
merged_key if it is a lefthand ancestor of tip_key.
905
None if no ancestor of tip_key merged merged_key.
907
descendants = self.find_descendants(merged_key, tip_key)
908
candidate_iterator = self.iter_lefthand_ancestry(tip_key)
909
last_candidate = None
910
for candidate in candidate_iterator:
911
if candidate not in descendants:
912
return last_candidate
913
last_candidate = candidate
865
915
def find_unique_lca(self, left_revision, right_revision,
866
916
count_steps=False):
867
917
"""Find a unique LCA.
1463
1532
return revs, ghosts
1466
class SearchResult(object):
1535
class AbstractSearchResult(object):
1536
"""The result of a search, describing a set of keys.
1538
Search results are typically used as the 'fetch_spec' parameter when
1541
:seealso: AbstractSearch
1544
def get_recipe(self):
1545
"""Return a recipe that can be used to replay this search.
1547
The recipe allows reconstruction of the same results at a later date.
1549
:return: A tuple of (search_kind_str, *details). The details vary by
1550
kind of search result.
1552
raise NotImplementedError(self.get_recipe)
1554
def get_network_struct(self):
1555
"""Return a tuple that can be transmitted via the HPSS protocol."""
1556
raise NotImplementedError(self.get_network_struct)
1559
"""Return the keys found in this search.
1561
:return: A set of keys.
1563
raise NotImplementedError(self.get_keys)
1566
"""Return false if the search lists 1 or more revisions."""
1567
raise NotImplementedError(self.is_empty)
1569
def refine(self, seen, referenced):
1570
"""Create a new search by refining this search.
1572
:param seen: Revisions that have been satisfied.
1573
:param referenced: Revision references observed while satisfying some
1575
:return: A search result.
1577
raise NotImplementedError(self.refine)
1580
class AbstractSearch(object):
1581
"""A search that can be executed, producing a search result.
1583
:seealso: AbstractSearchResult
1587
"""Construct a network-ready search result from this search description.
1589
This may take some time to search repositories, etc.
1591
:return: A search result (an object that implements
1592
AbstractSearchResult's API).
1594
raise NotImplementedError(self.execute)
1597
class SearchResult(AbstractSearchResult):
1467
1598
"""The result of a breadth first search.
1469
1600
A SearchResult provides the ability to reconstruct the search or access a
1484
1615
self._recipe = ('search', start_keys, exclude_keys, key_count)
1485
1616
self._keys = frozenset(keys)
1619
kind, start_keys, exclude_keys, key_count = self._recipe
1620
if len(start_keys) > 5:
1621
start_keys_repr = repr(list(start_keys)[:5])[:-1] + ', ...]'
1623
start_keys_repr = repr(start_keys)
1624
if len(exclude_keys) > 5:
1625
exclude_keys_repr = repr(list(exclude_keys)[:5])[:-1] + ', ...]'
1627
exclude_keys_repr = repr(exclude_keys)
1628
return '<%s %s:(%s, %s, %d)>' % (self.__class__.__name__,
1629
kind, start_keys_repr, exclude_keys_repr, key_count)
1487
1631
def get_recipe(self):
1488
1632
"""Return a recipe that can be used to replay this search.
1606
1770
return PendingAncestryResult(referenced - seen, self.repo)
1773
class EmptySearchResult(AbstractSearchResult):
1774
"""An empty search result."""
1780
class EverythingResult(AbstractSearchResult):
1781
"""A search result that simply requests everything in the repository."""
1783
def __init__(self, repo):
1787
return '%s(%r)' % (self.__class__.__name__, self._repo)
1789
def get_recipe(self):
1790
raise NotImplementedError(self.get_recipe)
1792
def get_network_struct(self):
1793
return ('everything',)
1796
if 'evil' in debug.debug_flags:
1797
from bzrlib import remote
1798
if isinstance(self._repo, remote.RemoteRepository):
1799
# warn developers (not users) not to do this
1800
trace.mutter_callsite(
1801
2, "EverythingResult(RemoteRepository).get_keys() is slow.")
1802
return self._repo.all_revision_ids()
1805
# It's ok for this to wrongly return False: the worst that can happen
1806
# is that RemoteStreamSource will initiate a get_stream on an empty
1807
# repository. And almost all repositories are non-empty.
1810
def refine(self, seen, referenced):
1811
heads = set(self._repo.all_revision_ids())
1812
heads.difference_update(seen)
1813
heads.update(referenced)
1814
return PendingAncestryResult(heads, self._repo)
1817
class EverythingNotInOther(AbstractSearch):
1818
"""Find all revisions in that are in one repo but not the other."""
1820
def __init__(self, to_repo, from_repo, find_ghosts=False):
1821
self.to_repo = to_repo
1822
self.from_repo = from_repo
1823
self.find_ghosts = find_ghosts
1826
return self.to_repo.search_missing_revision_ids(
1827
self.from_repo, find_ghosts=self.find_ghosts)
1830
class NotInOtherForRevs(AbstractSearch):
1831
"""Find all revisions missing in one repo for a some specific heads."""
1833
def __init__(self, to_repo, from_repo, required_ids, if_present_ids=None,
1837
:param required_ids: revision IDs of heads that must be found, or else
1838
the search will fail with NoSuchRevision. All revisions in their
1839
ancestry not already in the other repository will be included in
1841
:param if_present_ids: revision IDs of heads that may be absent in the
1842
source repository. If present, then their ancestry not already
1843
found in other will be included in the search result.
1845
self.to_repo = to_repo
1846
self.from_repo = from_repo
1847
self.find_ghosts = find_ghosts
1848
self.required_ids = required_ids
1849
self.if_present_ids = if_present_ids
1852
if len(self.required_ids) > 5:
1853
reqd_revs_repr = repr(list(self.required_ids)[:5])[:-1] + ', ...]'
1855
reqd_revs_repr = repr(self.required_ids)
1856
if self.if_present_ids and len(self.if_present_ids) > 5:
1857
ifp_revs_repr = repr(list(self.if_present_ids)[:5])[:-1] + ', ...]'
1859
ifp_revs_repr = repr(self.if_present_ids)
1861
return "<%s from:%r to:%r find_ghosts:%r req'd:%r if-present:%r>" % (
1862
self.__class__.__name__, self.from_repo, self.to_repo,
1863
self.find_ghosts, reqd_revs_repr, ifp_revs_repr)
1866
return self.to_repo.search_missing_revision_ids(
1867
self.from_repo, revision_ids=self.required_ids,
1868
if_present_ids=self.if_present_ids, find_ghosts=self.find_ghosts)
1609
1871
def collapse_linear_regions(parent_map):
1610
1872
"""Collapse regions of the graph that are 'linear'.