508
508
def __init__(self, revisions, parents_provider):
509
self._start = set(revisions)
510
self._search_revisions = None
511
self.seen = set(revisions)
510
self._next_query = set(revisions)
512
512
self._parents_provider = parents_provider
513
self._returning = 'next_with_ghosts'
514
515
def __repr__(self):
515
if self._search_revisions is not None:
516
search = 'searching=%r' % (list(self._search_revisions),)
518
search = 'starting=%r' % (list(self._start),)
519
return ('_BreadthFirstSearcher(%s,'
520
' seen=%r)' % (search, list(self.seen)))
520
search = '%s=%r' % (prefix, list(self._next_query))
521
return ('_BreadthFirstSearcher(iterations=%d, %s,'
522
' seen=%r)' % (self._iterations, search, list(self.seen)))
523
525
"""Return the next ancestors of this revision.
525
527
Ancestors are returned in the order they are seen in a breadth-first
526
traversal. No ancestor will be returned more than once.
528
traversal. No ancestor will be returned more than once. Ancestors are
529
returned before their parentage is queried, so ghosts and missing
530
revisions (including the start revisions) are included in the result.
531
This can save a round trip in LCA style calculation by allowing
532
convergence to be detected without reading the data for the revision
533
the convergence occurs on.
535
:return: A set of revision_ids.
528
if self._search_revisions is None:
529
self._search_revisions = self._start
537
if self._returning != 'next':
538
# switch to returning the query, not the results.
539
self._returning = 'next'
540
self._iterations += 1
541
self.seen.update(self._next_query)
531
new_search_revisions = set()
532
parent_map = self._parents_provider.get_parent_map(
533
self._search_revisions)
534
for parents in parent_map.itervalues():
535
new_search_revisions.update(p for p in parents if
537
self._search_revisions = new_search_revisions
538
if len(self._search_revisions) == 0:
539
raise StopIteration()
540
self.seen.update(self._search_revisions)
541
return self._search_revisions
544
if len(self._next_query) == 0:
545
raise StopIteration()
546
return self._next_query
548
def next_with_ghosts(self):
549
"""Return the next found ancestors, with ghosts split out.
551
Ancestors are returned in the order they are seen in a breadth-first
552
traversal. No ancestor will be returned more than once. Ancestors are
553
returned only after asking for their parents, which allows us to detect
554
which revisions are ghosts and which are not.
556
:return: A tuple with (present ancestors, ghost ancestors) sets.
558
if self._returning != 'next_with_ghosts':
559
# switch to returning the results, not the current query.
560
self._returning = 'next_with_ghosts'
562
if len(self._next_query) == 0:
563
raise StopIteration()
565
return self._current_present, self._current_ghosts
568
"""Advance the search.
570
Updates self.seen, self._next_query, self._current_present,
571
self._current_ghosts, self._current_parents and self._iterations.
573
self._iterations += 1
574
found, ghosts, next, parents = self._do_query(self._next_query)
575
self._current_present = found
576
self._current_ghosts = ghosts
577
self._next_query = next
578
self._current_parents = parents
580
def _do_query(self, revisions):
581
"""Query for revisions.
583
:param revisions: Revisions to query.
584
:return: A tuple: (set(found_revisions), set(ghost_revisions),
585
set(parents_of_found_revisions), dict(found_revisions:parents)).
587
found_parents = set()
588
parents_of_found = set()
589
parent_map = self._parents_provider.get_parent_map(revisions)
590
for rev_id, parents in parent_map.iteritems():
591
found_parents.add(rev_id)
592
parents_of_found.update(p for p in parents if p not in self.seen)
593
ghost_parents = revisions - found_parents
594
self.seen.update(parents_of_found)
595
return found_parents, ghost_parents, parents_of_found, parent_map
543
597
def __iter__(self):
562
616
None of the specified revisions are required to be present in the
563
617
search list. In this case, the call is a no-op.
565
stopped = self._search_revisions.intersection(revisions)
566
self._search_revisions = self._search_revisions.difference(revisions)
619
revisions = frozenset(revisions)
620
if self._returning == 'next':
621
stopped = self._next_query.intersection(revisions)
622
self._next_query = self._next_query.difference(revisions)
625
stopped.update(self._current_present.intersection(revisions))
626
stopped.update(self._current_ghosts.intersection(revisions))
627
self._current_present.difference_update(stopped)
628
self._current_ghosts.difference_update(stopped)
629
# stopping 'x' should stop returning parents of 'x', but
630
# not if 'y' always references those same parents
631
stop_rev_references = {}
633
for parent_id in self._current_parents[rev]:
634
if parent_id not in stop_rev_references:
635
stop_rev_references[parent_id] = 0
636
stop_rev_references[parent_id] += 1
637
# if only the stopped revisions reference it, the ref count will be
639
for parents in self._current_parents.itervalues():
640
for parent_id in parents:
642
stop_rev_references[parent_id] -= 1
646
for rev_id, refs in stop_rev_references.iteritems():
648
stop_parents.add(rev_id)
649
self._next_query.difference_update(stop_parents)
569
652
def start_searching(self, revisions):
570
if self._search_revisions is None:
571
self._start = set(revisions)
653
"""Add revisions to the search.
655
The parents of revisions will be returned from the next call to next()
656
or next_with_ghosts(). If next_with_ghosts was the most recently used
657
next* call then the return value is the result of looking up the
658
ghost/not ghost status of revisions. (A tuple (present, ghosted)).
660
revisions = frozenset(revisions)
661
if self._returning == 'next':
662
self._next_query.update(revisions.difference(self.seen))
663
self.seen.update(revisions)
573
self._search_revisions.update(revisions.difference(self.seen))
574
self.seen.update(revisions)
665
# perform a query on revisions
666
revs, ghosts, query, parents = self._do_query(revisions)
667
self._current_present.update(revs)
668
self._current_ghosts.update(ghosts)
669
self._next_query.update(query)
670
self._current_parents.update(parents)