Mercurial > hg
comparison mercurial/manifest.c @ 24214:a5f1bccd2996
manifest.c: new extension code to lazily parse manifests
This lets us iterate manifests in order, but do a _lot_ less work in
the common case when we only care about a few manifest entries.
Many thanks to Mike Edgar for reviewing this in advance of it going
out to the list, which caught many things I missed.
This version of the patch includes C89 fixes from Sean Farley and
many correctness/efficiency cleanups from Martin von
Zweigbergk. Thanks to both!
author | Augie Fackler <augie@google.com> |
---|---|
date | Tue, 13 Jan 2015 14:31:38 -0800 |
parents | |
children | 542c891274b2 |
comparison
equal
deleted
inserted
replaced
24213:e0c1328df872 | 24214:a5f1bccd2996 |
---|---|
1 /* | |
2 * manifest.c - manifest type that does on-demand parsing. | |
3 * | |
4 * Copyright 2015, Google Inc. | |
5 * | |
6 * This software may be used and distributed according to the terms of | |
7 * the GNU General Public License, incorporated herein by reference. | |
8 */ | |
9 #include <assert.h> | |
10 #include <string.h> | |
11 #include <stdlib.h> | |
12 | |
13 #include <Python.h> | |
14 | |
15 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */ | |
16 #ifdef _MSC_VER | |
17 #define true 1 | |
18 #define false 0 | |
19 typedef unsigned char bool; | |
20 #else | |
21 #include <stdbool.h> | |
22 #endif | |
23 | |
24 #define DEFAULT_LINES 100000 | |
25 | |
26 typedef struct { | |
27 char *start; | |
28 Py_ssize_t len; /* length of line including terminal newline */ | |
29 char hash_suffix; | |
30 bool from_malloc; | |
31 bool deleted; | |
32 } line; | |
33 | |
34 typedef struct { | |
35 PyObject_HEAD | |
36 PyObject *pydata; | |
37 line *lines; | |
38 int numlines; /* number of line entries */ | |
39 int livelines; /* number of non-deleted lines */ | |
40 int maxlines; /* allocated number of lines */ | |
41 bool dirty; | |
42 } lazymanifest; | |
43 | |
44 #define MANIFEST_OOM -1 | |
45 #define MANIFEST_NOT_SORTED -2 | |
46 #define MANIFEST_MALFORMED -3 | |
47 | |
48 /* defined in parsers.c */ | |
49 PyObject *unhexlify(const char *str, int len); | |
50 | |
51 /* get the length of the path for a line */ | |
52 static size_t pathlen(line *l) { | |
53 return strlen(l->start); | |
54 } | |
55 | |
56 /* get the node value of a single line */ | |
57 static PyObject *nodeof(line *l) { | |
58 char *s = l->start; | |
59 ssize_t llen = pathlen(l); | |
60 PyObject *hash = unhexlify(s + llen + 1, 40); | |
61 if (!hash) { | |
62 return NULL; | |
63 } | |
64 if (l->hash_suffix != '\0') { | |
65 char newhash[21]; | |
66 memcpy(newhash, PyString_AsString(hash), 20); | |
67 Py_DECREF(hash); | |
68 newhash[20] = l->hash_suffix; | |
69 hash = PyString_FromStringAndSize(newhash, 21); | |
70 } | |
71 return hash; | |
72 } | |
73 | |
74 /* get the node hash and flags of a line as a tuple */ | |
75 static PyObject *hashflags(line *l) | |
76 { | |
77 char *s = l->start; | |
78 size_t plen = pathlen(l); | |
79 PyObject *hash = nodeof(l); | |
80 | |
81 /* 40 for hash, 1 for null byte, 1 for newline */ | |
82 size_t hplen = plen + 42; | |
83 Py_ssize_t flen = l->len - hplen; | |
84 PyObject *flags; | |
85 PyObject *tup; | |
86 | |
87 if (!hash) | |
88 return NULL; | |
89 flags = PyString_FromStringAndSize(s + hplen - 1, flen); | |
90 if (!flags) { | |
91 Py_DECREF(hash); | |
92 return NULL; | |
93 } | |
94 tup = PyTuple_Pack(2, hash, flags); | |
95 Py_DECREF(flags); | |
96 Py_DECREF(hash); | |
97 return tup; | |
98 } | |
99 | |
100 /* if we're about to run out of space in the line index, add more */ | |
101 static bool realloc_if_full(lazymanifest *self) | |
102 { | |
103 if (self->numlines == self->maxlines) { | |
104 self->maxlines *= 2; | |
105 self->lines = realloc(self->lines, self->maxlines * sizeof(line)); | |
106 } | |
107 return self->lines; | |
108 } | |
109 | |
110 /* | |
111 * Find the line boundaries in the manifest that 'data' points to and store | |
112 * information about each line in 'self'. | |
113 */ | |
114 static int find_lines(lazymanifest *self, char *data, Py_ssize_t len) | |
115 { | |
116 char *prev = NULL; | |
117 while (len > 0) { | |
118 line *l; | |
119 char *next = memchr(data, '\n', len); | |
120 if (!next) { | |
121 return MANIFEST_MALFORMED; | |
122 } | |
123 next++; /* advance past newline */ | |
124 if (!realloc_if_full(self)) { | |
125 return MANIFEST_OOM; /* no memory */ | |
126 } | |
127 if (prev && strcmp(prev, data) > -1) { | |
128 /* This data isn't sorted, so we have to abort. */ | |
129 return MANIFEST_NOT_SORTED; | |
130 } | |
131 l = self->lines + ((self->numlines)++); | |
132 l->start = data; | |
133 l->len = next - data; | |
134 l->hash_suffix = '\0'; | |
135 l->from_malloc = false; | |
136 l->deleted = false; | |
137 len = len - l->len; | |
138 prev = data; | |
139 data = next; | |
140 } | |
141 self->livelines = self->numlines; | |
142 return 0; | |
143 } | |
144 | |
145 static int lazymanifest_init(lazymanifest *self, PyObject *args) | |
146 { | |
147 char *data; | |
148 Py_ssize_t len; | |
149 int err, ret; | |
150 PyObject *pydata; | |
151 if (!PyArg_ParseTuple(args, "S", &pydata)) { | |
152 return -1; | |
153 } | |
154 err = PyString_AsStringAndSize(pydata, &data, &len); | |
155 | |
156 self->dirty = false; | |
157 if (err == -1) | |
158 return -1; | |
159 self->pydata = pydata; | |
160 Py_INCREF(self->pydata); | |
161 Py_BEGIN_ALLOW_THREADS | |
162 self->lines = malloc(DEFAULT_LINES * sizeof(line)); | |
163 self->maxlines = DEFAULT_LINES; | |
164 self->numlines = 0; | |
165 if (!self->lines) | |
166 ret = MANIFEST_OOM; | |
167 else | |
168 ret = find_lines(self, data, len); | |
169 Py_END_ALLOW_THREADS | |
170 switch (ret) { | |
171 case 0: | |
172 break; | |
173 case MANIFEST_OOM: | |
174 PyErr_NoMemory(); | |
175 break; | |
176 case MANIFEST_NOT_SORTED: | |
177 PyErr_Format(PyExc_ValueError, | |
178 "Manifest lines not in sorted order."); | |
179 break; | |
180 case MANIFEST_MALFORMED: | |
181 PyErr_Format(PyExc_ValueError, | |
182 "Manifest did not end in a newline."); | |
183 break; | |
184 default: | |
185 PyErr_Format(PyExc_ValueError, | |
186 "Unknown problem parsing manifest."); | |
187 } | |
188 return ret == 0 ? 0 : -1; | |
189 } | |
190 | |
191 static void lazymanifest_dealloc(lazymanifest *self) | |
192 { | |
193 /* free any extra lines we had to allocate */ | |
194 int i; | |
195 for (i = 0; i < self->numlines; i++) { | |
196 if (self->lines[i].from_malloc) { | |
197 free(self->lines[i].start); | |
198 } | |
199 } | |
200 if (self->lines) { | |
201 free(self->lines); | |
202 self->lines = NULL; | |
203 } | |
204 if (self->pydata) { | |
205 Py_DECREF(self->pydata); | |
206 self->pydata = NULL; | |
207 } | |
208 PyObject_Del(self); | |
209 } | |
210 | |
211 /* iteration support */ | |
212 | |
213 typedef struct { | |
214 PyObject_HEAD lazymanifest *m; | |
215 Py_ssize_t pos; | |
216 } lmIter; | |
217 | |
218 static void lmiter_dealloc(PyObject *o) | |
219 { | |
220 lmIter *self = (lmIter *)o; | |
221 Py_DECREF(self->m); | |
222 PyObject_Del(self); | |
223 } | |
224 | |
225 static PyObject *lmiter_iternext(PyObject *o) | |
226 { | |
227 size_t pl; | |
228 line *l; | |
229 Py_ssize_t consumed; | |
230 PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL; | |
231 lmIter *self = (lmIter *)o; | |
232 do { | |
233 self->pos++; | |
234 if (self->pos >= self->m->numlines) { | |
235 goto bail; | |
236 } | |
237 /* skip over deleted manifest entries */ | |
238 } while (self->m->lines[self->pos].deleted); | |
239 l = self->m->lines + self->pos; | |
240 pl = pathlen(l); | |
241 path = PyString_FromStringAndSize(l->start, pl); | |
242 hash = nodeof(l); | |
243 consumed = pl + 41; | |
244 flags = PyString_FromStringAndSize(l->start + consumed, | |
245 l->len - consumed - 1); | |
246 if (!flags) { | |
247 goto bail; | |
248 } | |
249 ret = PyTuple_Pack(3, path, hash, flags); | |
250 bail: | |
251 Py_XDECREF(path); | |
252 Py_XDECREF(hash); | |
253 Py_XDECREF(flags); | |
254 return ret; | |
255 } | |
256 | |
257 static PyTypeObject lazymanifestIterator = { | |
258 PyObject_HEAD_INIT(NULL) | |
259 0, /*ob_size */ | |
260 "parsers.lazymanifest.iterator", /*tp_name */ | |
261 sizeof(lmIter), /*tp_basicsize */ | |
262 0, /*tp_itemsize */ | |
263 lmiter_dealloc, /*tp_dealloc */ | |
264 0, /*tp_print */ | |
265 0, /*tp_getattr */ | |
266 0, /*tp_setattr */ | |
267 0, /*tp_compare */ | |
268 0, /*tp_repr */ | |
269 0, /*tp_as_number */ | |
270 0, /*tp_as_sequence */ | |
271 0, /*tp_as_mapping */ | |
272 0, /*tp_hash */ | |
273 0, /*tp_call */ | |
274 0, /*tp_str */ | |
275 0, /*tp_getattro */ | |
276 0, /*tp_setattro */ | |
277 0, /*tp_as_buffer */ | |
278 /* tp_flags: Py_TPFLAGS_HAVE_ITER tells python to | |
279 use tp_iter and tp_iternext fields. */ | |
280 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, | |
281 "Iterator for a lazymanifest.", /* tp_doc */ | |
282 0, /* tp_traverse */ | |
283 0, /* tp_clear */ | |
284 0, /* tp_richcompare */ | |
285 0, /* tp_weaklistoffset */ | |
286 PyObject_SelfIter, /* tp_iter: __iter__() method */ | |
287 lmiter_iternext, /* tp_iternext: next() method */ | |
288 }; | |
289 | |
290 static lazymanifest *lazymanifest_copy(lazymanifest *self); | |
291 | |
292 static PyObject *lazymanifest_getiter(lazymanifest *self) | |
293 { | |
294 lmIter *i = NULL; | |
295 lazymanifest *t = lazymanifest_copy(self); | |
296 if (!t) { | |
297 PyErr_NoMemory(); | |
298 return NULL; | |
299 } | |
300 i = PyObject_New(lmIter, &lazymanifestIterator); | |
301 if (i) { | |
302 i->m = t; | |
303 i->pos = -1; | |
304 } else { | |
305 Py_DECREF(t); | |
306 PyErr_NoMemory(); | |
307 } | |
308 return (PyObject *)i; | |
309 } | |
310 | |
311 /* __getitem__ and __setitem__ support */ | |
312 | |
313 static Py_ssize_t lazymanifest_size(lazymanifest *self) | |
314 { | |
315 return self->livelines; | |
316 } | |
317 | |
318 static int linecmp(const void *left, const void *right) | |
319 { | |
320 return strcmp(((const line *)left)->start, | |
321 ((const line *)right)->start); | |
322 } | |
323 | |
324 static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key) | |
325 { | |
326 line needle; | |
327 line *hit; | |
328 if (!PyString_Check(key)) { | |
329 PyErr_Format(PyExc_TypeError, | |
330 "getitem: manifest keys must be a string."); | |
331 return NULL; | |
332 } | |
333 needle.start = PyString_AsString(key); | |
334 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line), | |
335 &linecmp); | |
336 if (!hit || hit->deleted) { | |
337 PyErr_Format(PyExc_KeyError, "No such manifest entry."); | |
338 return NULL; | |
339 } | |
340 return hashflags(hit); | |
341 } | |
342 | |
343 static int lazymanifest_delitem(lazymanifest *self, PyObject *key) | |
344 { | |
345 line needle; | |
346 line *hit; | |
347 if (!PyString_Check(key)) { | |
348 PyErr_Format(PyExc_TypeError, | |
349 "delitem: manifest keys must be a string."); | |
350 return -1; | |
351 } | |
352 needle.start = PyString_AsString(key); | |
353 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line), | |
354 &linecmp); | |
355 if (!hit || hit->deleted) { | |
356 PyErr_Format(PyExc_KeyError, | |
357 "Tried to delete nonexistent manifest entry."); | |
358 return -1; | |
359 } | |
360 self->dirty = true; | |
361 hit->deleted = true; | |
362 self->livelines--; | |
363 return 0; | |
364 } | |
365 | |
366 static int lazymanifest_setitem( | |
367 lazymanifest *self, PyObject *key, PyObject *value) | |
368 { | |
369 char *path; | |
370 Py_ssize_t plen; | |
371 PyObject *pyhash; | |
372 Py_ssize_t hlen; | |
373 char *hash; | |
374 PyObject *pyflags; | |
375 char *flags; | |
376 Py_ssize_t flen; | |
377 size_t dlen; | |
378 char *dest; | |
379 int i; | |
380 line new; | |
381 line *hit; | |
382 if (!PyString_Check(key)) { | |
383 PyErr_Format(PyExc_TypeError, | |
384 "setitem: manifest keys must be a string."); | |
385 return -1; | |
386 } | |
387 if (!value) { | |
388 return lazymanifest_delitem(self, key); | |
389 } | |
390 if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) { | |
391 PyErr_Format(PyExc_TypeError, | |
392 "Manifest values must be a tuple of (node, flags)."); | |
393 return -1; | |
394 } | |
395 if (PyString_AsStringAndSize(key, &path, &plen) == -1) { | |
396 return -1; | |
397 } | |
398 | |
399 pyhash = PyTuple_GetItem(value, 0); | |
400 if (!PyString_Check(pyhash)) { | |
401 PyErr_Format(PyExc_TypeError, | |
402 "node must be a 20-byte string"); | |
403 return -1; | |
404 } | |
405 hlen = PyString_Size(pyhash); | |
406 /* Some parts of the codebase try and set 21 or 22 | |
407 * byte "hash" values in order to perturb things for | |
408 * status. We have to preserve at least the 21st | |
409 * byte. Sigh. If there's a 22nd byte, we drop it on | |
410 * the floor, which works fine. | |
411 */ | |
412 if (hlen != 20 && hlen != 21 && hlen != 22) { | |
413 PyErr_Format(PyExc_TypeError, | |
414 "node must be a 20-byte string"); | |
415 return -1; | |
416 } | |
417 hash = PyString_AsString(pyhash); | |
418 | |
419 pyflags = PyTuple_GetItem(value, 1); | |
420 if (!PyString_Check(pyflags) || PyString_Size(pyflags) > 1) { | |
421 PyErr_Format(PyExc_TypeError, | |
422 "flags must a 0 or 1 byte string"); | |
423 return -1; | |
424 } | |
425 if (PyString_AsStringAndSize(pyflags, &flags, &flen) == -1) { | |
426 return -1; | |
427 } | |
428 /* one null byte and one newline */ | |
429 dlen = plen + 41 + flen + 1; | |
430 dest = malloc(dlen); | |
431 if (!dest) { | |
432 PyErr_NoMemory(); | |
433 return -1; | |
434 } | |
435 memcpy(dest, path, plen + 1); | |
436 for (i = 0; i < 20; i++) { | |
437 sprintf(dest + plen + 1 + (i * 2), "%02hhx", hash[i]); | |
438 } | |
439 memcpy(dest + plen + 41, flags, flen); | |
440 dest[plen + 41 + flen] = '\n'; | |
441 new.start = dest; | |
442 new.len = dlen; | |
443 new.hash_suffix = '\0'; | |
444 if (hlen > 20) { | |
445 new.hash_suffix = hash[20]; | |
446 } | |
447 new.from_malloc = true; /* is `start` a pointer we allocated? */ | |
448 new.deleted = false; /* is this entry deleted? */ | |
449 hit = bsearch(&new, self->lines, self->numlines, | |
450 sizeof(line), &linecmp); | |
451 self->dirty = true; | |
452 if (hit) { | |
453 /* updating a line we already had */ | |
454 if (hit->from_malloc) { | |
455 free(hit->start); | |
456 } | |
457 if (hit->deleted) { | |
458 self->livelines++; | |
459 } | |
460 *hit = new; | |
461 } else { | |
462 /* new line */ | |
463 if (!realloc_if_full(self)) { | |
464 PyErr_NoMemory(); | |
465 return -1; | |
466 } | |
467 self->lines[self->numlines++] = new; | |
468 self->livelines++; | |
469 /* TODO: do a binary search and insert rather than | |
470 * append and qsort. Also offer a batch-insert | |
471 * interface, which should be a nice little | |
472 * performance win. | |
473 */ | |
474 qsort(self->lines, self->numlines, sizeof(line), &linecmp); | |
475 } | |
476 return 0; | |
477 } | |
478 | |
479 static PyMappingMethods lazymanifest_mapping_methods = { | |
480 (lenfunc)lazymanifest_size, /* mp_length */ | |
481 (binaryfunc)lazymanifest_getitem, /* mp_subscript */ | |
482 (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */ | |
483 }; | |
484 | |
485 /* sequence methods (important or __contains__ builds an iterator */ | |
486 | |
487 static int lazymanifest_contains(lazymanifest *self, PyObject *key) | |
488 { | |
489 line needle; | |
490 line *hit; | |
491 if (!PyString_Check(key)) { | |
492 /* Our keys are always strings, so if the contains | |
493 * check is for a non-string, just return false. */ | |
494 return 0; | |
495 } | |
496 needle.start = PyString_AsString(key); | |
497 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line), | |
498 &linecmp); | |
499 if (!hit || hit->deleted) { | |
500 return 0; | |
501 } | |
502 return 1; | |
503 } | |
504 | |
505 static PySequenceMethods lazymanifest_seq_meths = { | |
506 (lenfunc)lazymanifest_size, /* sq_length */ | |
507 0, /* sq_concat */ | |
508 0, /* sq_repeat */ | |
509 0, /* sq_item */ | |
510 0, /* sq_slice */ | |
511 0, /* sq_ass_item */ | |
512 0, /* sq_ass_slice */ | |
513 (objobjproc)lazymanifest_contains, /* sq_contains */ | |
514 0, /* sq_inplace_concat */ | |
515 0, /* sq_inplace_repeat */ | |
516 }; | |
517 | |
518 | |
519 /* Other methods (copy, diff, etc) */ | |
520 static PyTypeObject lazymanifestType; | |
521 | |
522 /* If the manifest has changes, build the new manifest text and reindex it. */ | |
523 static int compact(lazymanifest *self) { | |
524 int i; | |
525 ssize_t need = 0; | |
526 char *data; | |
527 line *src, *dst; | |
528 PyObject *pydata; | |
529 if (!self->dirty) | |
530 return 0; | |
531 for (i = 0; i < self->numlines; i++) { | |
532 if (!self->lines[i].deleted) { | |
533 need += self->lines[i].len; | |
534 } | |
535 } | |
536 pydata = PyString_FromStringAndSize(NULL, need); | |
537 if (!pydata) | |
538 return -1; | |
539 data = PyString_AsString(pydata); | |
540 if (!data) { | |
541 return -1; | |
542 } | |
543 src = self->lines; | |
544 dst = self->lines; | |
545 for (i = 0; i < self->numlines; i++, src++) { | |
546 char *tofree = NULL; | |
547 if (src->from_malloc) { | |
548 tofree = src->start; | |
549 } | |
550 if (!src->deleted) { | |
551 memcpy(data, src->start, src->len); | |
552 *dst = *src; | |
553 dst->start = data; | |
554 dst->from_malloc = false; | |
555 data += dst->len; | |
556 dst++; | |
557 } | |
558 free(tofree); | |
559 } | |
560 Py_DECREF(self->pydata); | |
561 self->pydata = pydata; | |
562 self->numlines = self->livelines; | |
563 self->dirty = false; | |
564 return 0; | |
565 } | |
566 | |
567 static PyObject *lazymanifest_text(lazymanifest *self) | |
568 { | |
569 if (compact(self) != 0) { | |
570 PyErr_NoMemory(); | |
571 return NULL; | |
572 } | |
573 Py_INCREF(self->pydata); | |
574 return self->pydata; | |
575 } | |
576 | |
577 static lazymanifest *lazymanifest_copy(lazymanifest *self) | |
578 { | |
579 lazymanifest *copy = NULL; | |
580 if (compact(self) != 0) { | |
581 goto nomem; | |
582 } | |
583 copy = PyObject_New(lazymanifest, &lazymanifestType); | |
584 if (!copy) { | |
585 goto nomem; | |
586 } | |
587 copy->numlines = self->numlines; | |
588 copy->livelines = self->livelines; | |
589 copy->dirty = false; | |
590 copy->lines = malloc(self->maxlines *sizeof(line)); | |
591 if (!copy->lines) { | |
592 goto nomem; | |
593 } | |
594 memcpy(copy->lines, self->lines, self->numlines * sizeof(line)); | |
595 copy->maxlines = self->maxlines; | |
596 copy->pydata = self->pydata; | |
597 Py_INCREF(copy->pydata); | |
598 return copy; | |
599 nomem: | |
600 PyErr_NoMemory(); | |
601 Py_XDECREF(copy); | |
602 return NULL; | |
603 } | |
604 | |
605 static lazymanifest *lazymanifest_filtercopy( | |
606 lazymanifest *self, PyObject *matchfn) | |
607 { | |
608 lazymanifest *copy = NULL; | |
609 int i; | |
610 if (!PyCallable_Check(matchfn)) { | |
611 PyErr_SetString(PyExc_TypeError, "matchfn must be callable"); | |
612 return NULL; | |
613 } | |
614 /* compact ourselves first to avoid double-frees later when we | |
615 * compact tmp so that it doesn't have random pointers to our | |
616 * underlying from_malloc-data (self->pydata is safe) */ | |
617 if (compact(self) != 0) { | |
618 goto nomem; | |
619 } | |
620 copy = PyObject_New(lazymanifest, &lazymanifestType); | |
621 copy->dirty = true; | |
622 copy->lines = malloc(self->maxlines * sizeof(line)); | |
623 if (!copy->lines) { | |
624 goto nomem; | |
625 } | |
626 copy->maxlines = self->maxlines; | |
627 copy->numlines = 0; | |
628 copy->pydata = self->pydata; | |
629 Py_INCREF(self->pydata); | |
630 for (i = 0; i < self->numlines; i++) { | |
631 PyObject *arg = PyString_FromString(self->lines[i].start); | |
632 PyObject *arglist = PyTuple_Pack(1, arg); | |
633 PyObject *result = PyObject_CallObject(matchfn, arglist); | |
634 Py_DECREF(arglist); | |
635 Py_DECREF(arg); | |
636 /* if the callback raised an exception, just let it | |
637 * through and give up */ | |
638 if (!result) { | |
639 free(copy->lines); | |
640 Py_DECREF(self->pydata); | |
641 return NULL; | |
642 } | |
643 if (PyObject_IsTrue(result)) { | |
644 assert(!(self->lines[i].from_malloc)); | |
645 copy->lines[copy->numlines++] = self->lines[i]; | |
646 } | |
647 Py_DECREF(result); | |
648 } | |
649 copy->livelines = copy->numlines; | |
650 return copy; | |
651 nomem: | |
652 PyErr_NoMemory(); | |
653 Py_XDECREF(copy); | |
654 return NULL; | |
655 } | |
656 | |
657 static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args) | |
658 { | |
659 lazymanifest *other; | |
660 PyObject *pyclean = NULL; | |
661 bool listclean; | |
662 PyObject *emptyTup = NULL, *ret = NULL; | |
663 PyObject *es; | |
664 int sneedle = 0, oneedle = 0; | |
665 if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) { | |
666 return NULL; | |
667 } | |
668 listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean); | |
669 es = PyString_FromString(""); | |
670 if (!es) { | |
671 goto nomem; | |
672 } | |
673 emptyTup = PyTuple_Pack(2, Py_None, es); | |
674 Py_DECREF(es); | |
675 if (!emptyTup) { | |
676 goto nomem; | |
677 } | |
678 ret = PyDict_New(); | |
679 if (!ret) { | |
680 goto nomem; | |
681 } | |
682 while (sneedle != self->numlines || oneedle != other->numlines) { | |
683 line *left = self->lines + sneedle; | |
684 line *right = other->lines + oneedle; | |
685 int result; | |
686 PyObject *key; | |
687 PyObject *outer; | |
688 /* If we're looking at a deleted entry and it's not | |
689 * the end of the manifest, just skip it. */ | |
690 if (left->deleted && sneedle < self->numlines) { | |
691 sneedle++; | |
692 continue; | |
693 } | |
694 if (right->deleted && oneedle < other->numlines) { | |
695 oneedle++; | |
696 continue; | |
697 } | |
698 /* if we're at the end of either manifest, then we | |
699 * know the remaining items are adds so we can skip | |
700 * the strcmp. */ | |
701 if (sneedle == self->numlines) { | |
702 result = 1; | |
703 } else if (oneedle == other->numlines) { | |
704 result = -1; | |
705 } else { | |
706 result = linecmp(left, right); | |
707 } | |
708 key = result <= 0 ? | |
709 PyString_FromString(left->start) : | |
710 PyString_FromString(right->start); | |
711 if (!key) | |
712 goto nomem; | |
713 if (result < 0) { | |
714 PyObject *l = hashflags(left); | |
715 if (!l) { | |
716 goto nomem; | |
717 } | |
718 outer = PyTuple_Pack(2, l, emptyTup); | |
719 Py_DECREF(l); | |
720 if (!outer) { | |
721 goto nomem; | |
722 } | |
723 PyDict_SetItem(ret, key, outer); | |
724 Py_DECREF(outer); | |
725 sneedle++; | |
726 } else if (result > 0) { | |
727 PyObject *r = hashflags(right); | |
728 if (!r) { | |
729 goto nomem; | |
730 } | |
731 outer = PyTuple_Pack(2, emptyTup, r); | |
732 Py_DECREF(r); | |
733 if (!outer) { | |
734 goto nomem; | |
735 } | |
736 PyDict_SetItem(ret, key, outer); | |
737 Py_DECREF(outer); | |
738 oneedle++; | |
739 } else { | |
740 /* file exists in both manifests */ | |
741 if (left->len != right->len | |
742 || memcmp(left->start, right->start, left->len) | |
743 || left->hash_suffix != right->hash_suffix) { | |
744 PyObject *l = hashflags(left); | |
745 PyObject *r; | |
746 if (!l) { | |
747 goto nomem; | |
748 } | |
749 r = hashflags(right); | |
750 if (!r) { | |
751 Py_DECREF(l); | |
752 goto nomem; | |
753 } | |
754 outer = PyTuple_Pack(2, l, r); | |
755 Py_DECREF(l); | |
756 Py_DECREF(r); | |
757 if (!outer) { | |
758 goto nomem; | |
759 } | |
760 PyDict_SetItem(ret, key, outer); | |
761 Py_DECREF(outer); | |
762 } else if (listclean) { | |
763 PyDict_SetItem(ret, key, Py_None); | |
764 } | |
765 sneedle++; | |
766 oneedle++; | |
767 } | |
768 Py_DECREF(key); | |
769 } | |
770 Py_DECREF(emptyTup); | |
771 return ret; | |
772 nomem: | |
773 PyErr_NoMemory(); | |
774 Py_XDECREF(ret); | |
775 Py_XDECREF(emptyTup); | |
776 return NULL; | |
777 } | |
778 | |
779 static PyMethodDef lazymanifest_methods[] = { | |
780 {"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS, | |
781 "Make a copy of this lazymanifest."}, | |
782 {"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O, | |
783 "Make a copy of this manifest filtered by matchfn."}, | |
784 {"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS, | |
785 "Compare this lazymanifest to another one."}, | |
786 {"text", (PyCFunction)lazymanifest_text, METH_NOARGS, | |
787 "Encode this manifest to text."}, | |
788 {NULL}, | |
789 }; | |
790 | |
791 static PyTypeObject lazymanifestType = { | |
792 PyObject_HEAD_INIT(NULL) | |
793 0, /* ob_size */ | |
794 "parsers.lazymanifest", /* tp_name */ | |
795 sizeof(lazymanifest), /* tp_basicsize */ | |
796 0, /* tp_itemsize */ | |
797 (destructor)lazymanifest_dealloc, /* tp_dealloc */ | |
798 0, /* tp_print */ | |
799 0, /* tp_getattr */ | |
800 0, /* tp_setattr */ | |
801 0, /* tp_compare */ | |
802 0, /* tp_repr */ | |
803 0, /* tp_as_number */ | |
804 &lazymanifest_seq_meths, /* tp_as_sequence */ | |
805 &lazymanifest_mapping_methods, /* tp_as_mapping */ | |
806 0, /* tp_hash */ | |
807 0, /* tp_call */ | |
808 0, /* tp_str */ | |
809 0, /* tp_getattro */ | |
810 0, /* tp_setattro */ | |
811 0, /* tp_as_buffer */ | |
812 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN, /* tp_flags */ | |
813 "TODO(augie)", /* tp_doc */ | |
814 0, /* tp_traverse */ | |
815 0, /* tp_clear */ | |
816 0, /* tp_richcompare */ | |
817 0, /* tp_weaklistoffset */ | |
818 (getiterfunc)lazymanifest_getiter, /* tp_iter */ | |
819 0, /* tp_iternext */ | |
820 lazymanifest_methods, /* tp_methods */ | |
821 0, /* tp_members */ | |
822 0, /* tp_getset */ | |
823 0, /* tp_base */ | |
824 0, /* tp_dict */ | |
825 0, /* tp_descr_get */ | |
826 0, /* tp_descr_set */ | |
827 0, /* tp_dictoffset */ | |
828 (initproc)lazymanifest_init, /* tp_init */ | |
829 0, /* tp_alloc */ | |
830 }; | |
831 | |
832 void manifest_module_init(PyObject * mod) | |
833 { | |
834 lazymanifestType.tp_new = PyType_GenericNew; | |
835 if (PyType_Ready(&lazymanifestType) < 0) | |
836 return; | |
837 Py_INCREF(&lazymanifestType); | |
838 | |
839 PyModule_AddObject(mod, "lazymanifest", | |
840 (PyObject *)&lazymanifestType); | |
841 } |