Merge fix for formatting of 'extension not found' messages.
[jelmer/dulwich.git] / dulwich / _objects.c
1 /*
2  * Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; version 2
7  * of the License or (at your option) a later version of the License.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17  * MA  02110-1301, USA.
18  */
19
20 #include <Python.h>
21 #include <stdlib.h>
22 #include <sys/stat.h>
23
24 #if (PY_VERSION_HEX < 0x02050000)
25 typedef int Py_ssize_t;
26 #endif
27
28 #if defined(__MINGW32_VERSION) || defined(__APPLE__)
29 size_t rep_strnlen(char *text, size_t maxlen);
30 size_t rep_strnlen(char *text, size_t maxlen)
31 {
32         const char *last = memchr(text, '\0', maxlen);
33         return last ? (size_t) (last - text) : maxlen;
34 }
35 #define strnlen rep_strnlen
36 #endif
37
38 #define bytehex(x) (((x)<0xa)?('0'+(x)):('a'-0xa+(x)))
39
40 static PyObject *tree_entry_cls;
41 static PyObject *object_format_exception_cls;
42
43 static PyObject *sha_to_pyhex(const unsigned char *sha)
44 {
45         char hexsha[41];
46         int i;
47         for (i = 0; i < 20; i++) {
48                 hexsha[i*2] = bytehex((sha[i] & 0xF0) >> 4);
49                 hexsha[i*2+1] = bytehex(sha[i] & 0x0F);
50         }
51
52         return PyString_FromStringAndSize(hexsha, 40);
53 }
54
55 static PyObject *py_parse_tree(PyObject *self, PyObject *args, PyObject *kw)
56 {
57         char *text, *start, *end;
58         int len, namelen, strict;
59         PyObject *ret, *item, *name, *py_strict = NULL;
60         static char *kwlist[] = {"text", "strict", NULL};
61
62         if (!PyArg_ParseTupleAndKeywords(args, kw, "s#|O", kwlist,
63                                          &text, &len, &py_strict))
64                 return NULL;
65
66
67         strict = py_strict ?  PyObject_IsTrue(py_strict) : 0;
68
69         /* TODO: currently this returns a list; if memory usage is a concern,
70          * consider rewriting as a custom iterator object */
71         ret = PyList_New(0);
72
73         if (ret == NULL) {
74                 return NULL;
75         }
76
77         start = text;
78         end = text + len;
79
80         while (text < end) {
81                 long mode;
82                 if (strict && text[0] == '0') {
83                         PyErr_SetString(object_format_exception_cls,
84                                         "Illegal leading zero on mode");
85                         Py_DECREF(ret);
86                         return NULL;
87                 }
88
89                 mode = strtol(text, &text, 8);
90
91                 if (*text != ' ') {
92                         PyErr_SetString(PyExc_ValueError, "Expected space");
93                         Py_DECREF(ret);
94                         return NULL;
95                 }
96
97                 text++;
98
99                 namelen = strnlen(text, len - (text - start));
100
101                 name = PyString_FromStringAndSize(text, namelen);
102                 if (name == NULL) {
103                         Py_DECREF(ret);
104                         return NULL;
105                 }
106
107                 if (text + namelen + 20 >= end) {
108                         PyErr_SetString(PyExc_ValueError, "SHA truncated");
109                         Py_DECREF(ret);
110                         Py_DECREF(name);
111                         return NULL;
112                 }
113
114                 item = Py_BuildValue("(NlN)", name, mode,
115                                      sha_to_pyhex((unsigned char *)text+namelen+1));
116                 if (item == NULL) {
117                         Py_DECREF(ret);
118                         Py_DECREF(name);
119                         return NULL;
120                 }
121                 if (PyList_Append(ret, item) == -1) {
122                         Py_DECREF(ret);
123                         Py_DECREF(item);
124                         return NULL;
125                 }
126                 Py_DECREF(item);
127
128                 text += namelen+21;
129         }
130
131         return ret;
132 }
133
134 struct tree_item {
135         const char *name;
136         int mode;
137         PyObject *tuple;
138 };
139
140 int cmp_tree_item(const void *_a, const void *_b)
141 {
142         const struct tree_item *a = _a, *b = _b;
143         const char *remain_a, *remain_b;
144         int ret, common;
145         if (strlen(a->name) > strlen(b->name)) {
146                 common = strlen(b->name);
147                 remain_a = a->name + common;
148                 remain_b = (S_ISDIR(b->mode)?"/":"");
149         } else if (strlen(b->name) > strlen(a->name)) {
150                 common = strlen(a->name);
151                 remain_a = (S_ISDIR(a->mode)?"/":"");
152                 remain_b = b->name + common;
153         } else { /* strlen(a->name) == strlen(b->name) */
154                 common = 0;
155                 remain_a = a->name;
156                 remain_b = b->name;
157         }
158         ret = strncmp(a->name, b->name, common);
159         if (ret != 0)
160                 return ret;
161         return strcmp(remain_a, remain_b);
162 }
163
164 int cmp_tree_item_name_order(const void *_a, const void *_b) {
165         const struct tree_item *a = _a, *b = _b;
166         return strcmp(a->name, b->name);
167 }
168
169 static PyObject *py_sorted_tree_items(PyObject *self, PyObject *args)
170 {
171         struct tree_item *qsort_entries = NULL;
172         int name_order, num_entries, n = 0, i;
173         PyObject *entries, *py_name_order, *ret, *key, *value, *py_mode, *py_sha;
174         Py_ssize_t pos = 0;
175         int (*cmp)(const void *, const void *);
176
177         if (!PyArg_ParseTuple(args, "OO", &entries, &py_name_order))
178                 goto error;
179
180         if (!PyDict_Check(entries)) {
181                 PyErr_SetString(PyExc_TypeError, "Argument not a dictionary");
182                 goto error;
183         }
184
185         name_order = PyObject_IsTrue(py_name_order);
186         if (name_order == -1)
187                 goto error;
188         cmp = name_order ? cmp_tree_item_name_order : cmp_tree_item;
189
190         num_entries = PyDict_Size(entries);
191         if (PyErr_Occurred())
192                 goto error;
193         qsort_entries = PyMem_New(struct tree_item, num_entries);
194         if (!qsort_entries) {
195                 PyErr_NoMemory();
196                 goto error;
197         }
198
199         while (PyDict_Next(entries, &pos, &key, &value)) {
200                 if (!PyString_Check(key)) {
201                         PyErr_SetString(PyExc_TypeError, "Name is not a string");
202                         goto error;
203                 }
204
205                 if (PyTuple_Size(value) != 2) {
206                         PyErr_SetString(PyExc_ValueError, "Tuple has invalid size");
207                         goto error;
208                 }
209
210                 py_mode = PyTuple_GET_ITEM(value, 0);
211                 if (!PyInt_Check(py_mode)) {
212                         PyErr_SetString(PyExc_TypeError, "Mode is not an integral type");
213                         goto error;
214                 }
215
216                 py_sha = PyTuple_GET_ITEM(value, 1);
217                 if (!PyString_Check(py_sha)) {
218                         PyErr_SetString(PyExc_TypeError, "SHA is not a string");
219                         goto error;
220                 }
221                 qsort_entries[n].name = PyString_AS_STRING(key);
222                 qsort_entries[n].mode = PyInt_AS_LONG(py_mode);
223
224                 qsort_entries[n].tuple = PyObject_CallFunctionObjArgs(
225                                 tree_entry_cls, key, py_mode, py_sha, NULL);
226                 if (qsort_entries[n].tuple == NULL)
227                         goto error;
228                 n++;
229         }
230
231         qsort(qsort_entries, num_entries, sizeof(struct tree_item), cmp);
232
233         ret = PyList_New(num_entries);
234         if (ret == NULL) {
235                 PyErr_NoMemory();
236                 goto error;
237         }
238
239         for (i = 0; i < num_entries; i++) {
240                 PyList_SET_ITEM(ret, i, qsort_entries[i].tuple);
241         }
242         PyMem_Free(qsort_entries);
243         return ret;
244
245 error:
246         for (i = 0; i < n; i++) {
247                 Py_XDECREF(qsort_entries[i].tuple);
248         }
249         PyMem_Free(qsort_entries);
250         return NULL;
251 }
252
253 static PyMethodDef py_objects_methods[] = {
254         { "parse_tree", (PyCFunction)py_parse_tree, METH_VARARGS | METH_KEYWORDS,
255           NULL },
256         { "sorted_tree_items", py_sorted_tree_items, METH_VARARGS, NULL },
257         { NULL, NULL, 0, NULL }
258 };
259
260 PyMODINIT_FUNC
261 init_objects(void)
262 {
263         PyObject *m, *objects_mod, *errors_mod;
264
265         m = Py_InitModule3("_objects", py_objects_methods, NULL);
266         if (m == NULL)
267                 return;
268
269
270         errors_mod = PyImport_ImportModule("dulwich.errors");
271         if (errors_mod == NULL)
272                 return;
273
274         object_format_exception_cls = PyObject_GetAttrString(
275                 errors_mod, "ObjectFormatException");
276         Py_DECREF(errors_mod);
277         if (object_format_exception_cls == NULL)
278                 return;
279
280         /* This is a circular import but should be safe since this module is
281          * imported at at the very bottom of objects.py. */
282         objects_mod = PyImport_ImportModule("dulwich.objects");
283         if (objects_mod == NULL)
284                 return;
285
286         tree_entry_cls = PyObject_GetAttrString(objects_mod, "TreeEntry");
287         Py_DECREF(objects_mod);
288         if (tree_entry_cls == NULL)
289                 return;
290 }