2 * Copyright © 2010 Jelmer Vernooij <jelmer@jelmer.uk>
3 * -*- coding: utf-8 -*-
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.
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.
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
23 struct log_entry *next;
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;
34 apr_array_header_t *apr_paths;
35 apr_array_header_t *apr_revprops;
36 RemoteAccessObject *ra;
41 struct log_entry *head;
42 struct log_entry *tail;
45 static void log_iter_dealloc(PyObject *self)
47 LogIteratorObject *iter = (LogIteratorObject *)self;
50 struct log_entry *e = iter->head;
55 Py_XDECREF(iter->exc_type);
56 Py_XDECREF(iter->exc_val);
57 apr_pool_destroy(iter->pool);
62 static PyObject *log_iter_next(LogIteratorObject *iter)
64 struct log_entry *first;
68 while (iter->head == NULL) {
69 /* Done, raise exception */
70 if (iter->exc_type != NULL) {
71 PyErr_SetObject(iter->exc_type, iter->exc_val);
75 Py_BEGIN_ALLOW_THREADS
76 /* FIXME: Don't waste cycles */
81 ret = iter->head->tuple;
82 iter->head = first->next;
83 if (first == iter->tail)
91 static PyObject *py_iter_append(LogIteratorObject *iter, PyObject *tuple)
93 struct log_entry *entry;
95 entry = calloc(sizeof(struct log_entry), 1);
101 entry->tuple = tuple;
102 if (iter->tail == NULL) {
105 iter->tail->next = entry;
108 if (iter->head == NULL)
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 */
122 /* Methods to implement standard operations */
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; */
131 /* Method suites for standard classes */
133 NULL, /* PyNumberMethods *tp_as_number; */
134 NULL, /* PySequenceMethods *tp_as_sequence; */
135 NULL, /* PyMappingMethods *tp_as_mapping; */
137 /* More standard operations (here for binary compatibility) */
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; */
145 /* Functions to access object as input/output buffer */
146 NULL, /* PyBufferProcs *tp_as_buffer; */
148 /* Flags to define presence of optional/expanded features */
149 Py_TPFLAGS_HAVE_ITER, /* long tp_flags; */
151 NULL, /* const char *tp_doc; Documentation string */
153 /* Assigned meaning in release 2.0 */
154 /* call function for all accessible objects */
155 NULL, /* traverseproc tp_traverse; */
157 /* delete references to contained objects */
158 NULL, /* inquiry tp_clear; */
160 /* Assigned meaning in release 2.1 */
161 /* rich comparisons */
162 NULL, /* richcmpfunc tp_richcompare; */
164 /* weak reference enabler */
165 0, /* Py_ssize_t tp_weaklistoffset; */
167 /* Added in release 2.2 */
169 PyObject_SelfIter, /* getiterfunc tp_iter; */
170 (iternextfunc)log_iter_next, /* iternextfunc tp_iternext; */
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)
176 PyObject *revprops, *py_changed_paths, *ret, *tuple;
177 LogIteratorObject *iter = (LogIteratorObject *)baton;
179 PyGILState_STATE state;
181 state = PyGILState_Ensure();
183 #if ONLY_SINCE_SVN(1, 6)
184 py_changed_paths = pyify_changed_paths2(log_entry->changed_paths2, pool);
186 py_changed_paths = pyify_changed_paths(log_entry->changed_paths, true, pool);
188 if (py_changed_paths == NULL) {
189 PyGILState_Release(state);
190 return py_svn_error();
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();
200 tuple = Py_BuildValue("NlNb", py_changed_paths,
201 log_entry->revision, revprops, log_entry->has_children);
204 Py_DECREF(py_changed_paths);
205 PyGILState_Release(state);
206 return py_svn_error();
209 ret = py_iter_append(iter, tuple);
212 PyGILState_Release(state);
213 return py_svn_error();
218 PyGILState_Release(state);
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)
225 PyObject *revprops, *py_changed_paths, *ret, *tuple;
226 LogIteratorObject *iter = (LogIteratorObject *)baton;
228 PyGILState_STATE state;
230 state = PyGILState_Ensure();
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();
237 tuple = Py_BuildValue("NlN", py_changed_paths, revision, revprops);
239 Py_DECREF(py_changed_paths);
241 PyGILState_Release(state);
242 return py_svn_error();
245 ret = py_iter_append(iter, tuple);
249 PyGILState_Release(state);
250 return py_svn_error();
255 PyGILState_Release(state);
262 static void py_iter_log(void *baton)
264 LogIteratorObject *iter = (LogIteratorObject *)baton;
266 PyGILState_STATE state;
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);
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,
280 state = PyGILState_Ensure();
282 iter->exc_type = (PyObject *)PyErr_GetSubversionExceptionTypeObject();
283 iter->exc_val = PyErr_NewSubversionException(error);
284 svn_error_clear(error);
286 iter->exc_type = PyExc_StopIteration;
287 Py_INCREF(iter->exc_type);
288 iter->exc_val = Py_None;
289 Py_INCREF(iter->exc_val);
292 iter->ra->busy = false;
295 PyGILState_Release(state);
298 PyObject *ra_iter_log(PyObject *self, PyObject *args, PyObject *kwargs)
300 char *kwnames[] = { "paths", "start", "end", "limit",
301 "discover_changed_paths", "strict_node_history", "include_merged_revisions", "revprops", NULL };
303 svn_revnum_t start = 0, end = 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;
310 apr_array_header_t *apr_paths;
311 apr_array_header_t *apr_revprops;
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))
319 if (ra_check_busy(ra))
322 pool = Pool(ra->pool);
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);
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);
341 } else if (!PySequence_Check(revprops)) {
342 PyErr_SetString(PyExc_TypeError, "revprops should be a sequence");
343 apr_pool_destroy(pool);
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);
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);
368 if (!string_list_to_apr_array(pool, revprops, &apr_revprops)) {
369 apr_pool_destroy(pool);
373 ret = PyObject_New(LogIteratorObject, &LogIterator_Type);
377 ret->exc_type = NULL;
379 ret->discover_changed_paths = discover_changed_paths;
382 ret->apr_paths = apr_paths;
384 ret->include_merged_revisions = include_merged_revisions;
385 ret->strict_node_history = strict_node_history;
386 ret->apr_revprops = apr_revprops;
393 PyThread_start_new_thread(py_iter_log, ret);
395 return (PyObject *)ret;