1
# Copyright (C) 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Implementation of Graph algorithms when we have already loaded everything.
25
class _KnownGraphNode(object):
26
"""Represents a single object in the known graph."""
28
__slots__ = ('key', 'parent_keys', 'child_keys', 'gdfo')
30
def __init__(self, key, parent_keys):
32
self.parent_keys = parent_keys
34
# Greatest distance from origin
38
return '%s(%s gdfo:%s par:%s child:%s)' % (
39
self.__class__.__name__, self.key, self.gdfo,
40
self.parent_keys, self.child_keys)
43
class KnownGraph(object):
44
"""This is a class which assumes we already know the full graph."""
46
def __init__(self, parent_map, do_cache=True):
47
"""Create a new KnownGraph instance.
49
:param parent_map: A dictionary mapping key => parent_keys
52
# Maps {sorted(revision_id, revision_id): heads}
53
self._known_heads = {}
54
self.do_cache = do_cache
55
self._initialize_nodes(parent_map)
58
def _initialize_nodes(self, parent_map):
59
"""Populate self._nodes.
61
After this has finished:
62
- self._nodes will have an entry for every entry in parent_map.
63
- ghosts will have a parent_keys = None,
64
- all nodes found will also have .child_keys populated with all known
66
- self._tails will list all the nodes without parents.
68
tails = self._tails = set()
70
for key, parent_keys in parent_map.iteritems():
73
node.parent_keys = parent_keys
75
# This node has been added before being seen in parent_map
79
node = _KnownGraphNode(key, parent_keys)
81
for parent_key in parent_keys:
83
parent_node = nodes[parent_key]
85
parent_node = _KnownGraphNode(parent_key, None)
86
nodes[parent_key] = parent_node
87
# Potentially a tail, if we're wrong we'll remove it later
89
tails.add(parent_node)
90
parent_node.child_keys.append(key)
94
known_parent_gdfos = {}
97
for node in self._tails:
103
for child_key in node.child_keys:
104
child = nodes[child_key]
105
if child_key in known_parent_gdfos:
106
known_gdfo = known_parent_gdfos[child_key] + 1
111
if child.gdfo is None or node.gdfo + 1 > child.gdfo:
112
child.gdfo = node.gdfo + 1
113
if known_gdfo == len(child.parent_keys):
114
# We are the last parent updating that node, we can
115
# continue from there
116
pending.append(child)
118
del known_parent_gdfos[child_key]
120
# Update known_parent_gdfos for a key we couldn't process
121
known_parent_gdfos[child_key] = known_gdfo
123
def heads(self, keys):
124
"""Return the heads from amongst keys.
126
This is done by searching the ancestries of each key. Any key that is
127
reachable from another key is not returned; all the others are.
129
This operation scales with the relative depth between any two keys. It
130
uses gdfo to avoid walking all ancestry.
132
:param keys: An iterable of keys.
133
:return: A set of the heads. Note that as a set there is no ordering
134
information. Callers will need to filter their input to create
135
order if they need it.
137
candidate_nodes = dict((key, self._nodes[key]) for key in keys)
138
if revision.NULL_REVISION in candidate_nodes:
139
# NULL_REVISION is only a head if it is the only entry
140
candidate_nodes.pop(revision.NULL_REVISION)
141
if not candidate_nodes:
142
return frozenset([revision.NULL_REVISION])
143
if len(candidate_nodes) < 2:
144
# No or only one candidate
145
return frozenset(candidate_nodes)
146
heads_key = frozenset(candidate_nodes)
147
if heads_key != frozenset(keys):
149
note('%s != %s', heads_key, frozenset(keys))
150
# Do we have a cached result ?
152
heads = self._known_heads[heads_key]
156
# Let's compute the heads
160
for node in candidate_nodes.values():
162
pending.extend(node.parent_keys)
163
if min_gdfo is None or node.gdfo < min_gdfo:
167
node_key = pending.pop()
169
# node already appears in some ancestry
172
node = nodes[node_key]
173
if node.gdfo <= min_gdfo:
176
pending.extend(node.parent_keys)
177
heads = heads_key.difference(seen)
179
self._known_heads[heads_key] = heads