18
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]
21
51
class GraphWalker(object):
22
52
"""Provide incremental access to revision graphs.
25
55
specialize it for other repository types.
28
def __init__(self, graphs):
58
def __init__(self, parents_provider):
29
59
"""Construct a GraphWalker that uses several graphs as its input
31
61
This should not normally be invoked directly, because there may be
32
62
specialized implementations for particular repository types. See
33
63
Repository.get_graph_walker()
35
Note that the input graphs *will* be altered to use NULL_REVISION as
40
self._descendants = []
42
self._extract_data(graph)
44
def _extract_data(self, graph):
45
"""Convert graph to use NULL_REVISION as origin"""
46
ancestors = dict(graph.get_ancestors())
47
descendants = dict(graph.get_descendants())
48
descendants[NULL_REVISION] = {}
49
ancestors[NULL_REVISION] = []
50
for root in graph.roots:
51
descendants[NULL_REVISION][root] = 1
52
ancestors[root] = ancestors[root] + [NULL_REVISION]
53
for ghost in graph.ghosts:
54
# ghosts act as roots for the purpose of finding
55
# the longest paths from the root: any ghost *might*
56
# be directly attached to the root, so we treat them
58
# ghost now descends from NULL
59
descendants[NULL_REVISION][ghost] = 1
60
# that is it has an ancestor of NULL
61
ancestors[ghost] = [NULL_REVISION]
62
self._ancestors.append(ancestors)
63
self._descendants.append(descendants)
65
def distance_from_origin(self, revisions):
66
"""Determine the of the named revisions from the origin
68
:param revisions: The revisions to examine
69
:return: A list of revision distances. None is provided if no distance
72
distances = graph.node_distances(self._descendants[0],
75
return [distances.get(r) for r in revisions]
77
def distinct_common(self, *revisions):
78
"""Determine the distinct common ancestors of the provided revisions
80
A distinct common ancestor is a common ancestor none of whose
81
descendants are common ancestors.
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.
83
77
This algorithm has two phases. Phase 1 identifies border ancestors,
84
and phase 2 filters border ancestors to determine distinct ancestors.
78
and phase 2 filters border ancestors to determine lowest common
86
81
In phase 1, border ancestors are identified, using a breadth-first
87
82
search starting at the bottom of the graph. Searches are stopped
88
83
whenever a node or one of its descendants is determined to be common
90
In phase 2, the border ancestors are filtered to find the distinct
85
In phase 2, the border ancestors are filtered to find the least
91
86
common ancestors. This is done by searching the ancestries of each
94
Phase 2 is perfomed on the principle that a border ancestor that is not
95
an ancestor of any other border ancestor is a distinct ancestor.
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
97
93
Searches are stopped when they find a node that is determined to be a
98
94
common ancestor of all border ancestors, because this shows that it
138
134
if revision not in walker.seen:
141
maybe_distinct_common.add(revision)
137
border_ancestors.add(revision)
142
138
for walker in walkers:
143
w_seen_ancestors = walker.find_seen_ancestors(revision)
139
w_seen_ancestors = walker.find_seen_ancestors(
144
141
walker.stop_searching_any(w_seen_ancestors)
146
def _filter_candidate_dca(self, candidate_dca):
143
def _filter_candidate_lca(self, candidate_lca):
147
144
"""Remove candidates which are ancestors of other candidates.
149
146
This is done by searching the ancestries of each border ancestor. It
150
147
is perfomed on the principle that a border ancestor that is not an
151
ancestor of any other border ancestor is a distinct ancestor.
148
ancestor of any other border ancestor is a lowest common ancestor.
153
150
Searches are stopped when they find a node that is determined to be a
154
151
common ancestor of all border ancestors, because this shows that it
187
184
seen_ancestors =\
188
185
walker.find_seen_ancestors(ancestor)
189
186
walker.stop_searching_any(seen_ancestors)
192
def unique_common(self, left_revision, right_revision):
193
"""Find a unique distinct common ancestor.
195
Find distinct common ancestors. If there is no unique distinct common
196
ancestor, find the distinct common ancestors of those ancestors.
198
Iteration stops when a unique distinct common ancestor is found.
199
The graph origin is necessarily a unique common ancestor
189
def find_unique_lca(self, left_revision, right_revision):
190
"""Find a unique LCA.
192
Find lowest common ancestors. If there is no unique common
193
ancestor, find the lowest common ancestors of those ancestors.
195
Iteration stops when a unique lowest common ancestor is found.
196
The graph origin is necessarily a unique lowest common ancestor.
201
198
Note that None is not an acceptable substitute for NULL_REVISION.
199
in the input for this method.
203
201
revisions = [left_revision, right_revision]
205
distinct = self.distinct_common(*revisions)
206
if len(distinct) == 1:
207
return distinct.pop()
210
def get_parents(self, revision):
211
"""Determine the parents of a revision"""
212
for ancestors in self._ancestors:
214
return ancestors[revision]
203
lca = self.find_lca(*revisions)
221
209
class _AncestryWalker(object):