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
 | 
|
| 
4183.7.1
by Sabin Iacob
 update FSF mailing address  | 
15  | 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 | 
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
16  | 
|
| 
3377.4.5
by John Arbash Meinel
 Several updates. A bit more debug logging, only step the all_unique searcher 1/10th of the time.  | 
17  | 
import time  | 
18  | 
||
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
19  | 
from bzrlib import (  | 
| 
3377.3.33
by John Arbash Meinel
 Add some logging with -Dgraph  | 
20  | 
debug,  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
21  | 
errors,  | 
| 
3052.1.3
by John Arbash Meinel
 deprecate revision.is_ancestor, update the callers and the tests.  | 
22  | 
revision,  | 
| 
3099.3.3
by John Arbash Meinel
 Deprecate get_parents() in favor of get_parent_map()  | 
23  | 
symbol_versioning,  | 
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
24  | 
trace,  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
25  | 
tsort,  | 
26  | 
    )
 | 
|
| 
2490.2.1
by Aaron Bentley
 Start work on GraphWalker  | 
27  | 
|
| 
3377.4.9
by John Arbash Meinel
 STEP every 5  | 
28  | 
STEP_UNIQUE_SEARCHER_EVERY = 5  | 
| 
3377.4.5
by John Arbash Meinel
 Several updates. A bit more debug logging, only step the all_unique searcher 1/10th of the time.  | 
29  | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
30  | 
# DIAGRAM of terminology
 | 
31  | 
#       A
 | 
|
32  | 
#       /\
 | 
|
33  | 
#      B  C
 | 
|
34  | 
#      |  |\
 | 
|
35  | 
#      D  E F
 | 
|
36  | 
#      |\/| |
 | 
|
37  | 
#      |/\|/
 | 
|
38  | 
#      G  H
 | 
|
39  | 
#
 | 
|
40  | 
# In this diagram, relative to G and H:
 | 
|
41  | 
# A, B, C, D, E are common ancestors.
 | 
|
42  | 
# C, D and E are border ancestors, because each has a non-common descendant.
 | 
|
43  | 
# D and E are least common ancestors because none of their descendants are
 | 
|
44  | 
# common ancestors.
 | 
|
45  | 
# C is not a least common ancestor because its descendant, E, is a common
 | 
|
46  | 
# ancestor.
 | 
|
47  | 
#
 | 
|
48  | 
# The find_unique_lca algorithm will pick A in two steps:
 | 
|
49  | 
# 1. find_lca('G', 'H') => ['D', 'E']
 | 
|
50  | 
# 2. Since len(['D', 'E']) > 1, find_lca('D', 'E') => ['A']
 | 
|
51  | 
||
52  | 
||
| 
2988.1.3
by Robert Collins
 Add a new repositoy method _generate_text_key_index for use by reconcile/check.  | 
53  | 
class DictParentsProvider(object):  | 
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
54  | 
"""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.  | 
55  | 
|
56  | 
def __init__(self, ancestry):  | 
|
57  | 
self.ancestry = ancestry  | 
|
58  | 
||
59  | 
def __repr__(self):  | 
|
60  | 
return 'DictParentsProvider(%r)' % self.ancestry  | 
|
61  | 
||
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
62  | 
def get_parent_map(self, keys):  | 
63  | 
"""See _StackedParentsProvider.get_parent_map"""  | 
|
64  | 
ancestry = self.ancestry  | 
|
65  | 
return dict((k, ancestry[k]) for k in keys if k in ancestry)  | 
|
66  | 
||
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
67  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
68  | 
class _StackedParentsProvider(object):  | 
69  | 
||
70  | 
def __init__(self, parent_providers):  | 
|
71  | 
self._parent_providers = parent_providers  | 
|
72  | 
||
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
73  | 
def __repr__(self):  | 
74  | 
return "_StackedParentsProvider(%r)" % self._parent_providers  | 
|
75  | 
||
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
76  | 
def get_parent_map(self, keys):  | 
77  | 
"""Get a mapping of keys => parents  | 
|
78  | 
||
79  | 
        A dictionary is returned with an entry for each key present in this
 | 
|
80  | 
        source. If this source doesn't have information about a key, it should
 | 
|
81  | 
        not include an entry.
 | 
|
82  | 
||
83  | 
        [NULL_REVISION] is used as the parent of the first user-committed
 | 
|
84  | 
        revision.  Its parent list is empty.
 | 
|
85  | 
||
86  | 
        :param keys: An iterable returning keys to check (eg revision_ids)
 | 
|
87  | 
        :return: A dictionary mapping each key to its parents
 | 
|
88  | 
        """
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
89  | 
found = {}  | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
90  | 
remaining = set(keys)  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
91  | 
for parents_provider in self._parent_providers:  | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
92  | 
new_found = parents_provider.get_parent_map(remaining)  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
93  | 
found.update(new_found)  | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
94  | 
remaining.difference_update(new_found)  | 
95  | 
if not remaining:  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
96  | 
                break
 | 
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
97  | 
return found  | 
98  | 
||
99  | 
||
100  | 
class CachingParentsProvider(object):  | 
|
| 
3835.1.13
by Aaron Bentley
 Update documentation  | 
101  | 
"""A parents provider which will cache the revision => parents as a dict.  | 
102  | 
||
103  | 
    This is useful for providers which have an expensive look up.
 | 
|
104  | 
||
105  | 
    Either a ParentsProvider or a get_parent_map-like callback may be
 | 
|
106  | 
    supplied.  If it provides extra un-asked-for parents, they will be cached,
 | 
|
107  | 
    but filtered out of get_parent_map.
 | 
|
| 
3835.1.16
by Aaron Bentley
 Updates from review  | 
108  | 
|
109  | 
    The cache is enabled by default, but may be disabled and re-enabled.
 | 
|
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
110  | 
    """
 | 
| 
3896.1.1
by Andrew Bennetts
 Remove broken debugging cruft, and some unused imports.  | 
111  | 
def __init__(self, parent_provider=None, get_parent_map=None):  | 
| 
3835.1.13
by Aaron Bentley
 Update documentation  | 
112  | 
"""Constructor.  | 
113  | 
||
114  | 
        :param parent_provider: The ParentProvider to use.  It or
 | 
|
115  | 
            get_parent_map must be supplied.
 | 
|
116  | 
        :param get_parent_map: The get_parent_map callback to use.  It or
 | 
|
117  | 
            parent_provider must be supplied.
 | 
|
118  | 
        """
 | 
|
| 
3835.1.12
by Aaron Bentley
 Unify CachingExtraParentsProvider and CachingParentsProvider.  | 
119  | 
self._real_provider = parent_provider  | 
120  | 
if get_parent_map is None:  | 
|
121  | 
self._get_parent_map = self._real_provider.get_parent_map  | 
|
122  | 
else:  | 
|
123  | 
self._get_parent_map = get_parent_map  | 
|
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
124  | 
self._cache = None  | 
125  | 
self.enable_cache(True)  | 
|
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
126  | 
|
| 
3835.1.12
by Aaron Bentley
 Unify CachingExtraParentsProvider and CachingParentsProvider.  | 
127  | 
def __repr__(self):  | 
128  | 
return "%s(%r)" % (self.__class__.__name__, self._real_provider)  | 
|
129  | 
||
| 
3835.1.15
by Aaron Bentley
 Allow miss caching to be disabled.  | 
130  | 
def enable_cache(self, cache_misses=True):  | 
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
131  | 
"""Enable cache."""  | 
| 
3835.1.19
by Aaron Bentley
 Raise exception when caching is enabled twice.  | 
132  | 
if self._cache is not None:  | 
| 
3835.1.20
by Aaron Bentley
 Change custom error to an AssertionError.  | 
133  | 
raise AssertionError('Cache enabled when already enabled.')  | 
| 
3835.1.16
by Aaron Bentley
 Updates from review  | 
134  | 
self._cache = {}  | 
| 
3835.1.15
by Aaron Bentley
 Allow miss caching to be disabled.  | 
135  | 
self._cache_misses = cache_misses  | 
| 
4190.1.4
by Robert Collins
 Cache ghosts when we can get them from a RemoteRepository in get_parent_map.  | 
136  | 
self.missing_keys = set()  | 
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
137  | 
|
138  | 
def disable_cache(self):  | 
|
| 
3835.1.16
by Aaron Bentley
 Updates from review  | 
139  | 
"""Disable and clear the cache."""  | 
140  | 
self._cache = None  | 
|
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
141  | 
self._cache_misses = None  | 
| 
4190.1.4
by Robert Collins
 Cache ghosts when we can get them from a RemoteRepository in get_parent_map.  | 
142  | 
self.missing_keys = set()  | 
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
143  | 
|
144  | 
def get_cached_map(self):  | 
|
145  | 
"""Return any cached get_parent_map values."""  | 
|
| 
3835.1.16
by Aaron Bentley
 Updates from review  | 
146  | 
if self._cache is None:  | 
| 
3835.1.12
by Aaron Bentley
 Unify CachingExtraParentsProvider and CachingParentsProvider.  | 
147  | 
return None  | 
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
148  | 
return dict(self._cache)  | 
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
149  | 
|
150  | 
def get_parent_map(self, keys):  | 
|
| 
3835.1.16
by Aaron Bentley
 Updates from review  | 
151  | 
"""See _StackedParentsProvider.get_parent_map."""  | 
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
152  | 
cache = self._cache  | 
153  | 
if cache is None:  | 
|
154  | 
cache = self._get_parent_map(keys)  | 
|
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
155  | 
else:  | 
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
156  | 
needed_revisions = set(key for key in keys if key not in cache)  | 
157  | 
            # Do not ask for negatively cached keys
 | 
|
| 
4190.1.4
by Robert Collins
 Cache ghosts when we can get them from a RemoteRepository in get_parent_map.  | 
158  | 
needed_revisions.difference_update(self.missing_keys)  | 
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
159  | 
if needed_revisions:  | 
160  | 
parent_map = self._get_parent_map(needed_revisions)  | 
|
161  | 
cache.update(parent_map)  | 
|
162  | 
if self._cache_misses:  | 
|
163  | 
for key in needed_revisions:  | 
|
164  | 
if key not in parent_map:  | 
|
| 
4190.1.4
by Robert Collins
 Cache ghosts when we can get them from a RemoteRepository in get_parent_map.  | 
165  | 
self.note_missing_key(key)  | 
| 
4190.1.1
by Robert Collins
 Negatively cache misses during read-locks in RemoteRepository.  | 
166  | 
result = {}  | 
167  | 
for key in keys:  | 
|
168  | 
value = cache.get(key)  | 
|
169  | 
if value is not None:  | 
|
170  | 
result[key] = value  | 
|
171  | 
return result  | 
|
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
172  | 
|
| 
4190.1.4
by Robert Collins
 Cache ghosts when we can get them from a RemoteRepository in get_parent_map.  | 
173  | 
def note_missing_key(self, key):  | 
174  | 
"""Note that key is a missing key."""  | 
|
175  | 
if self._cache_misses:  | 
|
176  | 
self.missing_keys.add(key)  | 
|
177  | 
||
| 
3835.1.10
by Aaron Bentley
 Move CachingExtraParentsProvider to Graph  | 
178  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
179  | 
class Graph(object):  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
180  | 
"""Provide incremental access to revision graphs.  | 
181  | 
||
182  | 
    This is the generic implementation; it is intended to be subclassed to
 | 
|
183  | 
    specialize it for other repository types.
 | 
|
184  | 
    """
 | 
|
| 
2490.2.1
by Aaron Bentley
 Start work on GraphWalker  | 
185  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
186  | 
def __init__(self, parents_provider):  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
187  | 
"""Construct a Graph that uses several graphs as its input  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
188  | 
|
189  | 
        This should not normally be invoked directly, because there may be
 | 
|
190  | 
        specialized implementations for particular repository types.  See
 | 
|
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
191  | 
        Repository.get_graph().
 | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
192  | 
|
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
193  | 
        :param parents_provider: An object providing a get_parent_map call
 | 
194  | 
            conforming to the behavior of
 | 
|
195  | 
            StackedParentsProvider.get_parent_map.
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
196  | 
        """
 | 
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
197  | 
if getattr(parents_provider, 'get_parents', None) is not None:  | 
198  | 
self.get_parents = parents_provider.get_parents  | 
|
199  | 
if getattr(parents_provider, 'get_parent_map', None) is not None:  | 
|
200  | 
self.get_parent_map = parents_provider.get_parent_map  | 
|
| 
2490.2.29
by Aaron Bentley
 Make parents provider private  | 
201  | 
self._parents_provider = parents_provider  | 
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
202  | 
|
203  | 
def __repr__(self):  | 
|
| 
2490.2.29
by Aaron Bentley
 Make parents provider private  | 
204  | 
return 'Graph(%r)' % self._parents_provider  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
205  | 
|
206  | 
def find_lca(self, *revisions):  | 
|
207  | 
"""Determine the lowest common ancestors of the provided revisions  | 
|
208  | 
||
209  | 
        A lowest common ancestor is a common ancestor none of whose
 | 
|
210  | 
        descendants are common ancestors.  In graphs, unlike trees, there may
 | 
|
211  | 
        be multiple lowest common ancestors.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
212  | 
|
213  | 
        This algorithm has two phases.  Phase 1 identifies border ancestors,
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
214  | 
        and phase 2 filters border ancestors to determine lowest common
 | 
215  | 
        ancestors.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
216  | 
|
217  | 
        In phase 1, border ancestors are identified, using a breadth-first
 | 
|
218  | 
        search starting at the bottom of the graph.  Searches are stopped
 | 
|
219  | 
        whenever a node or one of its descendants is determined to be common
 | 
|
220  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
221  | 
        In phase 2, the border ancestors are filtered to find the least
 | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
222  | 
        common ancestors.  This is done by searching the ancestries of each
 | 
223  | 
        border ancestor.
 | 
|
224  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
225  | 
        Phase 2 is perfomed on the principle that a border ancestor that is
 | 
226  | 
        not an ancestor of any other border ancestor is a least common
 | 
|
227  | 
        ancestor.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
228  | 
|
229  | 
        Searches are stopped when they find a node that is determined to be a
 | 
|
230  | 
        common ancestor of all border ancestors, because this shows that it
 | 
|
231  | 
        cannot be a descendant of any border ancestor.
 | 
|
232  | 
||
233  | 
        The scaling of this operation should be proportional to
 | 
|
234  | 
        1. The number of uncommon ancestors
 | 
|
235  | 
        2. The number of border ancestors
 | 
|
236  | 
        3. The length of the shortest path between a border ancestor and an
 | 
|
237  | 
           ancestor of all border ancestors.
 | 
|
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
238  | 
        """
 | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
239  | 
border_common, common, sides = self._find_border_ancestors(revisions)  | 
| 
2776.3.1
by Robert Collins
 * Deprecated method ``find_previous_heads`` on  | 
240  | 
        # We may have common ancestors that can be reached from each other.
 | 
241  | 
        # - ask for the heads of them to filter it down to only ones that
 | 
|
242  | 
        # cannot be reached from each other - phase 2.
 | 
|
243  | 
return self.heads(border_common)  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
244  | 
|
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
245  | 
def find_difference(self, left_revision, right_revision):  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
246  | 
"""Determine the graph difference between two revisions"""  | 
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
247  | 
border, common, searchers = self._find_border_ancestors(  | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
248  | 
[left_revision, right_revision])  | 
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
249  | 
self._search_for_extra_common(common, searchers)  | 
250  | 
left = searchers[0].seen  | 
|
251  | 
right = searchers[1].seen  | 
|
252  | 
return (left.difference(right), right.difference(left))  | 
|
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
253  | 
|
| 
3445.1.4
by John Arbash Meinel
 Change the function to be called 'find_distance_to_null'  | 
254  | 
def find_distance_to_null(self, target_revision_id, known_revision_ids):  | 
255  | 
"""Find the left-hand distance to the NULL_REVISION.  | 
|
256  | 
||
257  | 
        (This can also be considered the revno of a branch at
 | 
|
258  | 
        target_revision_id.)
 | 
|
| 
3445.1.1
by John Arbash Meinel
 Start working on a new Graph api to make finding revision numbers faster.  | 
259  | 
|
260  | 
        :param target_revision_id: A revision_id which we would like to know
 | 
|
261  | 
            the revno for.
 | 
|
262  | 
        :param known_revision_ids: [(revision_id, revno)] A list of known
 | 
|
263  | 
            revno, revision_id tuples. We'll use this to seed the search.
 | 
|
264  | 
        """
 | 
|
265  | 
        # Map from revision_ids to a known value for their revno
 | 
|
266  | 
known_revnos = dict(known_revision_ids)  | 
|
267  | 
cur_tip = target_revision_id  | 
|
268  | 
num_steps = 0  | 
|
269  | 
NULL_REVISION = revision.NULL_REVISION  | 
|
| 
3445.1.2
by John Arbash Meinel
 Handle when a known revision is an ancestor.  | 
270  | 
known_revnos[NULL_REVISION] = 0  | 
| 
3445.1.1
by John Arbash Meinel
 Start working on a new Graph api to make finding revision numbers faster.  | 
271  | 
|
| 
3445.1.3
by John Arbash Meinel
 Search from all of the known revisions.  | 
272  | 
searching_known_tips = list(known_revnos.keys())  | 
273  | 
||
274  | 
unknown_searched = {}  | 
|
275  | 
||
| 
3445.1.2
by John Arbash Meinel
 Handle when a known revision is an ancestor.  | 
276  | 
while cur_tip not in known_revnos:  | 
| 
3445.1.3
by John Arbash Meinel
 Search from all of the known revisions.  | 
277  | 
unknown_searched[cur_tip] = num_steps  | 
278  | 
num_steps += 1  | 
|
| 
3445.1.2
by John Arbash Meinel
 Handle when a known revision is an ancestor.  | 
279  | 
to_search = set([cur_tip])  | 
| 
3445.1.3
by John Arbash Meinel
 Search from all of the known revisions.  | 
280  | 
to_search.update(searching_known_tips)  | 
| 
3445.1.2
by John Arbash Meinel
 Handle when a known revision is an ancestor.  | 
281  | 
parent_map = self.get_parent_map(to_search)  | 
| 
3445.1.1
by John Arbash Meinel
 Start working on a new Graph api to make finding revision numbers faster.  | 
282  | 
parents = parent_map.get(cur_tip, None)  | 
| 
3445.1.8
by John Arbash Meinel
 Clarity tweaks recommended by Ian  | 
283  | 
if not parents: # An empty list or None is a ghost  | 
| 
3445.1.1
by John Arbash Meinel
 Start working on a new Graph api to make finding revision numbers faster.  | 
284  | 
raise errors.GhostRevisionsHaveNoRevno(target_revision_id,  | 
285  | 
cur_tip)  | 
|
286  | 
cur_tip = parents[0]  | 
|
| 
3445.1.3
by John Arbash Meinel
 Search from all of the known revisions.  | 
287  | 
next_known_tips = []  | 
288  | 
for revision_id in searching_known_tips:  | 
|
289  | 
parents = parent_map.get(revision_id, None)  | 
|
290  | 
if not parents:  | 
|
291  | 
                    continue
 | 
|
292  | 
next = parents[0]  | 
|
293  | 
next_revno = known_revnos[revision_id] - 1  | 
|
294  | 
if next in unknown_searched:  | 
|
295  | 
                    # We have enough information to return a value right now
 | 
|
296  | 
return next_revno + unknown_searched[next]  | 
|
297  | 
if next in known_revnos:  | 
|
298  | 
                    continue
 | 
|
299  | 
known_revnos[next] = next_revno  | 
|
300  | 
next_known_tips.append(next)  | 
|
301  | 
searching_known_tips = next_known_tips  | 
|
| 
3445.1.1
by John Arbash Meinel
 Start working on a new Graph api to make finding revision numbers faster.  | 
302  | 
|
| 
3445.1.2
by John Arbash Meinel
 Handle when a known revision is an ancestor.  | 
303  | 
        # We reached a known revision, so just add in how many steps it took to
 | 
304  | 
        # get there.
 | 
|
305  | 
return known_revnos[cur_tip] + num_steps  | 
|
| 
3445.1.1
by John Arbash Meinel
 Start working on a new Graph api to make finding revision numbers faster.  | 
306  | 
|
| 
3377.3.21
by John Arbash Meinel
 Simple brute-force implementation of find_unique_ancestors  | 
307  | 
def find_unique_ancestors(self, unique_revision, common_revisions):  | 
308  | 
"""Find the unique ancestors for a revision versus others.  | 
|
309  | 
||
310  | 
        This returns the ancestry of unique_revision, excluding all revisions
 | 
|
311  | 
        in the ancestry of common_revisions. If unique_revision is in the
 | 
|
312  | 
        ancestry, then the empty set will be returned.
 | 
|
313  | 
||
314  | 
        :param unique_revision: The revision_id whose ancestry we are
 | 
|
315  | 
            interested in.
 | 
|
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
316  | 
            XXX: Would this API be better if we allowed multiple revisions on
 | 
317  | 
                 to be searched here?
 | 
|
| 
3377.3.21
by John Arbash Meinel
 Simple brute-force implementation of find_unique_ancestors  | 
318  | 
        :param common_revisions: Revision_ids of ancestries to exclude.
 | 
319  | 
        :return: A set of revisions in the ancestry of unique_revision
 | 
|
320  | 
        """
 | 
|
321  | 
if unique_revision in common_revisions:  | 
|
322  | 
return set()  | 
|
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
323  | 
|
324  | 
        # Algorithm description
 | 
|
325  | 
        # 1) Walk backwards from the unique node and all common nodes.
 | 
|
326  | 
        # 2) When a node is seen by both sides, stop searching it in the unique
 | 
|
327  | 
        #    walker, include it in the common walker.
 | 
|
328  | 
        # 3) Stop searching when there are no nodes left for the unique walker.
 | 
|
329  | 
        #    At this point, you have a maximal set of unique nodes. Some of
 | 
|
330  | 
        #    them may actually be common, and you haven't reached them yet.
 | 
|
331  | 
        # 4) Start new searchers for the unique nodes, seeded with the
 | 
|
332  | 
        #    information you have so far.
 | 
|
333  | 
        # 5) Continue searching, stopping the common searches when the search
 | 
|
334  | 
        #    tip is an ancestor of all unique nodes.
 | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
335  | 
        # 6) Aggregate together unique searchers when they are searching the
 | 
336  | 
        #    same tips. When all unique searchers are searching the same node,
 | 
|
337  | 
        #    stop move it to a single 'all_unique_searcher'.
 | 
|
338  | 
        # 7) The 'all_unique_searcher' represents the very 'tip' of searching.
 | 
|
339  | 
        #    Most of the time this produces very little important information.
 | 
|
340  | 
        #    So don't step it as quickly as the other searchers.
 | 
|
341  | 
        # 8) Search is done when all common searchers have completed.
 | 
|
342  | 
||
343  | 
unique_searcher, common_searcher = self._find_initial_unique_nodes(  | 
|
344  | 
[unique_revision], common_revisions)  | 
|
345  | 
||
346  | 
unique_nodes = unique_searcher.seen.difference(common_searcher.seen)  | 
|
347  | 
if not unique_nodes:  | 
|
348  | 
return unique_nodes  | 
|
349  | 
||
350  | 
(all_unique_searcher,  | 
|
351  | 
unique_tip_searchers) = self._make_unique_searchers(unique_nodes,  | 
|
352  | 
unique_searcher, common_searcher)  | 
|
353  | 
||
354  | 
self._refine_unique_nodes(unique_searcher, all_unique_searcher,  | 
|
355  | 
unique_tip_searchers, common_searcher)  | 
|
356  | 
true_unique_nodes = unique_nodes.difference(common_searcher.seen)  | 
|
| 
3377.3.33
by John Arbash Meinel
 Add some logging with -Dgraph  | 
357  | 
if 'graph' in debug.debug_flags:  | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
358  | 
trace.mutter('Found %d truly unique nodes out of %d',  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
359  | 
len(true_unique_nodes), len(unique_nodes))  | 
360  | 
return true_unique_nodes  | 
|
361  | 
||
362  | 
def _find_initial_unique_nodes(self, unique_revisions, common_revisions):  | 
|
363  | 
"""Steps 1-3 of find_unique_ancestors.  | 
|
364  | 
||
365  | 
        Find the maximal set of unique nodes. Some of these might actually
 | 
|
366  | 
        still be common, but we are sure that there are no other unique nodes.
 | 
|
367  | 
||
368  | 
        :return: (unique_searcher, common_searcher)
 | 
|
369  | 
        """
 | 
|
370  | 
||
371  | 
unique_searcher = self._make_breadth_first_searcher(unique_revisions)  | 
|
372  | 
        # we know that unique_revisions aren't in common_revisions, so skip
 | 
|
373  | 
        # past them.
 | 
|
| 
3377.3.27
by John Arbash Meinel
 some simple updates  | 
374  | 
unique_searcher.next()  | 
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
375  | 
common_searcher = self._make_breadth_first_searcher(common_revisions)  | 
376  | 
||
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
377  | 
        # As long as we are still finding unique nodes, keep searching
 | 
| 
3377.3.27
by John Arbash Meinel
 some simple updates  | 
378  | 
while unique_searcher._next_query:  | 
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
379  | 
next_unique_nodes = set(unique_searcher.step())  | 
380  | 
next_common_nodes = set(common_searcher.step())  | 
|
381  | 
||
382  | 
            # Check if either searcher encounters new nodes seen by the other
 | 
|
383  | 
            # side.
 | 
|
384  | 
unique_are_common_nodes = next_unique_nodes.intersection(  | 
|
385  | 
common_searcher.seen)  | 
|
386  | 
unique_are_common_nodes.update(  | 
|
387  | 
next_common_nodes.intersection(unique_searcher.seen))  | 
|
388  | 
if unique_are_common_nodes:  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
389  | 
ancestors = unique_searcher.find_seen_ancestors(  | 
390  | 
unique_are_common_nodes)  | 
|
| 
3377.4.5
by John Arbash Meinel
 Several updates. A bit more debug logging, only step the all_unique searcher 1/10th of the time.  | 
391  | 
                # TODO: This is a bit overboard, we only really care about
 | 
392  | 
                #       the ancestors of the tips because the rest we
 | 
|
393  | 
                #       already know. This is *correct* but causes us to
 | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
394  | 
                #       search too much ancestry.
 | 
| 
3377.3.29
by John Arbash Meinel
 Revert the _find_any_seen change.  | 
395  | 
ancestors.update(common_searcher.find_seen_ancestors(ancestors))  | 
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
396  | 
unique_searcher.stop_searching_any(ancestors)  | 
397  | 
common_searcher.start_searching(ancestors)  | 
|
398  | 
||
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
399  | 
return unique_searcher, common_searcher  | 
400  | 
||
401  | 
def _make_unique_searchers(self, unique_nodes, unique_searcher,  | 
|
402  | 
common_searcher):  | 
|
403  | 
"""Create a searcher for all the unique search tips (step 4).  | 
|
404  | 
||
405  | 
        As a side effect, the common_searcher will stop searching any nodes
 | 
|
406  | 
        that are ancestors of the unique searcher tips.
 | 
|
407  | 
||
408  | 
        :return: (all_unique_searcher, unique_tip_searchers)
 | 
|
409  | 
        """
 | 
|
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
410  | 
unique_tips = self._remove_simple_descendants(unique_nodes,  | 
411  | 
self.get_parent_map(unique_nodes))  | 
|
412  | 
||
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
413  | 
if len(unique_tips) == 1:  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
414  | 
unique_tip_searchers = []  | 
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
415  | 
ancestor_all_unique = unique_searcher.find_seen_ancestors(unique_tips)  | 
416  | 
else:  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
417  | 
unique_tip_searchers = []  | 
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
418  | 
for tip in unique_tips:  | 
419  | 
revs_to_search = unique_searcher.find_seen_ancestors([tip])  | 
|
420  | 
revs_to_search.update(  | 
|
421  | 
common_searcher.find_seen_ancestors(revs_to_search))  | 
|
422  | 
searcher = self._make_breadth_first_searcher(revs_to_search)  | 
|
423  | 
                # We don't care about the starting nodes.
 | 
|
424  | 
searcher._label = tip  | 
|
425  | 
searcher.step()  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
426  | 
unique_tip_searchers.append(searcher)  | 
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
427  | 
|
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
428  | 
ancestor_all_unique = None  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
429  | 
for searcher in unique_tip_searchers:  | 
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
430  | 
if ancestor_all_unique is None:  | 
431  | 
ancestor_all_unique = set(searcher.seen)  | 
|
432  | 
else:  | 
|
433  | 
ancestor_all_unique = ancestor_all_unique.intersection(  | 
|
434  | 
searcher.seen)  | 
|
| 
3377.3.33
by John Arbash Meinel
 Add some logging with -Dgraph  | 
435  | 
        # Collapse all the common nodes into a single searcher
 | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
436  | 
all_unique_searcher = self._make_breadth_first_searcher(  | 
437  | 
ancestor_all_unique)  | 
|
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
438  | 
if ancestor_all_unique:  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
439  | 
            # We've seen these nodes in all the searchers, so we'll just go to
 | 
440  | 
            # the next
 | 
|
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
441  | 
all_unique_searcher.step()  | 
442  | 
||
443  | 
            # Stop any search tips that are already known as ancestors of the
 | 
|
444  | 
            # unique nodes
 | 
|
445  | 
stopped_common = common_searcher.stop_searching_any(  | 
|
446  | 
common_searcher.find_seen_ancestors(ancestor_all_unique))  | 
|
447  | 
||
448  | 
total_stopped = 0  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
449  | 
for searcher in unique_tip_searchers:  | 
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
450  | 
total_stopped += len(searcher.stop_searching_any(  | 
451  | 
searcher.find_seen_ancestors(ancestor_all_unique)))  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
452  | 
if 'graph' in debug.debug_flags:  | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
453  | 
trace.mutter('For %d unique nodes, created %d + 1 unique searchers'  | 
454  | 
' (%d stopped search tips, %d common ancestors'  | 
|
455  | 
' (%d stopped common)',  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
456  | 
len(unique_nodes), len(unique_tip_searchers),  | 
457  | 
total_stopped, len(ancestor_all_unique),  | 
|
458  | 
len(stopped_common))  | 
|
459  | 
return all_unique_searcher, unique_tip_searchers  | 
|
460  | 
||
461  | 
def _step_unique_and_common_searchers(self, common_searcher,  | 
|
462  | 
unique_tip_searchers,  | 
|
463  | 
unique_searcher):  | 
|
| 
3377.4.7
by John Arbash Meinel
 Small documentation and code wrapping cleanup  | 
464  | 
"""Step all the searchers"""  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
465  | 
newly_seen_common = set(common_searcher.step())  | 
466  | 
newly_seen_unique = set()  | 
|
467  | 
for searcher in unique_tip_searchers:  | 
|
468  | 
next = set(searcher.step())  | 
|
469  | 
next.update(unique_searcher.find_seen_ancestors(next))  | 
|
470  | 
next.update(common_searcher.find_seen_ancestors(next))  | 
|
471  | 
for alt_searcher in unique_tip_searchers:  | 
|
472  | 
if alt_searcher is searcher:  | 
|
473  | 
                    continue
 | 
|
474  | 
next.update(alt_searcher.find_seen_ancestors(next))  | 
|
475  | 
searcher.start_searching(next)  | 
|
476  | 
newly_seen_unique.update(next)  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
477  | 
return newly_seen_common, newly_seen_unique  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
478  | 
|
479  | 
def _find_nodes_common_to_all_unique(self, unique_tip_searchers,  | 
|
480  | 
all_unique_searcher,  | 
|
481  | 
newly_seen_unique, step_all_unique):  | 
|
482  | 
"""Find nodes that are common to all unique_tip_searchers.  | 
|
483  | 
||
484  | 
        If it is time, step the all_unique_searcher, and add its nodes to the
 | 
|
485  | 
        result.
 | 
|
486  | 
        """
 | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
487  | 
common_to_all_unique_nodes = newly_seen_unique.copy()  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
488  | 
for searcher in unique_tip_searchers:  | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
489  | 
common_to_all_unique_nodes.intersection_update(searcher.seen)  | 
490  | 
common_to_all_unique_nodes.intersection_update(  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
491  | 
all_unique_searcher.seen)  | 
492  | 
        # Step all-unique less frequently than the other searchers.
 | 
|
493  | 
        # In the common case, we don't need to spider out far here, so
 | 
|
494  | 
        # avoid doing extra work.
 | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
495  | 
if step_all_unique:  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
496  | 
tstart = time.clock()  | 
497  | 
nodes = all_unique_searcher.step()  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
498  | 
common_to_all_unique_nodes.update(nodes)  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
499  | 
if 'graph' in debug.debug_flags:  | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
500  | 
tdelta = time.clock() - tstart  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
501  | 
trace.mutter('all_unique_searcher step() took %.3fs'  | 
502  | 
'for %d nodes (%d total), iteration: %s',  | 
|
503  | 
tdelta, len(nodes), len(all_unique_searcher.seen),  | 
|
504  | 
all_unique_searcher._iterations)  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
505  | 
return common_to_all_unique_nodes  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
506  | 
|
507  | 
def _collapse_unique_searchers(self, unique_tip_searchers,  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
508  | 
common_to_all_unique_nodes):  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
509  | 
"""Combine searchers that are searching the same tips.  | 
510  | 
||
511  | 
        When two searchers are searching the same tips, we can stop one of the
 | 
|
512  | 
        searchers. We also know that the maximal set of common ancestors is the
 | 
|
513  | 
        intersection of the two original searchers.
 | 
|
514  | 
||
515  | 
        :return: A list of searchers that are searching unique nodes.
 | 
|
516  | 
        """
 | 
|
517  | 
        # Filter out searchers that don't actually search different
 | 
|
518  | 
        # nodes. We already have the ancestry intersection for them
 | 
|
519  | 
unique_search_tips = {}  | 
|
520  | 
for searcher in unique_tip_searchers:  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
521  | 
stopped = searcher.stop_searching_any(common_to_all_unique_nodes)  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
522  | 
will_search_set = frozenset(searcher._next_query)  | 
523  | 
if not will_search_set:  | 
|
524  | 
if 'graph' in debug.debug_flags:  | 
|
525  | 
trace.mutter('Unique searcher %s was stopped.'  | 
|
526  | 
' (%s iterations) %d nodes stopped',  | 
|
527  | 
searcher._label,  | 
|
528  | 
searcher._iterations,  | 
|
529  | 
len(stopped))  | 
|
530  | 
elif will_search_set not in unique_search_tips:  | 
|
531  | 
                # This searcher is searching a unique set of nodes, let it
 | 
|
532  | 
unique_search_tips[will_search_set] = [searcher]  | 
|
533  | 
else:  | 
|
534  | 
unique_search_tips[will_search_set].append(searcher)  | 
|
535  | 
        # TODO: it might be possible to collapse searchers faster when they
 | 
|
536  | 
        #       only have *some* search tips in common.
 | 
|
537  | 
next_unique_searchers = []  | 
|
538  | 
for searchers in unique_search_tips.itervalues():  | 
|
539  | 
if len(searchers) == 1:  | 
|
540  | 
                # Searching unique tips, go for it
 | 
|
541  | 
next_unique_searchers.append(searchers[0])  | 
|
542  | 
else:  | 
|
543  | 
                # These searchers have started searching the same tips, we
 | 
|
544  | 
                # don't need them to cover the same ground. The
 | 
|
545  | 
                # intersection of their ancestry won't change, so create a
 | 
|
546  | 
                # new searcher, combining their histories.
 | 
|
547  | 
next_searcher = searchers[0]  | 
|
548  | 
for searcher in searchers[1:]:  | 
|
549  | 
next_searcher.seen.intersection_update(searcher.seen)  | 
|
550  | 
if 'graph' in debug.debug_flags:  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
551  | 
trace.mutter('Combining %d searchers into a single'  | 
552  | 
' searcher searching %d nodes with'  | 
|
553  | 
' %d ancestry',  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
554  | 
len(searchers),  | 
555  | 
len(next_searcher._next_query),  | 
|
556  | 
len(next_searcher.seen))  | 
|
557  | 
next_unique_searchers.append(next_searcher)  | 
|
558  | 
return next_unique_searchers  | 
|
559  | 
||
560  | 
def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,  | 
|
561  | 
unique_tip_searchers, common_searcher):  | 
|
562  | 
"""Steps 5-8 of find_unique_ancestors.  | 
|
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
563  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
564  | 
        This function returns when common_searcher has stopped searching for
 | 
565  | 
        more nodes.
 | 
|
566  | 
        """
 | 
|
567  | 
        # We step the ancestor_all_unique searcher only every
 | 
|
568  | 
        # STEP_UNIQUE_SEARCHER_EVERY steps.
 | 
|
569  | 
step_all_unique_counter = 0  | 
|
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
570  | 
        # While we still have common nodes to search
 | 
571  | 
while common_searcher._next_query:  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
572  | 
(newly_seen_common,  | 
573  | 
newly_seen_unique) = self._step_unique_and_common_searchers(  | 
|
574  | 
common_searcher, unique_tip_searchers, unique_searcher)  | 
|
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
575  | 
            # These nodes are common ancestors of all unique nodes
 | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
576  | 
common_to_all_unique_nodes = self._find_nodes_common_to_all_unique(  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
577  | 
unique_tip_searchers, all_unique_searcher, newly_seen_unique,  | 
578  | 
step_all_unique_counter==0)  | 
|
579  | 
step_all_unique_counter = ((step_all_unique_counter + 1)  | 
|
580  | 
% STEP_UNIQUE_SEARCHER_EVERY)  | 
|
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
581  | 
|
582  | 
if newly_seen_common:  | 
|
583  | 
                # If a 'common' node is an ancestor of all unique searchers, we
 | 
|
584  | 
                # can stop searching it.
 | 
|
585  | 
common_searcher.stop_searching_any(  | 
|
586  | 
all_unique_searcher.seen.intersection(newly_seen_common))  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
587  | 
if common_to_all_unique_nodes:  | 
588  | 
common_to_all_unique_nodes.update(  | 
|
| 
3377.4.7
by John Arbash Meinel
 Small documentation and code wrapping cleanup  | 
589  | 
common_searcher.find_seen_ancestors(  | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
590  | 
common_to_all_unique_nodes))  | 
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
591  | 
                # The all_unique searcher can start searching the common nodes
 | 
592  | 
                # but everyone else can stop.
 | 
|
| 
3377.4.7
by John Arbash Meinel
 Small documentation and code wrapping cleanup  | 
593  | 
                # This is the sort of thing where we would like to not have it
 | 
594  | 
                # start_searching all of the nodes, but only mark all of them
 | 
|
595  | 
                # as seen, and have it search only the actual tips. Otherwise
 | 
|
596  | 
                # it is another get_parent_map() traversal for it to figure out
 | 
|
597  | 
                # what we already should know.
 | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
598  | 
all_unique_searcher.start_searching(common_to_all_unique_nodes)  | 
599  | 
common_searcher.stop_searching_any(common_to_all_unique_nodes)  | 
|
| 
3377.4.4
by John Arbash Meinel
 Restore the previous code, but bring in a couple changes. Including an update to have lsprof show where the time is spent.  | 
600  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
601  | 
next_unique_searchers = self._collapse_unique_searchers(  | 
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
602  | 
unique_tip_searchers, common_to_all_unique_nodes)  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
603  | 
if len(unique_tip_searchers) != len(next_unique_searchers):  | 
604  | 
if 'graph' in debug.debug_flags:  | 
|
| 
3377.4.8
by John Arbash Meinel
 Final tweaks from Ian  | 
605  | 
trace.mutter('Collapsed %d unique searchers => %d'  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
606  | 
' at %s iterations',  | 
607  | 
len(unique_tip_searchers),  | 
|
608  | 
len(next_unique_searchers),  | 
|
609  | 
all_unique_searcher._iterations)  | 
|
610  | 
unique_tip_searchers = next_unique_searchers  | 
|
| 
3377.3.21
by John Arbash Meinel
 Simple brute-force implementation of find_unique_ancestors  | 
611  | 
|
| 
3172.1.2
by Robert Collins
 Parent Providers should now implement ``get_parent_map`` returning a  | 
612  | 
def get_parent_map(self, revisions):  | 
613  | 
"""Get a map of key:parent_list for revisions.  | 
|
614  | 
||
615  | 
        This implementation delegates to get_parents, for old parent_providers
 | 
|
616  | 
        that do not supply get_parent_map.
 | 
|
617  | 
        """
 | 
|
618  | 
result = {}  | 
|
619  | 
for rev, parents in self.get_parents(revisions):  | 
|
620  | 
if parents is not None:  | 
|
621  | 
result[rev] = parents  | 
|
622  | 
return result  | 
|
623  | 
||
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
624  | 
def _make_breadth_first_searcher(self, revisions):  | 
625  | 
return _BreadthFirstSearcher(revisions, self)  | 
|
626  | 
||
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
627  | 
def _find_border_ancestors(self, revisions):  | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
628  | 
"""Find common ancestors with at least one uncommon descendant.  | 
629  | 
||
630  | 
        Border ancestors are identified using a breadth-first
 | 
|
631  | 
        search starting at the bottom of the graph.  Searches are stopped
 | 
|
632  | 
        whenever a node or one of its descendants is determined to be common.
 | 
|
633  | 
||
634  | 
        This will scale with the number of uncommon ancestors.
 | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
635  | 
|
636  | 
        As well as the border ancestors, a set of seen common ancestors and a
 | 
|
637  | 
        list of sets of seen ancestors for each input revision is returned.
 | 
|
638  | 
        This allows calculation of graph difference from the results of this
 | 
|
639  | 
        operation.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
640  | 
        """
 | 
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
641  | 
if None in revisions:  | 
642  | 
raise errors.InvalidRevisionId(None, self)  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
643  | 
common_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
644  | 
searchers = [self._make_breadth_first_searcher([r])  | 
645  | 
for r in revisions]  | 
|
646  | 
active_searchers = searchers[:]  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
647  | 
border_ancestors = set()  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
648  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
649  | 
while True:  | 
650  | 
newly_seen = set()  | 
|
| 
3377.3.2
by John Arbash Meinel
 find_difference is fixed by updating _find_border_ancestors.... is that reasonable?  | 
651  | 
for searcher in searchers:  | 
652  | 
new_ancestors = searcher.step()  | 
|
653  | 
if new_ancestors:  | 
|
654  | 
newly_seen.update(new_ancestors)  | 
|
655  | 
new_common = set()  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
656  | 
for revision in newly_seen:  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
657  | 
if revision in common_ancestors:  | 
| 
3377.3.2
by John Arbash Meinel
 find_difference is fixed by updating _find_border_ancestors.... is that reasonable?  | 
658  | 
                    # Not a border ancestor because it was seen as common
 | 
659  | 
                    # already
 | 
|
660  | 
new_common.add(revision)  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
661  | 
                    continue
 | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
662  | 
for searcher in searchers:  | 
663  | 
if revision not in searcher.seen:  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
664  | 
                        break
 | 
665  | 
else:  | 
|
| 
3377.3.2
by John Arbash Meinel
 find_difference is fixed by updating _find_border_ancestors.... is that reasonable?  | 
666  | 
                    # This is a border because it is a first common that we see
 | 
667  | 
                    # after walking for a while.
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
668  | 
border_ancestors.add(revision)  | 
| 
3377.3.2
by John Arbash Meinel
 find_difference is fixed by updating _find_border_ancestors.... is that reasonable?  | 
669  | 
new_common.add(revision)  | 
670  | 
if new_common:  | 
|
671  | 
for searcher in searchers:  | 
|
672  | 
new_common.update(searcher.find_seen_ancestors(new_common))  | 
|
673  | 
for searcher in searchers:  | 
|
674  | 
searcher.start_searching(new_common)  | 
|
675  | 
common_ancestors.update(new_common)  | 
|
676  | 
||
677  | 
            # Figure out what the searchers will be searching next, and if
 | 
|
678  | 
            # there is only 1 set being searched, then we are done searching,
 | 
|
679  | 
            # since all searchers would have to be searching the same data,
 | 
|
680  | 
            # thus it *must* be in common.
 | 
|
681  | 
unique_search_sets = set()  | 
|
682  | 
for searcher in searchers:  | 
|
683  | 
will_search_set = frozenset(searcher._next_query)  | 
|
684  | 
if will_search_set not in unique_search_sets:  | 
|
685  | 
                    # This searcher is searching a unique set of nodes, let it
 | 
|
686  | 
unique_search_sets.add(will_search_set)  | 
|
687  | 
||
688  | 
if len(unique_search_sets) == 1:  | 
|
689  | 
nodes = unique_search_sets.pop()  | 
|
690  | 
uncommon_nodes = nodes.difference(common_ancestors)  | 
|
| 
3376.2.14
by Martin Pool
 Remove recently-introduced assert statements  | 
691  | 
if uncommon_nodes:  | 
692  | 
raise AssertionError("Somehow we ended up converging"  | 
|
693  | 
                                         " without actually marking them as"
 | 
|
694  | 
                                         " in common."
 | 
|
695  | 
"\nStart_nodes: %s"  | 
|
696  | 
"\nuncommon_nodes: %s"  | 
|
697  | 
% (revisions, uncommon_nodes))  | 
|
| 
3377.3.2
by John Arbash Meinel
 find_difference is fixed by updating _find_border_ancestors.... is that reasonable?  | 
698  | 
                break
 | 
699  | 
return border_ancestors, common_ancestors, searchers  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
700  | 
|
| 
2776.3.1
by Robert Collins
 * Deprecated method ``find_previous_heads`` on  | 
701  | 
def heads(self, keys):  | 
702  | 
"""Return the heads from amongst keys.  | 
|
703  | 
||
704  | 
        This is done by searching the ancestries of each key.  Any key that is
 | 
|
705  | 
        reachable from another key is not returned; all the others are.
 | 
|
706  | 
||
707  | 
        This operation scales with the relative depth between any two keys. If
 | 
|
708  | 
        any two keys are completely disconnected all ancestry of both sides
 | 
|
709  | 
        will be retrieved.
 | 
|
710  | 
||
711  | 
        :param keys: An iterable of keys.
 | 
|
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
712  | 
        :return: A set of the heads. Note that as a set there is no ordering
 | 
713  | 
            information. Callers will need to filter their input to create
 | 
|
714  | 
            order if they need it.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
715  | 
        """
 | 
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
716  | 
candidate_heads = set(keys)  | 
| 
3052.5.5
by John Arbash Meinel
 Special case Graph.heads() for NULL_REVISION rather than is_ancestor.  | 
717  | 
if revision.NULL_REVISION in candidate_heads:  | 
718  | 
            # NULL_REVISION is only a head if it is the only entry
 | 
|
719  | 
candidate_heads.remove(revision.NULL_REVISION)  | 
|
720  | 
if not candidate_heads:  | 
|
721  | 
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)  | 
722  | 
if len(candidate_heads) < 2:  | 
723  | 
return candidate_heads  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
724  | 
searchers = dict((c, self._make_breadth_first_searcher([c]))  | 
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
725  | 
for c in candidate_heads)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
726  | 
active_searchers = dict(searchers)  | 
727  | 
        # skip over the actual candidate for each searcher
 | 
|
728  | 
for searcher in active_searchers.itervalues():  | 
|
| 
1551.15.81
by Aaron Bentley
 Remove testing code  | 
729  | 
searcher.next()  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
730  | 
        # The common walker finds nodes that are common to two or more of the
 | 
731  | 
        # input keys, so that we don't access all history when a currently
 | 
|
732  | 
        # uncommon search point actually meets up with something behind a
 | 
|
733  | 
        # common search point. Common search points do not keep searches
 | 
|
734  | 
        # active; they just allow us to make searches inactive without
 | 
|
735  | 
        # accessing all history.
 | 
|
736  | 
common_walker = self._make_breadth_first_searcher([])  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
737  | 
while len(active_searchers) > 0:  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
738  | 
ancestors = set()  | 
739  | 
            # advance searches
 | 
|
740  | 
try:  | 
|
741  | 
common_walker.next()  | 
|
742  | 
except StopIteration:  | 
|
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
743  | 
                # 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  | 
744  | 
                pass
 | 
| 
1551.15.78
by Aaron Bentley
 Fix KeyError in filter_candidate_lca  | 
745  | 
for candidate in active_searchers.keys():  | 
746  | 
try:  | 
|
747  | 
searcher = active_searchers[candidate]  | 
|
748  | 
except KeyError:  | 
|
749  | 
                    # rare case: we deleted candidate in a previous iteration
 | 
|
750  | 
                    # through this for loop, because it was determined to be
 | 
|
751  | 
                    # a descendant of another candidate.
 | 
|
752  | 
                    continue
 | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
753  | 
try:  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
754  | 
ancestors.update(searcher.next())  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
755  | 
except StopIteration:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
756  | 
del active_searchers[candidate]  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
757  | 
                    continue
 | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
758  | 
            # process found nodes
 | 
759  | 
new_common = set()  | 
|
760  | 
for ancestor in ancestors:  | 
|
761  | 
if ancestor in candidate_heads:  | 
|
762  | 
candidate_heads.remove(ancestor)  | 
|
763  | 
del searchers[ancestor]  | 
|
764  | 
if ancestor in active_searchers:  | 
|
765  | 
del active_searchers[ancestor]  | 
|
766  | 
                # it may meet up with a known common node
 | 
|
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
767  | 
if ancestor in common_walker.seen:  | 
768  | 
                    # some searcher has encountered our known common nodes:
 | 
|
769  | 
                    # just stop it
 | 
|
770  | 
ancestor_set = set([ancestor])  | 
|
771  | 
for searcher in searchers.itervalues():  | 
|
772  | 
searcher.stop_searching_any(ancestor_set)  | 
|
773  | 
else:  | 
|
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
774  | 
                    # or it may have been just reached by all the searchers:
 | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
775  | 
for searcher in searchers.itervalues():  | 
776  | 
if ancestor not in searcher.seen:  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
777  | 
                            break
 | 
778  | 
else:  | 
|
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
779  | 
                        # The final active searcher has just reached this node,
 | 
780  | 
                        # making it be known as a descendant of all candidates,
 | 
|
781  | 
                        # so we can stop searching it, and any seen ancestors
 | 
|
782  | 
new_common.add(ancestor)  | 
|
783  | 
for searcher in searchers.itervalues():  | 
|
784  | 
seen_ancestors =\  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
785  | 
searcher.find_seen_ancestors([ancestor])  | 
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
786  | 
searcher.stop_searching_any(seen_ancestors)  | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
787  | 
common_walker.start_searching(new_common)  | 
| 
2776.1.4
by Robert Collins
 Trivial review feedback changes.  | 
788  | 
return candidate_heads  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
789  | 
|
| 
3514.2.8
by John Arbash Meinel
 The insertion ordering into the weave has an impact on conflicts.  | 
790  | 
def find_merge_order(self, tip_revision_id, lca_revision_ids):  | 
791  | 
"""Find the order that each revision was merged into tip.  | 
|
792  | 
||
793  | 
        This basically just walks backwards with a stack, and walks left-first
 | 
|
794  | 
        until it finds a node to stop.
 | 
|
795  | 
        """
 | 
|
796  | 
if len(lca_revision_ids) == 1:  | 
|
797  | 
return list(lca_revision_ids)  | 
|
798  | 
looking_for = set(lca_revision_ids)  | 
|
799  | 
        # TODO: Is there a way we could do this "faster" by batching up the
 | 
|
800  | 
        # get_parent_map requests?
 | 
|
801  | 
        # TODO: Should we also be culling the ancestry search right away? We
 | 
|
802  | 
        # could add looking_for to the "stop" list, and walk their
 | 
|
803  | 
        # ancestry in batched mode. The flip side is it might mean we walk a
 | 
|
804  | 
        # lot of "stop" nodes, rather than only the minimum.
 | 
|
805  | 
        # Then again, without it we may trace back into ancestry we could have
 | 
|
806  | 
        # stopped early.
 | 
|
807  | 
stack = [tip_revision_id]  | 
|
808  | 
found = []  | 
|
809  | 
stop = set()  | 
|
810  | 
while stack and looking_for:  | 
|
811  | 
next = stack.pop()  | 
|
812  | 
stop.add(next)  | 
|
813  | 
if next in looking_for:  | 
|
814  | 
found.append(next)  | 
|
815  | 
looking_for.remove(next)  | 
|
816  | 
if len(looking_for) == 1:  | 
|
817  | 
found.append(looking_for.pop())  | 
|
818  | 
                    break
 | 
|
819  | 
                continue
 | 
|
820  | 
parent_ids = self.get_parent_map([next]).get(next, None)  | 
|
821  | 
if not parent_ids: # Ghost, nothing to search here  | 
|
822  | 
                continue
 | 
|
823  | 
for parent_id in reversed(parent_ids):  | 
|
824  | 
                # TODO: (performance) We see the parent at this point, but we
 | 
|
825  | 
                #       wait to mark it until later to make sure we get left
 | 
|
826  | 
                #       parents before right parents. However, instead of
 | 
|
827  | 
                #       waiting until we have traversed enough parents, we
 | 
|
828  | 
                #       could instead note that we've found it, and once all
 | 
|
829  | 
                #       parents are in the stack, just reverse iterate the
 | 
|
830  | 
                #       stack for them.
 | 
|
831  | 
if parent_id not in stop:  | 
|
832  | 
                    # this will need to be searched
 | 
|
833  | 
stack.append(parent_id)  | 
|
834  | 
stop.add(parent_id)  | 
|
835  | 
return found  | 
|
836  | 
||
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
837  | 
def find_unique_lca(self, left_revision, right_revision,  | 
838  | 
count_steps=False):  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
839  | 
"""Find a unique LCA.  | 
840  | 
||
841  | 
        Find lowest common ancestors.  If there is no unique  common
 | 
|
842  | 
        ancestor, find the lowest common ancestors of those ancestors.
 | 
|
843  | 
||
844  | 
        Iteration stops when a unique lowest common ancestor is found.
 | 
|
845  | 
        The graph origin is necessarily a unique lowest common ancestor.
 | 
|
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
846  | 
|
847  | 
        Note that None is not an acceptable substitute for NULL_REVISION.
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
848  | 
        in the input for this method.
 | 
| 
1551.19.12
by Aaron Bentley
 Add documentation for the count_steps parameter of Graph.find_unique_lca  | 
849  | 
|
850  | 
        :param count_steps: If True, the return value will be a tuple of
 | 
|
851  | 
            (unique_lca, steps) where steps is the number of times that
 | 
|
852  | 
            find_lca was run.  If False, only unique_lca is returned.
 | 
|
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
853  | 
        """
 | 
854  | 
revisions = [left_revision, right_revision]  | 
|
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
855  | 
steps = 0  | 
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
856  | 
while True:  | 
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
857  | 
steps += 1  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
858  | 
lca = self.find_lca(*revisions)  | 
859  | 
if len(lca) == 1:  | 
|
| 
1551.19.10
by Aaron Bentley
 Merge now warns when it encounters a criss-cross  | 
860  | 
result = lca.pop()  | 
861  | 
if count_steps:  | 
|
862  | 
return result, steps  | 
|
863  | 
else:  | 
|
864  | 
return result  | 
|
| 
2520.4.104
by Aaron Bentley
 Avoid infinite loop when there is no unique lca  | 
865  | 
if len(lca) == 0:  | 
866  | 
raise errors.NoCommonAncestor(left_revision, right_revision)  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
867  | 
revisions = lca  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
868  | 
|
| 
3228.4.4
by John Arbash Meinel
 Change iter_ancestry to take a group instead of a single node,  | 
869  | 
def iter_ancestry(self, revision_ids):  | 
| 
3228.4.2
by John Arbash Meinel
 Add a Graph.iter_ancestry()  | 
870  | 
"""Iterate the ancestry of this revision.  | 
871  | 
||
| 
3228.4.4
by John Arbash Meinel
 Change iter_ancestry to take a group instead of a single node,  | 
872  | 
        :param revision_ids: Nodes to start the search
 | 
| 
3228.4.2
by John Arbash Meinel
 Add a Graph.iter_ancestry()  | 
873  | 
        :return: Yield tuples mapping a revision_id to its parents for the
 | 
874  | 
            ancestry of revision_id.
 | 
|
| 
3228.4.10
by John Arbash Meinel
 Respond to abentley's review comments.  | 
875  | 
            Ghosts will be returned with None as their parents, and nodes
 | 
| 
3228.4.4
by John Arbash Meinel
 Change iter_ancestry to take a group instead of a single node,  | 
876  | 
            with no parents will have NULL_REVISION as their only parent. (As
 | 
877  | 
            defined by get_parent_map.)
 | 
|
| 
3228.4.10
by John Arbash Meinel
 Respond to abentley's review comments.  | 
878  | 
            There will also be a node for (NULL_REVISION, ())
 | 
| 
3228.4.2
by John Arbash Meinel
 Add a Graph.iter_ancestry()  | 
879  | 
        """
 | 
| 
3228.4.4
by John Arbash Meinel
 Change iter_ancestry to take a group instead of a single node,  | 
880  | 
pending = set(revision_ids)  | 
| 
3228.4.2
by John Arbash Meinel
 Add a Graph.iter_ancestry()  | 
881  | 
processed = set()  | 
882  | 
while pending:  | 
|
883  | 
processed.update(pending)  | 
|
884  | 
next_map = self.get_parent_map(pending)  | 
|
885  | 
next_pending = set()  | 
|
886  | 
for item in next_map.iteritems():  | 
|
887  | 
yield item  | 
|
888  | 
next_pending.update(p for p in item[1] if p not in processed)  | 
|
889  | 
ghosts = pending.difference(next_map)  | 
|
890  | 
for ghost in ghosts:  | 
|
| 
3228.4.10
by John Arbash Meinel
 Respond to abentley's review comments.  | 
891  | 
yield (ghost, None)  | 
| 
3228.4.2
by John Arbash Meinel
 Add a Graph.iter_ancestry()  | 
892  | 
pending = next_pending  | 
893  | 
||
| 
2490.2.31
by Aaron Bentley
 Fix iter_topo_order to permit un-included parents  | 
894  | 
def iter_topo_order(self, revisions):  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
895  | 
"""Iterate through the input revisions in topological order.  | 
896  | 
||
897  | 
        This sorting only ensures that parents come before their children.
 | 
|
898  | 
        An ancestor may sort after a descendant if the relationship is not
 | 
|
899  | 
        visible in the supplied list of revisions.
 | 
|
900  | 
        """
 | 
|
| 
3099.3.3
by John Arbash Meinel
 Deprecate get_parents() in favor of get_parent_map()  | 
901  | 
sorter = tsort.TopoSorter(self.get_parent_map(revisions))  | 
| 
2490.2.34
by Aaron Bentley
 Update NEWS and change implementation to return an iterator  | 
902  | 
return sorter.iter_topo_order()  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
903  | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
904  | 
def is_ancestor(self, candidate_ancestor, candidate_descendant):  | 
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
905  | 
"""Determine whether a revision is an ancestor of another.  | 
906  | 
||
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
907  | 
        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  | 
908  | 
        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  | 
909  | 
        relationship between N revisions.
 | 
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
910  | 
        """
 | 
| 
2921.3.1
by Robert Collins
 * Graph ``heads()`` queries have been bugfixed to no longer access all  | 
911  | 
return set([candidate_descendant]) == self.heads(  | 
912  | 
[candidate_ancestor, candidate_descendant])  | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
913  | 
|
| 
3921.3.5
by Marius Kruger
 extract graph.is_between from builtins.cmd_tags.run, and test it  | 
914  | 
def is_between(self, revid, lower_bound_revid, upper_bound_revid):  | 
915  | 
"""Determine whether a revision is between two others.  | 
|
916  | 
||
917  | 
        returns true if and only if:
 | 
|
918  | 
        lower_bound_revid <= revid <= upper_bound_revid
 | 
|
919  | 
        """
 | 
|
920  | 
return ((upper_bound_revid is None or  | 
|
921  | 
self.is_ancestor(revid, upper_bound_revid)) and  | 
|
922  | 
(lower_bound_revid is None or  | 
|
923  | 
self.is_ancestor(lower_bound_revid, revid)))  | 
|
924  | 
||
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
925  | 
def _search_for_extra_common(self, common, searchers):  | 
926  | 
"""Make sure that unique nodes are genuinely unique.  | 
|
927  | 
||
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
928  | 
        After _find_border_ancestors, all nodes marked "common" are indeed
 | 
929  | 
        common. Some of the nodes considered unique are not, due to history
 | 
|
930  | 
        shortcuts stopping the searches early.
 | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
931  | 
|
932  | 
        We know that we have searched enough when all common search tips are
 | 
|
933  | 
        descended from all unique (uncommon) nodes because we know that a node
 | 
|
934  | 
        cannot be an ancestor of its own ancestor.
 | 
|
935  | 
||
936  | 
        :param common: A set of common nodes
 | 
|
937  | 
        :param searchers: The searchers returned from _find_border_ancestors
 | 
|
938  | 
        :return: None
 | 
|
939  | 
        """
 | 
|
940  | 
        # Basic algorithm...
 | 
|
941  | 
        #   A) The passed in searchers should all be on the same tips, thus
 | 
|
942  | 
        #      they should be considered the "common" searchers.
 | 
|
943  | 
        #   B) We find the difference between the searchers, these are the
 | 
|
944  | 
        #      "unique" nodes for each side.
 | 
|
945  | 
        #   C) We do a quick culling so that we only start searching from the
 | 
|
946  | 
        #      more interesting unique nodes. (A unique ancestor is more
 | 
|
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
947  | 
        #      interesting than any of its children.)
 | 
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
948  | 
        #   D) We start searching for ancestors common to all unique nodes.
 | 
949  | 
        #   E) We have the common searchers stop searching any ancestors of
 | 
|
950  | 
        #      nodes found by (D)
 | 
|
951  | 
        #   F) When there are no more common search tips, we stop
 | 
|
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
952  | 
|
953  | 
        # TODO: We need a way to remove unique_searchers when they overlap with
 | 
|
954  | 
        #       other unique searchers.
 | 
|
| 
3376.2.14
by Martin Pool
 Remove recently-introduced assert statements  | 
955  | 
if len(searchers) != 2:  | 
956  | 
raise NotImplementedError(  | 
|
957  | 
"Algorithm not yet implemented for > 2 searchers")  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
958  | 
common_searchers = searchers  | 
959  | 
left_searcher = searchers[0]  | 
|
960  | 
right_searcher = searchers[1]  | 
|
| 
3377.3.15
by John Arbash Meinel
 minor update  | 
961  | 
unique = left_searcher.seen.symmetric_difference(right_searcher.seen)  | 
| 
3377.3.17
by John Arbash Meinel
 Keep track of the intersection of unique ancestry,  | 
962  | 
if not unique: # No unique nodes, nothing to do  | 
963  | 
            return
 | 
|
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
964  | 
total_unique = len(unique)  | 
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
965  | 
unique = self._remove_simple_descendants(unique,  | 
966  | 
self.get_parent_map(unique))  | 
|
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
967  | 
simple_unique = len(unique)  | 
| 
3377.3.14
by John Arbash Meinel
 Take another tack on _search_for_extra  | 
968  | 
|
969  | 
unique_searchers = []  | 
|
970  | 
for revision_id in unique:  | 
|
| 
3377.3.15
by John Arbash Meinel
 minor update  | 
971  | 
if revision_id in left_searcher.seen:  | 
| 
3377.3.14
by John Arbash Meinel
 Take another tack on _search_for_extra  | 
972  | 
parent_searcher = left_searcher  | 
973  | 
else:  | 
|
974  | 
parent_searcher = right_searcher  | 
|
975  | 
revs_to_search = parent_searcher.find_seen_ancestors([revision_id])  | 
|
976  | 
if not revs_to_search: # XXX: This shouldn't be possible  | 
|
977  | 
revs_to_search = [revision_id]  | 
|
| 
3377.3.15
by John Arbash Meinel
 minor update  | 
978  | 
searcher = self._make_breadth_first_searcher(revs_to_search)  | 
979  | 
            # We don't care about the starting nodes.
 | 
|
980  | 
searcher.step()  | 
|
981  | 
unique_searchers.append(searcher)  | 
|
| 
3377.3.14
by John Arbash Meinel
 Take another tack on _search_for_extra  | 
982  | 
|
| 
3377.3.16
by John Arbash Meinel
 small cleanups  | 
983  | 
        # possible todo: aggregate the common searchers into a single common
 | 
984  | 
        #   searcher, just make sure that we include the nodes into the .seen
 | 
|
985  | 
        #   properties of the original searchers
 | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
986  | 
|
| 
3377.3.17
by John Arbash Meinel
 Keep track of the intersection of unique ancestry,  | 
987  | 
ancestor_all_unique = None  | 
988  | 
for searcher in unique_searchers:  | 
|
989  | 
if ancestor_all_unique is None:  | 
|
990  | 
ancestor_all_unique = set(searcher.seen)  | 
|
991  | 
else:  | 
|
992  | 
ancestor_all_unique = ancestor_all_unique.intersection(  | 
|
993  | 
searcher.seen)  | 
|
994  | 
||
| 
3377.3.23
by John Arbash Meinel
 Implement find_unique_ancestors using more explicit graph searching.  | 
995  | 
trace.mutter('Started %s unique searchers for %s unique revisions',  | 
996  | 
simple_unique, total_unique)  | 
|
| 
3377.3.19
by John Arbash Meinel
 Start culling unique searchers once they converge.  | 
997  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
998  | 
while True: # If we have no more nodes we have nothing to do  | 
999  | 
newly_seen_common = set()  | 
|
1000  | 
for searcher in common_searchers:  | 
|
1001  | 
newly_seen_common.update(searcher.step())  | 
|
1002  | 
newly_seen_unique = set()  | 
|
1003  | 
for searcher in unique_searchers:  | 
|
1004  | 
newly_seen_unique.update(searcher.step())  | 
|
1005  | 
new_common_unique = set()  | 
|
1006  | 
for revision in newly_seen_unique:  | 
|
1007  | 
for searcher in unique_searchers:  | 
|
1008  | 
if revision not in searcher.seen:  | 
|
1009  | 
                        break
 | 
|
1010  | 
else:  | 
|
1011  | 
                    # This is a border because it is a first common that we see
 | 
|
1012  | 
                    # after walking for a while.
 | 
|
1013  | 
new_common_unique.add(revision)  | 
|
1014  | 
if newly_seen_common:  | 
|
1015  | 
                # These are nodes descended from one of the 'common' searchers.
 | 
|
1016  | 
                # Make sure all searchers are on the same page
 | 
|
1017  | 
for searcher in common_searchers:  | 
|
| 
3377.3.16
by John Arbash Meinel
 small cleanups  | 
1018  | 
newly_seen_common.update(  | 
1019  | 
searcher.find_seen_ancestors(newly_seen_common))  | 
|
| 
3377.3.14
by John Arbash Meinel
 Take another tack on _search_for_extra  | 
1020  | 
                # We start searching the whole ancestry. It is a bit wasteful,
 | 
1021  | 
                # though. We really just want to mark all of these nodes as
 | 
|
1022  | 
                # 'seen' and then start just the tips. However, it requires a
 | 
|
1023  | 
                # get_parent_map() call to figure out the tips anyway, and all
 | 
|
1024  | 
                # redundant requests should be fairly fast.
 | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1025  | 
for searcher in common_searchers:  | 
1026  | 
searcher.start_searching(newly_seen_common)  | 
|
| 
3377.3.13
by John Arbash Meinel
 Change _search_for_extra_common slightly.  | 
1027  | 
|
| 
3377.3.17
by John Arbash Meinel
 Keep track of the intersection of unique ancestry,  | 
1028  | 
                # If a 'common' node is an ancestor of all unique searchers, we
 | 
| 
3377.3.13
by John Arbash Meinel
 Change _search_for_extra_common slightly.  | 
1029  | 
                # can stop searching it.
 | 
| 
3377.3.17
by John Arbash Meinel
 Keep track of the intersection of unique ancestry,  | 
1030  | 
stop_searching_common = ancestor_all_unique.intersection(  | 
1031  | 
newly_seen_common)  | 
|
| 
3377.3.13
by John Arbash Meinel
 Change _search_for_extra_common slightly.  | 
1032  | 
if stop_searching_common:  | 
1033  | 
for searcher in common_searchers:  | 
|
1034  | 
searcher.stop_searching_any(stop_searching_common)  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1035  | 
if new_common_unique:  | 
| 
3377.3.20
by John Arbash Meinel
 comment cleanups.  | 
1036  | 
                # We found some ancestors that are common
 | 
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
1037  | 
for searcher in unique_searchers:  | 
| 
3377.3.16
by John Arbash Meinel
 small cleanups  | 
1038  | 
new_common_unique.update(  | 
1039  | 
searcher.find_seen_ancestors(new_common_unique))  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1040  | 
                # Since these are common, we can grab another set of ancestors
 | 
1041  | 
                # that we have seen
 | 
|
1042  | 
for searcher in common_searchers:  | 
|
| 
3377.3.16
by John Arbash Meinel
 small cleanups  | 
1043  | 
new_common_unique.update(  | 
1044  | 
searcher.find_seen_ancestors(new_common_unique))  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1045  | 
|
1046  | 
                # We can tell all of the unique searchers to start at these
 | 
|
1047  | 
                # nodes, and tell all of the common searchers to *stop*
 | 
|
1048  | 
                # searching these nodes
 | 
|
1049  | 
for searcher in unique_searchers:  | 
|
1050  | 
searcher.start_searching(new_common_unique)  | 
|
1051  | 
for searcher in common_searchers:  | 
|
1052  | 
searcher.stop_searching_any(new_common_unique)  | 
|
| 
3377.3.17
by John Arbash Meinel
 Keep track of the intersection of unique ancestry,  | 
1053  | 
ancestor_all_unique.update(new_common_unique)  | 
| 
3377.3.19
by John Arbash Meinel
 Start culling unique searchers once they converge.  | 
1054  | 
|
| 
3377.3.20
by John Arbash Meinel
 comment cleanups.  | 
1055  | 
                # Filter out searchers that don't actually search different
 | 
1056  | 
                # nodes. We already have the ancestry intersection for them
 | 
|
| 
3377.3.19
by John Arbash Meinel
 Start culling unique searchers once they converge.  | 
1057  | 
next_unique_searchers = []  | 
1058  | 
unique_search_sets = set()  | 
|
1059  | 
for searcher in unique_searchers:  | 
|
1060  | 
will_search_set = frozenset(searcher._next_query)  | 
|
1061  | 
if will_search_set not in unique_search_sets:  | 
|
1062  | 
                        # This searcher is searching a unique set of nodes, let it
 | 
|
1063  | 
unique_search_sets.add(will_search_set)  | 
|
1064  | 
next_unique_searchers.append(searcher)  | 
|
1065  | 
unique_searchers = next_unique_searchers  | 
|
| 
3377.3.2
by John Arbash Meinel
 find_difference is fixed by updating _find_border_ancestors.... is that reasonable?  | 
1066  | 
for searcher in common_searchers:  | 
1067  | 
if searcher._next_query:  | 
|
1068  | 
                    break
 | 
|
1069  | 
else:  | 
|
1070  | 
                # All common searcher have stopped searching
 | 
|
| 
3377.3.16
by John Arbash Meinel
 small cleanups  | 
1071  | 
                return
 | 
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1072  | 
|
1073  | 
def _remove_simple_descendants(self, revisions, parent_map):  | 
|
1074  | 
"""remove revisions which are children of other ones in the set  | 
|
1075  | 
||
1076  | 
        This doesn't do any graph searching, it just checks the immediate
 | 
|
1077  | 
        parent_map to find if there are any children which can be removed.
 | 
|
1078  | 
||
1079  | 
        :param revisions: A set of revision_ids
 | 
|
1080  | 
        :return: A set of revision_ids with the children removed
 | 
|
1081  | 
        """
 | 
|
1082  | 
simple_ancestors = revisions.copy()  | 
|
1083  | 
        # TODO: jam 20071214 we *could* restrict it to searching only the
 | 
|
1084  | 
        #       parent_map of revisions already present in 'revisions', but
 | 
|
1085  | 
        #       considering the general use case, I think this is actually
 | 
|
1086  | 
        #       better.
 | 
|
1087  | 
||
1088  | 
        # This is the same as the following loop. I don't know that it is any
 | 
|
1089  | 
        # faster.
 | 
|
1090  | 
        ## simple_ancestors.difference_update(r for r, p_ids in parent_map.iteritems()
 | 
|
1091  | 
        ##     if p_ids is not None and revisions.intersection(p_ids))
 | 
|
1092  | 
        ## return simple_ancestors
 | 
|
1093  | 
||
1094  | 
        # Yet Another Way, invert the parent map (which can be cached)
 | 
|
1095  | 
        ## descendants = {}
 | 
|
1096  | 
        ## for revision_id, parent_ids in parent_map.iteritems():
 | 
|
1097  | 
        ##   for p_id in parent_ids:
 | 
|
1098  | 
        ##       descendants.setdefault(p_id, []).append(revision_id)
 | 
|
1099  | 
        ## for revision in revisions.intersection(descendants):
 | 
|
1100  | 
        ##   simple_ancestors.difference_update(descendants[revision])
 | 
|
1101  | 
        ## return simple_ancestors
 | 
|
1102  | 
for revision, parent_ids in parent_map.iteritems():  | 
|
1103  | 
if parent_ids is None:  | 
|
1104  | 
                continue
 | 
|
1105  | 
for parent_id in parent_ids:  | 
|
1106  | 
if parent_id in revisions:  | 
|
1107  | 
                    # This node has a parent present in the set, so we can
 | 
|
1108  | 
                    # remove it
 | 
|
1109  | 
simple_ancestors.discard(revision)  | 
|
1110  | 
                    break
 | 
|
1111  | 
return simple_ancestors  | 
|
1112  | 
||
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1113  | 
|
| 
2911.4.1
by Robert Collins
 Factor out the Graph.heads() cache from _RevisionTextVersionCache for reuse, and use it in commit.  | 
1114  | 
class HeadsCache(object):  | 
1115  | 
"""A cache of results for graph heads calls."""  | 
|
1116  | 
||
1117  | 
def __init__(self, graph):  | 
|
1118  | 
self.graph = graph  | 
|
1119  | 
self._heads = {}  | 
|
1120  | 
||
1121  | 
def heads(self, keys):  | 
|
1122  | 
"""Return the heads of keys.  | 
|
1123  | 
||
| 
2911.4.3
by Robert Collins
 Make the contract of HeadsCache.heads() more clear.  | 
1124  | 
        This matches the API of Graph.heads(), specifically the return value is
 | 
1125  | 
        a set which can be mutated, and ordering of the input is not preserved
 | 
|
1126  | 
        in the output.
 | 
|
1127  | 
||
| 
2911.4.1
by Robert Collins
 Factor out the Graph.heads() cache from _RevisionTextVersionCache for reuse, and use it in commit.  | 
1128  | 
        :see also: Graph.heads.
 | 
1129  | 
        :param keys: The keys to calculate heads for.
 | 
|
1130  | 
        :return: A set containing the heads, which may be mutated without
 | 
|
1131  | 
            affecting future lookups.
 | 
|
1132  | 
        """
 | 
|
| 
2911.4.2
by Robert Collins
 Make HeadsCache actually work.  | 
1133  | 
keys = frozenset(keys)  | 
| 
2911.4.1
by Robert Collins
 Factor out the Graph.heads() cache from _RevisionTextVersionCache for reuse, and use it in commit.  | 
1134  | 
try:  | 
1135  | 
return set(self._heads[keys])  | 
|
1136  | 
except KeyError:  | 
|
1137  | 
heads = self.graph.heads(keys)  | 
|
1138  | 
self._heads[keys] = heads  | 
|
1139  | 
return set(heads)  | 
|
1140  | 
||
1141  | 
||
| 
3224.1.20
by John Arbash Meinel
 Reduce the number of cache misses by caching known heads answers  | 
1142  | 
class FrozenHeadsCache(object):  | 
1143  | 
"""Cache heads() calls, assuming the caller won't modify them."""  | 
|
1144  | 
||
1145  | 
def __init__(self, graph):  | 
|
1146  | 
self.graph = graph  | 
|
1147  | 
self._heads = {}  | 
|
1148  | 
||
1149  | 
def heads(self, keys):  | 
|
1150  | 
"""Return the heads of keys.  | 
|
1151  | 
||
| 
3224.1.24
by John Arbash Meinel
 Fix up docstring since FrozenHeadsCache doesn't let you mutate the result.  | 
1152  | 
        Similar to Graph.heads(). The main difference is that the return value
 | 
1153  | 
        is a frozen set which cannot be mutated.
 | 
|
| 
3224.1.20
by John Arbash Meinel
 Reduce the number of cache misses by caching known heads answers  | 
1154  | 
|
1155  | 
        :see also: Graph.heads.
 | 
|
1156  | 
        :param keys: The keys to calculate heads for.
 | 
|
| 
3224.1.24
by John Arbash Meinel
 Fix up docstring since FrozenHeadsCache doesn't let you mutate the result.  | 
1157  | 
        :return: A frozenset containing the heads.
 | 
| 
3224.1.20
by John Arbash Meinel
 Reduce the number of cache misses by caching known heads answers  | 
1158  | 
        """
 | 
1159  | 
keys = frozenset(keys)  | 
|
1160  | 
try:  | 
|
1161  | 
return self._heads[keys]  | 
|
1162  | 
except KeyError:  | 
|
1163  | 
heads = frozenset(self.graph.heads(keys))  | 
|
1164  | 
self._heads[keys] = heads  | 
|
1165  | 
return heads  | 
|
1166  | 
||
1167  | 
def cache(self, keys, heads):  | 
|
1168  | 
"""Store a known value."""  | 
|
1169  | 
self._heads[frozenset(keys)] = frozenset(heads)  | 
|
1170  | 
||
1171  | 
||
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
1172  | 
class _BreadthFirstSearcher(object):  | 
| 
2921.3.4
by Robert Collins
 Review feedback.  | 
1173  | 
"""Parallel search breadth-first the ancestry of revisions.  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
1174  | 
|
1175  | 
    This class implements the iterator protocol, but additionally
 | 
|
1176  | 
    1. provides a set of seen ancestors, and
 | 
|
1177  | 
    2. allows some ancestries to be unsearched, via stop_searching_any
 | 
|
1178  | 
    """
 | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1179  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
1180  | 
def __init__(self, revisions, parents_provider):  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1181  | 
self._iterations = 0  | 
1182  | 
self._next_query = set(revisions)  | 
|
1183  | 
self.seen = set()  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1184  | 
self._started_keys = set(self._next_query)  | 
1185  | 
self._stopped_keys = set()  | 
|
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
1186  | 
self._parents_provider = parents_provider  | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1187  | 
self._returning = 'next_with_ghosts'  | 
| 
3184.1.2
by Robert Collins
 Add tests for starting and stopping searches in combination with get_recipe.  | 
1188  | 
self._current_present = set()  | 
1189  | 
self._current_ghosts = set()  | 
|
1190  | 
self._current_parents = {}  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1191  | 
|
1192  | 
def __repr__(self):  | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1193  | 
if self._iterations:  | 
1194  | 
prefix = "searching"  | 
|
| 
3099.3.1
by John Arbash Meinel
 Implement get_parent_map for ParentProviders  | 
1195  | 
else:  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1196  | 
prefix = "starting"  | 
1197  | 
search = '%s=%r' % (prefix, list(self._next_query))  | 
|
1198  | 
return ('_BreadthFirstSearcher(iterations=%d, %s,'  | 
|
1199  | 
' seen=%r)' % (self._iterations, search, list(self.seen)))  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1200  | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1201  | 
def get_result(self):  | 
1202  | 
"""Get a SearchResult for the current state of this searcher.  | 
|
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
1203  | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1204  | 
        :return: A SearchResult for this search so far. The SearchResult is
 | 
1205  | 
            static - the search can be advanced and the search result will not
 | 
|
1206  | 
            be invalidated or altered.
 | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1207  | 
        """
 | 
1208  | 
if self._returning == 'next':  | 
|
1209  | 
            # We have to know the current nodes children to be able to list the
 | 
|
1210  | 
            # exclude keys for them. However, while we could have a second
 | 
|
1211  | 
            # look-ahead result buffer and shuffle things around, this method
 | 
|
1212  | 
            # is typically only called once per search - when memoising the
 | 
|
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
1213  | 
            # results of the search.
 | 
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1214  | 
found, ghosts, next, parents = self._do_query(self._next_query)  | 
1215  | 
            # pretend we didn't query: perhaps we should tweak _do_query to be
 | 
|
1216  | 
            # entirely stateless?
 | 
|
1217  | 
self.seen.difference_update(next)  | 
|
| 
3184.1.3
by Robert Collins
 Automatically exclude ghosts.  | 
1218  | 
next_query = next.union(ghosts)  | 
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1219  | 
else:  | 
1220  | 
next_query = self._next_query  | 
|
| 
3184.1.5
by Robert Collins
 Record the number of found revisions for cross checking.  | 
1221  | 
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.  | 
1222  | 
included_keys = self.seen.difference(excludes)  | 
1223  | 
return SearchResult(self._started_keys, excludes, len(included_keys),  | 
|
1224  | 
included_keys)  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1225  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1226  | 
def step(self):  | 
1227  | 
try:  | 
|
1228  | 
return self.next()  | 
|
1229  | 
except StopIteration:  | 
|
1230  | 
return ()  | 
|
1231  | 
||
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1232  | 
def next(self):  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
1233  | 
"""Return the next ancestors of this revision.  | 
1234  | 
||
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
1235  | 
        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  | 
1236  | 
        traversal.  No ancestor will be returned more than once. Ancestors are
 | 
1237  | 
        returned before their parentage is queried, so ghosts and missing
 | 
|
1238  | 
        revisions (including the start revisions) are included in the result.
 | 
|
1239  | 
        This can save a round trip in LCA style calculation by allowing
 | 
|
1240  | 
        convergence to be detected without reading the data for the revision
 | 
|
1241  | 
        the convergence occurs on.
 | 
|
1242  | 
||
1243  | 
        :return: A set of revision_ids.
 | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
1244  | 
        """
 | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1245  | 
if self._returning != 'next':  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1246  | 
            # switch to returning the query, not the results.
 | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1247  | 
self._returning = 'next'  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1248  | 
self._iterations += 1  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1249  | 
else:  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1250  | 
self._advance()  | 
1251  | 
if len(self._next_query) == 0:  | 
|
1252  | 
raise StopIteration()  | 
|
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1253  | 
        # We have seen what we're querying at this point as we are returning
 | 
1254  | 
        # the query, not the results.
 | 
|
1255  | 
self.seen.update(self._next_query)  | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1256  | 
return self._next_query  | 
1257  | 
||
1258  | 
def next_with_ghosts(self):  | 
|
1259  | 
"""Return the next found ancestors, with ghosts split out.  | 
|
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
1260  | 
|
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1261  | 
        Ancestors are returned in the order they are seen in a breadth-first
 | 
1262  | 
        traversal.  No ancestor will be returned more than once. Ancestors are
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1263  | 
        returned only after asking for their parents, which allows us to detect
 | 
1264  | 
        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  | 
1265  | 
|
1266  | 
        :return: A tuple with (present ancestors, ghost ancestors) sets.
 | 
|
1267  | 
        """
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1268  | 
if self._returning != 'next_with_ghosts':  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1269  | 
            # switch to returning the results, not the current query.
 | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1270  | 
self._returning = 'next_with_ghosts'  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1271  | 
self._advance()  | 
1272  | 
if len(self._next_query) == 0:  | 
|
1273  | 
raise StopIteration()  | 
|
1274  | 
self._advance()  | 
|
1275  | 
return self._current_present, self._current_ghosts  | 
|
1276  | 
||
1277  | 
def _advance(self):  | 
|
1278  | 
"""Advance the search.  | 
|
1279  | 
||
1280  | 
        Updates self.seen, self._next_query, self._current_present,
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1281  | 
        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  | 
1282  | 
        """
 | 
1283  | 
self._iterations += 1  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1284  | 
found, ghosts, next, parents = self._do_query(self._next_query)  | 
1285  | 
self._current_present = found  | 
|
1286  | 
self._current_ghosts = ghosts  | 
|
1287  | 
self._next_query = next  | 
|
1288  | 
self._current_parents = parents  | 
|
| 
3184.1.3
by Robert Collins
 Automatically exclude ghosts.  | 
1289  | 
        # ghosts are implicit stop points, otherwise the search cannot be
 | 
1290  | 
        # repeated when ghosts are filled.
 | 
|
1291  | 
self._stopped_keys.update(ghosts)  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1292  | 
|
1293  | 
def _do_query(self, revisions):  | 
|
1294  | 
"""Query for revisions.  | 
|
1295  | 
||
| 
3184.1.4
by Robert Collins
 Correctly exclude ghosts when ghosts are started on an existing search.  | 
1296  | 
        Adds revisions to the seen set.
 | 
1297  | 
||
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1298  | 
        :param revisions: Revisions to query.
 | 
1299  | 
        :return: A tuple: (set(found_revisions), set(ghost_revisions),
 | 
|
1300  | 
           set(parents_of_found_revisions), dict(found_revisions:parents)).
 | 
|
1301  | 
        """
 | 
|
| 
3377.3.9
by John Arbash Meinel
 Small tweaks to _do_query  | 
1302  | 
found_revisions = set()  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1303  | 
parents_of_found = set()  | 
| 
3184.1.1
by Robert Collins
 Add basic get_recipe to the graph breadth first searcher.  | 
1304  | 
        # revisions may contain nodes that point to other nodes in revisions:
 | 
1305  | 
        # we want to filter them out.
 | 
|
1306  | 
self.seen.update(revisions)  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1307  | 
parent_map = self._parents_provider.get_parent_map(revisions)  | 
| 
3377.3.9
by John Arbash Meinel
 Small tweaks to _do_query  | 
1308  | 
found_revisions.update(parent_map)  | 
| 
3177.3.1
by Robert Collins
 * New method ``next_with_ghosts`` on the Graph breadth-first-search objects  | 
1309  | 
for rev_id, parents in parent_map.iteritems():  | 
| 
3517.4.2
by Martin Pool
 Make simple-annotation and graph code more tolerant of knits with no graph  | 
1310  | 
if parents is None:  | 
1311  | 
                continue
 | 
|
| 
3377.3.9
by John Arbash Meinel
 Small tweaks to _do_query  | 
1312  | 
new_found_parents = [p for p in parents if p not in self.seen]  | 
1313  | 
if new_found_parents:  | 
|
1314  | 
                # Calling set.update() with an empty generator is actually
 | 
|
1315  | 
                # rather expensive.
 | 
|
1316  | 
parents_of_found.update(new_found_parents)  | 
|
1317  | 
ghost_revisions = revisions - found_revisions  | 
|
1318  | 
return found_revisions, ghost_revisions, parents_of_found, parent_map  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1319  | 
|
| 
2490.2.8
by Aaron Bentley
 fix iteration stuff  | 
1320  | 
def __iter__(self):  | 
1321  | 
return self  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1322  | 
|
| 
3377.3.1
by John Arbash Meinel
 Bring in some of the changes from graph_update and graph_optimization  | 
1323  | 
def find_seen_ancestors(self, revisions):  | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
1324  | 
"""Find ancestors of these revisions that have already been seen.  | 
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
1325  | 
|
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
1326  | 
        This function generally makes the assumption that querying for the
 | 
1327  | 
        parents of a node that has already been queried is reasonably cheap.
 | 
|
1328  | 
        (eg, not a round trip to a remote host).
 | 
|
1329  | 
        """
 | 
|
1330  | 
        # TODO: Often we might ask one searcher for its seen ancestors, and
 | 
|
1331  | 
        #       then ask another searcher the same question. This can result in
 | 
|
1332  | 
        #       searching the same revisions repeatedly if the two searchers
 | 
|
1333  | 
        #       have a lot of overlap.
 | 
|
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
1334  | 
all_seen = self.seen  | 
1335  | 
pending = set(revisions).intersection(all_seen)  | 
|
1336  | 
seen_ancestors = set(pending)  | 
|
1337  | 
||
1338  | 
if self._returning == 'next':  | 
|
1339  | 
            # self.seen contains what nodes have been returned, not what nodes
 | 
|
1340  | 
            # have been queried. We don't want to probe for nodes that haven't
 | 
|
1341  | 
            # been searched yet.
 | 
|
1342  | 
not_searched_yet = self._next_query  | 
|
1343  | 
else:  | 
|
1344  | 
not_searched_yet = ()  | 
|
| 
3377.3.11
by John Arbash Meinel
 Committing a debug thunk that was very helpful  | 
1345  | 
pending.difference_update(not_searched_yet)  | 
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
1346  | 
get_parent_map = self._parents_provider.get_parent_map  | 
| 
3377.3.12
by John Arbash Meinel
 Remove the helpful but ugly thunk  | 
1347  | 
while pending:  | 
1348  | 
parent_map = get_parent_map(pending)  | 
|
1349  | 
all_parents = []  | 
|
1350  | 
            # We don't care if it is a ghost, since it can't be seen if it is
 | 
|
1351  | 
            # a ghost
 | 
|
1352  | 
for parent_ids in parent_map.itervalues():  | 
|
1353  | 
all_parents.extend(parent_ids)  | 
|
1354  | 
next_pending = all_seen.intersection(all_parents).difference(seen_ancestors)  | 
|
1355  | 
seen_ancestors.update(next_pending)  | 
|
1356  | 
next_pending.difference_update(not_searched_yet)  | 
|
1357  | 
pending = next_pending  | 
|
| 
3377.3.10
by John Arbash Meinel
 Tweak _BreadthFirstSearcher.find_seen_ancestors()  | 
1358  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
1359  | 
return seen_ancestors  | 
1360  | 
||
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
1361  | 
def stop_searching_any(self, revisions):  | 
1362  | 
"""  | 
|
1363  | 
        Remove any of the specified revisions from the search list.
 | 
|
1364  | 
||
1365  | 
        None of the specified revisions are required to be present in the
 | 
|
| 
3808.1.4
by John Arbash Meinel
 make _walk_to_common responsible for stopping ancestors  | 
1366  | 
        search list.
 | 
| 
3808.1.1
by Andrew Bennetts
 Possible fix for bug in new _walk_to_common_revisions.  | 
1367  | 
|
| 
3808.1.4
by John Arbash Meinel
 make _walk_to_common responsible for stopping ancestors  | 
1368  | 
        It is okay to call stop_searching_any() for revisions which were seen
 | 
1369  | 
        in previous iterations. It is the callers responsibility to call
 | 
|
1370  | 
        find_seen_ancestors() to make sure that current search tips that are
 | 
|
1371  | 
        ancestors of those revisions are also stopped.  All explicitly stopped
 | 
|
1372  | 
        revisions will be excluded from the search result's get_keys(), though.
 | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
1373  | 
        """
 | 
| 
3377.4.6
by John Arbash Meinel
 Lots of refactoring for find_unique_ancestors.  | 
1374  | 
        # TODO: does this help performance?
 | 
1375  | 
        # if not revisions:
 | 
|
1376  | 
        #     return set()
 | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1377  | 
revisions = frozenset(revisions)  | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1378  | 
if self._returning == 'next':  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1379  | 
stopped = self._next_query.intersection(revisions)  | 
1380  | 
self._next_query = self._next_query.difference(revisions)  | 
|
1381  | 
else:  | 
|
| 
3184.2.1
by Robert Collins
 Handle stopping ghosts in searches properly.  | 
1382  | 
stopped_present = self._current_present.intersection(revisions)  | 
1383  | 
stopped = stopped_present.union(  | 
|
1384  | 
self._current_ghosts.intersection(revisions))  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1385  | 
self._current_present.difference_update(stopped)  | 
1386  | 
self._current_ghosts.difference_update(stopped)  | 
|
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
1387  | 
            # stopping 'x' should stop returning parents of 'x', but
 | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1388  | 
            # not if 'y' always references those same parents
 | 
1389  | 
stop_rev_references = {}  | 
|
| 
3184.2.1
by Robert Collins
 Handle stopping ghosts in searches properly.  | 
1390  | 
for rev in stopped_present:  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1391  | 
for parent_id in self._current_parents[rev]:  | 
1392  | 
if parent_id not in stop_rev_references:  | 
|
1393  | 
stop_rev_references[parent_id] = 0  | 
|
1394  | 
stop_rev_references[parent_id] += 1  | 
|
1395  | 
            # if only the stopped revisions reference it, the ref count will be
 | 
|
1396  | 
            # 0 after this loop
 | 
|
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1397  | 
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.  | 
1398  | 
for parent_id in parents:  | 
1399  | 
try:  | 
|
1400  | 
stop_rev_references[parent_id] -= 1  | 
|
1401  | 
except KeyError:  | 
|
1402  | 
                        pass
 | 
|
1403  | 
stop_parents = set()  | 
|
1404  | 
for rev_id, refs in stop_rev_references.iteritems():  | 
|
1405  | 
if refs == 0:  | 
|
1406  | 
stop_parents.add(rev_id)  | 
|
1407  | 
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.  | 
1408  | 
self._stopped_keys.update(stopped)  | 
| 
4053.2.2
by Andrew Bennetts
 Better fix, with test.  | 
1409  | 
self._stopped_keys.update(revisions)  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
1410  | 
return stopped  | 
| 
2490.2.17
by Aaron Bentley
 Add start_searching, tweak stop_searching_any  | 
1411  | 
|
1412  | 
def start_searching(self, revisions):  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1413  | 
"""Add revisions to the search.  | 
1414  | 
||
1415  | 
        The parents of revisions will be returned from the next call to next()
 | 
|
1416  | 
        or next_with_ghosts(). If next_with_ghosts was the most recently used
 | 
|
1417  | 
        next* call then the return value is the result of looking up the
 | 
|
1418  | 
        ghost/not ghost status of revisions. (A tuple (present, ghosted)).
 | 
|
1419  | 
        """
 | 
|
1420  | 
revisions = frozenset(revisions)  | 
|
| 
3184.1.2
by Robert Collins
 Add tests for starting and stopping searches in combination with get_recipe.  | 
1421  | 
self._started_keys.update(revisions)  | 
| 
3184.1.4
by Robert Collins
 Correctly exclude ghosts when ghosts are started on an existing search.  | 
1422  | 
new_revisions = revisions.difference(self.seen)  | 
| 
3177.3.3
by Robert Collins
 Review feedback.  | 
1423  | 
if self._returning == 'next':  | 
| 
3184.1.4
by Robert Collins
 Correctly exclude ghosts when ghosts are started on an existing search.  | 
1424  | 
self._next_query.update(new_revisions)  | 
| 
3377.3.30
by John Arbash Meinel
 Can we avoid the extra _do_query in start_searching?  | 
1425  | 
self.seen.update(new_revisions)  | 
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1426  | 
else:  | 
1427  | 
            # perform a query on revisions
 | 
|
| 
3377.3.30
by John Arbash Meinel
 Can we avoid the extra _do_query in start_searching?  | 
1428  | 
revs, ghosts, query, parents = self._do_query(revisions)  | 
1429  | 
self._stopped_keys.update(ghosts)  | 
|
| 
3177.3.2
by Robert Collins
 Update graph searchers stop_searching_any and start_searching for next_with_ghosts.  | 
1430  | 
self._current_present.update(revs)  | 
1431  | 
self._current_ghosts.update(ghosts)  | 
|
1432  | 
self._next_query.update(query)  | 
|
1433  | 
self._current_parents.update(parents)  | 
|
1434  | 
return revs, ghosts  | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1435  | 
|
1436  | 
||
1437  | 
class SearchResult(object):  | 
|
1438  | 
"""The result of a breadth first search.  | 
|
1439  | 
||
1440  | 
    A SearchResult provides the ability to reconstruct the search or access a
 | 
|
1441  | 
    set of the keys the search found.
 | 
|
1442  | 
    """
 | 
|
1443  | 
||
1444  | 
def __init__(self, start_keys, exclude_keys, key_count, keys):  | 
|
1445  | 
"""Create a SearchResult.  | 
|
1446  | 
||
1447  | 
        :param start_keys: The keys the search started at.
 | 
|
1448  | 
        :param exclude_keys: The keys the search excludes.
 | 
|
1449  | 
        :param key_count: The total number of keys (from start to but not
 | 
|
1450  | 
            including exclude).
 | 
|
1451  | 
        :param keys: The keys the search found. Note that in future we may get
 | 
|
1452  | 
            a SearchResult from a smart server, in which case the keys list is
 | 
|
1453  | 
            not necessarily immediately available.
 | 
|
1454  | 
        """
 | 
|
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1455  | 
self._recipe = ('search', start_keys, exclude_keys, key_count)  | 
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1456  | 
self._keys = frozenset(keys)  | 
1457  | 
||
1458  | 
def get_recipe(self):  | 
|
1459  | 
"""Return a recipe that can be used to replay this search.  | 
|
| 
3943.8.1
by Marius Kruger
 remove all trailing whitespace from bzr source  | 
1460  | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1461  | 
        The recipe allows reconstruction of the same results at a later date
 | 
1462  | 
        without knowing all the found keys. The essential elements are a list
 | 
|
| 
4031.3.1
by Frank Aspell
 Fixing various typos  | 
1463  | 
        of keys to start and to stop at. In order to give reproducible
 | 
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1464  | 
        results when ghosts are encountered by a search they are automatically
 | 
1465  | 
        added to the exclude list (or else ghost filling may alter the
 | 
|
1466  | 
        results).
 | 
|
1467  | 
||
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1468  | 
        :return: A tuple ('search', start_keys_set, exclude_keys_set,
 | 
1469  | 
            revision_count). To recreate the results of this search, create a
 | 
|
1470  | 
            breadth first searcher on the same graph starting at start_keys.
 | 
|
1471  | 
            Then call next() (or next_with_ghosts()) repeatedly, and on every
 | 
|
1472  | 
            result, call stop_searching_any on any keys from the exclude_keys
 | 
|
1473  | 
            set. The revision_count value acts as a trivial cross-check - the
 | 
|
1474  | 
            found revisions of the new search should have as many elements as
 | 
|
| 
3184.1.6
by Robert Collins
 Create a SearchResult object which can be used as a replacement for sets.  | 
1475  | 
            revision_count. If it does not, then additional revisions have been
 | 
1476  | 
            ghosted since the search was executed the first time and the second
 | 
|
1477  | 
            time.
 | 
|
1478  | 
        """
 | 
|
1479  | 
return self._recipe  | 
|
1480  | 
||
1481  | 
def get_keys(self):  | 
|
1482  | 
"""Return the keys found in this search.  | 
|
1483  | 
||
1484  | 
        :return: A set of keys.
 | 
|
1485  | 
        """
 | 
|
1486  | 
return self._keys  | 
|
1487  | 
||
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1488  | 
def is_empty(self):  | 
| 
4204.2.2
by Matt Nordhoff
 Fix docstrings on graph.py's is_empty methods that said they returned true when they were *not* empty.  | 
1489  | 
"""Return false if the search lists 1 or more revisions."""  | 
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1490  | 
return self._recipe[3] == 0  | 
1491  | 
||
1492  | 
def refine(self, seen, referenced):  | 
|
1493  | 
"""Create a new search by refining this search.  | 
|
1494  | 
||
1495  | 
        :param seen: Revisions that have been satisfied.
 | 
|
1496  | 
        :param referenced: Revision references observed while satisfying some
 | 
|
1497  | 
            of this search.
 | 
|
1498  | 
        """
 | 
|
1499  | 
start = self._recipe[1]  | 
|
1500  | 
exclude = self._recipe[2]  | 
|
1501  | 
count = self._recipe[3]  | 
|
1502  | 
keys = self.get_keys()  | 
|
1503  | 
        # New heads = referenced + old heads - seen things - exclude
 | 
|
1504  | 
pending_refs = set(referenced)  | 
|
1505  | 
pending_refs.update(start)  | 
|
1506  | 
pending_refs.difference_update(seen)  | 
|
1507  | 
pending_refs.difference_update(exclude)  | 
|
1508  | 
        # New exclude = old exclude + satisfied heads
 | 
|
1509  | 
seen_heads = start.intersection(seen)  | 
|
1510  | 
exclude.update(seen_heads)  | 
|
1511  | 
        # keys gets seen removed
 | 
|
1512  | 
keys = keys - seen  | 
|
1513  | 
        # length is reduced by len(seen)
 | 
|
1514  | 
count -= len(seen)  | 
|
1515  | 
return SearchResult(pending_refs, exclude, count, keys)  | 
|
1516  | 
||
| 
3514.2.14
by John Arbash Meinel
 Bring in the code to collapse linear portions of the graph.  | 
1517  | 
|
| 
4070.9.14
by Andrew Bennetts
 Tweaks requested by Robert's review.  | 
1518  | 
class PendingAncestryResult(object):  | 
1519  | 
"""A search result that will reconstruct the ancestry for some graph heads.  | 
|
| 
4086.1.4
by Andrew Bennetts
 Fix whitespace nit.  | 
1520  | 
|
| 
4070.9.14
by Andrew Bennetts
 Tweaks requested by Robert's review.  | 
1521  | 
    Unlike SearchResult, this doesn't hold the complete search result in
 | 
1522  | 
    memory, it just holds a description of how to generate it.
 | 
|
1523  | 
    """
 | 
|
1524  | 
||
1525  | 
def __init__(self, heads, repo):  | 
|
1526  | 
"""Constructor.  | 
|
1527  | 
||
1528  | 
        :param heads: an iterable of graph heads.
 | 
|
1529  | 
        :param repo: a repository to use to generate the ancestry for the given
 | 
|
1530  | 
            heads.
 | 
|
1531  | 
        """
 | 
|
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1532  | 
self.heads = frozenset(heads)  | 
| 
4070.9.2
by Andrew Bennetts
 Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.  | 
1533  | 
self.repo = repo  | 
1534  | 
||
| 
4070.9.5
by Andrew Bennetts
 Better wire protocol: don't shoehorn MiniSearchResult serialisation into previous serialisation format.  | 
1535  | 
def get_recipe(self):  | 
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1536  | 
"""Return a recipe that can be used to replay this search.  | 
1537  | 
||
1538  | 
        The recipe allows reconstruction of the same results at a later date.
 | 
|
1539  | 
||
1540  | 
        :seealso SearchResult.get_recipe:
 | 
|
1541  | 
||
1542  | 
        :return: A tuple ('proxy-search', start_keys_set, set(), -1)
 | 
|
1543  | 
            To recreate this result, create a PendingAncestryResult with the
 | 
|
1544  | 
            start_keys_set.
 | 
|
1545  | 
        """
 | 
|
1546  | 
return ('proxy-search', self.heads, set(), -1)  | 
|
| 
4070.9.5
by Andrew Bennetts
 Better wire protocol: don't shoehorn MiniSearchResult serialisation into previous serialisation format.  | 
1547  | 
|
| 
4070.9.2
by Andrew Bennetts
 Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.  | 
1548  | 
def get_keys(self):  | 
| 
4098.1.1
by Andrew Bennetts
 Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.  | 
1549  | 
"""See SearchResult.get_keys.  | 
| 
4098.1.2
by Andrew Bennetts
 Fix 'trailing' whitespace (actually just a blank line in an indented docstring).  | 
1550  | 
|
| 
4098.1.1
by Andrew Bennetts
 Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.  | 
1551  | 
        Returns all the keys for the ancestry of the heads, excluding
 | 
1552  | 
        NULL_REVISION.
 | 
|
1553  | 
        """
 | 
|
1554  | 
return self._get_keys(self.repo.get_graph())  | 
|
| 
4098.1.3
by Andrew Bennetts
 Fix 'trailing' whitespace (actually just a blank line between methods).  | 
1555  | 
|
| 
4098.1.1
by Andrew Bennetts
 Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.  | 
1556  | 
def _get_keys(self, graph):  | 
1557  | 
NULL_REVISION = revision.NULL_REVISION  | 
|
1558  | 
keys = [key for (key, parents) in graph.iter_ancestry(self.heads)  | 
|
1559  | 
if key != NULL_REVISION]  | 
|
| 
4070.9.2
by Andrew Bennetts
 Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.  | 
1560  | 
return keys  | 
1561  | 
||
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1562  | 
def is_empty(self):  | 
| 
4204.2.2
by Matt Nordhoff
 Fix docstrings on graph.py's is_empty methods that said they returned true when they were *not* empty.  | 
1563  | 
"""Return false if the search lists 1 or more revisions."""  | 
| 
4152.1.2
by Robert Collins
 Add streaming from a stacked branch when the sort order is compatible with doing so.  | 
1564  | 
if revision.NULL_REVISION in self.heads:  | 
1565  | 
return len(self.heads) == 1  | 
|
1566  | 
else:  | 
|
1567  | 
return len(self.heads) == 0  | 
|
1568  | 
||
1569  | 
def refine(self, seen, referenced):  | 
|
1570  | 
"""Create a new search by refining this search.  | 
|
1571  | 
||
1572  | 
        :param seen: Revisions that have been satisfied.
 | 
|
1573  | 
        :param referenced: Revision references observed while satisfying some
 | 
|
1574  | 
            of this search.
 | 
|
1575  | 
        """
 | 
|
1576  | 
referenced = self.heads.union(referenced)  | 
|
1577  | 
return PendingAncestryResult(referenced - seen, self.repo)  | 
|
1578  | 
||
| 
4070.9.2
by Andrew Bennetts
 Rough prototype of allowing a SearchResult to be passed to fetch, and using that to improve network conversations.  | 
1579  | 
|
| 
3514.2.14
by John Arbash Meinel
 Bring in the code to collapse linear portions of the graph.  | 
1580  | 
def collapse_linear_regions(parent_map):  | 
1581  | 
"""Collapse regions of the graph that are 'linear'.  | 
|
1582  | 
||
1583  | 
    For example::
 | 
|
1584  | 
||
1585  | 
      A:[B], B:[C]
 | 
|
1586  | 
||
1587  | 
    can be collapsed by removing B and getting::
 | 
|
1588  | 
||
1589  | 
      A:[C]
 | 
|
1590  | 
||
1591  | 
    :param parent_map: A dictionary mapping children to their parents
 | 
|
1592  | 
    :return: Another dictionary with 'linear' chains collapsed
 | 
|
1593  | 
    """
 | 
|
1594  | 
    # Note: this isn't a strictly minimal collapse. For example:
 | 
|
1595  | 
    #   A
 | 
|
1596  | 
    #  / \
 | 
|
1597  | 
    # B   C
 | 
|
1598  | 
    #  \ /
 | 
|
1599  | 
    #   D
 | 
|
1600  | 
    #   |
 | 
|
1601  | 
    #   E
 | 
|
1602  | 
    # Will not have 'D' removed, even though 'E' could fit. Also:
 | 
|
1603  | 
    #   A
 | 
|
1604  | 
    #   |    A
 | 
|
1605  | 
    #   B => |
 | 
|
1606  | 
    #   |    C
 | 
|
1607  | 
    #   C
 | 
|
1608  | 
    # A and C are both kept because they are edges of the graph. We *could* get
 | 
|
1609  | 
    # rid of A if we wanted.
 | 
|
1610  | 
    #   A
 | 
|
1611  | 
    #  / \
 | 
|
1612  | 
    # B   C
 | 
|
1613  | 
    # |   |
 | 
|
1614  | 
    # D   E
 | 
|
1615  | 
    #  \ /
 | 
|
1616  | 
    #   F
 | 
|
1617  | 
    # Will not have any nodes removed, even though you do have an
 | 
|
1618  | 
    # 'uninteresting' linear D->B and E->C
 | 
|
1619  | 
children = {}  | 
|
1620  | 
for child, parents in parent_map.iteritems():  | 
|
1621  | 
children.setdefault(child, [])  | 
|
1622  | 
for p in parents:  | 
|
1623  | 
children.setdefault(p, []).append(child)  | 
|
1624  | 
||
1625  | 
orig_children = dict(children)  | 
|
1626  | 
removed = set()  | 
|
1627  | 
result = dict(parent_map)  | 
|
1628  | 
for node in parent_map:  | 
|
1629  | 
parents = result[node]  | 
|
1630  | 
if len(parents) == 1:  | 
|
1631  | 
parent_children = children[parents[0]]  | 
|
1632  | 
if len(parent_children) != 1:  | 
|
1633  | 
                # This is not the only child
 | 
|
1634  | 
                continue
 | 
|
1635  | 
node_children = children[node]  | 
|
1636  | 
if len(node_children) != 1:  | 
|
1637  | 
                continue
 | 
|
1638  | 
child_parents = result.get(node_children[0], None)  | 
|
1639  | 
if len(child_parents) != 1:  | 
|
1640  | 
                # This is not its only parent
 | 
|
1641  | 
                continue
 | 
|
1642  | 
            # The child of this node only points at it, and the parent only has
 | 
|
1643  | 
            # this as a child. remove this node, and join the others together
 | 
|
1644  | 
result[node_children[0]] = parents  | 
|
1645  | 
children[parents[0]] = node_children  | 
|
1646  | 
del result[node]  | 
|
1647  | 
del children[node]  | 
|
1648  | 
removed.add(node)  | 
|
1649  | 
||
1650  | 
return result  |