21
21
class GraphWalker(object):
22
"""Provide incremental access to revision graphs"""
22
"""Provide incremental access to revision graphs.
24
This is the generic implementation; it is intended to be subclassed to
25
specialize it for other repository types.
24
28
def __init__(self, graphs):
29
"""Construct a GraphWalker that uses several graphs as its input
31
This should not normally be invoked directly, because there may be
32
specialized implementations for particular repository types. See
33
Repository.get_graph_walker()
35
Note that the imput graphs *will* be altered to use NULL_REVISION as
25
38
self._graph = graphs
26
39
self._ancestors = []
27
40
self._descendants = []
68
81
descendants are common ancestors. (This is not quite the standard
69
82
graph theory definition)
71
candidate_mca = self._find_candidate_mca(revisions)
72
return self._filter_candidate_mca(candidate_mca)
84
border_common = self._find_border_ancestors(revisions)
85
return self._filter_candidate_mca(border_common)
74
def _find_candidate_mca(self, revisions):
87
def _find_border_ancestors(self, revisions):
88
"""Find common ancestors with at least one uncommon descendant"""
75
89
walkers = [_AncestryWalker(r, self) for r in revisions]
76
90
active_walkers = walkers[:]
77
91
maybe_minimal_common = set()
97
111
maybe_minimal_common.add(revision)
98
112
for walker in walkers:
99
113
w_seen_ancestors = walker.find_seen_ancestors(revision)
100
walker.stop_tracing_any(w_seen_ancestors)
114
walker.stop_searching_any(w_seen_ancestors)
102
116
def _filter_candidate_mca(self, candidate_mca):
103
117
"""Remove candidates which are ancestors of other candidates"""
126
140
# if this revision was seen by all walkers, then it
127
141
# is a descendant of all candidates, so we can stop
128
# tracing it, and any seen ancestors
142
# searching it, and any seen ancestors
129
143
for walker in walkers.itervalues():
130
144
seen_ancestors =\
131
145
walker.find_seen_ancestors(ancestor)
132
walker.stop_tracing_any(seen_ancestors)
146
walker.stop_searching_any(seen_ancestors)
133
147
return candidate_mca
135
149
def unique_common(self, left_revision, right_revision):
162
def _get_ancestry(self, revision):
163
if revision == NULL_REVISION:
166
for graph in self._graph:
168
ancestry = graph.get_ancestry(revision)
174
raise KeyError(revision)
175
ancestry.append(NULL_REVISION)
179
178
class _AncestryWalker(object):
180
"""Walk the ancestry of a single revision"""
179
"""Walk the ancestry of a single revision.
181
This class implements the iterator protocol, but additionally
182
1. provides a set of seen ancestors, and
183
2. allows some ancestries to be unsearched, via stop_searching_any
182
186
def __init__(self, revision, graph_walker):
183
187
self._start = set([revision])
188
self._search_revisions = None
185
189
self.seen = set()
186
190
self._graph_walker = graph_walker
188
192
def __repr__(self):
189
return '_AncestryWalker(self._lines=%r, self.seen=%r)' %\
190
(self._lines, self.seen)
193
return '_AncestryWalker(self._search_revisions=%r, self.seen=%r)' %\
194
(self._search_revisions, self.seen)
193
"""Return the next parents of this revision"""
194
if self._lines is None:
195
self._lines = self._start
197
"""Return the next ancestors of this revision.
199
Ancestors are returned in the order they are seen. No ancestor will
200
be returned more than once.
202
if self._search_revisions is None:
203
self._search_revisions = self._start
198
for revision in self._lines:
199
new_lines.update(p for p in
205
new_search_revisions = set()
206
for revision in self._search_revisions:
207
new_search_revisions.update(p for p in
200
208
self._graph_walker.get_parents(revision) if p
201
209
not in self.seen)
202
self._lines = new_lines
203
if len(self._lines) == 0:
210
self._search_revisions = new_search_revisions
211
if len(self._search_revisions) == 0:
204
212
raise StopIteration()
205
self.seen.update(self._lines)
213
self.seen.update(self._search_revisions)
214
return self._search_revisions
208
216
def __iter__(self):
215
223
for ancestors in walker:
216
224
for ancestor in ancestors:
217
225
if ancestor not in self.seen:
218
walker.stop_tracing_any([ancestor])
226
walker.stop_searching_any([ancestor])
220
228
seen_ancestors.add(ancestor)
221
229
return seen_ancestors
223
def stop_tracing_any(self, revisions):
224
self._lines = set(l for l in self._lines if l not in revisions)
231
def stop_searching_any(self, revisions):
233
Remove any of the specified revisions from the search list.
235
None of the specified revisions are required to be present in the
238
self._search_revisions = set(l for l in self._search_revisions
239
if l not in revisions)