2 parsers.c - efficient content parsing
4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
14 static int hexdigit(char c)
16 if (c >= '0' && c <= '9')
18 if (c >= 'a' && c <= 'f')
20 if (c >= 'A' && c <= 'F')
23 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
28 * Turn a hex-encoded string into binary.
30 static PyObject *unhexlify(const char *str, int len)
36 ret = PyString_FromStringAndSize(NULL, len / 2);
40 d = PyString_AS_STRING(ret);
41 for (c = str; c < str + len;) {
42 int hi = hexdigit(*c++);
43 int lo = hexdigit(*c++);
44 *d++ = (hi << 4) | lo;
51 * This code assumes that a manifest is stitched together with newline
54 static PyObject *parse_manifest(PyObject *self, PyObject *args)
56 PyObject *mfdict, *fdict;
57 char *str, *cur, *start, *zero;
60 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
61 &PyDict_Type, &mfdict,
66 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
67 PyObject *file = NULL, *node = NULL;
68 PyObject *flags = NULL;
75 else if (*cur != '\n')
79 PyErr_SetString(PyExc_ValueError,
80 "manifest entry has no separator");
84 file = PyString_FromStringAndSize(start, zero - start);
88 nlen = cur - zero - 1;
90 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
97 flags = PyString_FromStringAndSize(zero + 41,
102 if (PyDict_SetItem(fdict, file, flags) == -1)
106 if (PyDict_SetItem(mfdict, file, node) == -1)
123 if (len > 0 && *(cur - 1) != '\n') {
124 PyErr_SetString(PyExc_ValueError,
125 "manifest contains trailing garbage");
137 /* msvc 6.0 has problems */
138 # define inline __inline
139 typedef unsigned long uint32_t;
140 typedef unsigned __int64 uint64_t;
144 static uint32_t ntohl(uint32_t x)
146 return ((x & 0x000000ffUL) << 24) |
147 ((x & 0x0000ff00UL) << 8) |
148 ((x & 0x00ff0000UL) >> 8) |
149 ((x & 0xff000000UL) >> 24);
153 # include <sys/types.h>
154 # if defined __BEOS__ && !defined __HAIKU__
155 # include <ByteOrder.h>
157 # include <arpa/inet.h>
159 # include <inttypes.h>
162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
166 char *str, *cur, *end, *cpos;
167 int state, mode, size, mtime;
170 char decode[16]; /* for alignment */
172 if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
182 parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
190 while (cur < end - 17) {
193 memcpy(decode, cur + 1, 16);
194 mode = ntohl(*(uint32_t *)(decode));
195 size = ntohl(*(uint32_t *)(decode + 4));
196 mtime = ntohl(*(uint32_t *)(decode + 8));
197 flen = ntohl(*(uint32_t *)(decode + 12));
199 if (flen > end - cur) {
200 PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
204 entry = Py_BuildValue("ciii", state, mode, size, mtime);
207 PyObject_GC_UnTrack(entry); /* don't waste time with this */
209 cpos = memchr(cur, 0, flen);
211 fname = PyString_FromStringAndSize(cur, cpos - cur);
212 cname = PyString_FromStringAndSize(cpos + 1,
213 flen - (cpos - cur) - 1);
214 if (!fname || !cname ||
215 PyDict_SetItem(cmap, fname, cname) == -1 ||
216 PyDict_SetItem(dmap, fname, entry) == -1)
220 fname = PyString_FromStringAndSize(cur, flen);
222 PyDict_SetItem(dmap, fname, entry) == -1)
228 fname = cname = entry = NULL;
241 const char nullid[20];
242 const int nullrev = -1;
244 /* create an index tuple, insert into the nodemap */
245 static PyObject * _build_idx_entry(PyObject *nodemap, int n, uint64_t offset_flags,
246 int comp_len, int uncomp_len, int base_rev,
247 int link_rev, int parent_1, int parent_2,
248 const char *c_node_id)
251 PyObject *entry, *node_id, *n_obj;
253 node_id = PyString_FromStringAndSize(c_node_id, 20);
254 n_obj = PyInt_FromLong(n);
255 if (!node_id || !n_obj)
258 err = PyDict_SetItem(nodemap, node_id, n_obj);
264 entry = Py_BuildValue("LiiiiiiN", offset_flags, comp_len,
265 uncomp_len, base_rev, link_rev,
266 parent_1, parent_2, node_id);
269 PyObject_GC_UnTrack(entry); /* don't waste time with this */
278 /* RevlogNG format (all in big endian, data may be inlined):
281 * 4 bytes: compressed length
282 * 4 bytes: uncompressed length
283 * 4 bytes: base revision
284 * 4 bytes: link revision
285 * 4 bytes: parent 1 revision
286 * 4 bytes: parent 2 revision
287 * 32 bytes: nodeid (only 20 bytes used)
289 static int _parse_index_ng (const char *data, int size, int inlined,
290 PyObject *index, PyObject *nodemap)
294 uint64_t offset_flags;
295 int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
296 const char *c_node_id;
297 const char *end = data + size;
298 char decode[64]; /* to enforce alignment with inline data */
303 memcpy(decode, data, 64);
304 offset_flags = ntohl(*((uint32_t *) (decode + 4)));
305 if (n == 0) /* mask out version number for the first entry */
306 offset_flags &= 0xFFFF;
308 uint32_t offset_high = ntohl(*((uint32_t *) decode));
309 offset_flags |= ((uint64_t) offset_high) << 32;
312 comp_len = ntohl(*((uint32_t *) (decode + 8)));
313 uncomp_len = ntohl(*((uint32_t *) (decode + 12)));
314 base_rev = ntohl(*((uint32_t *) (decode + 16)));
315 link_rev = ntohl(*((uint32_t *) (decode + 20)));
316 parent_1 = ntohl(*((uint32_t *) (decode + 24)));
317 parent_2 = ntohl(*((uint32_t *) (decode + 28)));
318 c_node_id = decode + 32;
320 entry = _build_idx_entry(nodemap, n, offset_flags,
321 comp_len, uncomp_len, base_rev,
322 link_rev, parent_1, parent_2,
328 err = PyList_Append(index, entry);
333 PyList_SET_ITEM(index, n, entry); /* steals reference */
336 step = 64 + (inlined ? comp_len : 0);
337 if (end - data < step)
342 if (!PyErr_Occurred())
343 PyErr_SetString(PyExc_ValueError, "corrupt index file");
347 /* create the nullid/nullrev entry in the nodemap and the
348 * magic nullid entry in the index at [-1] */
349 entry = _build_idx_entry(nodemap,
350 nullrev, 0, 0, 0, -1, -1, -1, -1, nullid);
354 err = PyList_Append(index, entry);
359 PyList_SET_ITEM(index, n, entry); /* steals reference */
364 /* This function parses a index file and returns a Python tuple of the
365 * following format: (index, nodemap, cache)
367 * index: a list of tuples containing the RevlogNG records
368 * nodemap: a dict mapping node ids to indices in the index list
369 * cache: if data is inlined, a tuple (index_file_content, 0) else None
371 static PyObject *parse_index(PyObject *self, PyObject *args)
375 PyObject *rval = NULL, *index = NULL, *nodemap = NULL, *cache = NULL;
376 PyObject *data_obj = NULL, *inlined_obj;
378 if (!PyArg_ParseTuple(args, "s#O", &data, &size, &inlined_obj))
380 inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
382 /* If no data is inlined, we know the size of the index list in
383 * advance: size divided by size of one one revlog record (64 bytes)
384 * plus one for the nullid */
385 index = inlined ? PyList_New(0) : PyList_New(size / 64 + 1);
389 nodemap = PyDict_New();
393 /* set up the cache return value */
395 /* Note that the reference to data_obj is only borrowed */
396 data_obj = PyTuple_GET_ITEM(args, 0);
397 cache = Py_BuildValue("iO", 0, data_obj);
405 /* actually populate the index and the nodemap with data */
406 if (!_parse_index_ng (data, size, inlined, index, nodemap))
409 rval = Py_BuildValue("NNN", index, nodemap, cache);
423 static char parsers_doc[] = "Efficient content parsing.";
425 static PyMethodDef methods[] = {
426 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
427 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
428 {"parse_index", parse_index, METH_VARARGS, "parse a revlog index\n"},
432 PyMODINIT_FUNC initparsers(void)
434 Py_InitModule3("parsers", methods, parsers_doc);