430cad1094d850c6a187c254e60e8b78cbc93e25
[jelmer/subvertpy.git] / subvertpy / repos.c
1 /*
2  * Copyright © 2008 Jelmer Vernooij <jelmer@samba.org>
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 #include <stdbool.h>
20 #include <Python.h>
21 #include <apr_general.h>
22 #include <svn_fs.h>
23 #include <svn_path.h>
24 #include <svn_repos.h>
25 #include <apr_md5.h>
26
27 #include "util.h"
28
29 extern PyTypeObject FileSystemRoot_Type;
30 extern PyTypeObject Repository_Type;
31 extern PyTypeObject FileSystem_Type;
32 extern PyTypeObject Stream_Type;
33
34 typedef struct { 
35         PyObject_HEAD
36     apr_pool_t *pool;
37     svn_repos_t *repos;
38 } RepositoryObject;
39
40 static PyObject *repos_create(PyObject *self, PyObject *args)
41 {
42         char *path;
43         PyObject *config=Py_None, *fs_config=Py_None;
44         svn_repos_t *repos = NULL;
45         apr_pool_t *pool;
46         apr_hash_t *hash_config, *hash_fs_config;
47         RepositoryObject *ret;
48
49         if (!PyArg_ParseTuple(args, "s|OO:create", &path, &config, &fs_config))
50                 return NULL;
51
52     pool = Pool(NULL);
53         if (pool == NULL)
54                 return NULL;
55     hash_config = config_hash_from_object(config, pool);
56         if (hash_config == NULL)
57                 return NULL;
58     hash_fs_config = apr_hash_make(pool); /* FIXME */
59         if (hash_fs_config == NULL) {
60                 PyErr_SetString(PyExc_RuntimeError, "Unable to create fs config hash");
61                 return NULL;
62         }
63     RUN_SVN_WITH_POOL(pool, svn_repos_create(&repos,
64                 svn_path_canonicalize(path, pool), NULL, NULL,
65                 hash_config, hash_fs_config, pool));
66
67         ret = PyObject_New(RepositoryObject, &Repository_Type);
68         if (ret == NULL)
69                 return NULL;
70
71         ret->pool = pool;
72         ret->repos = repos;
73
74     return (PyObject *)ret;
75 }
76
77 static void repos_dealloc(PyObject *self)
78 {
79         RepositoryObject *repos = (RepositoryObject *)self;
80
81         apr_pool_destroy(repos->pool);
82         PyObject_Del(repos);
83 }
84
85 static PyObject *repos_init(PyTypeObject *type, PyObject *args, PyObject *kwargs)
86 {
87         char *path;
88         char *kwnames[] = { "path", NULL };
89         svn_error_t *err;
90         RepositoryObject *ret;
91
92         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwnames, &path))
93                 return NULL;
94
95         ret = PyObject_New(RepositoryObject, &Repository_Type);
96         if (ret == NULL)
97                 return NULL;
98
99         ret->pool = Pool(NULL);
100         if (ret->pool == NULL)
101                 return NULL;
102         Py_BEGIN_ALLOW_THREADS
103         err = svn_repos_open(&ret->repos, svn_path_canonicalize(path, ret->pool),
104                             ret->pool);
105         Py_END_ALLOW_THREADS
106
107         if (err != NULL) {
108                 handle_svn_error(err);
109                 svn_error_clear(err);
110                 Py_DECREF(ret);
111                 return NULL;
112         }
113
114         return (PyObject *)ret;
115 }
116
117 typedef struct {
118         PyObject_HEAD
119         apr_pool_t *pool;
120         svn_fs_root_t *root;
121 } FileSystemRootObject;
122
123 typedef struct {
124         PyObject_HEAD
125         RepositoryObject *repos;
126         apr_pool_t *pool;
127         svn_fs_t *fs;
128 } FileSystemObject;
129
130 static PyObject *repos_fs(PyObject *self)
131 {
132         RepositoryObject *reposobj = (RepositoryObject *)self;
133         FileSystemObject *ret;
134         svn_fs_t *fs;
135
136         fs = svn_repos_fs(reposobj->repos);
137
138         if (fs == NULL) {
139                 PyErr_SetString(PyExc_RuntimeError, "Unable to obtain fs handle");
140                 return NULL;
141         }
142
143         ret = PyObject_New(FileSystemObject, &FileSystem_Type);
144         if (ret == NULL)
145                 return NULL;
146
147         ret->fs = fs;
148         ret->repos = reposobj;
149         ret->pool = reposobj->pool;
150         Py_INCREF(reposobj);
151
152         return (PyObject *)ret;
153 }
154
155 static PyObject *fs_get_uuid(PyObject *self)
156 {
157         FileSystemObject *fsobj = (FileSystemObject *)self;
158         const char *uuid;
159         PyObject *ret;
160         apr_pool_t *temp_pool;
161
162         temp_pool = Pool(NULL);
163         if (temp_pool == NULL)
164                 return NULL;
165         RUN_SVN_WITH_POOL(temp_pool, svn_fs_get_uuid(fsobj->fs, &uuid, temp_pool));
166         ret = PyString_FromString(uuid);
167         apr_pool_destroy(temp_pool);
168
169         return ret;
170 }
171
172 static PyObject *fs_get_youngest_revision(FileSystemObject *self)
173 {
174         svn_revnum_t rev;
175         apr_pool_t *temp_pool;
176         PyObject *ret;
177
178         temp_pool = Pool(NULL);
179         if (temp_pool == NULL)
180                 return NULL;
181         RUN_SVN_WITH_POOL(temp_pool, svn_fs_youngest_rev(&rev, self->fs, temp_pool));
182         ret = PyInt_FromLong(rev);
183         apr_pool_destroy(temp_pool);
184
185         return ret;
186 }
187
188 static PyObject *fs_get_revision_root(FileSystemObject *self, PyObject *args)
189 {
190         svn_revnum_t rev;
191         FileSystemRootObject *ret;
192         apr_pool_t *pool;
193         svn_fs_root_t *root;
194
195         if (!PyArg_ParseTuple(args, "l", &rev))
196                 return NULL;
197
198         pool = Pool(NULL);
199         if (pool == NULL)
200                 return NULL;
201
202         RUN_SVN_WITH_POOL(pool, svn_fs_revision_root(&root, self->fs, rev, pool));
203
204         ret = PyObject_New(FileSystemRootObject, &FileSystemRoot_Type);
205         if (ret == NULL)
206                 return NULL;
207
208         ret->root = root;
209         ret->pool = pool;
210
211         return (PyObject *)ret;
212 }
213
214 static PyObject *fs_get_revision_proplist(FileSystemObject *self, PyObject *args)
215 {
216         svn_revnum_t rev;
217         PyObject *ret;
218         apr_pool_t *temp_pool;
219         apr_hash_t *props;
220
221         if (!PyArg_ParseTuple(args, "l", &rev))
222                 return NULL;
223
224         temp_pool = Pool(NULL);
225         if (temp_pool == NULL)
226                 return NULL;
227
228         RUN_SVN_WITH_POOL(temp_pool, svn_fs_revision_proplist(&props, self->fs, rev, temp_pool));
229
230         ret = prop_hash_to_dict(props);
231
232         apr_pool_destroy(temp_pool);
233
234         return (PyObject *)ret;
235 }
236
237 static PyMethodDef fs_methods[] = {
238         { "get_uuid", (PyCFunction)fs_get_uuid, METH_NOARGS, NULL },
239         { "youngest_revision", (PyCFunction)fs_get_youngest_revision, METH_NOARGS, NULL },
240         { "revision_root", (PyCFunction)fs_get_revision_root, METH_VARARGS, NULL },
241         { "revision_proplist", (PyCFunction)fs_get_revision_proplist, METH_VARARGS, NULL },
242         { NULL, }
243 };
244
245 static void fs_dealloc(PyObject *self)
246 {
247         FileSystemObject *fsobj = (FileSystemObject *)self;
248         Py_DECREF(fsobj->repos);
249         apr_pool_destroy(fsobj->pool);
250         PyObject_DEL(fsobj);
251 }
252
253 PyTypeObject FileSystem_Type = {
254         PyObject_HEAD_INIT(NULL) 0,
255         "repos.FileSystem", /*  const char *tp_name;  For printing, in format "<module>.<name>" */
256         sizeof(FileSystemObject), 
257         0,/*    Py_ssize_t tp_basicsize, tp_itemsize;  For allocation */
258         
259         /* Methods to implement standard operations */
260         
261         fs_dealloc, /*  destructor tp_dealloc;  */
262         NULL, /*        printfunc tp_print;     */
263         NULL, /*        getattrfunc tp_getattr; */
264         NULL, /*        setattrfunc tp_setattr; */
265         NULL, /*        cmpfunc tp_compare;     */
266         NULL, /*        reprfunc tp_repr;       */
267         
268         /* Method suites for standard classes */
269         
270         NULL, /*        PyNumberMethods *tp_as_number;  */
271         NULL, /*        PySequenceMethods *tp_as_sequence;      */
272         NULL, /*        PyMappingMethods *tp_as_mapping;        */
273         
274         /* More standard operations (here for binary compatibility) */
275         
276         NULL, /*        hashfunc tp_hash;       */
277         NULL, /*        ternaryfunc tp_call;    */
278         NULL, /*        reprfunc tp_str;        */
279         NULL, /*        getattrofunc tp_getattro;       */
280         NULL, /*        setattrofunc tp_setattro;       */
281         
282         /* Functions to access object as input/output buffer */
283         NULL, /*        PyBufferProcs *tp_as_buffer;    */
284         
285         /* Flags to define presence of optional/expanded features */
286         0, /*   long tp_flags;  */
287         
288         NULL, /*        const char *tp_doc;  Documentation string */
289         
290         /* Assigned meaning in release 2.0 */
291         /* call function for all accessible objects */
292         NULL, /*        traverseproc tp_traverse;       */
293         
294         /* delete references to contained objects */
295         NULL, /*        inquiry tp_clear;       */
296         
297         /* Assigned meaning in release 2.1 */
298         /* rich comparisons */
299         NULL, /*        richcmpfunc tp_richcompare;     */
300         
301         /* weak reference enabler */
302         0, /*   Py_ssize_t tp_weaklistoffset;   */
303         
304         /* Added in release 2.2 */
305         /* Iterators */
306         NULL, /*        getiterfunc tp_iter;    */
307         NULL, /*        iternextfunc tp_iternext;       */
308         
309         /* Attribute descriptor and subclassing stuff */
310         fs_methods, /*  struct PyMethodDef *tp_methods; */
311
312 };
313
314 static PyObject *repos_load_fs(PyObject *self, PyObject *args, PyObject *kwargs)
315 {
316         const char *parent_dir = NULL;
317         PyObject *dumpstream, *feedback_stream;
318         unsigned char use_pre_commit_hook = 0, use_post_commit_hook = 0;
319         char *kwnames[] = { "dumpstream", "feedback_stream", "uuid_action",
320                                 "parent_dir", "use_pre_commit_hook", 
321                                                 "use_post_commit_hook", NULL };
322         int uuid_action;
323         apr_pool_t *temp_pool;
324         RepositoryObject *reposobj = (RepositoryObject *)self;
325
326         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOi|zbb", kwnames,
327                                                                 &dumpstream, &feedback_stream, &uuid_action,
328                                                                 &parent_dir, &use_pre_commit_hook,
329                                                                 &use_post_commit_hook))
330                 return NULL;
331
332         if (uuid_action != svn_repos_load_uuid_default &&
333                 uuid_action != svn_repos_load_uuid_ignore &&
334                 uuid_action != svn_repos_load_uuid_force) {
335                 PyErr_SetString(PyExc_RuntimeError, "Invalid UUID action");
336                 return NULL;
337         }
338
339         temp_pool = Pool(NULL);
340         if (temp_pool == NULL)
341                 return NULL;
342         RUN_SVN_WITH_POOL(temp_pool, svn_repos_load_fs2(reposobj->repos, 
343                                 new_py_stream(temp_pool, dumpstream), 
344                                 new_py_stream(temp_pool, feedback_stream),
345                                 uuid_action, parent_dir, use_pre_commit_hook, 
346                                 use_post_commit_hook, py_cancel_check, NULL,
347                                 temp_pool));
348         apr_pool_destroy(temp_pool);
349         Py_RETURN_NONE;
350 }
351
352 static PyObject *repos_delete(PyObject *self, PyObject *args)
353 {
354         char *path;
355         apr_pool_t *temp_pool;
356
357         if (!PyArg_ParseTuple(args, "s", &path))
358                 return NULL;
359
360         temp_pool = Pool(NULL);
361         if (temp_pool == NULL)
362                 return NULL;
363         RUN_SVN_WITH_POOL(temp_pool, svn_repos_delete(path, temp_pool));
364
365         apr_pool_destroy(temp_pool);
366
367         Py_RETURN_NONE;
368 }
369
370 static PyObject *repos_hotcopy(RepositoryObject *self, PyObject *args)
371 {
372         char *src_path, *dest_path;
373         svn_boolean_t clean_logs = FALSE;
374         apr_pool_t *temp_pool;
375
376         if (!PyArg_ParseTuple(args, "ss|b", &src_path, &dest_path, &clean_logs))
377                 return NULL;
378
379         temp_pool = Pool(NULL);
380         if (temp_pool == NULL)
381                 return NULL;
382
383         RUN_SVN_WITH_POOL(temp_pool,
384                 svn_repos_hotcopy(src_path, dest_path, clean_logs, temp_pool));
385
386         apr_pool_destroy(temp_pool);
387
388         Py_RETURN_NONE;
389 }
390
391 /**
392  * Get runtime libsvn_wc version information.
393  *
394  * :return: tuple with major, minor, patch version number and tag.
395  */
396 static PyObject *version(PyObject *self)
397 {
398         const svn_version_t *ver = svn_repos_version();
399         return Py_BuildValue("(iiis)", ver->major, ver->minor, 
400                                                  ver->patch, ver->tag);
401 }
402
403 SVN_VERSION_DEFINE(svn_api_version);
404
405 /**
406  * Get compile-time libsvn_wc version information.
407  *
408  * :return: tuple with major, minor, patch version number and tag.
409  */
410 static PyObject *api_version(PyObject *self)
411 {
412         const svn_version_t *ver = &svn_api_version;
413         return Py_BuildValue("(iiis)", ver->major, ver->minor, 
414                                                  ver->patch, ver->tag);
415 }
416
417 static PyMethodDef repos_module_methods[] = {
418         { "create", (PyCFunction)repos_create, METH_VARARGS, 
419                 "create(path, config=None, fs_config=None)\n\n"
420                 "Create a new repository." },
421         { "delete", (PyCFunction)repos_delete, METH_VARARGS, 
422                 "delete(path)\n\n"
423                 "Delete a repository." },
424         { "hotcopy", (PyCFunction)repos_hotcopy, METH_VARARGS, 
425                 "hotcopy(src_path, dest_path, clean_logs=False)\n\n"
426                 "Make a hot copy of a repository." },
427         { "api_version", (PyCFunction)api_version, METH_NOARGS,
428                 "api_version() -> (major, minor, patch, tag)\n\n"
429                 "Version of libsvn_client Subvertpy was compiled against."
430         },
431         { "version", (PyCFunction)version, METH_NOARGS,
432                 "version() -> (major, minor, patch, tag)\n\n"
433                 "Version of libsvn_wc currently used."
434         },
435
436         { NULL, }
437 };
438
439 static PyObject *repos_has_capability(RepositoryObject *self, PyObject *args)
440 {
441 #if ONLY_SINCE_SVN(1, 5)
442         char *name;
443         svn_boolean_t has;
444         apr_pool_t *temp_pool;
445         if (!PyArg_ParseTuple(args, "s", &name))
446                 return NULL;
447         temp_pool = Pool(NULL);
448         if (temp_pool == NULL)
449                 return NULL;
450         RUN_SVN_WITH_POOL(temp_pool,
451                 svn_repos_has_capability(self->repos, &has, name, temp_pool));
452         apr_pool_destroy(temp_pool);
453         return PyBool_FromLong(has);
454 #else
455         PyErr_SetString(PyExc_NotImplementedError, "has_capability is only supported in Subversion >= 1.5");
456         return NULL;
457 #endif
458 }
459
460 static PyObject *repos_verify(RepositoryObject *self, PyObject *args)
461 {
462         apr_pool_t *temp_pool;
463         PyObject *py_feedback_stream;
464         long start_rev, end_rev;
465         if (!PyArg_ParseTuple(args, "Oii", &py_feedback_stream, &start_rev, &end_rev))
466                 return NULL;
467         temp_pool = Pool(NULL);
468         if (temp_pool == NULL)
469                 return NULL;
470         RUN_SVN_WITH_POOL(temp_pool,
471                 svn_repos_verify_fs(self->repos,
472                         new_py_stream(temp_pool, py_feedback_stream), start_rev, end_rev,
473                         py_cancel_check, NULL, temp_pool));
474         apr_pool_destroy(temp_pool);
475
476         Py_RETURN_NONE;
477 }
478
479 static svn_error_t *py_pack_notify(void *baton, apr_int64_t shard, svn_fs_pack_notify_action_t action, apr_pool_t *pool)
480 {
481         PyObject *ret;
482         if (baton == Py_None)
483                 return NULL;
484         ret = PyObject_CallFunction((PyObject *)baton, "li", shard, action);
485         if (ret == NULL)
486                 return py_svn_error();
487         Py_DECREF(ret);
488         return NULL;
489 }
490
491 static PyObject *repos_pack(RepositoryObject *self, PyObject *args)
492 {
493         apr_pool_t *temp_pool;
494         PyObject *notify_func = Py_None;
495         if (!PyArg_ParseTuple(args, "|O", &notify_func))
496                 return NULL;
497         temp_pool = Pool(NULL);
498         if (temp_pool == NULL)
499                 return NULL;
500         RUN_SVN_WITH_POOL(temp_pool,
501                 svn_repos_fs_pack(self->repos, py_pack_notify, notify_func,
502                         py_cancel_check, NULL, temp_pool));
503         apr_pool_destroy(temp_pool);
504
505         Py_RETURN_NONE;
506 }
507
508 static PyMethodDef repos_methods[] = {
509         { "load_fs", (PyCFunction)repos_load_fs, METH_VARARGS|METH_KEYWORDS, NULL },
510         { "fs", (PyCFunction)repos_fs, METH_NOARGS, NULL },
511         { "has_capability", (PyCFunction)repos_has_capability, METH_VARARGS, NULL },
512         { "verify_fs", (PyCFunction)repos_verify, METH_VARARGS,
513                 "S.verify_repos(feedback_stream, start_revnum, end_revnum)" },
514         { "pack_fs", (PyCFunction)repos_pack, METH_VARARGS, NULL },
515         { NULL, }
516 };
517
518 PyTypeObject Repository_Type = {
519         PyObject_HEAD_INIT(NULL) 0,
520         "repos.Repository", /*  const char *tp_name;  For printing, in format "<module>.<name>" */
521         sizeof(RepositoryObject), 
522         0,/*    Py_ssize_t tp_basicsize, tp_itemsize;  For allocation */
523         
524         /* Methods to implement standard operations */
525         
526         repos_dealloc, /*       destructor tp_dealloc;  */
527         NULL, /*        printfunc tp_print;     */
528         NULL, /*        getattrfunc tp_getattr; */
529         NULL, /*        setattrfunc tp_setattr; */
530         NULL, /*        cmpfunc tp_compare;     */
531         NULL, /*        reprfunc tp_repr;       */
532         
533         /* Method suites for standard classes */
534         
535         NULL, /*        PyNumberMethods *tp_as_number;  */
536         NULL, /*        PySequenceMethods *tp_as_sequence;      */
537         NULL, /*        PyMappingMethods *tp_as_mapping;        */
538         
539         /* More standard operations (here for binary compatibility) */
540         
541         NULL, /*        hashfunc tp_hash;       */
542         NULL, /*        ternaryfunc tp_call;    */
543         NULL, /*        reprfunc tp_str;        */
544         NULL, /*        getattrofunc tp_getattro;       */
545         NULL, /*        setattrofunc tp_setattro;       */
546         
547         /* Functions to access object as input/output buffer */
548         NULL, /*        PyBufferProcs *tp_as_buffer;    */
549         
550         /* Flags to define presence of optional/expanded features */
551         0, /*   long tp_flags;  */
552         
553         "Local repository", /*  const char *tp_doc;  Documentation string */
554         
555         /* Assigned meaning in release 2.0 */
556         /* call function for all accessible objects */
557         NULL, /*        traverseproc tp_traverse;       */
558         
559         /* delete references to contained objects */
560         NULL, /*        inquiry tp_clear;       */
561         
562         /* Assigned meaning in release 2.1 */
563         /* rich comparisons */
564         NULL, /*        richcmpfunc tp_richcompare;     */
565         
566         /* weak reference enabler */
567         0, /*   Py_ssize_t tp_weaklistoffset;   */
568         
569         /* Added in release 2.2 */
570         /* Iterators */
571         NULL, /*        getiterfunc tp_iter;    */
572         NULL, /*        iternextfunc tp_iternext;       */
573         
574         /* Attribute descriptor and subclassing stuff */
575         repos_methods, /*       struct PyMethodDef *tp_methods; */
576         NULL, /*        struct PyMemberDef *tp_members; */
577         NULL, /*        struct PyGetSetDef *tp_getset;  */
578         NULL, /*        struct _typeobject *tp_base;    */
579         NULL, /*        PyObject *tp_dict;      */
580         NULL, /*        descrgetfunc tp_descr_get;      */
581         NULL, /*        descrsetfunc tp_descr_set;      */
582         0, /*   Py_ssize_t tp_dictoffset;       */
583         NULL, /*        initproc tp_init;       */
584         NULL, /*        allocfunc tp_alloc;     */
585         repos_init, /*  newfunc tp_new; */
586
587 };
588
589 static void fs_root_dealloc(PyObject *self)
590 {
591         FileSystemRootObject *fsobj = (FileSystemRootObject *)self;
592
593         apr_pool_destroy(fsobj->pool);
594         PyObject_DEL(fsobj);
595 }
596
597 static PyObject *py_string_from_svn_node_id(const svn_fs_id_t *id)
598 {
599         apr_pool_t *temp_pool;
600         svn_string_t *str;
601         temp_pool = Pool(NULL);
602         if (temp_pool == NULL)
603                 return NULL;
604         str = svn_fs_unparse_id(id, temp_pool);
605         if (str == NULL) {
606                 apr_pool_destroy(temp_pool);
607                 return NULL;
608         }
609         return PyString_FromStringAndSize(str->data, str->len);
610 }
611
612 #if ONLY_BEFORE_SVN(1, 6)
613 static PyObject *py_fs_path_change(svn_fs_path_change_t *val)
614 {
615         PyObject *ret, *py_node_id;
616
617         py_node_id = py_string_from_svn_node_id(val->node_rev_id);
618         if (py_node_id == NULL) {
619                 return NULL;
620         }
621         ret = Py_BuildValue("(Oibb)", py_node_id,
622                                                    val->change_kind, val->text_mod, val->prop_mod);
623         Py_DECREF(py_node_id);
624         if (ret == NULL) {
625                 return NULL;
626         }
627
628         return ret;
629 }
630
631 #else
632
633 static PyObject *py_fs_path_change2(svn_fs_path_change2_t *val)
634 {
635         PyObject *ret, *py_node_id;
636
637         py_node_id = py_string_from_svn_node_id(val->node_rev_id);
638         if (py_node_id == NULL) {
639                 return NULL;
640         }
641         ret = Py_BuildValue("(Oibb)", py_node_id,
642                                                    val->change_kind, val->text_mod, val->prop_mod);
643         Py_DECREF(py_node_id);
644         if (ret == NULL) {
645                 return NULL;
646         }
647
648         /* FIXME: copyfrom information */
649
650         return ret;
651 }
652 #endif
653
654 static PyObject *fs_root_paths_changed(FileSystemRootObject *self)
655 {
656         apr_pool_t *temp_pool;
657         apr_hash_t *changed_paths;
658         const char *key;
659         apr_ssize_t klen;
660         apr_hash_index_t *idx;
661         PyObject *ret;
662         temp_pool = Pool(NULL);
663         if (temp_pool == NULL)
664                 return NULL;
665 #if ONLY_SINCE_SVN(1, 6)
666         RUN_SVN_WITH_POOL(temp_pool, 
667                                           svn_fs_paths_changed2(&changed_paths, self->root, temp_pool));
668 #else
669         RUN_SVN_WITH_POOL(temp_pool, 
670                                           svn_fs_paths_changed(&changed_paths, self->root, temp_pool));
671 #endif
672         ret = PyDict_New();
673         for (idx = apr_hash_first(temp_pool, changed_paths); idx != NULL;
674                  idx = apr_hash_next(idx)) {
675                 PyObject *py_val;
676 #if ONLY_SINCE_SVN(1, 6)
677                 svn_fs_path_change2_t *val;
678 #else
679                 svn_fs_path_change_t *val;
680 #endif
681                 apr_hash_this(idx, (const void **)&key, &klen, (void **)&val);
682 #if ONLY_SINCE_SVN(1, 6)
683                 py_val = py_fs_path_change2(val);
684 #else
685                 py_val = py_fs_path_change(val);
686 #endif
687                 if (py_val == NULL) {
688                         apr_pool_destroy(temp_pool);
689                         PyObject_Del(ret);
690                         return NULL;
691                 }
692                 PyDict_SetItemString(ret, key, py_val);
693                 Py_DECREF(py_val);
694         }
695         apr_pool_destroy(temp_pool);
696         return ret;
697 }
698
699 static PyObject *fs_root_is_dir(FileSystemRootObject *self, PyObject *args)
700 {
701         svn_boolean_t is_dir;
702         apr_pool_t *temp_pool;
703         char *path;
704
705         if (!PyArg_ParseTuple(args, "s", &path))
706                 return NULL;
707
708         temp_pool = Pool(NULL);
709         if (temp_pool == NULL)
710                 return NULL;
711         RUN_SVN_WITH_POOL(temp_pool, svn_fs_is_dir(&is_dir, self->root, 
712                                                                                            path, temp_pool));
713         apr_pool_destroy(temp_pool);
714         return PyBool_FromLong(is_dir);
715 }
716
717 static PyObject *fs_root_is_file(FileSystemRootObject *self, PyObject *args)
718 {
719         svn_boolean_t is_file;
720         apr_pool_t *temp_pool;
721         char *path;
722
723         if (!PyArg_ParseTuple(args, "s", &path))
724                 return NULL;
725
726         temp_pool = Pool(NULL);
727         if (temp_pool == NULL)
728                 return NULL;
729         RUN_SVN_WITH_POOL(temp_pool, svn_fs_is_file(&is_file, self->root, 
730                                                                                            path, temp_pool));
731         apr_pool_destroy(temp_pool);
732         return PyBool_FromLong(is_file);
733 }
734
735 static PyObject *fs_root_file_length(FileSystemRootObject *self, PyObject *args)
736 {
737         svn_filesize_t filesize;
738         apr_pool_t *temp_pool;
739         char *path;
740
741         if (!PyArg_ParseTuple(args, "s", &path))
742                 return NULL;
743
744         temp_pool = Pool(NULL);
745         if (temp_pool == NULL)
746                 return NULL;
747         RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_length(&filesize, self->root, 
748                                                                                            path, temp_pool));
749         apr_pool_destroy(temp_pool);
750         return PyInt_FromLong(filesize);
751 }
752
753 static PyObject *fs_node_file_proplist(FileSystemRootObject *self, PyObject *args)
754 {
755         apr_pool_t *temp_pool;
756         PyObject *ret;
757         char *path;
758         apr_hash_t *proplist;
759
760         if (!PyArg_ParseTuple(args, "s", &path))
761                 return NULL;
762
763         temp_pool = Pool(NULL);
764         if (temp_pool == NULL)
765                 return NULL;
766         RUN_SVN_WITH_POOL(temp_pool, svn_fs_node_proplist(&proplist, self->root, 
767                                                                                            path, temp_pool));
768         ret = prop_hash_to_dict(proplist);
769         apr_pool_destroy(temp_pool);
770         return ret;
771 }
772
773 static PyObject *fs_root_file_checksum(FileSystemRootObject *self, PyObject *args)
774 {
775         apr_pool_t *temp_pool;
776         svn_boolean_t force = FALSE;
777         char *path;
778 #if ONLY_SINCE_SVN(1, 6)
779         svn_checksum_kind_t kind;
780         const char *cstr;
781         svn_checksum_t *checksum;
782 #else
783         int kind;
784         unsigned char checksum[APR_MD5_DIGESTSIZE];
785 #endif
786         PyObject *ret;
787
788         if (!PyArg_ParseTuple(args, "s|ib", &path, &kind, &force))
789                 return NULL;
790
791         temp_pool = Pool(NULL);
792         if (temp_pool == NULL)
793                 return NULL;
794 #if ONLY_SINCE_SVN(1, 6)
795         RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_checksum(&checksum, kind, 
796                                                                                                           self->root, 
797                                                                                            path, force, temp_pool));
798         cstr = svn_checksum_to_cstring(checksum, temp_pool);
799         if (cstr == NULL) {
800                 ret = Py_None;
801                 Py_INCREF(ret);
802         } else {
803                 ret = PyString_FromString(cstr);
804         }
805 #else
806         if (kind > 0)  {
807                 PyErr_SetString(PyExc_ValueError, "Only MD5 checksums allowed with subversion < 1.6");
808                 return NULL;
809         }
810
811         RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_md5_checksum(checksum, 
812                                                                                                           self->root, 
813                                                                                            path, temp_pool));
814         ret = PyString_FromStringAndSize((char *)checksum, APR_MD5_DIGESTSIZE);
815 #endif
816         apr_pool_destroy(temp_pool);
817         return ret;
818 }
819
820 static PyObject *fs_root_file_contents(FileSystemRootObject *self, PyObject *args)
821 {
822         svn_stream_t *stream;
823         apr_pool_t *pool;
824         StreamObject *ret;
825         char *path;
826
827         if (!PyArg_ParseTuple(args, "s", &path))
828                 return NULL;
829
830         pool = Pool(NULL);
831         if (pool == NULL)
832                 return NULL;
833         RUN_SVN_WITH_POOL(pool, svn_fs_file_contents(&stream, self->root, 
834                                                                                            path, pool));
835
836         ret = PyObject_New(StreamObject, &Stream_Type);
837         if (ret == NULL)
838                 return NULL;
839
840         ret->pool = pool;
841         ret->closed = FALSE;
842         ret->stream = stream;
843
844     return (PyObject *)ret;
845 }
846
847 static PyMethodDef fs_root_methods[] = {
848         { "paths_changed", (PyCFunction)fs_root_paths_changed, METH_NOARGS, NULL },
849         { "is_dir", (PyCFunction)fs_root_is_dir, METH_VARARGS, NULL },
850         { "is_file", (PyCFunction)fs_root_is_file, METH_VARARGS, NULL },
851         { "file_length", (PyCFunction)fs_root_file_length, METH_VARARGS, NULL },
852         { "file_content", (PyCFunction)fs_root_file_contents, METH_VARARGS, NULL },
853         { "file_checksum", (PyCFunction)fs_root_file_checksum, METH_VARARGS, NULL },
854         { "proplist", (PyCFunction)fs_node_file_proplist, METH_VARARGS, NULL },
855         { NULL, }
856 };
857
858 PyTypeObject FileSystemRoot_Type = {
859         PyObject_HEAD_INIT(NULL) 0,
860         "repos.FileSystemRoot", /*      const char *tp_name;  For printing, in format "<module>.<name>" */
861         sizeof(FileSystemRootObject), 
862         0,/*    Py_ssize_t tp_basicsize, tp_itemsize;  For allocation */
863         
864         /* Methods to implement standard operations */
865         
866         fs_root_dealloc, /*     destructor tp_dealloc;  */
867         NULL, /*        printfunc tp_print;     */
868         NULL, /*        getattrfunc tp_getattr; */
869         NULL, /*        setattrfunc tp_setattr; */
870         NULL, /*        cmpfunc tp_compare;     */
871         NULL, /*        reprfunc tp_repr;       */
872         
873         /* Method suites for standard classes */
874         
875         NULL, /*        PyNumberMethods *tp_as_number;  */
876         NULL, /*        PySequenceMethods *tp_as_sequence;      */
877         NULL, /*        PyMappingMethods *tp_as_mapping;        */
878         
879         /* More standard operations (here for binary compatibility) */
880         
881         NULL, /*        hashfunc tp_hash;       */
882         NULL, /*        ternaryfunc tp_call;    */
883         NULL, /*        reprfunc tp_str;        */
884         NULL, /*        getattrofunc tp_getattro;       */
885         NULL, /*        setattrofunc tp_setattro;       */
886         
887         /* Functions to access object as input/output buffer */
888         NULL, /*        PyBufferProcs *tp_as_buffer;    */
889         
890         /* Flags to define presence of optional/expanded features */
891         0, /*   long tp_flags;  */
892         
893         NULL, /*        const char *tp_doc;  Documentation string */
894         
895         /* Assigned meaning in release 2.0 */
896         /* call function for all accessible objects */
897         NULL, /*        traverseproc tp_traverse;       */
898         
899         /* delete references to contained objects */
900         NULL, /*        inquiry tp_clear;       */
901         
902         /* Assigned meaning in release 2.1 */
903         /* rich comparisons */
904         NULL, /*        richcmpfunc tp_richcompare;     */
905         
906         /* weak reference enabler */
907         0, /*   Py_ssize_t tp_weaklistoffset;   */
908         
909         /* Added in release 2.2 */
910         /* Iterators */
911         NULL, /*        getiterfunc tp_iter;    */
912         NULL, /*        iternextfunc tp_iternext;       */
913         
914         /* Attribute descriptor and subclassing stuff */
915         fs_root_methods, /*     struct PyMethodDef *tp_methods; */
916 };
917
918
919 void initrepos(void)
920 {
921         static apr_pool_t *pool;
922         PyObject *mod;
923
924         if (PyType_Ready(&Repository_Type) < 0)
925                 return;
926
927         if (PyType_Ready(&FileSystem_Type) < 0)
928                 return;
929
930         if (PyType_Ready(&FileSystemRoot_Type) < 0)
931                 return;
932
933         if (PyType_Ready(&Stream_Type) < 0)
934                 return;
935
936         apr_initialize();
937         pool = Pool(NULL);
938         if (pool == NULL)
939                 return;
940
941         svn_fs_initialize(pool);
942
943         mod = Py_InitModule3("repos", repos_module_methods, "Local repository management");
944         if (mod == NULL)
945                 return;
946
947         PyModule_AddObject(mod, "LOAD_UUID_DEFAULT", PyLong_FromLong(svn_repos_load_uuid_default));
948         PyModule_AddObject(mod, "LOAD_UUID_IGNORE", PyLong_FromLong(svn_repos_load_uuid_ignore));
949         PyModule_AddObject(mod, "LOAD_UUID_FORCE", PyLong_FromLong(svn_repos_load_uuid_force));
950
951         PyModule_AddObject(mod, "PATH_CHANGE_MODIFY", PyInt_FromLong(svn_fs_path_change_modify));
952         PyModule_AddObject(mod, "PATH_CHANGE_ADD", PyInt_FromLong(svn_fs_path_change_add));
953         PyModule_AddObject(mod, "PATH_CHANGE_DELETE", PyInt_FromLong(svn_fs_path_change_delete));
954         PyModule_AddObject(mod, "PATH_CHANGE_REPLACE", PyInt_FromLong(svn_fs_path_change_replace));
955
956 #if ONLY_SINCE_SVN(1, 6)
957         PyModule_AddObject(mod, "CHECKSUM_MD5", PyInt_FromLong(svn_checksum_md5));
958         PyModule_AddObject(mod, "CHECKSUM_SHA1", PyInt_FromLong(svn_checksum_sha1));
959 #else
960         PyModule_AddObject(mod, "CHECKSUM_MD5", PyInt_FromLong(0));
961 #endif
962
963         PyModule_AddObject(mod, "Repository", (PyObject *)&Repository_Type);
964         Py_INCREF(&Repository_Type);
965
966         PyModule_AddObject(mod, "Stream", (PyObject *)&Stream_Type);
967         Py_INCREF(&Stream_Type);
968 }