226
204
return (left.difference(right).difference(common),
227
205
right.difference(left).difference(common))
207
@symbol_versioning.deprecated_method(symbol_versioning.one_one)
208
def get_parents(self, revisions):
209
"""Find revision ids of the parents of a list of revisions
211
A list is returned of the same length as the input. Each entry
212
is a list of parent ids for the corresponding input revision.
214
[NULL_REVISION] is used as the parent of the first user-committed
215
revision. Its parent list is empty.
217
If the revision is not present (i.e. a ghost), None is used in place
218
of the list of parents.
220
Deprecated in bzr 1.2 - please see get_parent_map.
222
parents = self.get_parent_map(revisions)
223
return [parent.get(r, None) for r in revisions]
225
def get_parent_map(self, revisions):
226
"""Get a map of key:parent_list for revisions.
228
This implementation delegates to get_parents, for old parent_providers
229
that do not supply get_parent_map.
232
for rev, parents in self.get_parents(revisions):
233
if parents is not None:
234
result[rev] = parents
229
237
def _make_breadth_first_searcher(self, revisions):
230
238
return _BreadthFirstSearcher(revisions, self)
500
508
def __init__(self, revisions, parents_provider):
501
self._start = set(revisions)
502
self._search_revisions = None
503
self.seen = set(revisions)
510
self._next_query = set(revisions)
512
self._started_keys = set(self._next_query)
513
self._stopped_keys = set()
504
514
self._parents_provider = parents_provider
515
self._returning = 'next_with_ghosts'
516
self._current_present = set()
517
self._current_ghosts = set()
518
self._current_parents = {}
506
520
def __repr__(self):
507
if self._search_revisions is not None:
508
search = 'searching=%r' % (list(self._search_revisions),)
510
search = 'starting=%r' % (list(self._start),)
511
return ('_BreadthFirstSearcher(%s,'
512
' seen=%r)' % (search, list(self.seen)))
525
search = '%s=%r' % (prefix, list(self._next_query))
526
return ('_BreadthFirstSearcher(iterations=%d, %s,'
527
' seen=%r)' % (self._iterations, search, list(self.seen)))
529
def get_result(self):
530
"""Get a SearchResult for the current state of this searcher.
532
:return: A SearchResult for this search so far. The SearchResult is
533
static - the search can be advanced and the search result will not
534
be invalidated or altered.
536
if self._returning == 'next':
537
# We have to know the current nodes children to be able to list the
538
# exclude keys for them. However, while we could have a second
539
# look-ahead result buffer and shuffle things around, this method
540
# is typically only called once per search - when memoising the
541
# results of the search.
542
found, ghosts, next, parents = self._do_query(self._next_query)
543
# pretend we didn't query: perhaps we should tweak _do_query to be
544
# entirely stateless?
545
self.seen.difference_update(next)
546
next_query = next.union(ghosts)
548
next_query = self._next_query
549
excludes = self._stopped_keys.union(next_query)
550
included_keys = self.seen.difference(excludes)
551
return SearchResult(self._started_keys, excludes, len(included_keys),
515
555
"""Return the next ancestors of this revision.
517
557
Ancestors are returned in the order they are seen in a breadth-first
518
traversal. No ancestor will be returned more than once.
558
traversal. No ancestor will be returned more than once. Ancestors are
559
returned before their parentage is queried, so ghosts and missing
560
revisions (including the start revisions) are included in the result.
561
This can save a round trip in LCA style calculation by allowing
562
convergence to be detected without reading the data for the revision
563
the convergence occurs on.
565
:return: A set of revision_ids.
520
if self._search_revisions is None:
521
self._search_revisions = self._start
567
if self._returning != 'next':
568
# switch to returning the query, not the results.
569
self._returning = 'next'
570
self._iterations += 1
523
new_search_revisions = set()
524
parent_map = self._parents_provider.get_parent_map(
525
self._search_revisions)
526
for parents in parent_map.itervalues():
527
new_search_revisions.update(p for p in parents if
529
self._search_revisions = new_search_revisions
530
if len(self._search_revisions) == 0:
531
raise StopIteration()
532
self.seen.update(self._search_revisions)
533
return self._search_revisions
573
if len(self._next_query) == 0:
574
raise StopIteration()
575
# We have seen what we're querying at this point as we are returning
576
# the query, not the results.
577
self.seen.update(self._next_query)
578
return self._next_query
580
def next_with_ghosts(self):
581
"""Return the next found ancestors, with ghosts split out.
583
Ancestors are returned in the order they are seen in a breadth-first
584
traversal. No ancestor will be returned more than once. Ancestors are
585
returned only after asking for their parents, which allows us to detect
586
which revisions are ghosts and which are not.
588
:return: A tuple with (present ancestors, ghost ancestors) sets.
590
if self._returning != 'next_with_ghosts':
591
# switch to returning the results, not the current query.
592
self._returning = 'next_with_ghosts'
594
if len(self._next_query) == 0:
595
raise StopIteration()
597
return self._current_present, self._current_ghosts
600
"""Advance the search.
602
Updates self.seen, self._next_query, self._current_present,
603
self._current_ghosts, self._current_parents and self._iterations.
605
self._iterations += 1
606
found, ghosts, next, parents = self._do_query(self._next_query)
607
self._current_present = found
608
self._current_ghosts = ghosts
609
self._next_query = next
610
self._current_parents = parents
611
# ghosts are implicit stop points, otherwise the search cannot be
612
# repeated when ghosts are filled.
613
self._stopped_keys.update(ghosts)
615
def _do_query(self, revisions):
616
"""Query for revisions.
618
Adds revisions to the seen set.
620
:param revisions: Revisions to query.
621
:return: A tuple: (set(found_revisions), set(ghost_revisions),
622
set(parents_of_found_revisions), dict(found_revisions:parents)).
624
found_parents = set()
625
parents_of_found = set()
626
# revisions may contain nodes that point to other nodes in revisions:
627
# we want to filter them out.
628
self.seen.update(revisions)
629
parent_map = self._parents_provider.get_parent_map(revisions)
630
for rev_id, parents in parent_map.iteritems():
631
found_parents.add(rev_id)
632
parents_of_found.update(p for p in parents if p not in self.seen)
633
ghost_parents = revisions - found_parents
634
return found_parents, ghost_parents, parents_of_found, parent_map
535
636
def __iter__(self):
554
655
None of the specified revisions are required to be present in the
555
656
search list. In this case, the call is a no-op.
557
stopped = self._search_revisions.intersection(revisions)
558
self._search_revisions = self._search_revisions.difference(revisions)
658
revisions = frozenset(revisions)
659
if self._returning == 'next':
660
stopped = self._next_query.intersection(revisions)
661
self._next_query = self._next_query.difference(revisions)
663
stopped_present = self._current_present.intersection(revisions)
664
stopped = stopped_present.union(
665
self._current_ghosts.intersection(revisions))
666
self._current_present.difference_update(stopped)
667
self._current_ghosts.difference_update(stopped)
668
# stopping 'x' should stop returning parents of 'x', but
669
# not if 'y' always references those same parents
670
stop_rev_references = {}
671
for rev in stopped_present:
672
for parent_id in self._current_parents[rev]:
673
if parent_id not in stop_rev_references:
674
stop_rev_references[parent_id] = 0
675
stop_rev_references[parent_id] += 1
676
# if only the stopped revisions reference it, the ref count will be
678
for parents in self._current_parents.itervalues():
679
for parent_id in parents:
681
stop_rev_references[parent_id] -= 1
685
for rev_id, refs in stop_rev_references.iteritems():
687
stop_parents.add(rev_id)
688
self._next_query.difference_update(stop_parents)
689
self._stopped_keys.update(stopped)
561
692
def start_searching(self, revisions):
562
if self._search_revisions is None:
563
self._start = set(revisions)
693
"""Add revisions to the search.
695
The parents of revisions will be returned from the next call to next()
696
or next_with_ghosts(). If next_with_ghosts was the most recently used
697
next* call then the return value is the result of looking up the
698
ghost/not ghost status of revisions. (A tuple (present, ghosted)).
700
revisions = frozenset(revisions)
701
self._started_keys.update(revisions)
702
new_revisions = revisions.difference(self.seen)
703
revs, ghosts, query, parents = self._do_query(revisions)
704
self._stopped_keys.update(ghosts)
705
if self._returning == 'next':
706
self._next_query.update(new_revisions)
565
self._search_revisions.update(revisions.difference(self.seen))
566
self.seen.update(revisions)
708
# perform a query on revisions
709
self._current_present.update(revs)
710
self._current_ghosts.update(ghosts)
711
self._next_query.update(query)
712
self._current_parents.update(parents)
716
class SearchResult(object):
717
"""The result of a breadth first search.
719
A SearchResult provides the ability to reconstruct the search or access a
720
set of the keys the search found.
723
def __init__(self, start_keys, exclude_keys, key_count, keys):
724
"""Create a SearchResult.
726
:param start_keys: The keys the search started at.
727
:param exclude_keys: The keys the search excludes.
728
:param key_count: The total number of keys (from start to but not
730
:param keys: The keys the search found. Note that in future we may get
731
a SearchResult from a smart server, in which case the keys list is
732
not necessarily immediately available.
734
self._recipe = (start_keys, exclude_keys, key_count)
735
self._keys = frozenset(keys)
737
def get_recipe(self):
738
"""Return a recipe that can be used to replay this search.
740
The recipe allows reconstruction of the same results at a later date
741
without knowing all the found keys. The essential elements are a list
742
of keys to start and and to stop at. In order to give reproducible
743
results when ghosts are encountered by a search they are automatically
744
added to the exclude list (or else ghost filling may alter the
747
:return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
748
recreate the results of this search, create a breadth first
749
searcher on the same graph starting at start_keys. Then call next()
750
(or next_with_ghosts()) repeatedly, and on every result, call
751
stop_searching_any on any keys from the exclude_keys set. The
752
revision_count value acts as a trivial cross-check - the found
753
revisions of the new search should have as many elements as
754
revision_count. If it does not, then additional revisions have been
755
ghosted since the search was executed the first time and the second
761
"""Return the keys found in this search.
763
:return: A set of keys.