~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
473
473
            return set(heads)
474
474
 
475
475
 
476
 
class HeadsCache(object):
477
 
    """A cache of results for graph heads calls."""
 
476
class FrozenHeadsCache(object):
 
477
    """Cache heads() calls, assuming the caller won't modify them."""
478
478
 
479
479
    def __init__(self, graph):
480
480
        self.graph = graph
483
483
    def heads(self, keys):
484
484
        """Return the heads of keys.
485
485
 
 
486
        Similar to Graph.heads(). The main difference is that the return value
 
487
        is a frozen set which cannot be mutated.
 
488
 
486
489
        :see also: Graph.heads.
487
490
        :param keys: The keys to calculate heads for.
488
 
        :return: A set containing the heads, which may be mutated without
489
 
            affecting future lookups.
 
491
        :return: A frozenset containing the heads.
490
492
        """
491
493
        keys = frozenset(keys)
492
494
        try:
493
 
            return set(self._heads[keys])
 
495
            return self._heads[keys]
494
496
        except KeyError:
495
 
            heads = self.graph.heads(keys)
 
497
            heads = frozenset(self.graph.heads(keys))
496
498
            self._heads[keys] = heads
497
 
            return set(heads)
 
499
            return heads
 
500
 
 
501
    def cache(self, keys, heads):
 
502
        """Store a known value."""
 
503
        self._heads[frozenset(keys)] = frozenset(heads)
498
504
 
499
505
 
500
506
class _BreadthFirstSearcher(object):
509
515
        self._iterations = 0
510
516
        self._next_query = set(revisions)
511
517
        self.seen = set()
 
518
        self._started_keys = set(self._next_query)
 
519
        self._stopped_keys = set()
512
520
        self._parents_provider = parents_provider
513
521
        self._returning = 'next_with_ghosts'
 
522
        self._current_present = set()
 
523
        self._current_ghosts = set()
 
524
        self._current_parents = {}
514
525
 
515
526
    def __repr__(self):
516
527
        if self._iterations:
521
532
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
522
533
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
523
534
 
 
535
    def get_result(self):
 
536
        """Get a SearchResult for the current state of this searcher.
 
537
        
 
538
        :return: A SearchResult for this search so far. The SearchResult is
 
539
            static - the search can be advanced and the search result will not
 
540
            be invalidated or altered.
 
541
        """
 
542
        if self._returning == 'next':
 
543
            # We have to know the current nodes children to be able to list the
 
544
            # exclude keys for them. However, while we could have a second
 
545
            # look-ahead result buffer and shuffle things around, this method
 
546
            # is typically only called once per search - when memoising the
 
547
            # results of the search. 
 
548
            found, ghosts, next, parents = self._do_query(self._next_query)
 
549
            # pretend we didn't query: perhaps we should tweak _do_query to be
 
550
            # entirely stateless?
 
551
            self.seen.difference_update(next)
 
552
            next_query = next.union(ghosts)
 
553
        else:
 
554
            next_query = self._next_query
 
555
        excludes = self._stopped_keys.union(next_query)
 
556
        included_keys = self.seen.difference(excludes)
 
557
        return SearchResult(self._started_keys, excludes, len(included_keys),
 
558
            included_keys)
 
559
 
524
560
    def next(self):
525
561
        """Return the next ancestors of this revision.
526
562
 
538
574
            # switch to returning the query, not the results.
539
575
            self._returning = 'next'
540
576
            self._iterations += 1
541
 
            self.seen.update(self._next_query)
542
577
        else:
543
578
            self._advance()
544
579
        if len(self._next_query) == 0:
545
580
            raise StopIteration()
 
581
        # We have seen what we're querying at this point as we are returning
 
582
        # the query, not the results.
 
583
        self.seen.update(self._next_query)
546
584
        return self._next_query
547
585
 
548
586
    def next_with_ghosts(self):
576
614
        self._current_ghosts = ghosts
577
615
        self._next_query = next
578
616
        self._current_parents = parents
 
617
        # ghosts are implicit stop points, otherwise the search cannot be
 
618
        # repeated when ghosts are filled.
 
619
        self._stopped_keys.update(ghosts)
579
620
 
580
621
    def _do_query(self, revisions):
581
622
        """Query for revisions.
582
623
 
 
624
        Adds revisions to the seen set.
 
625
 
583
626
        :param revisions: Revisions to query.
584
627
        :return: A tuple: (set(found_revisions), set(ghost_revisions),
585
628
           set(parents_of_found_revisions), dict(found_revisions:parents)).
586
629
        """
587
630
        found_parents = set()
588
631
        parents_of_found = set()
 
632
        # revisions may contain nodes that point to other nodes in revisions:
 
633
        # we want to filter them out.
 
634
        self.seen.update(revisions)
589
635
        parent_map = self._parents_provider.get_parent_map(revisions)
590
636
        for rev_id, parents in parent_map.iteritems():
591
637
            found_parents.add(rev_id)
592
638
            parents_of_found.update(p for p in parents if p not in self.seen)
593
639
        ghost_parents = revisions - found_parents
594
 
        self.seen.update(parents_of_found)
595
640
        return found_parents, ghost_parents, parents_of_found, parent_map
596
641
 
597
642
    def __iter__(self):
621
666
            stopped = self._next_query.intersection(revisions)
622
667
            self._next_query = self._next_query.difference(revisions)
623
668
        else:
624
 
            stopped = set()
625
 
            stopped.update(self._current_present.intersection(revisions))
626
 
            stopped.update(self._current_ghosts.intersection(revisions))
 
669
            stopped_present = self._current_present.intersection(revisions)
 
670
            stopped = stopped_present.union(
 
671
                self._current_ghosts.intersection(revisions))
627
672
            self._current_present.difference_update(stopped)
628
673
            self._current_ghosts.difference_update(stopped)
629
674
            # stopping 'x' should stop returning parents of 'x', but 
630
675
            # not if 'y' always references those same parents
631
676
            stop_rev_references = {}
632
 
            for rev in stopped:
 
677
            for rev in stopped_present:
633
678
                for parent_id in self._current_parents[rev]:
634
679
                    if parent_id not in stop_rev_references:
635
680
                        stop_rev_references[parent_id] = 0
647
692
                if refs == 0:
648
693
                    stop_parents.add(rev_id)
649
694
            self._next_query.difference_update(stop_parents)
 
695
        self._stopped_keys.update(stopped)
650
696
        return stopped
651
697
 
652
698
    def start_searching(self, revisions):
658
704
        ghost/not ghost status of revisions. (A tuple (present, ghosted)).
659
705
        """
660
706
        revisions = frozenset(revisions)
 
707
        self._started_keys.update(revisions)
 
708
        new_revisions = revisions.difference(self.seen)
 
709
        revs, ghosts, query, parents = self._do_query(revisions)
 
710
        self._stopped_keys.update(ghosts)
661
711
        if self._returning == 'next':
662
 
            self._next_query.update(revisions.difference(self.seen))
663
 
            self.seen.update(revisions)
 
712
            self._next_query.update(new_revisions)
664
713
        else:
665
714
            # perform a query on revisions
666
 
            revs, ghosts, query, parents = self._do_query(revisions)
667
715
            self._current_present.update(revs)
668
716
            self._current_ghosts.update(ghosts)
669
717
            self._next_query.update(query)
670
718
            self._current_parents.update(parents)
671
719
            return revs, ghosts
 
720
 
 
721
 
 
722
class SearchResult(object):
 
723
    """The result of a breadth first search.
 
724
 
 
725
    A SearchResult provides the ability to reconstruct the search or access a
 
726
    set of the keys the search found.
 
727
    """
 
728
 
 
729
    def __init__(self, start_keys, exclude_keys, key_count, keys):
 
730
        """Create a SearchResult.
 
731
 
 
732
        :param start_keys: The keys the search started at.
 
733
        :param exclude_keys: The keys the search excludes.
 
734
        :param key_count: The total number of keys (from start to but not
 
735
            including exclude).
 
736
        :param keys: The keys the search found. Note that in future we may get
 
737
            a SearchResult from a smart server, in which case the keys list is
 
738
            not necessarily immediately available.
 
739
        """
 
740
        self._recipe = (start_keys, exclude_keys, key_count)
 
741
        self._keys = frozenset(keys)
 
742
 
 
743
    def get_recipe(self):
 
744
        """Return a recipe that can be used to replay this search.
 
745
        
 
746
        The recipe allows reconstruction of the same results at a later date
 
747
        without knowing all the found keys. The essential elements are a list
 
748
        of keys to start and and to stop at. In order to give reproducible
 
749
        results when ghosts are encountered by a search they are automatically
 
750
        added to the exclude list (or else ghost filling may alter the
 
751
        results).
 
752
 
 
753
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
 
754
            recreate the results of this search, create a breadth first
 
755
            searcher on the same graph starting at start_keys. Then call next()
 
756
            (or next_with_ghosts()) repeatedly, and on every result, call
 
757
            stop_searching_any on any keys from the exclude_keys set. The
 
758
            revision_count value acts as a trivial cross-check - the found
 
759
            revisions of the new search should have as many elements as
 
760
            revision_count. If it does not, then additional revisions have been
 
761
            ghosted since the search was executed the first time and the second
 
762
            time.
 
763
        """
 
764
        return self._recipe
 
765
 
 
766
    def get_keys(self):
 
767
        """Return the keys found in this search.
 
768
 
 
769
        :return: A set of keys.
 
770
        """
 
771
        return self._keys
 
772