Factor common svn_log_message_receiver_t code into pyify_log_message()
[jelmer/subvertpy.git] / subvertpy / _ra_iter_log.c
1 /*
2  * Copyright © 2010 Jelmer Vernooij <jelmer@jelmer.uk>
3  * -*- coding: utf-8 -*-
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation; either version 2.1 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 #include <pythread.h>
20
21 struct log_entry {
22         PyObject *tuple;
23         struct log_entry *next;
24 };
25
26 typedef struct {
27         PyObject_HEAD
28         svn_revnum_t start, end;
29         svn_boolean_t discover_changed_paths;
30         svn_boolean_t strict_node_history;
31         svn_boolean_t include_merged_revisions;
32         int limit;
33         apr_pool_t *pool;
34         apr_array_header_t *apr_paths;
35         apr_array_header_t *apr_revprops;
36         RemoteAccessObject *ra;
37         svn_boolean_t done;
38         PyObject *exc_type;
39         PyObject *exc_val;
40         int queue_size;
41         struct log_entry *head;
42         struct log_entry *tail;
43 } LogIteratorObject;
44
45 static void log_iter_dealloc(PyObject *self)
46 {
47         LogIteratorObject *iter = (LogIteratorObject *)self;
48
49         while (iter->head) {
50                 struct log_entry *e = iter->head;
51                 Py_DECREF(e->tuple);
52                 iter->head = e->next;
53                 free(e);
54         }
55         Py_XDECREF(iter->exc_type);
56         Py_XDECREF(iter->exc_val);
57         apr_pool_destroy(iter->pool);
58         Py_DECREF(iter->ra);
59         PyObject_Del(iter);
60 }
61
62 static PyObject *log_iter_next(LogIteratorObject *iter)
63 {
64         struct log_entry *first;
65         PyObject *ret;
66         Py_INCREF(iter);
67
68         while (iter->head == NULL) {
69                 /* Done, raise exception */
70                 if (iter->exc_type != NULL) {
71                         PyErr_SetObject(iter->exc_type, iter->exc_val);
72                         Py_DECREF(iter);
73                         return NULL;
74                 } else {
75                         Py_BEGIN_ALLOW_THREADS
76                         /* FIXME: Don't waste cycles */
77                         Py_END_ALLOW_THREADS
78                 }
79         }
80         first = iter->head;
81         ret = iter->head->tuple;
82         iter->head = first->next;
83         if (first == iter->tail)
84                 iter->tail = NULL;
85         free(first);
86         iter->queue_size--;
87         Py_DECREF(iter);
88         return ret;
89 }
90
91 static PyObject *py_iter_append(LogIteratorObject *iter, PyObject *tuple)
92 {
93         struct log_entry *entry;
94
95         entry = calloc(sizeof(struct log_entry), 1);
96         if (entry == NULL) {
97                 PyErr_NoMemory();
98                 return NULL;
99         }
100
101         entry->tuple = tuple;
102         if (iter->tail == NULL) {
103                 iter->tail = entry;
104         } else {
105                 iter->tail->next = entry;
106                 iter->tail = entry;
107         }
108         if (iter->head == NULL)
109                 iter->head = entry;
110
111         iter->queue_size++;
112
113         Py_RETURN_NONE;
114 }
115
116 PyTypeObject LogIterator_Type = {
117         PyObject_HEAD_INIT(NULL) 0,
118         "_ra.LogIterator", /*   const char *tp_name;  For printing, in format "<module>.<name>" */
119         sizeof(LogIteratorObject), 
120         0,/*    Py_ssize_t tp_basicsize, tp_itemsize;  For allocation */
121         
122         /* Methods to implement standard operations */
123         
124         (destructor)log_iter_dealloc, /*        destructor tp_dealloc;  */
125         NULL, /*        printfunc tp_print;     */
126         NULL, /*        getattrfunc tp_getattr; */
127         NULL, /*        setattrfunc tp_setattr; */
128         NULL, /*        cmpfunc tp_compare;     */
129         NULL, /*        reprfunc tp_repr;       */
130         
131         /* Method suites for standard classes */
132         
133         NULL, /*        PyNumberMethods *tp_as_number;  */
134         NULL, /*        PySequenceMethods *tp_as_sequence;      */
135         NULL, /*        PyMappingMethods *tp_as_mapping;        */
136         
137         /* More standard operations (here for binary compatibility) */
138         
139         NULL, /*        hashfunc tp_hash;       */
140         NULL, /*        ternaryfunc tp_call;    */
141         NULL, /*        reprfunc tp_str;        */
142         NULL, /*        getattrofunc tp_getattro;       */
143         NULL, /*        setattrofunc tp_setattro;       */
144         
145         /* Functions to access object as input/output buffer */
146         NULL, /*        PyBufferProcs *tp_as_buffer;    */
147         
148         /* Flags to define presence of optional/expanded features */
149         Py_TPFLAGS_HAVE_ITER, /*        long tp_flags;  */
150         
151         NULL, /*        const char *tp_doc;  Documentation string */
152         
153         /* Assigned meaning in release 2.0 */
154         /* call function for all accessible objects */
155         NULL, /*        traverseproc tp_traverse;       */
156         
157         /* delete references to contained objects */
158         NULL, /*        inquiry tp_clear;       */
159         
160         /* Assigned meaning in release 2.1 */
161         /* rich comparisons */
162         NULL, /*        richcmpfunc tp_richcompare;     */
163         
164         /* weak reference enabler */
165         0, /*   Py_ssize_t tp_weaklistoffset;   */
166         
167         /* Added in release 2.2 */
168         /* Iterators */
169         PyObject_SelfIter, /*   getiterfunc tp_iter;    */
170         (iternextfunc)log_iter_next, /* iternextfunc tp_iternext;       */
171 };
172
173 #if ONLY_SINCE_SVN(1, 5)
174 static svn_error_t *py_iter_log_entry_cb(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
175 {
176         PyObject *revprops, *py_changed_paths, *ret, *tuple;
177         LogIteratorObject *iter = (LogIteratorObject *)baton;
178
179         PyGILState_STATE state;
180
181         state = PyGILState_Ensure();
182
183 #if ONLY_SINCE_SVN(1, 6)
184         py_changed_paths = pyify_changed_paths2(log_entry->changed_paths2, pool);
185 #else
186         py_changed_paths = pyify_changed_paths(log_entry->changed_paths, true, pool);
187 #endif
188         if (py_changed_paths == NULL) {
189                 PyGILState_Release(state);
190                 return py_svn_error();
191         }
192
193         revprops = prop_hash_to_dict(log_entry->revprops);
194         if (revprops == NULL) {
195                 Py_DECREF(py_changed_paths);
196                 PyGILState_Release(state);
197                 return py_svn_error();
198         }
199
200         tuple = Py_BuildValue("NlNb", py_changed_paths,
201                                                 log_entry->revision, revprops, log_entry->has_children);
202         if (tuple == NULL) {
203                 Py_DECREF(revprops);
204                 Py_DECREF(py_changed_paths);
205                 PyGILState_Release(state);
206                 return py_svn_error();
207         }
208
209         ret = py_iter_append(iter, tuple);
210         if (ret == NULL) {
211                 Py_DECREF(tuple);
212                 PyGILState_Release(state);
213                 return py_svn_error();
214         }
215
216         Py_DECREF(ret);
217
218         PyGILState_Release(state);
219
220         return NULL;
221 }
222 #else
223 static svn_error_t *py_iter_log_cb(void *baton, apr_hash_t *changed_paths, svn_revnum_t revision, const char *author, const char *date, const char *message, apr_pool_t *pool)
224 {
225         PyObject *revprops, *py_changed_paths, *ret, *tuple;
226         LogIteratorObject *iter = (LogIteratorObject *)baton;
227
228         PyGILState_STATE state;
229
230         state = PyGILState_Ensure();
231
232         if (!pyify_log_message(changed_paths, author, date, message, true,
233         pool, &py_changed_paths, &revprops)) {
234                 PyGILState_Release(state);
235                 return py_svn_error();
236         }
237         tuple = Py_BuildValue("NlN", py_changed_paths, revision, revprops);
238         if (tuple == NULL) {
239                 Py_DECREF(py_changed_paths);
240                 Py_DECREF(revprops);
241                 PyGILState_Release(state);
242                 return py_svn_error();
243         }
244
245         ret = py_iter_append(iter, tuple);
246
247         if (ret == NULL) {
248                 Py_DECREF(tuple);
249                 PyGILState_Release(state);
250                 return py_svn_error();
251         }
252
253         Py_DECREF(ret);
254
255         PyGILState_Release(state);
256
257         return NULL;
258 }
259 #endif
260
261
262 static void py_iter_log(void *baton)
263 {
264         LogIteratorObject *iter = (LogIteratorObject *)baton;
265         svn_error_t *error;
266         PyGILState_STATE state;
267
268 #if ONLY_SINCE_SVN(1, 5)
269         error = svn_ra_get_log2(iter->ra->ra, 
270                         iter->apr_paths, iter->start, iter->end, iter->limit,
271                         iter->discover_changed_paths, iter->strict_node_history, 
272                         iter->include_merged_revisions, iter->apr_revprops,
273                         py_iter_log_entry_cb, iter, iter->pool);
274 #else
275         error = svn_ra_get_log(iter->ra->ra, 
276                         iter->apr_paths, iter->start, iter->end, iter->limit,
277                         iter->discover_changed_paths, iter->strict_node_history, py_iter_log_cb, 
278                         iter, iter->pool);
279 #endif
280         state = PyGILState_Ensure();
281         if (error != NULL) {
282                 iter->exc_type = (PyObject *)PyErr_GetSubversionExceptionTypeObject();
283                 iter->exc_val  = PyErr_NewSubversionException(error);
284                 svn_error_clear(error);
285         } else {
286                 iter->exc_type = PyExc_StopIteration;
287                 Py_INCREF(iter->exc_type);
288                 iter->exc_val = Py_None;
289                 Py_INCREF(iter->exc_val);
290         }
291         iter->done = TRUE;
292         iter->ra->busy = false;
293
294         Py_DECREF(iter);
295         PyGILState_Release(state);
296 }
297
298 PyObject *ra_iter_log(PyObject *self, PyObject *args, PyObject *kwargs)
299 {
300         char *kwnames[] = { "paths", "start", "end", "limit",
301                 "discover_changed_paths", "strict_node_history", "include_merged_revisions", "revprops", NULL };
302         PyObject *paths;
303         svn_revnum_t start = 0, end = 0;
304         int limit=0; 
305         bool discover_changed_paths=false, strict_node_history=true, include_merged_revisions=false;
306         RemoteAccessObject *ra = (RemoteAccessObject *)self;
307         PyObject *revprops = Py_None;
308         LogIteratorObject *ret;
309         apr_pool_t *pool;
310         apr_array_header_t *apr_paths;
311         apr_array_header_t *apr_revprops;
312
313         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oll|ibbbO:iter_log", kwnames, 
314                                                  &paths, &start, &end, &limit,
315                                                  &discover_changed_paths, &strict_node_history,
316                                                  &include_merged_revisions, &revprops))
317                 return NULL;
318
319         if (ra_check_busy(ra))
320                 return NULL;
321
322         pool = Pool(ra->pool);
323         if (pool == NULL)
324                 return NULL;
325         if (paths == Py_None) {
326                 /* The subversion libraries don't behave as expected, 
327                  * so tweak our own parameters a bit. */
328                 apr_paths = apr_array_make(pool, 1, sizeof(char *));
329                 APR_ARRAY_PUSH(apr_paths, char *) = apr_pstrdup(pool, "");
330         } else if (!path_list_to_apr_array(pool, paths, &apr_paths)) {
331                 apr_pool_destroy(pool);
332                 return NULL;
333         }
334
335 #if ONLY_BEFORE_SVN(1, 5)
336         if (revprops == Py_None) {
337                 PyErr_SetString(PyExc_NotImplementedError,
338                 "fetching all revision properties not supported");
339                 apr_pool_destroy(pool);
340                 return NULL;
341         } else if (!PySequence_Check(revprops)) {
342                 PyErr_SetString(PyExc_TypeError, "revprops should be a sequence");
343                 apr_pool_destroy(pool);
344                 return NULL;
345         } else {
346                 int i;
347                 for (i = 0; i < PySequence_Size(revprops); i++) {
348                         const char *n = PyString_AsString(PySequence_GetItem(revprops, i));
349                         if (strcmp(SVN_PROP_REVISION_LOG, n) &&
350                                 strcmp(SVN_PROP_REVISION_AUTHOR, n) &&
351                                 strcmp(SVN_PROP_REVISION_DATE, n)) {
352                                 PyErr_SetString(PyExc_NotImplementedError,
353                                                                 "fetching custom revision properties not supported");
354                                 apr_pool_destroy(pool);
355                                 return NULL;
356                         }
357                 }
358         }
359
360         if (include_merged_revisions) {
361                 PyErr_SetString(PyExc_NotImplementedError, 
362                         "include_merged_revisions not supported in Subversion 1.4");
363                 apr_pool_destroy(pool);
364                 return NULL;
365         }
366 #endif
367
368         if (!string_list_to_apr_array(pool, revprops, &apr_revprops)) {
369                 apr_pool_destroy(pool);
370                 return NULL;
371         }
372
373         ret = PyObject_New(LogIteratorObject, &LogIterator_Type);
374         ret->ra = ra;
375         Py_INCREF(ret->ra);
376         ret->start = start;
377         ret->exc_type = NULL;
378         ret->exc_val = NULL;
379         ret->discover_changed_paths = discover_changed_paths;
380         ret->end = end;
381         ret->limit = limit;
382         ret->apr_paths = apr_paths;
383         ret->pool = pool;
384         ret->include_merged_revisions = include_merged_revisions;
385         ret->strict_node_history = strict_node_history;
386         ret->apr_revprops = apr_revprops;
387         ret->done = FALSE;
388         ret->queue_size = 0;
389         ret->head = NULL;
390         ret->tail = NULL;
391
392         Py_INCREF(ret);
393         PyThread_start_new_thread(py_iter_log, ret);
394
395         return (PyObject *)ret;
396 }
397
398