/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/_annotator_pyx.pyx

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009, 2010 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
"""Functionality for doing annotations in the 'optimal' way"""
 
18
 
 
19
 
 
20
cdef extern from "python-compat.h":
 
21
    pass
 
22
 
 
23
from cpython.dict cimport (
 
24
    PyDict_GetItem,
 
25
    PyDict_SetItem,
 
26
    )
 
27
from cpython.list cimport (
 
28
    PyList_Append,
 
29
    PyList_CheckExact,
 
30
    PyList_GET_ITEM,
 
31
    PyList_GET_SIZE,
 
32
    PyList_SetItem,
 
33
    PyList_Sort,
 
34
    )
 
35
from cpython.object cimport (
 
36
    Py_EQ,
 
37
    Py_LT,
 
38
    PyObject,
 
39
    PyObject_RichCompareBool,
 
40
    )
 
41
from cpython.ref cimport (
 
42
    Py_INCREF,
 
43
    )
 
44
from cpython.tuple cimport (
 
45
    PyTuple_CheckExact,
 
46
    PyTuple_GET_ITEM,
 
47
    PyTuple_GET_SIZE,
 
48
    PyTuple_New,
 
49
    PyTuple_SET_ITEM,
 
50
    )
 
51
 
 
52
cdef extern from "Python.h":
 
53
    ctypedef struct PyListObject:
 
54
        PyObject **ob_item
 
55
 
 
56
    void PyTuple_SET_ITEM_ptr "PyTuple_SET_ITEM" (object, Py_ssize_t,
 
57
                                                  PyObject *)
 
58
 
 
59
    void Py_INCREF_ptr "Py_INCREF" (PyObject *)
 
60
    void Py_DECREF_ptr "Py_DECREF" (PyObject *)
 
61
 
 
62
    int PyObject_RichCompareBool_ptr "PyObject_RichCompareBool" (
 
63
        PyObject *, PyObject *, int opid)
 
64
 
 
65
 
 
66
from . import _annotator_py
 
67
 
 
68
 
 
69
cdef int _check_annotations_are_lists(annotations,
 
70
                                      parent_annotations) except -1:
 
71
    if not PyList_CheckExact(annotations):
 
72
        raise TypeError('annotations must be a list')
 
73
    if not PyList_CheckExact(parent_annotations):
 
74
        raise TypeError('parent_annotations must be a list')
 
75
    return 0
 
76
 
 
77
 
 
78
cdef int _check_match_ranges(parent_annotations, annotations,
 
79
                             Py_ssize_t parent_idx, Py_ssize_t lines_idx,
 
80
                             Py_ssize_t match_len) except -1:
 
81
    if parent_idx + match_len > PyList_GET_SIZE(parent_annotations):
 
82
        raise ValueError('Match length exceeds len of'
 
83
                         ' parent_annotations %s > %s'
 
84
                         % (parent_idx + match_len,
 
85
                            PyList_GET_SIZE(parent_annotations)))
 
86
    if lines_idx + match_len > PyList_GET_SIZE(annotations):
 
87
        raise ValueError('Match length exceeds len of'
 
88
                         ' annotations %s > %s'
 
89
                         % (lines_idx + match_len,
 
90
                            PyList_GET_SIZE(annotations)))
 
91
    return 0
 
92
 
 
93
 
 
94
cdef PyObject *_next_tuple_entry(object tpl, Py_ssize_t *pos): # cannot_raise
 
95
    """Return the next entry from this tuple.
 
96
 
 
97
    :param tpl: The tuple we are investigating, *must* be a PyTuple
 
98
    :param pos: The last item we found. Will be updated to the new position.
 
99
 
 
100
    This cannot raise an exception, as it does no error checking.
 
101
    """
 
102
    pos[0] = pos[0] + 1
 
103
    if pos[0] >= PyTuple_GET_SIZE(tpl):
 
104
        return NULL
 
105
    return PyTuple_GET_ITEM(tpl, pos[0])
 
106
 
 
107
 
 
108
cdef object _combine_annotations(ann_one, ann_two, cache):
 
109
    """Combine the annotations from both sides."""
 
110
    cdef Py_ssize_t pos_one, pos_two, len_one, len_two
 
111
    cdef Py_ssize_t out_pos
 
112
    cdef PyObject *temp
 
113
    cdef PyObject *left
 
114
    cdef PyObject *right
 
115
 
 
116
    if (PyObject_RichCompareBool(ann_one, ann_two, Py_LT)):
 
117
        cache_key = (ann_one, ann_two)
 
118
    else:
 
119
        cache_key = (ann_two, ann_one)
 
120
    temp = PyDict_GetItem(cache, cache_key)
 
121
    if temp != NULL:
 
122
        return <object>temp
 
123
 
 
124
    if not PyTuple_CheckExact(ann_one) or not PyTuple_CheckExact(ann_two):
 
125
        raise TypeError('annotations must be tuples')
 
126
    # We know that annotations are tuples, and that both sides are already
 
127
    # sorted, so we can just walk and update a new list.
 
128
    pos_one = -1
 
129
    pos_two = -1
 
130
    out_pos = 0
 
131
    left = _next_tuple_entry(ann_one, &pos_one)
 
132
    right = _next_tuple_entry(ann_two, &pos_two)
 
133
    new_ann = PyTuple_New(PyTuple_GET_SIZE(ann_one)
 
134
                          + PyTuple_GET_SIZE(ann_two))
 
135
    while left != NULL and right != NULL:
 
136
        # left == right is done by PyObject_RichCompareBool_ptr, however it
 
137
        # avoids a function call for a very common case. Drops 'time bzr
 
138
        # annotate NEWS' from 7.25s to 7.16s, so it *is* a visible impact.
 
139
        if (left == right
 
140
            or PyObject_RichCompareBool_ptr(left, right, Py_EQ)):
 
141
            # Identical values, step both
 
142
            Py_INCREF_ptr(left)
 
143
            PyTuple_SET_ITEM_ptr(new_ann, out_pos, left)
 
144
            left = _next_tuple_entry(ann_one, &pos_one)
 
145
            right = _next_tuple_entry(ann_two, &pos_two)
 
146
        elif (PyObject_RichCompareBool_ptr(left, right, Py_LT)):
 
147
            # left < right or right == NULL
 
148
            Py_INCREF_ptr(left)
 
149
            PyTuple_SET_ITEM_ptr(new_ann, out_pos, left)
 
150
            left = _next_tuple_entry(ann_one, &pos_one)
 
151
        else: # right < left or left == NULL
 
152
            Py_INCREF_ptr(right)
 
153
            PyTuple_SET_ITEM_ptr(new_ann, out_pos, right)
 
154
            right = _next_tuple_entry(ann_two, &pos_two)
 
155
        out_pos = out_pos + 1
 
156
    while left != NULL:
 
157
        Py_INCREF_ptr(left)
 
158
        PyTuple_SET_ITEM_ptr(new_ann, out_pos, left)
 
159
        left = _next_tuple_entry(ann_one, &pos_one)
 
160
        out_pos = out_pos + 1
 
161
    while right != NULL:
 
162
        Py_INCREF_ptr(right)
 
163
        PyTuple_SET_ITEM_ptr(new_ann, out_pos, right)
 
164
        right = _next_tuple_entry(ann_two, &pos_two)
 
165
        out_pos = out_pos + 1
 
166
    if out_pos != PyTuple_GET_SIZE(new_ann):
 
167
        # Timing _PyTuple_Resize was not significantly faster that slicing
 
168
        # PyTuple_Resize((<PyObject **>new_ann), out_pos)
 
169
        new_ann = new_ann[0:out_pos]
 
170
    PyDict_SetItem(cache, cache_key, new_ann)
 
171
    return new_ann
 
172
 
 
173
 
 
174
cdef int _apply_parent_annotations(annotations, parent_annotations,
 
175
                                   matching_blocks) except -1:
 
176
    """Apply the annotations from parent_annotations into annotations.
 
177
 
 
178
    matching_blocks defines the ranges that match.
 
179
    """
 
180
    cdef Py_ssize_t parent_idx, lines_idx, match_len, idx
 
181
    cdef PyListObject *par_list
 
182
    cdef PyListObject *ann_list
 
183
    cdef PyObject **par_temp
 
184
    cdef PyObject **ann_temp
 
185
 
 
186
    _check_annotations_are_lists(annotations, parent_annotations)
 
187
    par_list = <PyListObject *>parent_annotations
 
188
    ann_list = <PyListObject *>annotations
 
189
    # For NEWS and breezy/builtins.py, over 99% of the lines are simply copied
 
190
    # across from the parent entry. So this routine is heavily optimized for
 
191
    # that. Would be interesting if we could use memcpy() but we have to incref
 
192
    # and decref
 
193
    for parent_idx, lines_idx, match_len in matching_blocks:
 
194
        _check_match_ranges(parent_annotations, annotations,
 
195
                            parent_idx, lines_idx, match_len)
 
196
        par_temp = par_list.ob_item + parent_idx
 
197
        ann_temp = ann_list.ob_item + lines_idx
 
198
        for idx from 0 <= idx < match_len:
 
199
            Py_INCREF_ptr(par_temp[idx])
 
200
            Py_DECREF_ptr(ann_temp[idx])
 
201
            ann_temp[idx] = par_temp[idx]
 
202
    return 0
 
203
 
 
204
 
 
205
cdef int _merge_annotations(this_annotation, annotations, parent_annotations,
 
206
                            matching_blocks, ann_cache) except -1:
 
207
    cdef Py_ssize_t parent_idx, ann_idx, lines_idx, match_len, idx
 
208
    cdef Py_ssize_t pos
 
209
    cdef PyObject *ann_temp
 
210
    cdef PyObject *par_temp
 
211
 
 
212
    _check_annotations_are_lists(annotations, parent_annotations)
 
213
    last_ann = None
 
214
    last_parent = None
 
215
    last_res = None
 
216
    for parent_idx, lines_idx, match_len in matching_blocks:
 
217
        _check_match_ranges(parent_annotations, annotations,
 
218
                            parent_idx, lines_idx, match_len)
 
219
        # For lines which match this parent, we will now resolve whether
 
220
        # this parent wins over the current annotation
 
221
        for idx from 0 <= idx < match_len:
 
222
            ann_idx = lines_idx + idx
 
223
            ann_temp = PyList_GET_ITEM(annotations, ann_idx)
 
224
            par_temp = PyList_GET_ITEM(parent_annotations, parent_idx + idx)
 
225
            if (ann_temp == par_temp):
 
226
                # This is parent, do nothing
 
227
                # Pointer comparison is fine here. Value comparison would
 
228
                # be ok, but it will be handled in the final if clause by
 
229
                # merging the two tuples into the same tuple
 
230
                # Avoiding the Py_INCREF and function call to
 
231
                # PyObject_RichCompareBool using pointer comparison drops
 
232
                # timing from 215ms => 125ms
 
233
                continue
 
234
            par_ann = <object>par_temp
 
235
            ann = <object>ann_temp
 
236
            if (ann is this_annotation):
 
237
                # Originally claimed 'this', but it was really in this
 
238
                # parent
 
239
                Py_INCREF(par_ann)
 
240
                PyList_SetItem(annotations, ann_idx, par_ann)
 
241
                continue
 
242
            # Resolve the fact that both sides have a different value for
 
243
            # last modified
 
244
            if (ann is last_ann and par_ann is last_parent):
 
245
                Py_INCREF(last_res)
 
246
                PyList_SetItem(annotations, ann_idx, last_res)
 
247
            else:
 
248
                new_ann = _combine_annotations(ann, par_ann, ann_cache)
 
249
                Py_INCREF(new_ann)
 
250
                PyList_SetItem(annotations, ann_idx, new_ann)
 
251
                last_ann = ann
 
252
                last_parent = par_ann
 
253
                last_res = new_ann
 
254
    return 0
 
255
 
 
256
 
 
257
class Annotator(_annotator_py.Annotator):
 
258
    """Class that drives performing annotations."""
 
259
 
 
260
    def _update_from_first_parent(self, key, annotations, lines, parent_key):
 
261
        """Reannotate this text relative to its first parent."""
 
262
        (parent_annotations,
 
263
         matching_blocks) = self._get_parent_annotations_and_matches(
 
264
                                key, lines, parent_key)
 
265
 
 
266
        _apply_parent_annotations(annotations, parent_annotations,
 
267
                                  matching_blocks)
 
268
 
 
269
    def _update_from_other_parents(self, key, annotations, lines,
 
270
                                   this_annotation, parent_key):
 
271
        """Reannotate this text relative to a second (or more) parent."""
 
272
        (parent_annotations,
 
273
         matching_blocks) = self._get_parent_annotations_and_matches(
 
274
                                key, lines, parent_key)
 
275
        _merge_annotations(this_annotation, annotations, parent_annotations,
 
276
                           matching_blocks, self._ann_tuple_cache)
 
277
 
 
278
    def annotate_flat(self, key):
 
279
        """Determine the single-best-revision to source for each line.
 
280
 
 
281
        This is meant as a compatibility thunk to how annotate() used to work.
 
282
        """
 
283
        cdef Py_ssize_t pos, num_lines
 
284
 
 
285
        from . import annotate
 
286
 
 
287
        custom_tiebreaker = annotate._break_annotation_tie
 
288
        annotations, lines = self.annotate(key)
 
289
        num_lines = len(lines)
 
290
        out = []
 
291
        heads = self._get_heads_provider().heads
 
292
        for pos from 0 <= pos < num_lines:
 
293
            annotation = annotations[pos]
 
294
            line = lines[pos]
 
295
            if len(annotation) == 1:
 
296
                head = annotation[0]
 
297
            else:
 
298
                the_heads = heads(annotation)
 
299
                if len(the_heads) == 1:
 
300
                    for head in the_heads: break # get the item out of the set
 
301
                else:
 
302
                    # We need to resolve the ambiguity, for now just pick the
 
303
                    # sorted smallest
 
304
                    head = self._resolve_annotation_tie(the_heads, line,
 
305
                                                        custom_tiebreaker)
 
306
            PyList_Append(out, (head, line))
 
307
        return out