1
# Copyright (C) 2007 Canonical Ltd
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.
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.
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
17
from bzrlib.deprecated_graph import (node_distances, select_farthest)
18
from bzrlib.revision import NULL_REVISION
21
class _StackedParentsProvider(object):
23
def __init__(self, parent_providers):
24
self._parent_providers = parent_providers
26
def get_parents(self, revision_ids):
28
Find revision ids of the parents of a list of revisions
30
A list is returned of the same length as the input. Each entry
31
is a list of parent ids for the corresponding input revision.
33
[NULL_REVISION] is used as the parent of the first user-committed
34
revision. Its parent list is empty.
36
If the revision is not present (i.e. a ghost), None is used in place
37
of the list of parents.
40
for parents_provider in self._parent_providers:
41
parent_list = parents_provider.get_parents(
42
[r for r in revision_ids if r not in found])
43
new_found = dict((k, v) for k, v in zip(revision_ids, parent_list)
45
found.update(new_found)
46
if len(found) == len(revision_ids):
48
return [found.get(r) for r in revision_ids]
51
class GraphWalker(object):
52
"""Provide incremental access to revision graphs.
54
This is the generic implementation; it is intended to be subclassed to
55
specialize it for other repository types.
58
def __init__(self, parents_provider):
59
"""Construct a GraphWalker that uses several graphs as its input
61
This should not normally be invoked directly, because there may be
62
specialized implementations for particular repository types. See
63
Repository.get_graph()
65
:param parents_func: a list of objects providing a get_parents call
66
conforming to the behavior of GraphWalker.get_parents
68
self.get_parents = parents_provider.get_parents
70
def find_lca(self, *revisions):
71
"""Determine the lowest common ancestors of the provided revisions
73
A lowest common ancestor is a common ancestor none of whose
74
descendants are common ancestors. In graphs, unlike trees, there may
75
be multiple lowest common ancestors.
77
This algorithm has two phases. Phase 1 identifies border ancestors,
78
and phase 2 filters border ancestors to determine lowest common
81
In phase 1, border ancestors are identified, using a breadth-first
82
search starting at the bottom of the graph. Searches are stopped
83
whenever a node or one of its descendants is determined to be common
85
In phase 2, the border ancestors are filtered to find the least
86
common ancestors. This is done by searching the ancestries of each
89
Phase 2 is perfomed on the principle that a border ancestor that is
90
not an ancestor of any other border ancestor is a least common
93
Searches are stopped when they find a node that is determined to be a
94
common ancestor of all border ancestors, because this shows that it
95
cannot be a descendant of any border ancestor.
97
The scaling of this operation should be proportional to
98
1. The number of uncommon ancestors
99
2. The number of border ancestors
100
3. The length of the shortest path between a border ancestor and an
101
ancestor of all border ancestors.
103
border_common = self._find_border_ancestors(revisions)
104
return self._filter_candidate_lca(border_common)
106
def _find_border_ancestors(self, revisions):
107
"""Find common ancestors with at least one uncommon descendant.
109
Border ancestors are identified using a breadth-first
110
search starting at the bottom of the graph. Searches are stopped
111
whenever a node or one of its descendants is determined to be common.
113
This will scale with the number of uncommon ancestors.
115
common_walker = _AncestryWalker([], self)
116
common_ancestors = set()
117
walkers = [_AncestryWalker([r], self) for r in revisions]
118
active_walkers = walkers[:]
119
border_ancestors = set()
120
def update_common(walker, revisions):
121
w_seen_ancestors = walker.find_seen_ancestors(
123
stopped = walker.stop_searching_any(w_seen_ancestors)
124
common_ancestors.update(w_seen_ancestors)
125
common_walker.start_searching(stopped)
128
if len(active_walkers) == 0:
129
return border_ancestors
131
new_common = common_walker.next()
132
common_ancestors.update(new_common)
133
except StopIteration:
136
for walker in active_walkers:
137
for revision in new_common.intersection(walker.seen):
138
update_common(walker, revision)
141
new_active_walkers = []
142
for walker in active_walkers:
144
newly_seen.update(walker.next())
145
except StopIteration:
148
new_active_walkers.append(walker)
149
active_walkers = new_active_walkers
150
for revision in newly_seen:
151
if revision in common_ancestors:
152
for walker in walkers:
153
update_common(walker, revision)
155
for walker in walkers:
156
if revision not in walker.seen:
159
border_ancestors.add(revision)
160
for walker in walkers:
161
update_common(walker, revision)
163
def _filter_candidate_lca(self, candidate_lca):
164
"""Remove candidates which are ancestors of other candidates.
166
This is done by searching the ancestries of each border ancestor. It
167
is perfomed on the principle that a border ancestor that is not an
168
ancestor of any other border ancestor is a lowest common ancestor.
170
Searches are stopped when they find a node that is determined to be a
171
common ancestor of all border ancestors, because this shows that it
172
cannot be a descendant of any border ancestor.
174
This will scale with the number of candidate ancestors and the length
175
of the shortest path from a candidate to an ancestor common to all
178
walkers = dict((c, _AncestryWalker([c], self))
179
for c in candidate_lca)
180
active_walkers = dict(walkers)
181
# skip over the actual candidate for each walker
182
for walker in active_walkers.itervalues():
184
while len(active_walkers) > 0:
185
for candidate, walker in list(active_walkers.iteritems()):
187
ancestors = walker.next()
188
except StopIteration:
189
del active_walkers[candidate]
191
for ancestor in ancestors:
192
if ancestor in candidate_lca:
193
candidate_lca.remove(ancestor)
194
del walkers[ancestor]
195
if ancestor in active_walkers:
196
del active_walkers[ancestor]
197
for walker in walkers.itervalues():
198
if ancestor not in walker.seen:
201
# if this revision was seen by all walkers, then it
202
# is a descendant of all candidates, so we can stop
203
# searching it, and any seen ancestors
204
for walker in walkers.itervalues():
206
walker.find_seen_ancestors(ancestor)
207
walker.stop_searching_any(seen_ancestors)
210
def find_unique_lca(self, left_revision, right_revision):
211
"""Find a unique LCA.
213
Find lowest common ancestors. If there is no unique common
214
ancestor, find the lowest common ancestors of those ancestors.
216
Iteration stops when a unique lowest common ancestor is found.
217
The graph origin is necessarily a unique lowest common ancestor.
219
Note that None is not an acceptable substitute for NULL_REVISION.
220
in the input for this method.
222
revisions = [left_revision, right_revision]
224
lca = self.find_lca(*revisions)
230
class _AncestryWalker(object):
231
"""Walk the ancestry of a single revision.
233
This class implements the iterator protocol, but additionally
234
1. provides a set of seen ancestors, and
235
2. allows some ancestries to be unsearched, via stop_searching_any
238
def __init__(self, revisions, graph_walker):
239
self._start = set(revisions)
240
self._search_revisions = None
241
self.seen = set(revisions)
242
self._graph_walker = graph_walker
245
return '_AncestryWalker(self._search_revisions=%r, self.seen=%r)' %\
246
(self._search_revisions, self.seen)
249
"""Return the next ancestors of this revision.
251
Ancestors are returned in the order they are seen in a breadth-first
252
traversal. No ancestor will be returned more than once.
254
if self._search_revisions is None:
255
self._search_revisions = self._start
257
new_search_revisions = set()
258
for parents in self._graph_walker.get_parents(
259
self._search_revisions):
262
new_search_revisions.update(p for p in parents if
264
self._search_revisions = new_search_revisions
265
if len(self._search_revisions) == 0:
266
raise StopIteration()
267
self.seen.update(self._search_revisions)
268
return self._search_revisions
273
def find_seen_ancestors(self, revision):
274
"""Find ancstors of this revision that have already been seen."""
275
walker = _AncestryWalker([revision], self._graph_walker)
276
seen_ancestors = set()
277
for ancestors in walker:
278
for ancestor in ancestors:
279
if ancestor not in self.seen:
280
walker.stop_searching_any([ancestor])
282
seen_ancestors.add(ancestor)
283
return seen_ancestors
285
def stop_searching_any(self, revisions):
287
Remove any of the specified revisions from the search list.
289
None of the specified revisions are required to be present in the
290
search list. In this case, the call is a no-op.
292
stopped_searches = set(l for l in self._search_revisions
294
self._search_revisions = set(l for l in self._search_revisions
295
if l not in revisions)
296
return stopped_searches
298
def start_searching(self, revisions):
299
if self._search_revisions is None:
300
self._start = set(revisions)
302
self._search_revisions.update(r for r in revisions if
304
self.seen.update(revisions)