~bzr-pqm/bzr/bzr.dev

4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
1
# Copyright (C) 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Implementation of Graph algorithms when we have already loaded everything.
18
"""
19
20
cdef extern from "python-compat.h":
21
    pass
22
23
cdef extern from "Python.h":
24
    ctypedef int Py_ssize_t
25
    ctypedef struct PyObject:
26
        pass
27
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
28
    object PyFrozenSet_New(object)
4371.4.23 by John Arbash Meinel
Clean up the imports at the top.
29
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
30
    object PyTuple_New(Py_ssize_t n)
4371.4.23 by John Arbash Meinel
Clean up the imports at the top.
31
    Py_ssize_t PyTuple_GET_SIZE(object t)
32
    PyObject * PyTuple_GET_ITEM(object t, Py_ssize_t o)
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
33
    void PyTuple_SET_ITEM(object t, Py_ssize_t o, object v)
4371.4.23 by John Arbash Meinel
Clean up the imports at the top.
34
35
    Py_ssize_t PyList_GET_SIZE(object l)
36
    PyObject * PyList_GET_ITEM(object l, Py_ssize_t o)
37
    int PyList_SetItem(object l, Py_ssize_t o, object l) except -1
38
    int PyList_Append(object l, object v) except -1
39
40
    int PyDict_CheckExact(object d)
41
    Py_ssize_t PyDict_Size(object d) except -1
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
42
    PyObject * PyDict_GetItem(object d, object k)
4371.4.23 by John Arbash Meinel
Clean up the imports at the top.
43
    int PyDict_SetItem(object d, object k, object v) except -1
4371.4.18 by John Arbash Meinel
Big performance win, back to 650ms.
44
    int PyDict_DelItem(object d, object k) except -1
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
45
    int PyDict_Next(object d, Py_ssize_t *pos, PyObject **k, PyObject **v)
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
46
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
47
    void Py_INCREF(object)
48
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
49
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
50
from bzrlib import revision
51
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
52
cdef object NULL_REVISION
53
NULL_REVISION = revision.NULL_REVISION
54
55
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
56
cdef class _KnownGraphNode:
57
    """Represents a single object in the known graph."""
58
4371.3.35 by John Arbash Meinel
Changing the heapq algorithm actually ends up with better overall performance.
59
    cdef object key
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
60
    cdef object parents
61
    cdef object children
4371.4.13 by Vincent Ladeuil
Fixed as per John's review.
62
    cdef public long gdfo
4371.4.22 by John Arbash Meinel
Changing from a set of seen to a list to cleanup and a seen attribute.
63
    cdef int seen
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
64
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
65
    def __init__(self, key):
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
66
        cdef int i
67
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
68
        self.key = key
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
69
        self.parents = None
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
70
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
71
        self.children = []
72
        # Greatest distance from origin
73
        self.gdfo = -1
4371.4.22 by John Arbash Meinel
Changing from a set of seen to a list to cleanup and a seen attribute.
74
        self.seen = 0
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
75
76
    property child_keys:
77
        def __get__(self):
78
            cdef _KnownGraphNode child
79
80
            keys = []
81
            for child in self.children:
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
82
                PyList_Append(keys, child.key)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
83
            return keys
84
85
    cdef clear_references(self):
86
        self.parents = None
87
        self.children = None
88
89
    def __repr__(self):
4371.3.35 by John Arbash Meinel
Changing the heapq algorithm actually ends up with better overall performance.
90
        cdef _KnownGraphNode node
91
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
92
        parent_keys = []
4371.3.35 by John Arbash Meinel
Changing the heapq algorithm actually ends up with better overall performance.
93
        if self.parents is not None:
94
            for node in self.parents:
95
                parent_keys.append(node.key)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
96
        child_keys = []
4371.3.35 by John Arbash Meinel
Changing the heapq algorithm actually ends up with better overall performance.
97
        if self.children is not None:
98
            for node in self.children:
99
                child_keys.append(node.key)
4371.4.10 by Vincent Ladeuil
Cleanup.
100
        return '%s(%s  gdfo:%s par:%s child:%s)' % (
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
101
            self.__class__.__name__, self.key, self.gdfo,
4371.4.12 by Vincent Ladeuil
Fix typos.
102
            parent_keys, child_keys)
4371.4.10 by Vincent Ladeuil
Cleanup.
103
4371.3.37 by John Arbash Meinel
cleanup passes.
104
4371.4.14 by John Arbash Meinel
Optimize _find_gdfo and make known_parent_gdfos a member of KnownGraphNode
105
cdef _KnownGraphNode _get_list_node(lst, Py_ssize_t pos):
106
    cdef PyObject *temp_node
107
108
    temp_node = PyList_GET_ITEM(lst, pos)
109
    return <_KnownGraphNode>temp_node
110
111
112
cdef _KnownGraphNode _get_parent(parents, Py_ssize_t pos):
113
    cdef PyObject *temp_node
114
    cdef _KnownGraphNode node
115
116
    temp_node = PyTuple_GET_ITEM(parents, pos)
117
    return <_KnownGraphNode>temp_node
118
119
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
120
# TODO: slab allocate all _KnownGraphNode objects.
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
121
#       We already know how many we are going to need, except for a couple of
122
#       ghosts that could be allocated on demand.
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
123
124
cdef class KnownGraph:
125
    """This is a class which assumes we already know the full graph."""
126
127
    cdef public object _nodes
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
128
    cdef object _known_heads
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
129
    cdef public int do_cache
130
131
    def __init__(self, parent_map, do_cache=True):
132
        """Create a new KnownGraph instance.
133
134
        :param parent_map: A dictionary mapping key => parent_keys
135
        """
4371.4.29 by John Arbash Meinel
Pre-allocating self._nodes turned out to be slower.
136
        # tests at pre-allocating the node dict actually slowed things down
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
137
        self._nodes = {}
138
        # Maps {sorted(revision_id, revision_id): heads}
139
        self._known_heads = {}
140
        self.do_cache = int(do_cache)
141
        self._initialize_nodes(parent_map)
142
        self._find_gdfo()
143
144
    def __dealloc__(self):
145
        cdef _KnownGraphNode child
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
146
        cdef Py_ssize_t pos
147
        cdef PyObject *temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
148
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
149
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
150
            child = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
151
            child.clear_references()
152
4371.4.28 by John Arbash Meinel
We spent a lot of time adding and removing keys from tails.
153
    cdef _KnownGraphNode _get_or_create_node(self, key):
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
154
        cdef PyObject *temp_node
155
        cdef _KnownGraphNode node
156
157
        temp_node = PyDict_GetItem(self._nodes, key)
158
        if temp_node == NULL:
159
            node = _KnownGraphNode(key)
160
            PyDict_SetItem(self._nodes, key, node)
161
        else:
162
            node = <_KnownGraphNode>temp_node
163
        return node
164
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
165
    def _initialize_nodes(self, parent_map):
166
        """Populate self._nodes.
167
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
168
        After this has finished:
169
        - self._nodes will have an entry for every entry in parent_map.
170
        - ghosts will have a parent_keys = None,
4371.4.12 by Vincent Ladeuil
Fix typos.
171
        - all nodes found will also have child_keys populated with all known
172
          child keys,
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
173
        """
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
174
        cdef PyObject *temp_key, *temp_parent_keys, *temp_node
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
175
        cdef Py_ssize_t pos, pos2, num_parent_keys
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
176
        cdef _KnownGraphNode node
177
        cdef _KnownGraphNode parent_node
178
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
179
        if not PyDict_CheckExact(parent_map):
180
            raise TypeError('parent_map should be a dict of {key:parent_keys}')
181
        # for key, parent_keys in parent_map.iteritems():
182
        pos = 0
183
        while PyDict_Next(parent_map, &pos, &temp_key, &temp_parent_keys):
184
            key = <object>temp_key
185
            parent_keys = <object>temp_parent_keys
4371.4.9 by Vincent Ladeuil
Simplify gdfo computing by finding tails when at graph build time.
186
            num_parent_keys = len(parent_keys)
4371.4.28 by John Arbash Meinel
We spent a lot of time adding and removing keys from tails.
187
            node = self._get_or_create_node(key)
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
188
            # We know how many parents, so we could pre allocate an exact sized
189
            # tuple here
190
            parent_nodes = PyTuple_New(num_parent_keys)
191
            # We use iter here, because parent_keys maybe be a list or tuple
192
            for pos2 from 0 <= pos2 < num_parent_keys:
4371.4.28 by John Arbash Meinel
We spent a lot of time adding and removing keys from tails.
193
                parent_node = self._get_or_create_node(parent_keys[pos2])
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
194
                # PyTuple_SET_ITEM will steal a reference, so INCREF first
195
                Py_INCREF(parent_node)
196
                PyTuple_SET_ITEM(parent_nodes, pos2, parent_node)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
197
                PyList_Append(parent_node.children, node)
198
            node.parents = parent_nodes
4371.4.30 by John Arbash Meinel
We don't need self._tails anymore, nor does it need to be a set.
199
200
    def _find_tails(self):
201
        cdef PyObject *temp_node
202
        cdef _KnownGraphNode node
203
        cdef Py_ssize_t pos
204
205
        tails = []
4371.4.28 by John Arbash Meinel
We spent a lot of time adding and removing keys from tails.
206
        pos = 0
207
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
208
            node = <_KnownGraphNode>temp_node
209
            if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
4371.4.30 by John Arbash Meinel
We don't need self._tails anymore, nor does it need to be a set.
210
                node.gdfo = 1
211
                PyList_Append(tails, node)
212
        return tails
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
213
214
    def _find_gdfo(self):
4371.4.8 by Vincent Ladeuil
Replace the existing KnownGraph._find_gdfo.
215
        cdef _KnownGraphNode node
216
        cdef _KnownGraphNode child
4371.4.14 by John Arbash Meinel
Optimize _find_gdfo and make known_parent_gdfos a member of KnownGraphNode
217
        cdef PyObject *temp
4371.4.30 by John Arbash Meinel
We don't need self._tails anymore, nor does it need to be a set.
218
        cdef Py_ssize_t pos
4371.4.14 by John Arbash Meinel
Optimize _find_gdfo and make known_parent_gdfos a member of KnownGraphNode
219
        cdef int replace
4371.4.26 by John Arbash Meinel
Don't shrink the pending list.
220
        cdef Py_ssize_t last_item
4371.4.25 by John Arbash Meinel
We had an 'child_node.gdfo is None' check in the inner loop.
221
        cdef long next_gdfo
4371.4.8 by Vincent Ladeuil
Replace the existing KnownGraph._find_gdfo.
222
4371.4.30 by John Arbash Meinel
We don't need self._tails anymore, nor does it need to be a set.
223
        pending = self._find_tails()
4371.4.8 by Vincent Ladeuil
Replace the existing KnownGraph._find_gdfo.
224
4371.4.26 by John Arbash Meinel
Don't shrink the pending list.
225
        last_item = PyList_GET_SIZE(pending) - 1
226
        while last_item >= 0:
4371.4.14 by John Arbash Meinel
Optimize _find_gdfo and make known_parent_gdfos a member of KnownGraphNode
227
            # Avoid pop followed by push, instead, peek, and replace
4371.4.16 by John Arbash Meinel
Document some perf issues with using known_parent_gdfos, but leave it as a dict.
228
            # timing shows this is 930ms => 770ms for OOo
4371.4.26 by John Arbash Meinel
Don't shrink the pending list.
229
            node = _get_list_node(pending, last_item)
230
            last_item = last_item - 1
4371.4.25 by John Arbash Meinel
We had an 'child_node.gdfo is None' check in the inner loop.
231
            next_gdfo = node.gdfo + 1
4371.4.30 by John Arbash Meinel
We don't need self._tails anymore, nor does it need to be a set.
232
            for pos from 0 <= pos < PyList_GET_SIZE(node.children):
233
                child = _get_list_node(node.children, pos)
4371.4.25 by John Arbash Meinel
We had an 'child_node.gdfo is None' check in the inner loop.
234
                if next_gdfo > child.gdfo:
235
                    child.gdfo = next_gdfo
4371.4.27 by John Arbash Meinel
Re-use the child.seen attribute for tracking num parents.
236
                child.seen = child.seen + 1
237
                if child.seen == PyTuple_GET_SIZE(child.parents):
4371.4.17 by John Arbash Meinel
tried a different tack, to use a new list and append.
238
                    # This child is populated, queue it to be walked
4371.4.26 by John Arbash Meinel
Don't shrink the pending list.
239
                    last_item = last_item + 1
240
                    if last_item < PyList_GET_SIZE(pending):
4371.4.14 by John Arbash Meinel
Optimize _find_gdfo and make known_parent_gdfos a member of KnownGraphNode
241
                        Py_INCREF(child) # SetItem steals a ref
4371.4.26 by John Arbash Meinel
Don't shrink the pending list.
242
                        PyList_SetItem(pending, last_item, child)
4371.4.14 by John Arbash Meinel
Optimize _find_gdfo and make known_parent_gdfos a member of KnownGraphNode
243
                    else:
244
                        PyList_Append(pending, child)
4371.4.27 by John Arbash Meinel
Re-use the child.seen attribute for tracking num parents.
245
                    # We have queued this node, we don't need to track it
246
                    # anymore
247
                    child.seen = 0
4371.4.8 by Vincent Ladeuil
Replace the existing KnownGraph._find_gdfo.
248
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
249
    def heads(self, keys):
250
        """Return the heads from amongst keys.
251
252
        This is done by searching the ancestries of each key.  Any key that is
253
        reachable from another key is not returned; all the others are.
254
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
255
        This operation scales with the relative depth between any two keys. It
256
        uses gdfo to avoid walking all ancestry.
257
258
        :param keys: An iterable of keys.
259
        :return: A set of the heads. Note that as a set there is no ordering
260
            information. Callers will need to filter their input to create
261
            order if they need it.
262
        """
263
        cdef PyObject *maybe_node
264
        cdef PyObject *maybe_heads
265
        cdef PyObject *temp_node
266
        cdef _KnownGraphNode node
4371.4.31 by John Arbash Meinel
Apply the same 'never pop' logic to the heads() code.
267
        cdef Py_ssize_t pos, last_item
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
268
        cdef long min_gdfo
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
269
270
        heads_key = PyFrozenSet_New(keys)
271
        maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
272
        if maybe_heads != NULL:
273
            return <object>maybe_heads
274
        # Not cached, compute it ourselves
275
        candidate_nodes = {}
276
        for key in keys:
4371.4.28 by John Arbash Meinel
We spent a lot of time adding and removing keys from tails.
277
            maybe_node = PyDict_GetItem(self._nodes, key)
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
278
            if maybe_node == NULL:
279
                raise KeyError('key %s not in nodes' % (key,))
280
            PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
281
        maybe_node = PyDict_GetItem(candidate_nodes, NULL_REVISION)
282
        if maybe_node != NULL:
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
283
            # NULL_REVISION is only a head if it is the only entry
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
284
            candidate_nodes.pop(NULL_REVISION)
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
285
            if not candidate_nodes:
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
286
                return frozenset([NULL_REVISION])
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
287
            # The keys changed, so recalculate heads_key
288
            heads_key = PyFrozenSet_New(candidate_nodes)
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
289
        if PyDict_Size(candidate_nodes) < 2:
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
290
            return heads_key
291
4371.4.22 by John Arbash Meinel
Changing from a set of seen to a list to cleanup and a seen attribute.
292
        cleanup = []
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
293
        pending = []
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
294
        # we know a gdfo cannot be longer than a linear chain of all nodes
295
        min_gdfo = PyDict_Size(self._nodes) + 1
296
        # Build up nodes that need to be walked, note that starting nodes are
297
        # not added to seen()
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
298
        pos = 0
299
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
300
            node = <_KnownGraphNode>temp_node
301
            if node.parents is not None:
302
                pending.extend(node.parents)
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
303
            if node.gdfo < min_gdfo:
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
304
                min_gdfo = node.gdfo
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
305
306
        # Now do all the real work
4371.4.31 by John Arbash Meinel
Apply the same 'never pop' logic to the heads() code.
307
        last_item = PyList_GET_SIZE(pending) - 1
308
        while last_item >= 0:
309
            node = _get_list_node(pending, last_item)
310
            last_item = last_item - 1
4371.4.22 by John Arbash Meinel
Changing from a set of seen to a list to cleanup and a seen attribute.
311
            if node.seen:
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
312
                # node already appears in some ancestry
313
                continue
4371.4.22 by John Arbash Meinel
Changing from a set of seen to a list to cleanup and a seen attribute.
314
            PyList_Append(cleanup, node)
315
            node.seen = 1
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
316
            if node.gdfo <= min_gdfo:
317
                continue
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
318
            if node.parents is not None and PyTuple_GET_SIZE(node.parents) > 0:
319
                for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
320
                    parent_node = _get_parent(node.parents, pos)
4371.4.31 by John Arbash Meinel
Apply the same 'never pop' logic to the heads() code.
321
                    last_item = last_item + 1
322
                    if last_item < PyList_GET_SIZE(pending):
323
                        Py_INCREF(parent_node) # SetItem steals a ref
324
                        PyList_SetItem(pending, last_item, parent_node)
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
325
                    else:
326
                        PyList_Append(pending, parent_node)
4371.4.22 by John Arbash Meinel
Changing from a set of seen to a list to cleanup and a seen attribute.
327
        heads = []
328
        pos = 0
329
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
330
            node = <_KnownGraphNode>temp_node
331
            if not node.seen:
332
                PyList_Append(heads, node.key)
333
        heads = PyFrozenSet_New(heads)
334
        for pos from 0 <= pos < PyList_GET_SIZE(cleanup):
335
            node = _get_list_node(cleanup, pos)
336
            node.seen = 0
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
337
        if self.do_cache:
4371.4.21 by John Arbash Meinel
Spend some time optimizing the inner heads() loop, approx 2x faster.
338
            PyDict_SetItem(self._known_heads, heads_key, heads)
4371.4.7 by Vincent Ladeuil
Simple Pyrex version for the new heads() method.
339
        return heads