bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
1  | 
# Copyright (C) 2007 Canonical Ltd
 | 
2  | 
#
 | 
|
3  | 
# This program is free software; you can redistribute it and/or modify
 | 
|
4  | 
# it under the terms of the GNU General Public License as published by
 | 
|
5  | 
# the Free Software Foundation; either version 2 of the License, or
 | 
|
6  | 
# (at your option) any later version.
 | 
|
7  | 
#
 | 
|
8  | 
# This program is distributed in the hope that it will be useful,
 | 
|
9  | 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
|
10  | 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
|
11  | 
# GNU General Public License for more details.
 | 
|
12  | 
#
 | 
|
13  | 
# You should have received a copy of the GNU General Public License
 | 
|
14  | 
# along with this program; if not, write to the Free Software
 | 
|
15  | 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
|
16  | 
||
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
17  | 
from bzrlib import (  | 
18  | 
errors,  | 
|
19  | 
tsort,  | 
|
20  | 
    )
 | 
|
| 
2490.2.21
by Aaron Bentley
 Rename graph to deprecated_graph  | 
21  | 
from bzrlib.deprecated_graph import (node_distances, select_farthest)  | 
| 
2490.2.1
by Aaron Bentley
 Start work on GraphWalker  | 
22  | 
from bzrlib.revision import NULL_REVISION  | 
23  | 
||
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
24  | 
# DIAGRAM of terminology
 | 
25  | 
#       A
 | 
|
26  | 
#       /\
 | 
|
27  | 
#      B  C
 | 
|
28  | 
#      |  |\
 | 
|
29  | 
#      D  E F
 | 
|
30  | 
#      |\/| |
 | 
|
31  | 
#      |/\|/
 | 
|
32  | 
#      G  H
 | 
|
33  | 
#
 | 
|
34  | 
# In this diagram, relative to G and H:
 | 
|
35  | 
# A, B, C, D, E are common ancestors.
 | 
|
36  | 
# C, D and E are border ancestors, because each has a non-common descendant.
 | 
|
37  | 
# D and E are least common ancestors because none of their descendants are
 | 
|
38  | 
# common ancestors.
 | 
|
39  | 
# C is not a least common ancestor because its descendant, E, is a common
 | 
|
40  | 
# ancestor.
 | 
|
41  | 
#
 | 
|
42  | 
# The find_unique_lca algorithm will pick A in two steps:
 | 
|
43  | 
# 1. find_lca('G', 'H') => ['D', 'E']
 | 
|
44  | 
# 2. Since len(['D', 'E']) > 1, find_lca('D', 'E') => ['A']
 | 
|
45  | 
||
46  | 
||
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
47  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
48  | 
class _StackedParentsProvider(object):  | 
49  | 
||
50  | 
def __init__(self, parent_providers):  | 
|
51  | 
self._parent_providers = parent_providers  | 
|
52  | 
||
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
53  | 
def __repr__(self):  | 
54  | 
return "_StackedParentsProvider(%r)" % self._parent_providers  | 
|
55  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
56  | 
def get_parents(self, revision_ids):  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
57  | 
"""Find revision ids of the parents of a list of revisions  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
58  | 
|
59  | 
        A list is returned of the same length as the input.  Each entry
 | 
|
60  | 
        is a list of parent ids for the corresponding input revision.
 | 
|
61  | 
||
62  | 
        [NULL_REVISION] is used as the parent of the first user-committed
 | 
|
63  | 
        revision.  Its parent list is empty.
 | 
|
64  | 
||
65  | 
        If the revision is not present (i.e. a ghost), None is used in place
 | 
|
66  | 
        of the list of parents.
 | 
|
67  | 
        """
 | 
|
68  | 
found = {}  | 
|
69  | 
for parents_provider in self._parent_providers:  | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
70  | 
pending_revisions = [r for r in revision_ids if r not in found]  | 
71  | 
parent_list = parents_provider.get_parents(pending_revisions)  | 
|
72  | 
new_found = dict((k, v) for k, v in zip(pending_revisions,  | 
|
73  | 
parent_list) if v is not None)  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
74  | 
found.update(new_found)  | 
75  | 
if len(found) == len(revision_ids):  | 
|
76  | 
                break
 | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
77  | 
return [found.get(r, None) for r in revision_ids]  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
78  | 
|
79  | 
||
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
80  | 
class Graph(object):  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
81  | 
"""Provide incremental access to revision graphs.  | 
82  | 
||
83  | 
    This is the generic implementation; it is intended to be subclassed to
 | 
|
84  | 
    specialize it for other repository types.
 | 
|
85  | 
    """
 | 
|
| 
2490.2.1
by Aaron Bentley
 Start work on GraphWalker  | 
86  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
87  | 
def __init__(self, parents_provider):  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
88  | 
"""Construct a Graph that uses several graphs as its input  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
89  | 
|
90  | 
        This should not normally be invoked directly, because there may be
 | 
|
91  | 
        specialized implementations for particular repository types.  See
 | 
|
| 
2490.2.21
by Aaron Bentley
 Rename graph to deprecated_graph  | 
92  | 
        Repository.get_graph()
 | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
93  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
94  | 
        :param parents_func: an object providing a get_parents call
 | 
95  | 
            conforming to the behavior of StackedParentsProvider.get_parents
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
96  | 
        """
 | 
97  | 
self.get_parents = parents_provider.get_parents  | 
|
| 
2490.2.29
by Aaron Bentley
 Make parents provider private  | 
98  | 
self._parents_provider = parents_provider  | 
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
99  | 
|
100  | 
def __repr__(self):  | 
|
| 
2490.2.29
by Aaron Bentley
 Make parents provider private  | 
101  | 
return 'Graph(%r)' % self._parents_provider  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
102  | 
|
103  | 
def find_lca(self, *revisions):  | 
|
104  | 
"""Determine the lowest common ancestors of the provided revisions  | 
|
105  | 
||
106  | 
        A lowest common ancestor is a common ancestor none of whose
 | 
|
107  | 
        descendants are common ancestors.  In graphs, unlike trees, there may
 | 
|
108  | 
        be multiple lowest common ancestors.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
109  | 
|
110  | 
        This algorithm has two phases.  Phase 1 identifies border ancestors,
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
111  | 
        and phase 2 filters border ancestors to determine lowest common
 | 
112  | 
        ancestors.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
113  | 
|
114  | 
        In phase 1, border ancestors are identified, using a breadth-first
 | 
|
115  | 
        search starting at the bottom of the graph.  Searches are stopped
 | 
|
116  | 
        whenever a node or one of its descendants is determined to be common
 | 
|
117  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
118  | 
        In phase 2, the border ancestors are filtered to find the least
 | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
119  | 
        common ancestors.  This is done by searching the ancestries of each
 | 
120  | 
        border ancestor.
 | 
|
121  | 
||
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
122  | 
        Phase 2 is perfomed on the principle that a border ancestor that is
 | 
123  | 
        not an ancestor of any other border ancestor is a least common
 | 
|
124  | 
        ancestor.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
125  | 
|
126  | 
        Searches are stopped when they find a node that is determined to be a
 | 
|
127  | 
        common ancestor of all border ancestors, because this shows that it
 | 
|
128  | 
        cannot be a descendant of any border ancestor.
 | 
|
129  | 
||
130  | 
        The scaling of this operation should be proportional to
 | 
|
131  | 
        1. The number of uncommon ancestors
 | 
|
132  | 
        2. The number of border ancestors
 | 
|
133  | 
        3. The length of the shortest path between a border ancestor and an
 | 
|
134  | 
           ancestor of all border ancestors.
 | 
|
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
135  | 
        """
 | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
136  | 
border_common, common, sides = self._find_border_ancestors(revisions)  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
137  | 
return self._filter_candidate_lca(border_common)  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
138  | 
|
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
139  | 
def find_difference(self, left_revision, right_revision):  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
140  | 
"""Determine the graph difference between two revisions"""  | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
141  | 
border, common, (left, right) = self._find_border_ancestors(  | 
142  | 
[left_revision, right_revision])  | 
|
143  | 
return (left.difference(right).difference(common),  | 
|
144  | 
right.difference(left).difference(common))  | 
|
145  | 
||
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
146  | 
def _make_breadth_first_searcher(self, revisions):  | 
147  | 
return _BreadthFirstSearcher(revisions, self)  | 
|
148  | 
||
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
149  | 
def _find_border_ancestors(self, revisions):  | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
150  | 
"""Find common ancestors with at least one uncommon descendant.  | 
151  | 
||
152  | 
        Border ancestors are identified using a breadth-first
 | 
|
153  | 
        search starting at the bottom of the graph.  Searches are stopped
 | 
|
154  | 
        whenever a node or one of its descendants is determined to be common.
 | 
|
155  | 
||
156  | 
        This will scale with the number of uncommon ancestors.
 | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
157  | 
|
158  | 
        As well as the border ancestors, a set of seen common ancestors and a
 | 
|
159  | 
        list of sets of seen ancestors for each input revision is returned.
 | 
|
160  | 
        This allows calculation of graph difference from the results of this
 | 
|
161  | 
        operation.
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
162  | 
        """
 | 
| 
2490.2.28
by Aaron Bentley
 Fix handling of null revision  | 
163  | 
if None in revisions:  | 
164  | 
raise errors.InvalidRevisionId(None, self)  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
165  | 
common_searcher = self._make_breadth_first_searcher([])  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
166  | 
common_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
167  | 
searchers = [self._make_breadth_first_searcher([r])  | 
168  | 
for r in revisions]  | 
|
169  | 
active_searchers = searchers[:]  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
170  | 
border_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
171  | 
def update_common(searcher, revisions):  | 
172  | 
w_seen_ancestors = searcher.find_seen_ancestors(  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
173  | 
revision)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
174  | 
stopped = searcher.stop_searching_any(w_seen_ancestors)  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
175  | 
common_ancestors.update(w_seen_ancestors)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
176  | 
common_searcher.start_searching(stopped)  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
177  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
178  | 
while True:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
179  | 
if len(active_searchers) == 0:  | 
| 
2490.2.23
by Aaron Bentley
 Adapt find_borders to produce a graph difference  | 
180  | 
return border_ancestors, common_ancestors, [s.seen for s in  | 
181  | 
searchers]  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
182  | 
try:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
183  | 
new_common = common_searcher.next()  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
184  | 
common_ancestors.update(new_common)  | 
185  | 
except StopIteration:  | 
|
186  | 
                pass
 | 
|
187  | 
else:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
188  | 
for searcher in active_searchers:  | 
189  | 
for revision in new_common.intersection(searcher.seen):  | 
|
190  | 
update_common(searcher, revision)  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
191  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
192  | 
newly_seen = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
193  | 
new_active_searchers = []  | 
194  | 
for searcher in active_searchers:  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
195  | 
try:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
196  | 
newly_seen.update(searcher.next())  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
197  | 
except StopIteration:  | 
198  | 
                    pass
 | 
|
199  | 
else:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
200  | 
new_active_searchers.append(searcher)  | 
201  | 
active_searchers = new_active_searchers  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
202  | 
for revision in newly_seen:  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
203  | 
if revision in common_ancestors:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
204  | 
for searcher in searchers:  | 
205  | 
update_common(searcher, revision)  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
206  | 
                    continue
 | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
207  | 
for searcher in searchers:  | 
208  | 
if revision not in searcher.seen:  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
209  | 
                        break
 | 
210  | 
else:  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
211  | 
border_ancestors.add(revision)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
212  | 
for searcher in searchers:  | 
213  | 
update_common(searcher, revision)  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
214  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
215  | 
def _filter_candidate_lca(self, candidate_lca):  | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
216  | 
"""Remove candidates which are ancestors of other candidates.  | 
217  | 
||
218  | 
        This is done by searching the ancestries of each border ancestor.  It
 | 
|
219  | 
        is perfomed on the principle that a border ancestor that is not an
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
220  | 
        ancestor of any other border ancestor is a lowest common ancestor.
 | 
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
221  | 
|
222  | 
        Searches are stopped when they find a node that is determined to be a
 | 
|
223  | 
        common ancestor of all border ancestors, because this shows that it
 | 
|
224  | 
        cannot be a descendant of any border ancestor.
 | 
|
225  | 
||
226  | 
        This will scale with the number of candidate ancestors and the length
 | 
|
227  | 
        of the shortest path from a candidate to an ancestor common to all
 | 
|
228  | 
        candidates.
 | 
|
229  | 
        """
 | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
230  | 
searchers = dict((c, self._make_breadth_first_searcher([c]))  | 
231  | 
for c in candidate_lca)  | 
|
232  | 
active_searchers = dict(searchers)  | 
|
233  | 
        # skip over the actual candidate for each searcher
 | 
|
234  | 
for searcher in active_searchers.itervalues():  | 
|
235  | 
searcher.next()  | 
|
236  | 
while len(active_searchers) > 0:  | 
|
237  | 
for candidate, searcher in list(active_searchers.iteritems()):  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
238  | 
try:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
239  | 
ancestors = searcher.next()  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
240  | 
except StopIteration:  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
241  | 
del active_searchers[candidate]  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
242  | 
                    continue
 | 
243  | 
for ancestor in ancestors:  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
244  | 
if ancestor in candidate_lca:  | 
245  | 
candidate_lca.remove(ancestor)  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
246  | 
del searchers[ancestor]  | 
247  | 
if ancestor in active_searchers:  | 
|
248  | 
del active_searchers[ancestor]  | 
|
249  | 
for searcher in searchers.itervalues():  | 
|
250  | 
if ancestor not in searcher.seen:  | 
|
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
251  | 
                            break
 | 
252  | 
else:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
253  | 
                        # if this revision was seen by all searchers, then it
 | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
254  | 
                        # is a descendant of all candidates, so we can stop
 | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
255  | 
                        # searching it, and any seen ancestors
 | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
256  | 
for searcher in searchers.itervalues():  | 
| 
2490.2.9
by Aaron Bentley
 Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors  | 
257  | 
seen_ancestors =\  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
258  | 
searcher.find_seen_ancestors(ancestor)  | 
259  | 
searcher.stop_searching_any(seen_ancestors)  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
260  | 
return candidate_lca  | 
261  | 
||
262  | 
def find_unique_lca(self, left_revision, right_revision):  | 
|
263  | 
"""Find a unique LCA.  | 
|
264  | 
||
265  | 
        Find lowest common ancestors.  If there is no unique  common
 | 
|
266  | 
        ancestor, find the lowest common ancestors of those ancestors.
 | 
|
267  | 
||
268  | 
        Iteration stops when a unique lowest common ancestor is found.
 | 
|
269  | 
        The graph origin is necessarily a unique lowest common ancestor.
 | 
|
| 
2490.2.5
by Aaron Bentley
 Use GraphWalker.unique_ancestor to determine merge base  | 
270  | 
|
271  | 
        Note that None is not an acceptable substitute for NULL_REVISION.
 | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
272  | 
        in the input for this method.
 | 
| 
2490.2.3
by Aaron Bentley
 Implement new merge base picker  | 
273  | 
        """
 | 
274  | 
revisions = [left_revision, right_revision]  | 
|
275  | 
while True:  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
276  | 
lca = self.find_lca(*revisions)  | 
277  | 
if len(lca) == 1:  | 
|
278  | 
return lca.pop()  | 
|
| 
2520.4.104
by Aaron Bentley
 Avoid infinite loop when there is no unique lca  | 
279  | 
if len(lca) == 0:  | 
280  | 
raise errors.NoCommonAncestor(left_revision, right_revision)  | 
|
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
281  | 
revisions = lca  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
282  | 
|
| 
2490.2.31
by Aaron Bentley
 Fix iter_topo_order to permit un-included parents  | 
283  | 
def iter_topo_order(self, revisions):  | 
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
284  | 
"""Iterate through the input revisions in topological order.  | 
285  | 
||
286  | 
        This sorting only ensures that parents come before their children.
 | 
|
287  | 
        An ancestor may sort after a descendant if the relationship is not
 | 
|
288  | 
        visible in the supplied list of revisions.
 | 
|
289  | 
        """
 | 
|
| 
2490.2.34
by Aaron Bentley
 Update NEWS and change implementation to return an iterator  | 
290  | 
sorter = tsort.TopoSorter(zip(revisions, self.get_parents(revisions)))  | 
291  | 
return sorter.iter_topo_order()  | 
|
| 
2490.2.30
by Aaron Bentley
 Add functionality for tsorting graphs  | 
292  | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
293  | 
def is_ancestor(self, candidate_ancestor, candidate_descendant):  | 
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
294  | 
"""Determine whether a revision is an ancestor of another.  | 
295  | 
||
296  | 
        There are two possible outcomes: True and False, but there are three
 | 
|
297  | 
        possible relationships:
 | 
|
298  | 
||
299  | 
        a) candidate_ancestor is an ancestor of candidate_descendant
 | 
|
300  | 
        b) candidate_ancestor is an descendant of candidate_descendant
 | 
|
301  | 
        c) candidate_ancestor is an sibling of candidate_descendant
 | 
|
302  | 
||
303  | 
        To check for a, we walk from candidate_descendant, looking for
 | 
|
304  | 
        candidate_ancestor.
 | 
|
305  | 
||
306  | 
        To check for b, we walk from candidate_ancestor, looking for
 | 
|
307  | 
        candidate_descendant.
 | 
|
308  | 
||
309  | 
        To make a and b more efficient, we can stop any searches that hit
 | 
|
310  | 
        common ancestors.
 | 
|
311  | 
||
312  | 
        If we exhaust our searches, but neither a or b is true, then c is true.
 | 
|
313  | 
||
314  | 
        In order to find c efficiently, we must avoid searching from
 | 
|
315  | 
        candidate_descendant or candidate_ancestor into common ancestors.  But
 | 
|
316  | 
        if we don't search common ancestors at all, we won't know if we hit
 | 
|
317  | 
        common ancestors.  So we have a walker for common ancestors.  Note that
 | 
|
318  | 
        its searches are not required to terminate in order to determine c to
 | 
|
319  | 
        be true.
 | 
|
320  | 
        """
 | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
321  | 
ancestor_walker = self._make_breadth_first_searcher(  | 
322  | 
[candidate_ancestor])  | 
|
323  | 
descendant_walker = self._make_breadth_first_searcher(  | 
|
324  | 
[candidate_descendant])  | 
|
325  | 
common_walker = self._make_breadth_first_searcher([])  | 
|
326  | 
active_ancestor = True  | 
|
327  | 
active_descendant = True  | 
|
328  | 
while (active_ancestor or active_descendant):  | 
|
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
329  | 
new_common = set()  | 
| 
2653.2.3
by Aaron Bentley
 correctly handle Graph.is_ancestor(x, x)  | 
330  | 
if active_descendant:  | 
331  | 
try:  | 
|
332  | 
nodes = descendant_walker.next()  | 
|
333  | 
except StopIteration:  | 
|
334  | 
active_descendant = False  | 
|
335  | 
else:  | 
|
336  | 
if candidate_ancestor in nodes:  | 
|
337  | 
return True  | 
|
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
338  | 
new_common.update(nodes.intersection(ancestor_walker.seen))  | 
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
339  | 
if active_ancestor:  | 
340  | 
try:  | 
|
341  | 
nodes = ancestor_walker.next()  | 
|
342  | 
except StopIteration:  | 
|
343  | 
active_ancestor = False  | 
|
344  | 
else:  | 
|
345  | 
if candidate_descendant in nodes:  | 
|
346  | 
return False  | 
|
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
347  | 
new_common.update(nodes.intersection(  | 
348  | 
descendant_walker.seen))  | 
|
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
349  | 
try:  | 
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
350  | 
new_common.update(common_walker.next())  | 
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
351  | 
except StopIteration:  | 
352  | 
                pass
 | 
|
353  | 
for walker in (ancestor_walker, descendant_walker):  | 
|
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
354  | 
for node in new_common:  | 
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
355  | 
c_ancestors = walker.find_seen_ancestors(node)  | 
356  | 
walker.stop_searching_any(c_ancestors)  | 
|
| 
2653.2.5
by Aaron Bentley
 Update to clarify algorithm  | 
357  | 
common_walker.start_searching(new_common)  | 
| 
2653.2.1
by Aaron Bentley
 Implement Graph.is_ancestor  | 
358  | 
return False  | 
359  | 
||
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
360  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
361  | 
class _BreadthFirstSearcher(object):  | 
362  | 
"""Parallel search the breadth-first the ancestry of revisions.  | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
363  | 
|
364  | 
    This class implements the iterator protocol, but additionally
 | 
|
365  | 
    1. provides a set of seen ancestors, and
 | 
|
366  | 
    2. allows some ancestries to be unsearched, via stop_searching_any
 | 
|
367  | 
    """
 | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
368  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
369  | 
def __init__(self, revisions, parents_provider):  | 
| 
2490.2.18
by Aaron Bentley
 Change AncestryWalker to take a set of ancestors  | 
370  | 
self._start = set(revisions)  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
371  | 
self._search_revisions = None  | 
| 
2490.2.18
by Aaron Bentley
 Change AncestryWalker to take a set of ancestors  | 
372  | 
self.seen = set(revisions)  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
373  | 
self._parents_provider = parents_provider  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
374  | 
|
375  | 
def __repr__(self):  | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
376  | 
return ('_BreadthFirstSearcher(self._search_revisions=%r,'  | 
377  | 
' self.seen=%r)' % (self._search_revisions, self.seen))  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
378  | 
|
379  | 
def next(self):  | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
380  | 
"""Return the next ancestors of this revision.  | 
381  | 
||
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
382  | 
        Ancestors are returned in the order they are seen in a breadth-first
 | 
383  | 
        traversal.  No ancestor will be returned more than once.
 | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
384  | 
        """
 | 
385  | 
if self._search_revisions is None:  | 
|
386  | 
self._search_revisions = self._start  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
387  | 
else:  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
388  | 
new_search_revisions = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
389  | 
for parents in self._parents_provider.get_parents(  | 
| 
2490.2.13
by Aaron Bentley
 Update distinct -> lowest, refactor, add ParentsProvider concept  | 
390  | 
self._search_revisions):  | 
391  | 
if parents is None:  | 
|
392  | 
                    continue
 | 
|
393  | 
new_search_revisions.update(p for p in parents if  | 
|
394  | 
p not in self.seen)  | 
|
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
395  | 
self._search_revisions = new_search_revisions  | 
396  | 
if len(self._search_revisions) == 0:  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
397  | 
raise StopIteration()  | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
398  | 
self.seen.update(self._search_revisions)  | 
399  | 
return self._search_revisions  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
400  | 
|
| 
2490.2.8
by Aaron Bentley
 fix iteration stuff  | 
401  | 
def __iter__(self):  | 
402  | 
return self  | 
|
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
403  | 
|
404  | 
def find_seen_ancestors(self, revision):  | 
|
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
405  | 
"""Find ancestors of this revision that have already been seen."""  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
406  | 
searcher = _BreadthFirstSearcher([revision], self._parents_provider)  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
407  | 
seen_ancestors = set()  | 
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
408  | 
for ancestors in searcher:  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
409  | 
for ancestor in ancestors:  | 
410  | 
if ancestor not in self.seen:  | 
|
| 
2490.2.22
by Aaron Bentley
 Rename GraphWalker -> Graph, _AncestryWalker -> _BreadthFirstSearcher  | 
411  | 
searcher.stop_searching_any([ancestor])  | 
| 
2490.2.7
by Aaron Bentley
 Start implementing mca that scales with number of uncommon ancestors  | 
412  | 
else:  | 
413  | 
seen_ancestors.add(ancestor)  | 
|
414  | 
return seen_ancestors  | 
|
415  | 
||
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
416  | 
def stop_searching_any(self, revisions):  | 
417  | 
"""  | 
|
418  | 
        Remove any of the specified revisions from the search list.
 | 
|
419  | 
||
420  | 
        None of the specified revisions are required to be present in the
 | 
|
| 
2490.2.12
by Aaron Bentley
 Improve documentation  | 
421  | 
        search list.  In this case, the call is a no-op.
 | 
| 
2490.2.10
by Aaron Bentley
 Clarify text, remove unused _get_ancestry method  | 
422  | 
        """
 | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
423  | 
stopped = self._search_revisions.intersection(revisions)  | 
424  | 
self._search_revisions = self._search_revisions.difference(revisions)  | 
|
425  | 
return stopped  | 
|
| 
2490.2.17
by Aaron Bentley
 Add start_searching, tweak stop_searching_any  | 
426  | 
|
427  | 
def start_searching(self, revisions):  | 
|
428  | 
if self._search_revisions is None:  | 
|
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
429  | 
self._start = set(revisions)  | 
| 
2490.2.17
by Aaron Bentley
 Add start_searching, tweak stop_searching_any  | 
430  | 
else:  | 
| 
2490.2.25
by Aaron Bentley
 Update from review  | 
431  | 
self._search_revisions.update(revisions.difference(self.seen))  | 
| 
2490.2.19
by Aaron Bentley
 Implement common-ancestor-based culling  | 
432  | 
self.seen.update(revisions)  |