bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
1  | 
# Copyright (C) 2007 Canonical Ltd
 | 
2  | 
#
 | 
|
3  | 
# This program is free software; you can redistribute it and/or modify
 | 
|
4  | 
# it under the terms of the GNU General Public License as published by
 | 
|
5  | 
# the Free Software Foundation; either version 2 of the License, or
 | 
|
6  | 
# (at your option) any later version.
 | 
|
7  | 
#
 | 
|
8  | 
# This program is distributed in the hope that it will be useful,
 | 
|
9  | 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
|
10  | 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
|
11  | 
# GNU General Public License for more details.
 | 
|
12  | 
#
 | 
|
13  | 
# You should have received a copy of the GNU General Public License
 | 
|
14  | 
# along with this program; if not, write to the Free Software
 | 
|
15  | 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
|
16  | 
||
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
17  | 
from bzrlib import (  | 
18  | 
errors,  | 
|
| 
3052.1.3
by John Arbash Meinel
 deprecate revision.is_ancestor, update the callers and the tests.  | 
19  | 
revision,  | 
| 
3099.3.3
by John Arbash Meinel
 Deprecate get_parents() in favor of get_parent_map()  | 
20  | 
symbol_versioning,  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
21  | 
tsort,  | 
22  | 
    )
 | 
|
| 
2490.2.21
by Aaron Bentley
 Rename graph to deprecated_graph  | 
23  | 
from bzrlib.deprecated_graph import (node_distances, select_farthest)  | 
| 
2490.2.1
by Aaron Bentley
 Start work on GraphWalker  | 
24  | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
25  | 
# DIAGRAM of terminology
 | 
26  | 
#       A
 | 
|
27  | 
#       /\
 | 
|
28  | 
#      B  C
 | 
|
29  | 
#      |  |\
 | 
|
30  | 
#      D  E F
 | 
|
31  | 
#      |\/| |
 | 
|
32  | 
#      |/\|/
 | 
|
33  | 
#      G  H
 | 
|
34  | 
#
 | 
|
35  | 
# In this diagram, relative to G and H:
 | 
|
36  | 
# A, B, C, D, E are common ancestors.
 | 
|
37  | 
# C, D and E are border ancestors, because each has a non-common descendant.
 | 
|
38  | 
# D and E are least common ancestors because none of their descendants are
 | 
|
39  | 
# common ancestors.
 | 
|
40  | 
# C is not a least common ancestor because its descendant, E, is a common
 | 
|
41  | 
# ancestor.
 | 
|
42  | 
#
 | 
|
43  | 
# The find_unique_lca algorithm will pick A in two steps:
 | 
|
44  | 
# 1. find_lca('G', 'H') => ['D', 'E']
 | 
|
45  | 
# 2. Since len(['D', 'E']) > 1, find_lca('D', 'E') => ['A']
 | 
|
46  | 
||
47  | 
||
| 
2988.1.3
by Robert Collins
 Add a new repositoy method _generate_text_key_index for use by reconcile/check.  | 
48  | 
class DictParentsProvider(object):  | 
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
49  | 
"""A parents provider for Graph objects."""  | 
| 
2988.1.3
by Robert Collins
 Add a new repositoy method _generate_text_key_index for use by reconcile/check.  | 
50  | 
|
51  | 
def __init__(self, ancestry):  | 
|
52  | 
self.ancestry = ancestry  | 
|
53  | 
||
54  | 
def __repr__(self):  | 
|
55  | 
return 'DictParentsProvider(%r)' % self.ancestry  | 
|
56  | 
||
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
57  | 
def get_parent_map(self, keys):  | 
58  | 
"""See _StackedParentsProvider.get_parent_map"""  | 
|
59  | 
ancestry = self.ancestry  | 
|
60  | 
return dict((k, ancestry[k]) for k in keys if k in ancestry)  | 
|
61  | 
||
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
62  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
63  | 
class _StackedParentsProvider(object):  | 
64  | 
||
65  | 
def __init__(self, parent_providers):  | 
|
66  | 
self._parent_providers = parent_providers  | 
|
67  | 
||
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
68  | 
def __repr__(self):  | 
69  | 
return "_StackedParentsProvider(%r)" % self._parent_providers  | 
|
70  | 
||
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
71  | 
def get_parent_map(self, keys):  | 
72  | 
"""Get a mapping of keys => parents  | 
|
73  | 
||
74  | 
        A dictionary is returned with an entry for each key present in this
 | 
|
75  | 
        source. If this source doesn't have information about a key, it should
 | 
|
76  | 
        not include an entry.
 | 
|
77  | 
||
78  | 
        [NULL_REVISION] is used as the parent of the first user-committed
 | 
|
79  | 
        revision.  Its parent list is empty.
 | 
|
80  | 
||
81  | 
        :param keys: An iterable returning keys to check (eg revision_ids)
 | 
|
82  | 
        :return: A dictionary mapping each key to its parents
 | 
|
83  | 
        """
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
84  | 
found = {}  | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
85  | 
remaining = set(keys)  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
86  | 
for parents_provider in self._parent_providers:  | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
87  | 
new_found = parents_provider.get_parent_map(remaining)  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
88  | 
found.update(new_found)  | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
89  | 
remaining.difference_update(new_found)  | 
90  | 
if not remaining:  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
91  | 
                break
 | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
92  | 
return found  | 
93  | 
||
94  | 
||
95  | 
class CachingParentsProvider(object):  | 
|
96  | 
"""A parents provider which will cache the revision => parents in a dict.  | 
|
97  | 
||
98  | 
    This is useful for providers that have an expensive lookup.
 | 
|
99  | 
    """
 | 
|
100  | 
||
101  | 
def __init__(self, parent_provider):  | 
|
102  | 
self._real_provider = parent_provider  | 
|
103  | 
        # Theoretically we could use an LRUCache here
 | 
|
104  | 
self._cache = {}  | 
|
105  | 
||
106  | 
def __repr__(self):  | 
|
107  | 
return "%s(%r)" % (self.__class__.__name__, self._real_provider)  | 
|
108  | 
||
109  | 
def get_parent_map(self, keys):  | 
|
110  | 
"""See _StackedParentsProvider.get_parent_map"""  | 
|
111  | 
needed = set()  | 
|
112  | 
        # If the _real_provider doesn't have a key, we cache a value of None,
 | 
|
113  | 
        # which we then later use to realize we cannot provide a value for that
 | 
|
114  | 
        # key.
 | 
|
115  | 
parent_map = {}  | 
|
116  | 
cache = self._cache  | 
|
117  | 
for key in keys:  | 
|
118  | 
if key in cache:  | 
|
119  | 
value = cache[key]  | 
|
120  | 
if value is not None:  | 
|
121  | 
parent_map[key] = value  | 
|
122  | 
else:  | 
|
123  | 
needed.add(key)  | 
|
124  | 
||
125  | 
if needed:  | 
|
126  | 
new_parents = self._real_provider.get_parent_map(needed)  | 
|
127  | 
cache.update(new_parents)  | 
|
128  | 
parent_map.update(new_parents)  | 
|
129  | 
needed.difference_update(new_parents)  | 
|
130  | 
cache.update(dict.fromkeys(needed, None))  | 
|
131  | 
return parent_map  | 
|
132  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
133  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
134  | 
class Graph(object):  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
135  | 
"""Provide incremental access to revision graphs.  | 
136  | 
||
137  | 
    This is the generic implementation; it is intended to be subclassed to
 | 
|
138  | 
    specialize it for other repository types.
 | 
|
139  | 
    """
 | 
|
| 
2490.2.1
by Aaron Bentley
 Start work on GraphWalker  | 
140  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
141  | 
def __init__(self, parents_provider):  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
142  | 
"""Construct a Graph that uses several graphs as its input  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
143  | 
|
144  | 
        This should not normally be invoked directly, because there may be
 | 
|
145  | 
        specialized implementations for particular repository types.  See
 | 
|
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
146  | 
        Repository.get_graph().
 | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
147  | 
|
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
148  | 
        :param parents_provider: An object providing a get_parent_map call
 | 
149  | 
            conforming to the behavior of
 | 
|
150  | 
            StackedParentsProvider.get_parent_map.
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
151  | 
        """
 | 
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
152  | 
if getattr(parents_provider, 'get_parents', None) is not None:  | 
153  | 
self.get_parents = parents_provider.get_parents  | 
|
154  | 
if getattr(parents_provider, 'get_parent_map', None) is not None:  | 
|
155  | 
self.get_parent_map = parents_provider.get_parent_map  | 
|
| 
2490.2.29
by Aaron Bentley
 Make parents provider private  | 
156  | 
self._parents_provider = parents_provider  | 
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
157  | 
|
158  | 
def __repr__(self):  | 
|
| 
2490.2.29
by Aaron Bentley
 Make parents provider private  | 
159  | 
return 'Graph(%r)' % self._parents_provider  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
160  | 
|
161  | 
def find_lca(self, *revisions):  | 
|
162  | 
"""Determine the lowest common ancestors of the provided revisions  | 
|
163  | 
||
164  | 
        A lowest common ancestor is a common ancestor none of whose
 | 
|
165  | 
        descendants are common ancestors.  In graphs, unlike trees, there may
 | 
|
166  | 
        be multiple lowest common ancestors.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
167  | 
|
168  | 
        This algorithm has two phases.  Phase 1 identifies border ancestors,
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
169  | 
        and phase 2 filters border ancestors to determine lowest common
 | 
170  | 
        ancestors.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
171  | 
|
172  | 
        In phase 1, border ancestors are identified, using a breadth-first
 | 
|
173  | 
        search starting at the bottom of the graph.  Searches are stopped
 | 
|
174  | 
        whenever a node or one of its descendants is determined to be common
 | 
|
175  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
176  | 
        In phase 2, the border ancestors are filtered to find the least
 | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
177  | 
        common ancestors.  This is done by searching the ancestries of each
 | 
178  | 
        border ancestor.
 | 
|
179  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
180  | 
        Phase 2 is perfomed on the principle that a border ancestor that is
 | 
181  | 
        not an ancestor of any other border ancestor is a least common
 | 
|
182  | 
        ancestor.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
183  | 
|
184  | 
        Searches are stopped when they find a node that is determined to be a
 | 
|
185  | 
        common ancestor of all border ancestors, because this shows that it
 | 
|
186  | 
        cannot be a descendant of any border ancestor.
 | 
|
187  | 
||
188  | 
        The scaling of this operation should be proportional to
 | 
|
189  | 
        1. The number of uncommon ancestors
 | 
|
190  | 
        2. The number of border ancestors
 | 
|
191  | 
        3. The length of the shortest path between a border ancestor and an
 | 
|
192  | 
           ancestor of all border ancestors.
 | 
|
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
193  | 
        """
 | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
194  | 
border_common, common, sides = self._find_border_ancestors(revisions)  | 
| 
2776.3.1
by Robert Collins
 * Deprecated method ``find_previous_heads`` on  | 
195  | 
        # We may have common ancestors that can be reached from each other.
 | 
196  | 
        # - ask for the heads of them to filter it down to only ones that
 | 
|
197  | 
        # cannot be reached from each other - phase 2.
 | 
|
198  | 
return self.heads(border_common)  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
199  | 
|
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
200  | 
def find_difference(self, left_revision, right_revision):  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
201  | 
"""Determine the graph difference between two revisions"""  | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
202  | 
border, common, (left, right) = self._find_border_ancestors(  | 
203  | 
[left_revision, right_revision])  | 
|
204  | 
return (left.difference(right).difference(common),  | 
|
205  | 
right.difference(left).difference(common))  | 
|
206  | 
||
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
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  | 
|
210  | 
||
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.
 | 
|
213  | 
||
214  | 
        [NULL_REVISION] is used as the parent of the first user-committed
 | 
|
215  | 
        revision.  Its parent list is empty.
 | 
|
216  | 
||
217  | 
        If the revision is not present (i.e. a ghost), None is used in place
 | 
|
218  | 
        of the list of parents.
 | 
|
219  | 
||
220  | 
        Deprecated in bzr 1.2 - please see get_parent_map.
 | 
|
221  | 
        """
 | 
|
222  | 
parents = self.get_parent_map(revisions)  | 
|
223  | 
return [parent.get(r, None) for r in revisions]  | 
|
224  | 
||
225  | 
def get_parent_map(self, revisions):  | 
|
226  | 
"""Get a map of key:parent_list for revisions.  | 
|
227  | 
||
228  | 
        This implementation delegates to get_parents, for old parent_providers
 | 
|
229  | 
        that do not supply get_parent_map.
 | 
|
230  | 
        """
 | 
|
231  | 
result = {}  | 
|
232  | 
for rev, parents in self.get_parents(revisions):  | 
|
233  | 
if parents is not None:  | 
|
234  | 
result[rev] = parents  | 
|
235  | 
return result  | 
|
236  | 
||
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
237  | 
def _make_breadth_first_searcher(self, revisions):  | 
238  | 
return _BreadthFirstSearcher(revisions, self)  | 
|
239  | 
||
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
240  | 
def _find_border_ancestors(self, revisions):  | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
241  | 
"""Find common ancestors with at least one uncommon descendant.  | 
242  | 
||
243  | 
        Border ancestors are identified using a breadth-first
 | 
|
244  | 
        search starting at the bottom of the graph.  Searches are stopped
 | 
|
245  | 
        whenever a node or one of its descendants is determined to be common.
 | 
|
246  | 
||
247  | 
        This will scale with the number of uncommon ancestors.
 | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
248  | 
|
249  | 
        As well as the border ancestors, a set of seen common ancestors and a
 | 
|
250  | 
        list of sets of seen ancestors for each input revision is returned.
 | 
|
251  | 
        This allows calculation of graph difference from the results of this
 | 
|
252  | 
        operation.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
253  | 
        """
 | 
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
254  | 
if None in revisions:  | 
255  | 
raise errors.InvalidRevisionId(None, self)  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
256  | 
common_searcher = self._make_breadth_first_searcher([])  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
257  | 
common_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
258  | 
searchers = [self._make_breadth_first_searcher([r])  | 
259  | 
for r in revisions]  | 
|
260  | 
active_searchers = searchers[:]  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
261  | 
border_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
262  | 
def update_common(searcher, revisions):  | 
263  | 
w_seen_ancestors = searcher.find_seen_ancestors(  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
264  | 
revision)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
265  | 
stopped = searcher.stop_searching_any(w_seen_ancestors)  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
266  | 
common_ancestors.update(w_seen_ancestors)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
267  | 
common_searcher.start_searching(stopped)  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
268  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
269  | 
while True:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
270  | 
if len(active_searchers) == 0:  | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
271  | 
return border_ancestors, common_ancestors, [s.seen for s in  | 
272  | 
searchers]  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
273  | 
try:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
274  | 
new_common = common_searcher.next()  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
275  | 
common_ancestors.update(new_common)  | 
276  | 
except StopIteration:  | 
|
277  | 
                pass
 | 
|
278  | 
else:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
279  | 
for searcher in active_searchers:  | 
280  | 
for revision in new_common.intersection(searcher.seen):  | 
|
281  | 
update_common(searcher, revision)  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
282  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
283  | 
newly_seen = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
284  | 
new_active_searchers = []  | 
285  | 
for searcher in active_searchers:  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
286  | 
try:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
287  | 
newly_seen.update(searcher.next())  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
288  | 
except StopIteration:  | 
289  | 
                    pass
 | 
|
290  | 
else:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
291  | 
new_active_searchers.append(searcher)  | 
292  | 
active_searchers = new_active_searchers  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
293  | 
for revision in newly_seen:  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
294  | 
if revision in common_ancestors:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
295  | 
for searcher in searchers:  | 
296  | 
update_common(searcher, revision)  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
297  | 
                    continue
 | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
298  | 
for searcher in searchers:  | 
299  | 
if revision not in searcher.seen:  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
300  | 
                        break
 | 
301  | 
else:  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
302  | 
border_ancestors.add(revision)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
303  | 
for searcher in searchers:  | 
304  | 
update_common(searcher, revision)  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
305  | 
|
| 
2776.3.1
by Robert Collins
 * Deprecated method ``find_previous_heads`` on  | 
306  | 
def heads(self, keys):  | 
307  | 
"""Return the heads from amongst keys.  | 
|
308  | 
||
309  | 
        This is done by searching the ancestries of each key.  Any key that is
 | 
|
310  | 
        reachable from another key is not returned; all the others are.
 | 
|
311  | 
||
312  | 
        This operation scales with the relative depth between any two keys. If
 | 
|
313  | 
        any two keys are completely disconnected all ancestry of both sides
 | 
|
314  | 
        will be retrieved.
 | 
|
315  | 
||
316  | 
        :param keys: An iterable of keys.
 | 
|
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
317  | 
        :return: A set of the heads. Note that as a set there is no ordering
 | 
318  | 
            information. Callers will need to filter their input to create
 | 
|
319  | 
            order if they need it.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
320  | 
        """
 | 
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
321  | 
candidate_heads = set(keys)  | 
| 
3052.5.5
by John Arbash Meinel
 Special case Graph.heads() for NULL_REVISION rather than is_ancestor.  | 
322  | 
if revision.NULL_REVISION in candidate_heads:  | 
323  | 
            # NULL_REVISION is only a head if it is the only entry
 | 
|
324  | 
candidate_heads.remove(revision.NULL_REVISION)  | 
|
325  | 
if not candidate_heads:  | 
|
326  | 
return set([revision.NULL_REVISION])  | 
|
| 
2850.2.1
by Robert Collins
 (robertc) Special case the zero-or-no-heads case for Graph.heads(). (Robert Collins)  | 
327  | 
if len(candidate_heads) < 2:  | 
328  | 
return candidate_heads  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
329  | 
searchers = dict((c, self._make_breadth_first_searcher([c]))  | 
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
330  | 
for c in candidate_heads)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
331  | 
active_searchers = dict(searchers)  | 
332  | 
        # skip over the actual candidate for each searcher
 | 
|
333  | 
for searcher in active_searchers.itervalues():  | 
|
| 
1551.15.81
by Aaron Bentley
 Remove testing code  | 
334  | 
searcher.next()  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
335  | 
        # The common walker finds nodes that are common to two or more of the
 | 
336  | 
        # input keys, so that we don't access all history when a currently
 | 
|
337  | 
        # uncommon search point actually meets up with something behind a
 | 
|
338  | 
        # common search point. Common search points do not keep searches
 | 
|
339  | 
        # active; they just allow us to make searches inactive without
 | 
|
340  | 
        # accessing all history.
 | 
|
341  | 
common_walker = self._make_breadth_first_searcher([])  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
342  | 
while len(active_searchers) > 0:  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
343  | 
ancestors = set()  | 
344  | 
            # advance searches
 | 
|
345  | 
try:  | 
|
346  | 
common_walker.next()  | 
|
347  | 
except StopIteration:  | 
|
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
348  | 
                # No common points being searched at this time.
 | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
349  | 
                pass
 | 
| 
1551.15.78
by Aaron Bentley
 Fix KeyError in filter_candidate_lca  | 
350  | 
for candidate in active_searchers.keys():  | 
351  | 
try:  | 
|
352  | 
searcher = active_searchers[candidate]  | 
|
353  | 
except KeyError:  | 
|
354  | 
                    # rare case: we deleted candidate in a previous iteration
 | 
|
355  | 
                    # through this for loop, because it was determined to be
 | 
|
356  | 
                    # a descendant of another candidate.
 | 
|
357  | 
                    continue
 | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
358  | 
try:  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
359  | 
ancestors.update(searcher.next())  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
360  | 
except StopIteration:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
361  | 
del active_searchers[candidate]  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
362  | 
                    continue
 | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
363  | 
            # process found nodes
 | 
364  | 
new_common = set()  | 
|
365  | 
for ancestor in ancestors:  | 
|
366  | 
if ancestor in candidate_heads:  | 
|
367  | 
candidate_heads.remove(ancestor)  | 
|
368  | 
del searchers[ancestor]  | 
|
369  | 
if ancestor in active_searchers:  | 
|
370  | 
del active_searchers[ancestor]  | 
|
371  | 
                # it may meet up with a known common node
 | 
|
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
372  | 
if ancestor in common_walker.seen:  | 
373  | 
                    # some searcher has encountered our known common nodes:
 | 
|
374  | 
                    # just stop it
 | 
|
375  | 
ancestor_set = set([ancestor])  | 
|
376  | 
for searcher in searchers.itervalues():  | 
|
377  | 
searcher.stop_searching_any(ancestor_set)  | 
|
378  | 
else:  | 
|
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
379  | 
                    # or it may have been just reached by all the searchers:
 | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
380  | 
for searcher in searchers.itervalues():  | 
381  | 
if ancestor not in searcher.seen:  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
382  | 
                            break
 | 
383  | 
else:  | 
|
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
384  | 
                        # The final active searcher has just reached this node,
 | 
385  | 
                        # making it be known as a descendant of all candidates,
 | 
|
386  | 
                        # so we can stop searching it, and any seen ancestors
 | 
|
387  | 
new_common.add(ancestor)  | 
|
388  | 
for searcher in searchers.itervalues():  | 
|
389  | 
seen_ancestors =\  | 
|
390  | 
searcher.find_seen_ancestors(ancestor)  | 
|
391  | 
searcher.stop_searching_any(seen_ancestors)  | 
|
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
392  | 
common_walker.start_searching(new_common)  | 
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
393  | 
return candidate_heads  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
394  | 
|
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
395  | 
def find_unique_lca(self, left_revision, right_revision,  | 
396  | 
count_steps=False):  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
397  | 
"""Find a unique LCA.  | 
398  | 
||
399  | 
        Find lowest common ancestors.  If there is no unique  common
 | 
|
400  | 
        ancestor, find the lowest common ancestors of those ancestors.
 | 
|
401  | 
||
402  | 
        Iteration stops when a unique lowest common ancestor is found.
 | 
|
403  | 
        The graph origin is necessarily a unique lowest common ancestor.
 | 
|
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
404  | 
|
405  | 
        Note that None is not an acceptable substitute for NULL_REVISION.
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
406  | 
        in the input for this method.
 | 
| 
1551.19.12
by Aaron Bentley
 Add documentation for the count_steps parameter of Graph.find_unique_lca  | 
407  | 
|
408  | 
        :param count_steps: If True, the return value will be a tuple of
 | 
|
409  | 
            (unique_lca, steps) where steps is the number of times that
 | 
|
410  | 
            find_lca was run.  If False, only unique_lca is returned.
 | 
|
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
411  | 
        """
 | 
412  | 
revisions = [left_revision, right_revision]  | 
|
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
413  | 
steps = 0  | 
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
414  | 
while True:  | 
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
415  | 
steps += 1  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
416  | 
lca = self.find_lca(*revisions)  | 
417  | 
if len(lca) == 1:  | 
|
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
418  | 
result = lca.pop()  | 
419  | 
if count_steps:  | 
|
420  | 
return result, steps  | 
|
421  | 
else:  | 
|
422  | 
return result  | 
|
| 
2520.4.104
by Aaron Bentley
 Avoid infinite loop when there is no unique lca  | 
423  | 
if len(lca) == 0:  | 
424  | 
raise errors.NoCommonAncestor(left_revision, right_revision)  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
425  | 
revisions = lca  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
426  | 
|
| 
2490.2.31
by Aaron Bentley
 Fix iter_topo_order to permit un-included parents  | 
427  | 
def iter_topo_order(self, revisions):  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
428  | 
"""Iterate through the input revisions in topological order.  | 
429  | 
||
430  | 
        This sorting only ensures that parents come before their children.
 | 
|
431  | 
        An ancestor may sort after a descendant if the relationship is not
 | 
|
432  | 
        visible in the supplied list of revisions.
 | 
|
433  | 
        """
 | 
|
| 
3099.3.3
by John Arbash Meinel
 Deprecate get_parents() in favor of get_parent_map()  | 
434  | 
sorter = tsort.TopoSorter(self.get_parent_map(revisions))  | 
| 
2490.2.34
by Aaron Bentley
 Update NEWS and change implementation to return an iterator  | 
435  | 
return sorter.iter_topo_order()  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
436  | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
437  | 
def is_ancestor(self, candidate_ancestor, candidate_descendant):  | 
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
438  | 
"""Determine whether a revision is an ancestor of another.  | 
439  | 
||
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
440  | 
        We answer this using heads() as heads() has the logic to perform the
 | 
| 
3078.2.6
by Ian Clatworthy
 fix efficiency of local commit detection as recommended by jameinel's review  | 
441  | 
        smallest number of parent lookups to determine the ancestral
 | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
442  | 
        relationship between N revisions.
 | 
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
443  | 
        """
 | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
444  | 
return set([candidate_descendant]) == self.heads(  | 
445  | 
[candidate_ancestor, candidate_descendant])  | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
446  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
447  | 
|
| 
2911.4.1
by Robert Collins
 Factor out the Graph.heads() cache from _RevisionTextVersionCache for reuse, and use it in commit.  | 
448  | 
class HeadsCache(object):  | 
449  | 
"""A cache of results for graph heads calls."""  | 
|
450  | 
||
451  | 
def __init__(self, graph):  | 
|
452  | 
self.graph = graph  | 
|
453  | 
self._heads = {}  | 
|
454  | 
||
455  | 
def heads(self, keys):  | 
|
456  | 
"""Return the heads of keys.  | 
|
457  | 
||
| 
2911.4.3
by Robert Collins
 Make the contract of HeadsCache.heads() more clear.  | 
458  | 
        This matches the API of Graph.heads(), specifically the return value is
 | 
459  | 
        a set which can be mutated, and ordering of the input is not preserved
 | 
|
460  | 
        in the output.
 | 
|
461  | 
||
| 
2911.4.1
by Robert Collins
 Factor out the Graph.heads() cache from _RevisionTextVersionCache for reuse, and use it in commit.  | 
462  | 
        :see also: Graph.heads.
 | 
463  | 
        :param keys: The keys to calculate heads for.
 | 
|
464  | 
        :return: A set containing the heads, which may be mutated without
 | 
|
465  | 
            affecting future lookups.
 | 
|
466  | 
        """
 | 
|
| 
2911.4.2
by Robert Collins
 Make HeadsCache actually work.  | 
467  | 
keys = frozenset(keys)  | 
| 
2911.4.1
by Robert Collins
 Factor out the Graph.heads() cache from _RevisionTextVersionCache for reuse, and use it in commit.  | 
468  | 
try:  | 
469  | 
return set(self._heads[keys])  | 
|
470  | 
except KeyError:  | 
|
471  | 
heads = self.graph.heads(keys)  | 
|
472  | 
self._heads[keys] = heads  | 
|
473  | 
return set(heads)  | 
|
474  | 
||
475  | 
||
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
476  | 
class _BreadthFirstSearcher(object):  | 
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
477  | 
"""Parallel search breadth-first the ancestry of revisions.  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
478  | 
|
479  | 
    This class implements the iterator protocol, but additionally
 | 
|
480  | 
    1. provides a set of seen ancestors, and
 | 
|
481  | 
    2. allows some ancestries to be unsearched, via stop_searching_any
 | 
|
482  | 
    """
 | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
483  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
484  | 
def __init__(self, revisions, parents_provider):  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
485  | 
self._iterations = 0  | 
486  | 
self._next_query = set(revisions)  | 
|
487  | 
self.seen = set()  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
488  | 
self._started_keys = set(self._next_query)  | 
489  | 
self._stopped_keys = set()  | 
|
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
490  | 
self._parents_provider = parents_provider  | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
491  | 
self._returning = 'next_with_ghosts'  | 
| 
3184.1.2
by Robert Collins
 Add tests for starting and stopping searches in combination with get_recipe.  | 
492  | 
self._current_present = set()  | 
493  | 
self._current_ghosts = set()  | 
|
494  | 
self._current_parents = {}  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
495  | 
|
496  | 
def __repr__(self):  | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
497  | 
if self._iterations:  | 
498  | 
prefix = "searching"  | 
|
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
499  | 
else:  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
500  | 
prefix = "starting"  | 
501  | 
search = '%s=%r' % (prefix, list(self._next_query))  | 
|
502  | 
return ('_BreadthFirstSearcher(iterations=%d, %s,'  | 
|
503  | 
' seen=%r)' % (self._iterations, search, list(self.seen)))  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
504  | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
505  | 
def get_result(self):  | 
506  | 
"""Get a SearchResult for the current state of this searcher.  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
507  | 
        
 | 
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
508  | 
        :return: A SearchResult for this search so far. The SearchResult is
 | 
509  | 
            static - the search can be advanced and the search result will not
 | 
|
510  | 
            be invalidated or altered.
 | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
511  | 
        """
 | 
512  | 
if self._returning == 'next':  | 
|
513  | 
            # We have to know the current nodes children to be able to list the
 | 
|
514  | 
            # exclude keys for them. However, while we could have a second
 | 
|
515  | 
            # look-ahead result buffer and shuffle things around, this method
 | 
|
516  | 
            # is typically only called once per search - when memoising the
 | 
|
517  | 
            # results of the search.
 | 
|
518  | 
found, ghosts, next, parents = self._do_query(self._next_query)  | 
|
519  | 
            # pretend we didn't query: perhaps we should tweak _do_query to be
 | 
|
520  | 
            # entirely stateless?
 | 
|
521  | 
self.seen.difference_update(next)  | 
|
| 
3184.1.3
by Robert Collins
 Automatically exclude ghosts.  | 
522  | 
next_query = next.union(ghosts)  | 
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
523  | 
else:  | 
524  | 
next_query = self._next_query  | 
|
| 
3184.1.5
by Robert Collins
 Record the number of found revisions for cross checking.  | 
525  | 
excludes = self._stopped_keys.union(next_query)  | 
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
526  | 
included_keys = self.seen.difference(excludes)  | 
527  | 
return SearchResult(self._started_keys, excludes, len(included_keys),  | 
|
528  | 
included_keys)  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
529  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
530  | 
def next(self):  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
531  | 
"""Return the next ancestors of this revision.  | 
532  | 
||
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
533  | 
        Ancestors are returned in the order they are seen in a breadth-first
 | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
534  | 
        traversal.  No ancestor will be returned more than once. Ancestors are
 | 
535  | 
        returned before their parentage is queried, so ghosts and missing
 | 
|
536  | 
        revisions (including the start revisions) are included in the result.
 | 
|
537  | 
        This can save a round trip in LCA style calculation by allowing
 | 
|
538  | 
        convergence to be detected without reading the data for the revision
 | 
|
539  | 
        the convergence occurs on.
 | 
|
540  | 
||
541  | 
        :return: A set of revision_ids.
 | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
542  | 
        """
 | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
543  | 
if self._returning != 'next':  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
544  | 
            # switch to returning the query, not the results.
 | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
545  | 
self._returning = 'next'  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
546  | 
self._iterations += 1  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
547  | 
else:  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
548  | 
self._advance()  | 
549  | 
if len(self._next_query) == 0:  | 
|
550  | 
raise StopIteration()  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
551  | 
        # We have seen what we're querying at this point as we are returning
 | 
552  | 
        # the query, not the results.
 | 
|
553  | 
self.seen.update(self._next_query)  | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
554  | 
return self._next_query  | 
555  | 
||
556  | 
def next_with_ghosts(self):  | 
|
557  | 
"""Return the next found ancestors, with ghosts split out.  | 
|
558  | 
        
 | 
|
559  | 
        Ancestors are returned in the order they are seen in a breadth-first
 | 
|
560  | 
        traversal.  No ancestor will be returned more than once. Ancestors are
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
561  | 
        returned only after asking for their parents, which allows us to detect
 | 
562  | 
        which revisions are ghosts and which are not.
 | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
563  | 
|
564  | 
        :return: A tuple with (present ancestors, ghost ancestors) sets.
 | 
|
565  | 
        """
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
566  | 
if self._returning != 'next_with_ghosts':  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
567  | 
            # switch to returning the results, not the current query.
 | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
568  | 
self._returning = 'next_with_ghosts'  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
569  | 
self._advance()  | 
570  | 
if len(self._next_query) == 0:  | 
|
571  | 
raise StopIteration()  | 
|
572  | 
self._advance()  | 
|
573  | 
return self._current_present, self._current_ghosts  | 
|
574  | 
||
575  | 
def _advance(self):  | 
|
576  | 
"""Advance the search.  | 
|
577  | 
||
578  | 
        Updates self.seen, self._next_query, self._current_present,
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
579  | 
        self._current_ghosts, self._current_parents and self._iterations.
 | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
580  | 
        """
 | 
581  | 
self._iterations += 1  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
582  | 
found, ghosts, next, parents = self._do_query(self._next_query)  | 
583  | 
self._current_present = found  | 
|
584  | 
self._current_ghosts = ghosts  | 
|
585  | 
self._next_query = next  | 
|
586  | 
self._current_parents = parents  | 
|
| 
3184.1.3
by Robert Collins
 Automatically exclude ghosts.  | 
587  | 
        # ghosts are implicit stop points, otherwise the search cannot be
 | 
588  | 
        # repeated when ghosts are filled.
 | 
|
589  | 
self._stopped_keys.update(ghosts)  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
590  | 
|
591  | 
def _do_query(self, revisions):  | 
|
592  | 
"""Query for revisions.  | 
|
593  | 
||
| 
3184.1.4
by Robert Collins
 Correctly exclude ghosts when ghosts are started on an existing search.  | 
594  | 
        Adds revisions to the seen set.
 | 
595  | 
||
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
596  | 
        :param revisions: Revisions to query.
 | 
597  | 
        :return: A tuple: (set(found_revisions), set(ghost_revisions),
 | 
|
598  | 
           set(parents_of_found_revisions), dict(found_revisions:parents)).
 | 
|
599  | 
        """
 | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
600  | 
found_parents = set()  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
601  | 
parents_of_found = set()  | 
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
602  | 
        # revisions may contain nodes that point to other nodes in revisions:
 | 
603  | 
        # we want to filter them out.
 | 
|
604  | 
self.seen.update(revisions)  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
605  | 
parent_map = self._parents_provider.get_parent_map(revisions)  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
606  | 
for rev_id, parents in parent_map.iteritems():  | 
607  | 
found_parents.add(rev_id)  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
608  | 
parents_of_found.update(p for p in parents if p not in self.seen)  | 
609  | 
ghost_parents = revisions - found_parents  | 
|
610  | 
return found_parents, ghost_parents, parents_of_found, parent_map  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
611  | 
|
| 
2490.2.8
by Aaron Bentley
 fix iteration stuff  | 
612  | 
def __iter__(self):  | 
613  | 
return self  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
614  | 
|
615  | 
def find_seen_ancestors(self, revision):  | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
616  | 
"""Find ancestors of this revision that have already been seen."""  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
617  | 
searcher = _BreadthFirstSearcher([revision], self._parents_provider)  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
618  | 
seen_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
619  | 
for ancestors in searcher:  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
620  | 
for ancestor in ancestors:  | 
621  | 
if ancestor not in self.seen:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
622  | 
searcher.stop_searching_any([ancestor])  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
623  | 
else:  | 
624  | 
seen_ancestors.add(ancestor)  | 
|
625  | 
return seen_ancestors  | 
|
626  | 
||
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
627  | 
def stop_searching_any(self, revisions):  | 
628  | 
"""  | 
|
629  | 
        Remove any of the specified revisions from the search list.
 | 
|
630  | 
||
631  | 
        None of the specified revisions are required to be present in the
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
632  | 
        search list.  In this case, the call is a no-op.
 | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
633  | 
        """
 | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
634  | 
revisions = frozenset(revisions)  | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
635  | 
if self._returning == 'next':  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
636  | 
stopped = self._next_query.intersection(revisions)  | 
637  | 
self._next_query = self._next_query.difference(revisions)  | 
|
638  | 
else:  | 
|
| 
3184.2.1
by Robert Collins
 Handle stopping ghosts in searches properly.  | 
639  | 
stopped_present = self._current_present.intersection(revisions)  | 
640  | 
stopped = stopped_present.union(  | 
|
641  | 
self._current_ghosts.intersection(revisions))  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
642  | 
self._current_present.difference_update(stopped)  | 
643  | 
self._current_ghosts.difference_update(stopped)  | 
|
644  | 
            # stopping 'x' should stop returning parents of 'x', but 
 | 
|
645  | 
            # not if 'y' always references those same parents
 | 
|
646  | 
stop_rev_references = {}  | 
|
| 
3184.2.1
by Robert Collins
 Handle stopping ghosts in searches properly.  | 
647  | 
for rev in stopped_present:  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
648  | 
for parent_id in self._current_parents[rev]:  | 
649  | 
if parent_id not in stop_rev_references:  | 
|
650  | 
stop_rev_references[parent_id] = 0  | 
|
651  | 
stop_rev_references[parent_id] += 1  | 
|
652  | 
            # if only the stopped revisions reference it, the ref count will be
 | 
|
653  | 
            # 0 after this loop
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
654  | 
for parents in self._current_parents.itervalues():  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
655  | 
for parent_id in parents:  | 
656  | 
try:  | 
|
657  | 
stop_rev_references[parent_id] -= 1  | 
|
658  | 
except KeyError:  | 
|
659  | 
                        pass
 | 
|
660  | 
stop_parents = set()  | 
|
661  | 
for rev_id, refs in stop_rev_references.iteritems():  | 
|
662  | 
if refs == 0:  | 
|
663  | 
stop_parents.add(rev_id)  | 
|
664  | 
self._next_query.difference_update(stop_parents)  | 
|
| 
3184.1.2
by Robert Collins
 Add tests for starting and stopping searches in combination with get_recipe.  | 
665  | 
self._stopped_keys.update(stopped)  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
666  | 
return stopped  | 
| 
2490.2.17
by Aaron Bentley
 Add start_searching, tweak stop_searching_any  | 
667  | 
|
668  | 
def start_searching(self, revisions):  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
669  | 
"""Add revisions to the search.  | 
670  | 
||
671  | 
        The parents of revisions will be returned from the next call to next()
 | 
|
672  | 
        or next_with_ghosts(). If next_with_ghosts was the most recently used
 | 
|
673  | 
        next* call then the return value is the result of looking up the
 | 
|
674  | 
        ghost/not ghost status of revisions. (A tuple (present, ghosted)).
 | 
|
675  | 
        """
 | 
|
676  | 
revisions = frozenset(revisions)  | 
|
| 
3184.1.2
by Robert Collins
 Add tests for starting and stopping searches in combination with get_recipe.  | 
677  | 
self._started_keys.update(revisions)  | 
| 
3184.1.4
by Robert Collins
 Correctly exclude ghosts when ghosts are started on an existing search.  | 
678  | 
new_revisions = revisions.difference(self.seen)  | 
679  | 
revs, ghosts, query, parents = self._do_query(revisions)  | 
|
680  | 
self._stopped_keys.update(ghosts)  | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
681  | 
if self._returning == 'next':  | 
| 
3184.1.4
by Robert Collins
 Correctly exclude ghosts when ghosts are started on an existing search.  | 
682  | 
self._next_query.update(new_revisions)  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
683  | 
else:  | 
684  | 
            # perform a query on revisions
 | 
|
685  | 
self._current_present.update(revs)  | 
|
686  | 
self._current_ghosts.update(ghosts)  | 
|
687  | 
self._next_query.update(query)  | 
|
688  | 
self._current_parents.update(parents)  | 
|
689  | 
return revs, ghosts  | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
690  | 
|
691  | 
||
692  | 
class SearchResult(object):  | 
|
693  | 
"""The result of a breadth first search.  | 
|
694  | 
||
695  | 
    A SearchResult provides the ability to reconstruct the search or access a
 | 
|
696  | 
    set of the keys the search found.
 | 
|
697  | 
    """
 | 
|
698  | 
||
699  | 
def __init__(self, start_keys, exclude_keys, key_count, keys):  | 
|
700  | 
"""Create a SearchResult.  | 
|
701  | 
||
702  | 
        :param start_keys: The keys the search started at.
 | 
|
703  | 
        :param exclude_keys: The keys the search excludes.
 | 
|
704  | 
        :param key_count: The total number of keys (from start to but not
 | 
|
705  | 
            including exclude).
 | 
|
706  | 
        :param keys: The keys the search found. Note that in future we may get
 | 
|
707  | 
            a SearchResult from a smart server, in which case the keys list is
 | 
|
708  | 
            not necessarily immediately available.
 | 
|
709  | 
        """
 | 
|
710  | 
self._recipe = (start_keys, exclude_keys, key_count)  | 
|
711  | 
self._keys = frozenset(keys)  | 
|
712  | 
||
713  | 
def get_recipe(self):  | 
|
714  | 
"""Return a recipe that can be used to replay this search.  | 
|
715  | 
        
 | 
|
716  | 
        The recipe allows reconstruction of the same results at a later date
 | 
|
717  | 
        without knowing all the found keys. The essential elements are a list
 | 
|
718  | 
        of keys to start and and to stop at. In order to give reproducible
 | 
|
719  | 
        results when ghosts are encountered by a search they are automatically
 | 
|
720  | 
        added to the exclude list (or else ghost filling may alter the
 | 
|
721  | 
        results).
 | 
|
722  | 
||
723  | 
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
 | 
|
724  | 
            recreate the results of this search, create a breadth first
 | 
|
725  | 
            searcher on the same graph starting at start_keys. Then call next()
 | 
|
726  | 
            (or next_with_ghosts()) repeatedly, and on every result, call
 | 
|
727  | 
            stop_searching_any on any keys from the exclude_keys set. The
 | 
|
728  | 
            revision_count value acts as a trivial cross-check - the found
 | 
|
729  | 
            revisions of the new search should have as many elements as
 | 
|
730  | 
            revision_count. If it does not, then additional revisions have been
 | 
|
731  | 
            ghosted since the search was executed the first time and the second
 | 
|
732  | 
            time.
 | 
|
733  | 
        """
 | 
|
734  | 
return self._recipe  | 
|
735  | 
||
736  | 
def get_keys(self):  | 
|
737  | 
"""Return the keys found in this search.  | 
|
738  | 
||
739  | 
        :return: A set of keys.
 | 
|
740  | 
        """
 | 
|
741  | 
return self._keys  | 
|
742  |