~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_known_graph_pyx.pyx

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
28
 
    object PyTuple_New(Py_ssize_t n)
29
 
    Py_ssize_t PyTuple_GET_SIZE(object t)
30
 
    PyObject * PyTuple_GET_ITEM(object t, Py_ssize_t o)
31
 
    void PyTuple_SET_ITEM(object t, Py_ssize_t o, object v)
32
 
 
33
 
    Py_ssize_t PyList_GET_SIZE(object l)
34
 
    PyObject * PyList_GET_ITEM(object l, Py_ssize_t o)
35
 
    int PyList_SetItem(object l, Py_ssize_t o, object l) except -1
36
 
    int PyList_Append(object l, object v) except -1
37
 
 
38
 
    int PyDict_CheckExact(object d)
39
 
    Py_ssize_t PyDict_Size(object d) except -1
40
 
    PyObject * PyDict_GetItem(object d, object k)
41
 
    int PyDict_SetItem(object d, object k, object v) except -1
42
 
    int PyDict_DelItem(object d, object k) except -1
43
 
    int PyDict_Next(object d, Py_ssize_t *pos, PyObject **k, PyObject **v)
44
 
 
45
 
    void Py_INCREF(object)
46
 
 
47
 
 
48
 
from bzrlib import revision
49
 
 
50
 
cdef object NULL_REVISION
51
 
NULL_REVISION = revision.NULL_REVISION
52
 
 
53
 
 
54
 
cdef class _KnownGraphNode:
55
 
    """Represents a single object in the known graph."""
56
 
 
57
 
    cdef object key
58
 
    cdef object parents
59
 
    cdef object children
60
 
    cdef public long gdfo
61
 
    cdef int seen
62
 
 
63
 
    def __init__(self, key):
64
 
        cdef int i
65
 
 
66
 
        self.key = key
67
 
        self.parents = None
68
 
 
69
 
        self.children = []
70
 
        # Greatest distance from origin
71
 
        self.gdfo = -1
72
 
        self.seen = 0
73
 
 
74
 
    property child_keys:
75
 
        def __get__(self):
76
 
            cdef _KnownGraphNode child
77
 
 
78
 
            keys = []
79
 
            for child in self.children:
80
 
                PyList_Append(keys, child.key)
81
 
            return keys
82
 
 
83
 
    cdef clear_references(self):
84
 
        self.parents = None
85
 
        self.children = None
86
 
 
87
 
    def __repr__(self):
88
 
        cdef _KnownGraphNode node
89
 
 
90
 
        parent_keys = []
91
 
        if self.parents is not None:
92
 
            for node in self.parents:
93
 
                parent_keys.append(node.key)
94
 
        child_keys = []
95
 
        if self.children is not None:
96
 
            for node in self.children:
97
 
                child_keys.append(node.key)
98
 
        return '%s(%s  gdfo:%s par:%s child:%s)' % (
99
 
            self.__class__.__name__, self.key, self.gdfo,
100
 
            parent_keys, child_keys)
101
 
 
102
 
 
103
 
cdef _KnownGraphNode _get_list_node(lst, Py_ssize_t pos):
104
 
    cdef PyObject *temp_node
105
 
 
106
 
    temp_node = PyList_GET_ITEM(lst, pos)
107
 
    return <_KnownGraphNode>temp_node
108
 
 
109
 
 
110
 
cdef _KnownGraphNode _get_parent(parents, Py_ssize_t pos):
111
 
    cdef PyObject *temp_node
112
 
    cdef _KnownGraphNode node
113
 
 
114
 
    temp_node = PyTuple_GET_ITEM(parents, pos)
115
 
    return <_KnownGraphNode>temp_node
116
 
 
117
 
 
118
 
# TODO: slab allocate all _KnownGraphNode objects.
119
 
#       We already know how many we are going to need, except for a couple of
120
 
#       ghosts that could be allocated on demand.
121
 
 
122
 
cdef class KnownGraph:
123
 
    """This is a class which assumes we already know the full graph."""
124
 
 
125
 
    cdef public object _nodes
126
 
    cdef object _known_heads
127
 
    cdef public int do_cache
128
 
 
129
 
    def __init__(self, parent_map, do_cache=True):
130
 
        """Create a new KnownGraph instance.
131
 
 
132
 
        :param parent_map: A dictionary mapping key => parent_keys
133
 
        """
134
 
        # tests at pre-allocating the node dict actually slowed things down
135
 
        self._nodes = {}
136
 
        # Maps {sorted(revision_id, revision_id): heads}
137
 
        self._known_heads = {}
138
 
        self.do_cache = int(do_cache)
139
 
        self._initialize_nodes(parent_map)
140
 
        self._find_gdfo()
141
 
 
142
 
    def __dealloc__(self):
143
 
        cdef _KnownGraphNode child
144
 
        cdef Py_ssize_t pos
145
 
        cdef PyObject *temp_node
146
 
 
147
 
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
148
 
            child = <_KnownGraphNode>temp_node
149
 
            child.clear_references()
150
 
 
151
 
    cdef _KnownGraphNode _get_or_create_node(self, key):
152
 
        cdef PyObject *temp_node
153
 
        cdef _KnownGraphNode node
154
 
 
155
 
        temp_node = PyDict_GetItem(self._nodes, key)
156
 
        if temp_node == NULL:
157
 
            node = _KnownGraphNode(key)
158
 
            PyDict_SetItem(self._nodes, key, node)
159
 
        else:
160
 
            node = <_KnownGraphNode>temp_node
161
 
        return node
162
 
 
163
 
    def _initialize_nodes(self, parent_map):
164
 
        """Populate self._nodes.
165
 
 
166
 
        After this has finished:
167
 
        - self._nodes will have an entry for every entry in parent_map.
168
 
        - ghosts will have a parent_keys = None,
169
 
        - all nodes found will also have child_keys populated with all known
170
 
          child keys,
171
 
        """
172
 
        cdef PyObject *temp_key, *temp_parent_keys, *temp_node
173
 
        cdef Py_ssize_t pos, pos2, num_parent_keys
174
 
        cdef _KnownGraphNode node
175
 
        cdef _KnownGraphNode parent_node
176
 
 
177
 
        if not PyDict_CheckExact(parent_map):
178
 
            raise TypeError('parent_map should be a dict of {key:parent_keys}')
179
 
        # for key, parent_keys in parent_map.iteritems():
180
 
        pos = 0
181
 
        while PyDict_Next(parent_map, &pos, &temp_key, &temp_parent_keys):
182
 
            key = <object>temp_key
183
 
            parent_keys = <object>temp_parent_keys
184
 
            num_parent_keys = len(parent_keys)
185
 
            node = self._get_or_create_node(key)
186
 
            # We know how many parents, so we could pre allocate an exact sized
187
 
            # tuple here
188
 
            parent_nodes = PyTuple_New(num_parent_keys)
189
 
            # We use iter here, because parent_keys maybe be a list or tuple
190
 
            for pos2 from 0 <= pos2 < num_parent_keys:
191
 
                parent_node = self._get_or_create_node(parent_keys[pos2])
192
 
                # PyTuple_SET_ITEM will steal a reference, so INCREF first
193
 
                Py_INCREF(parent_node)
194
 
                PyTuple_SET_ITEM(parent_nodes, pos2, parent_node)
195
 
                PyList_Append(parent_node.children, node)
196
 
            node.parents = parent_nodes
197
 
 
198
 
    def _find_tails(self):
199
 
        cdef PyObject *temp_node
200
 
        cdef _KnownGraphNode node
201
 
        cdef Py_ssize_t pos
202
 
 
203
 
        tails = []
204
 
        pos = 0
205
 
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
206
 
            node = <_KnownGraphNode>temp_node
207
 
            if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
208
 
                node.gdfo = 1
209
 
                PyList_Append(tails, node)
210
 
        return tails
211
 
 
212
 
    def _find_gdfo(self):
213
 
        cdef _KnownGraphNode node
214
 
        cdef _KnownGraphNode child
215
 
        cdef PyObject *temp
216
 
        cdef Py_ssize_t pos
217
 
        cdef int replace
218
 
        cdef Py_ssize_t last_item
219
 
        cdef long next_gdfo
220
 
 
221
 
        pending = self._find_tails()
222
 
 
223
 
        last_item = PyList_GET_SIZE(pending) - 1
224
 
        while last_item >= 0:
225
 
            # Avoid pop followed by push, instead, peek, and replace
226
 
            # timing shows this is 930ms => 770ms for OOo
227
 
            node = _get_list_node(pending, last_item)
228
 
            last_item = last_item - 1
229
 
            next_gdfo = node.gdfo + 1
230
 
            for pos from 0 <= pos < PyList_GET_SIZE(node.children):
231
 
                child = _get_list_node(node.children, pos)
232
 
                if next_gdfo > child.gdfo:
233
 
                    child.gdfo = next_gdfo
234
 
                child.seen = child.seen + 1
235
 
                if child.seen == PyTuple_GET_SIZE(child.parents):
236
 
                    # This child is populated, queue it to be walked
237
 
                    last_item = last_item + 1
238
 
                    if last_item < PyList_GET_SIZE(pending):
239
 
                        Py_INCREF(child) # SetItem steals a ref
240
 
                        PyList_SetItem(pending, last_item, child)
241
 
                    else:
242
 
                        PyList_Append(pending, child)
243
 
                    # We have queued this node, we don't need to track it
244
 
                    # anymore
245
 
                    child.seen = 0
246
 
 
247
 
    def heads(self, keys):
248
 
        """Return the heads from amongst keys.
249
 
 
250
 
        This is done by searching the ancestries of each key.  Any key that is
251
 
        reachable from another key is not returned; all the others are.
252
 
 
253
 
        This operation scales with the relative depth between any two keys. It
254
 
        uses gdfo to avoid walking all ancestry.
255
 
 
256
 
        :param keys: An iterable of keys.
257
 
        :return: A set of the heads. Note that as a set there is no ordering
258
 
            information. Callers will need to filter their input to create
259
 
            order if they need it.
260
 
        """
261
 
        cdef PyObject *maybe_node
262
 
        cdef PyObject *maybe_heads
263
 
        cdef PyObject *temp_node
264
 
        cdef _KnownGraphNode node
265
 
        cdef Py_ssize_t pos, last_item
266
 
        cdef long min_gdfo
267
 
 
268
 
        heads_key = frozenset(keys)
269
 
        maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
270
 
        if maybe_heads != NULL:
271
 
            return <object>maybe_heads
272
 
        # Not cached, compute it ourselves
273
 
        candidate_nodes = {}
274
 
        for key in keys:
275
 
            maybe_node = PyDict_GetItem(self._nodes, key)
276
 
            if maybe_node == NULL:
277
 
                raise KeyError('key %s not in nodes' % (key,))
278
 
            PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
279
 
        maybe_node = PyDict_GetItem(candidate_nodes, NULL_REVISION)
280
 
        if maybe_node != NULL:
281
 
            # NULL_REVISION is only a head if it is the only entry
282
 
            candidate_nodes.pop(NULL_REVISION)
283
 
            if not candidate_nodes:
284
 
                return frozenset([NULL_REVISION])
285
 
            # The keys changed, so recalculate heads_key
286
 
            heads_key = frozenset(candidate_nodes)
287
 
        if PyDict_Size(candidate_nodes) < 2:
288
 
            return heads_key
289
 
 
290
 
        cleanup = []
291
 
        pending = []
292
 
        # we know a gdfo cannot be longer than a linear chain of all nodes
293
 
        min_gdfo = PyDict_Size(self._nodes) + 1
294
 
        # Build up nodes that need to be walked, note that starting nodes are
295
 
        # not added to seen()
296
 
        pos = 0
297
 
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
298
 
            node = <_KnownGraphNode>temp_node
299
 
            if node.parents is not None:
300
 
                pending.extend(node.parents)
301
 
            if node.gdfo < min_gdfo:
302
 
                min_gdfo = node.gdfo
303
 
 
304
 
        # Now do all the real work
305
 
        last_item = PyList_GET_SIZE(pending) - 1
306
 
        while last_item >= 0:
307
 
            node = _get_list_node(pending, last_item)
308
 
            last_item = last_item - 1
309
 
            if node.seen:
310
 
                # node already appears in some ancestry
311
 
                continue
312
 
            PyList_Append(cleanup, node)
313
 
            node.seen = 1
314
 
            if node.gdfo <= min_gdfo:
315
 
                continue
316
 
            if node.parents is not None and PyTuple_GET_SIZE(node.parents) > 0:
317
 
                for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
318
 
                    parent_node = _get_parent(node.parents, pos)
319
 
                    last_item = last_item + 1
320
 
                    if last_item < PyList_GET_SIZE(pending):
321
 
                        Py_INCREF(parent_node) # SetItem steals a ref
322
 
                        PyList_SetItem(pending, last_item, parent_node)
323
 
                    else:
324
 
                        PyList_Append(pending, parent_node)
325
 
        heads = []
326
 
        pos = 0
327
 
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
328
 
            node = <_KnownGraphNode>temp_node
329
 
            if not node.seen:
330
 
                PyList_Append(heads, node.key)
331
 
        heads = frozenset(heads)
332
 
        for pos from 0 <= pos < PyList_GET_SIZE(cleanup):
333
 
            node = _get_list_node(cleanup, pos)
334
 
            node.seen = 0
335
 
        if self.do_cache:
336
 
            PyDict_SetItem(self._known_heads, heads_key, heads)
337
 
        return heads