##// END OF EJS Templates
manifest: make sure there's a filename before bothering to look for newline...
Augie Fackler -
r40635:9eeda719 default
parent child Browse files
Show More
@@ -1,960 +1,972
1 /*
1 /*
2 * manifest.c - manifest type that does on-demand parsing.
2 * manifest.c - manifest type that does on-demand parsing.
3 *
3 *
4 * Copyright 2015, Google Inc.
4 * Copyright 2015, Google Inc.
5 *
5 *
6 * This software may be used and distributed according to the terms of
6 * This software may be used and distributed according to the terms of
7 * the GNU General Public License, incorporated herein by reference.
7 * the GNU General Public License, incorporated herein by reference.
8 */
8 */
9 #include <Python.h>
9 #include <Python.h>
10
10
11 #include <assert.h>
11 #include <assert.h>
12 #include <stdlib.h>
12 #include <stdlib.h>
13 #include <string.h>
13 #include <string.h>
14
14
15 #include "charencode.h"
15 #include "charencode.h"
16 #include "util.h"
16 #include "util.h"
17
17
18 #define DEFAULT_LINES 100000
18 #define DEFAULT_LINES 100000
19
19
20 typedef struct {
20 typedef struct {
21 char *start;
21 char *start;
22 Py_ssize_t len; /* length of line including terminal newline */
22 Py_ssize_t len; /* length of line including terminal newline */
23 char hash_suffix;
23 char hash_suffix;
24 bool from_malloc;
24 bool from_malloc;
25 bool deleted;
25 bool deleted;
26 } line;
26 } line;
27
27
28 typedef struct {
28 typedef struct {
29 PyObject_HEAD
29 PyObject_HEAD
30 PyObject *pydata;
30 PyObject *pydata;
31 line *lines;
31 line *lines;
32 int numlines; /* number of line entries */
32 int numlines; /* number of line entries */
33 int livelines; /* number of non-deleted lines */
33 int livelines; /* number of non-deleted lines */
34 int maxlines; /* allocated number of lines */
34 int maxlines; /* allocated number of lines */
35 bool dirty;
35 bool dirty;
36 } lazymanifest;
36 } lazymanifest;
37
37
38 #define MANIFEST_OOM -1
38 #define MANIFEST_OOM -1
39 #define MANIFEST_NOT_SORTED -2
39 #define MANIFEST_NOT_SORTED -2
40 #define MANIFEST_MALFORMED -3
40 #define MANIFEST_MALFORMED -3
41 #define MANIFEST_BOGUS_FILENAME -4
41
42
42 /* get the length of the path for a line */
43 /* get the length of the path for a line */
43 static size_t pathlen(line *l)
44 static size_t pathlen(line *l)
44 {
45 {
45 const char *end = memchr(l->start, '\0', l->len);
46 const char *end = memchr(l->start, '\0', l->len);
46 return (end) ? (size_t)(end - l->start) : l->len;
47 return (end) ? (size_t)(end - l->start) : l->len;
47 }
48 }
48
49
49 /* get the node value of a single line */
50 /* get the node value of a single line */
50 static PyObject *nodeof(line *l)
51 static PyObject *nodeof(line *l)
51 {
52 {
52 char *s = l->start;
53 char *s = l->start;
53 ssize_t llen = pathlen(l);
54 ssize_t llen = pathlen(l);
54 PyObject *hash;
55 PyObject *hash;
55 if (llen + 1 + 40 + 1 > l->len) { /* path '\0' hash '\n' */
56 if (llen + 1 + 40 + 1 > l->len) { /* path '\0' hash '\n' */
56 PyErr_SetString(PyExc_ValueError, "manifest line too short");
57 PyErr_SetString(PyExc_ValueError, "manifest line too short");
57 return NULL;
58 return NULL;
58 }
59 }
59 hash = unhexlify(s + llen + 1, 40);
60 hash = unhexlify(s + llen + 1, 40);
60 if (!hash) {
61 if (!hash) {
61 return NULL;
62 return NULL;
62 }
63 }
63 if (l->hash_suffix != '\0') {
64 if (l->hash_suffix != '\0') {
64 char newhash[21];
65 char newhash[21];
65 memcpy(newhash, PyBytes_AsString(hash), 20);
66 memcpy(newhash, PyBytes_AsString(hash), 20);
66 Py_DECREF(hash);
67 Py_DECREF(hash);
67 newhash[20] = l->hash_suffix;
68 newhash[20] = l->hash_suffix;
68 hash = PyBytes_FromStringAndSize(newhash, 21);
69 hash = PyBytes_FromStringAndSize(newhash, 21);
69 }
70 }
70 return hash;
71 return hash;
71 }
72 }
72
73
73 /* get the node hash and flags of a line as a tuple */
74 /* get the node hash and flags of a line as a tuple */
74 static PyObject *hashflags(line *l)
75 static PyObject *hashflags(line *l)
75 {
76 {
76 char *s = l->start;
77 char *s = l->start;
77 size_t plen = pathlen(l);
78 size_t plen = pathlen(l);
78 PyObject *hash = nodeof(l);
79 PyObject *hash = nodeof(l);
79
80
80 /* 40 for hash, 1 for null byte, 1 for newline */
81 /* 40 for hash, 1 for null byte, 1 for newline */
81 size_t hplen = plen + 42;
82 size_t hplen = plen + 42;
82 Py_ssize_t flen = l->len - hplen;
83 Py_ssize_t flen = l->len - hplen;
83 PyObject *flags;
84 PyObject *flags;
84 PyObject *tup;
85 PyObject *tup;
85
86
86 if (!hash)
87 if (!hash)
87 return NULL;
88 return NULL;
88 flags = PyBytes_FromStringAndSize(s + hplen - 1, flen);
89 flags = PyBytes_FromStringAndSize(s + hplen - 1, flen);
89 if (!flags) {
90 if (!flags) {
90 Py_DECREF(hash);
91 Py_DECREF(hash);
91 return NULL;
92 return NULL;
92 }
93 }
93 tup = PyTuple_Pack(2, hash, flags);
94 tup = PyTuple_Pack(2, hash, flags);
94 Py_DECREF(flags);
95 Py_DECREF(flags);
95 Py_DECREF(hash);
96 Py_DECREF(hash);
96 return tup;
97 return tup;
97 }
98 }
98
99
99 /* if we're about to run out of space in the line index, add more */
100 /* if we're about to run out of space in the line index, add more */
100 static bool realloc_if_full(lazymanifest *self)
101 static bool realloc_if_full(lazymanifest *self)
101 {
102 {
102 if (self->numlines == self->maxlines) {
103 if (self->numlines == self->maxlines) {
103 self->maxlines *= 2;
104 self->maxlines *= 2;
104 self->lines = realloc(self->lines, self->maxlines * sizeof(line));
105 self->lines = realloc(self->lines, self->maxlines * sizeof(line));
105 }
106 }
106 return !!self->lines;
107 return !!self->lines;
107 }
108 }
108
109
109 /*
110 /*
110 * Find the line boundaries in the manifest that 'data' points to and store
111 * Find the line boundaries in the manifest that 'data' points to and store
111 * information about each line in 'self'.
112 * information about each line in 'self'.
112 */
113 */
113 static int find_lines(lazymanifest *self, char *data, Py_ssize_t len)
114 static int find_lines(lazymanifest *self, char *data, Py_ssize_t len)
114 {
115 {
115 char *prev = NULL;
116 char *prev = NULL;
116 while (len > 0) {
117 while (len > 0) {
117 line *l;
118 line *l;
118 char *next = memchr(data, '\n', len);
119 char *next;
120 if (*data == '\0') {
121 /* It's implausible there's no filename, don't
122 * even bother looking for the newline. */
123 return MANIFEST_BOGUS_FILENAME;
124 }
125 next = memchr(data, '\n', len);
119 if (!next) {
126 if (!next) {
120 return MANIFEST_MALFORMED;
127 return MANIFEST_MALFORMED;
121 }
128 }
122 next++; /* advance past newline */
129 next++; /* advance past newline */
123 if (!realloc_if_full(self)) {
130 if (!realloc_if_full(self)) {
124 return MANIFEST_OOM; /* no memory */
131 return MANIFEST_OOM; /* no memory */
125 }
132 }
126 if (prev && strcmp(prev, data) > -1) {
133 if (prev && strcmp(prev, data) > -1) {
127 /* This data isn't sorted, so we have to abort. */
134 /* This data isn't sorted, so we have to abort. */
128 return MANIFEST_NOT_SORTED;
135 return MANIFEST_NOT_SORTED;
129 }
136 }
130 l = self->lines + ((self->numlines)++);
137 l = self->lines + ((self->numlines)++);
131 l->start = data;
138 l->start = data;
132 l->len = next - data;
139 l->len = next - data;
133 l->hash_suffix = '\0';
140 l->hash_suffix = '\0';
134 l->from_malloc = false;
141 l->from_malloc = false;
135 l->deleted = false;
142 l->deleted = false;
136 len = len - l->len;
143 len = len - l->len;
137 prev = data;
144 prev = data;
138 data = next;
145 data = next;
139 }
146 }
140 self->livelines = self->numlines;
147 self->livelines = self->numlines;
141 return 0;
148 return 0;
142 }
149 }
143
150
144 static void lazymanifest_init_early(lazymanifest *self)
151 static void lazymanifest_init_early(lazymanifest *self)
145 {
152 {
146 self->pydata = NULL;
153 self->pydata = NULL;
147 self->lines = NULL;
154 self->lines = NULL;
148 self->numlines = 0;
155 self->numlines = 0;
149 self->maxlines = 0;
156 self->maxlines = 0;
150 }
157 }
151
158
152 static int lazymanifest_init(lazymanifest *self, PyObject *args)
159 static int lazymanifest_init(lazymanifest *self, PyObject *args)
153 {
160 {
154 char *data;
161 char *data;
155 Py_ssize_t len;
162 Py_ssize_t len;
156 int err, ret;
163 int err, ret;
157 PyObject *pydata;
164 PyObject *pydata;
158
165
159 lazymanifest_init_early(self);
166 lazymanifest_init_early(self);
160 if (!PyArg_ParseTuple(args, "S", &pydata)) {
167 if (!PyArg_ParseTuple(args, "S", &pydata)) {
161 return -1;
168 return -1;
162 }
169 }
163 err = PyBytes_AsStringAndSize(pydata, &data, &len);
170 err = PyBytes_AsStringAndSize(pydata, &data, &len);
164
171
165 self->dirty = false;
172 self->dirty = false;
166 if (err == -1)
173 if (err == -1)
167 return -1;
174 return -1;
168 self->pydata = pydata;
175 self->pydata = pydata;
169 Py_INCREF(self->pydata);
176 Py_INCREF(self->pydata);
170 Py_BEGIN_ALLOW_THREADS
177 Py_BEGIN_ALLOW_THREADS
171 self->lines = malloc(DEFAULT_LINES * sizeof(line));
178 self->lines = malloc(DEFAULT_LINES * sizeof(line));
172 self->maxlines = DEFAULT_LINES;
179 self->maxlines = DEFAULT_LINES;
173 self->numlines = 0;
180 self->numlines = 0;
174 if (!self->lines)
181 if (!self->lines)
175 ret = MANIFEST_OOM;
182 ret = MANIFEST_OOM;
176 else
183 else
177 ret = find_lines(self, data, len);
184 ret = find_lines(self, data, len);
178 Py_END_ALLOW_THREADS
185 Py_END_ALLOW_THREADS
179 switch (ret) {
186 switch (ret) {
180 case 0:
187 case 0:
181 break;
188 break;
182 case MANIFEST_OOM:
189 case MANIFEST_OOM:
183 PyErr_NoMemory();
190 PyErr_NoMemory();
184 break;
191 break;
185 case MANIFEST_NOT_SORTED:
192 case MANIFEST_NOT_SORTED:
186 PyErr_Format(PyExc_ValueError,
193 PyErr_Format(PyExc_ValueError,
187 "Manifest lines not in sorted order.");
194 "Manifest lines not in sorted order.");
188 break;
195 break;
189 case MANIFEST_MALFORMED:
196 case MANIFEST_MALFORMED:
190 PyErr_Format(PyExc_ValueError,
197 PyErr_Format(PyExc_ValueError,
191 "Manifest did not end in a newline.");
198 "Manifest did not end in a newline.");
192 break;
199 break;
200 case MANIFEST_BOGUS_FILENAME:
201 PyErr_Format(
202 PyExc_ValueError,
203 "Manifest had an entry with a zero-length filename.");
204 break;
193 default:
205 default:
194 PyErr_Format(PyExc_ValueError,
206 PyErr_Format(PyExc_ValueError,
195 "Unknown problem parsing manifest.");
207 "Unknown problem parsing manifest.");
196 }
208 }
197 return ret == 0 ? 0 : -1;
209 return ret == 0 ? 0 : -1;
198 }
210 }
199
211
200 static void lazymanifest_dealloc(lazymanifest *self)
212 static void lazymanifest_dealloc(lazymanifest *self)
201 {
213 {
202 /* free any extra lines we had to allocate */
214 /* free any extra lines we had to allocate */
203 int i;
215 int i;
204 for (i = 0; self->lines && (i < self->numlines); i++) {
216 for (i = 0; self->lines && (i < self->numlines); i++) {
205 if (self->lines[i].from_malloc) {
217 if (self->lines[i].from_malloc) {
206 free(self->lines[i].start);
218 free(self->lines[i].start);
207 }
219 }
208 }
220 }
209 free(self->lines);
221 free(self->lines);
210 self->lines = NULL;
222 self->lines = NULL;
211 if (self->pydata) {
223 if (self->pydata) {
212 Py_DECREF(self->pydata);
224 Py_DECREF(self->pydata);
213 self->pydata = NULL;
225 self->pydata = NULL;
214 }
226 }
215 PyObject_Del(self);
227 PyObject_Del(self);
216 }
228 }
217
229
218 /* iteration support */
230 /* iteration support */
219
231
220 typedef struct {
232 typedef struct {
221 PyObject_HEAD lazymanifest *m;
233 PyObject_HEAD lazymanifest *m;
222 Py_ssize_t pos;
234 Py_ssize_t pos;
223 } lmIter;
235 } lmIter;
224
236
225 static void lmiter_dealloc(PyObject *o)
237 static void lmiter_dealloc(PyObject *o)
226 {
238 {
227 lmIter *self = (lmIter *)o;
239 lmIter *self = (lmIter *)o;
228 Py_DECREF(self->m);
240 Py_DECREF(self->m);
229 PyObject_Del(self);
241 PyObject_Del(self);
230 }
242 }
231
243
232 static line *lmiter_nextline(lmIter *self)
244 static line *lmiter_nextline(lmIter *self)
233 {
245 {
234 do {
246 do {
235 self->pos++;
247 self->pos++;
236 if (self->pos >= self->m->numlines) {
248 if (self->pos >= self->m->numlines) {
237 return NULL;
249 return NULL;
238 }
250 }
239 /* skip over deleted manifest entries */
251 /* skip over deleted manifest entries */
240 } while (self->m->lines[self->pos].deleted);
252 } while (self->m->lines[self->pos].deleted);
241 return self->m->lines + self->pos;
253 return self->m->lines + self->pos;
242 }
254 }
243
255
244 static PyObject *lmiter_iterentriesnext(PyObject *o)
256 static PyObject *lmiter_iterentriesnext(PyObject *o)
245 {
257 {
246 size_t pl;
258 size_t pl;
247 line *l;
259 line *l;
248 Py_ssize_t consumed;
260 Py_ssize_t consumed;
249 PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL;
261 PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL;
250 l = lmiter_nextline((lmIter *)o);
262 l = lmiter_nextline((lmIter *)o);
251 if (!l) {
263 if (!l) {
252 goto done;
264 goto done;
253 }
265 }
254 pl = pathlen(l);
266 pl = pathlen(l);
255 path = PyBytes_FromStringAndSize(l->start, pl);
267 path = PyBytes_FromStringAndSize(l->start, pl);
256 hash = nodeof(l);
268 hash = nodeof(l);
257 if (!path || !hash) {
269 if (!path || !hash) {
258 goto done;
270 goto done;
259 }
271 }
260 consumed = pl + 41;
272 consumed = pl + 41;
261 flags = PyBytes_FromStringAndSize(l->start + consumed,
273 flags = PyBytes_FromStringAndSize(l->start + consumed,
262 l->len - consumed - 1);
274 l->len - consumed - 1);
263 if (!flags) {
275 if (!flags) {
264 goto done;
276 goto done;
265 }
277 }
266 ret = PyTuple_Pack(3, path, hash, flags);
278 ret = PyTuple_Pack(3, path, hash, flags);
267 done:
279 done:
268 Py_XDECREF(path);
280 Py_XDECREF(path);
269 Py_XDECREF(hash);
281 Py_XDECREF(hash);
270 Py_XDECREF(flags);
282 Py_XDECREF(flags);
271 return ret;
283 return ret;
272 }
284 }
273
285
274 #ifdef IS_PY3K
286 #ifdef IS_PY3K
275 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
287 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
276 #else
288 #else
277 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
289 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
278 | Py_TPFLAGS_HAVE_ITER
290 | Py_TPFLAGS_HAVE_ITER
279 #endif
291 #endif
280
292
281 static PyTypeObject lazymanifestEntriesIterator = {
293 static PyTypeObject lazymanifestEntriesIterator = {
282 PyVarObject_HEAD_INIT(NULL, 0) /* header */
294 PyVarObject_HEAD_INIT(NULL, 0) /* header */
283 "parsers.lazymanifest.entriesiterator", /*tp_name */
295 "parsers.lazymanifest.entriesiterator", /*tp_name */
284 sizeof(lmIter), /*tp_basicsize */
296 sizeof(lmIter), /*tp_basicsize */
285 0, /*tp_itemsize */
297 0, /*tp_itemsize */
286 lmiter_dealloc, /*tp_dealloc */
298 lmiter_dealloc, /*tp_dealloc */
287 0, /*tp_print */
299 0, /*tp_print */
288 0, /*tp_getattr */
300 0, /*tp_getattr */
289 0, /*tp_setattr */
301 0, /*tp_setattr */
290 0, /*tp_compare */
302 0, /*tp_compare */
291 0, /*tp_repr */
303 0, /*tp_repr */
292 0, /*tp_as_number */
304 0, /*tp_as_number */
293 0, /*tp_as_sequence */
305 0, /*tp_as_sequence */
294 0, /*tp_as_mapping */
306 0, /*tp_as_mapping */
295 0, /*tp_hash */
307 0, /*tp_hash */
296 0, /*tp_call */
308 0, /*tp_call */
297 0, /*tp_str */
309 0, /*tp_str */
298 0, /*tp_getattro */
310 0, /*tp_getattro */
299 0, /*tp_setattro */
311 0, /*tp_setattro */
300 0, /*tp_as_buffer */
312 0, /*tp_as_buffer */
301 LAZYMANIFESTENTRIESITERATOR_TPFLAGS, /* tp_flags */
313 LAZYMANIFESTENTRIESITERATOR_TPFLAGS, /* tp_flags */
302 "Iterator for 3-tuples in a lazymanifest.", /* tp_doc */
314 "Iterator for 3-tuples in a lazymanifest.", /* tp_doc */
303 0, /* tp_traverse */
315 0, /* tp_traverse */
304 0, /* tp_clear */
316 0, /* tp_clear */
305 0, /* tp_richcompare */
317 0, /* tp_richcompare */
306 0, /* tp_weaklistoffset */
318 0, /* tp_weaklistoffset */
307 PyObject_SelfIter, /* tp_iter: __iter__() method */
319 PyObject_SelfIter, /* tp_iter: __iter__() method */
308 lmiter_iterentriesnext, /* tp_iternext: next() method */
320 lmiter_iterentriesnext, /* tp_iternext: next() method */
309 };
321 };
310
322
311 static PyObject *lmiter_iterkeysnext(PyObject *o)
323 static PyObject *lmiter_iterkeysnext(PyObject *o)
312 {
324 {
313 size_t pl;
325 size_t pl;
314 line *l = lmiter_nextline((lmIter *)o);
326 line *l = lmiter_nextline((lmIter *)o);
315 if (!l) {
327 if (!l) {
316 return NULL;
328 return NULL;
317 }
329 }
318 pl = pathlen(l);
330 pl = pathlen(l);
319 return PyBytes_FromStringAndSize(l->start, pl);
331 return PyBytes_FromStringAndSize(l->start, pl);
320 }
332 }
321
333
322 #ifdef IS_PY3K
334 #ifdef IS_PY3K
323 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
335 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
324 #else
336 #else
325 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
337 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
326 | Py_TPFLAGS_HAVE_ITER
338 | Py_TPFLAGS_HAVE_ITER
327 #endif
339 #endif
328
340
329 static PyTypeObject lazymanifestKeysIterator = {
341 static PyTypeObject lazymanifestKeysIterator = {
330 PyVarObject_HEAD_INIT(NULL, 0) /* header */
342 PyVarObject_HEAD_INIT(NULL, 0) /* header */
331 "parsers.lazymanifest.keysiterator", /*tp_name */
343 "parsers.lazymanifest.keysiterator", /*tp_name */
332 sizeof(lmIter), /*tp_basicsize */
344 sizeof(lmIter), /*tp_basicsize */
333 0, /*tp_itemsize */
345 0, /*tp_itemsize */
334 lmiter_dealloc, /*tp_dealloc */
346 lmiter_dealloc, /*tp_dealloc */
335 0, /*tp_print */
347 0, /*tp_print */
336 0, /*tp_getattr */
348 0, /*tp_getattr */
337 0, /*tp_setattr */
349 0, /*tp_setattr */
338 0, /*tp_compare */
350 0, /*tp_compare */
339 0, /*tp_repr */
351 0, /*tp_repr */
340 0, /*tp_as_number */
352 0, /*tp_as_number */
341 0, /*tp_as_sequence */
353 0, /*tp_as_sequence */
342 0, /*tp_as_mapping */
354 0, /*tp_as_mapping */
343 0, /*tp_hash */
355 0, /*tp_hash */
344 0, /*tp_call */
356 0, /*tp_call */
345 0, /*tp_str */
357 0, /*tp_str */
346 0, /*tp_getattro */
358 0, /*tp_getattro */
347 0, /*tp_setattro */
359 0, /*tp_setattro */
348 0, /*tp_as_buffer */
360 0, /*tp_as_buffer */
349 LAZYMANIFESTKEYSITERATOR_TPFLAGS, /* tp_flags */
361 LAZYMANIFESTKEYSITERATOR_TPFLAGS, /* tp_flags */
350 "Keys iterator for a lazymanifest.", /* tp_doc */
362 "Keys iterator for a lazymanifest.", /* tp_doc */
351 0, /* tp_traverse */
363 0, /* tp_traverse */
352 0, /* tp_clear */
364 0, /* tp_clear */
353 0, /* tp_richcompare */
365 0, /* tp_richcompare */
354 0, /* tp_weaklistoffset */
366 0, /* tp_weaklistoffset */
355 PyObject_SelfIter, /* tp_iter: __iter__() method */
367 PyObject_SelfIter, /* tp_iter: __iter__() method */
356 lmiter_iterkeysnext, /* tp_iternext: next() method */
368 lmiter_iterkeysnext, /* tp_iternext: next() method */
357 };
369 };
358
370
359 static lazymanifest *lazymanifest_copy(lazymanifest *self);
371 static lazymanifest *lazymanifest_copy(lazymanifest *self);
360
372
361 static PyObject *lazymanifest_getentriesiter(lazymanifest *self)
373 static PyObject *lazymanifest_getentriesiter(lazymanifest *self)
362 {
374 {
363 lmIter *i = NULL;
375 lmIter *i = NULL;
364 lazymanifest *t = lazymanifest_copy(self);
376 lazymanifest *t = lazymanifest_copy(self);
365 if (!t) {
377 if (!t) {
366 PyErr_NoMemory();
378 PyErr_NoMemory();
367 return NULL;
379 return NULL;
368 }
380 }
369 i = PyObject_New(lmIter, &lazymanifestEntriesIterator);
381 i = PyObject_New(lmIter, &lazymanifestEntriesIterator);
370 if (i) {
382 if (i) {
371 i->m = t;
383 i->m = t;
372 i->pos = -1;
384 i->pos = -1;
373 } else {
385 } else {
374 Py_DECREF(t);
386 Py_DECREF(t);
375 PyErr_NoMemory();
387 PyErr_NoMemory();
376 }
388 }
377 return (PyObject *)i;
389 return (PyObject *)i;
378 }
390 }
379
391
380 static PyObject *lazymanifest_getkeysiter(lazymanifest *self)
392 static PyObject *lazymanifest_getkeysiter(lazymanifest *self)
381 {
393 {
382 lmIter *i = NULL;
394 lmIter *i = NULL;
383 lazymanifest *t = lazymanifest_copy(self);
395 lazymanifest *t = lazymanifest_copy(self);
384 if (!t) {
396 if (!t) {
385 PyErr_NoMemory();
397 PyErr_NoMemory();
386 return NULL;
398 return NULL;
387 }
399 }
388 i = PyObject_New(lmIter, &lazymanifestKeysIterator);
400 i = PyObject_New(lmIter, &lazymanifestKeysIterator);
389 if (i) {
401 if (i) {
390 i->m = t;
402 i->m = t;
391 i->pos = -1;
403 i->pos = -1;
392 } else {
404 } else {
393 Py_DECREF(t);
405 Py_DECREF(t);
394 PyErr_NoMemory();
406 PyErr_NoMemory();
395 }
407 }
396 return (PyObject *)i;
408 return (PyObject *)i;
397 }
409 }
398
410
399 /* __getitem__ and __setitem__ support */
411 /* __getitem__ and __setitem__ support */
400
412
401 static Py_ssize_t lazymanifest_size(lazymanifest *self)
413 static Py_ssize_t lazymanifest_size(lazymanifest *self)
402 {
414 {
403 return self->livelines;
415 return self->livelines;
404 }
416 }
405
417
406 static int linecmp(const void *left, const void *right)
418 static int linecmp(const void *left, const void *right)
407 {
419 {
408 return strcmp(((const line *)left)->start,
420 return strcmp(((const line *)left)->start,
409 ((const line *)right)->start);
421 ((const line *)right)->start);
410 }
422 }
411
423
412 static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key)
424 static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key)
413 {
425 {
414 line needle;
426 line needle;
415 line *hit;
427 line *hit;
416 if (!PyBytes_Check(key)) {
428 if (!PyBytes_Check(key)) {
417 PyErr_Format(PyExc_TypeError,
429 PyErr_Format(PyExc_TypeError,
418 "getitem: manifest keys must be a string.");
430 "getitem: manifest keys must be a string.");
419 return NULL;
431 return NULL;
420 }
432 }
421 needle.start = PyBytes_AsString(key);
433 needle.start = PyBytes_AsString(key);
422 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
434 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
423 &linecmp);
435 &linecmp);
424 if (!hit || hit->deleted) {
436 if (!hit || hit->deleted) {
425 PyErr_Format(PyExc_KeyError, "No such manifest entry.");
437 PyErr_Format(PyExc_KeyError, "No such manifest entry.");
426 return NULL;
438 return NULL;
427 }
439 }
428 return hashflags(hit);
440 return hashflags(hit);
429 }
441 }
430
442
431 static int lazymanifest_delitem(lazymanifest *self, PyObject *key)
443 static int lazymanifest_delitem(lazymanifest *self, PyObject *key)
432 {
444 {
433 line needle;
445 line needle;
434 line *hit;
446 line *hit;
435 if (!PyBytes_Check(key)) {
447 if (!PyBytes_Check(key)) {
436 PyErr_Format(PyExc_TypeError,
448 PyErr_Format(PyExc_TypeError,
437 "delitem: manifest keys must be a string.");
449 "delitem: manifest keys must be a string.");
438 return -1;
450 return -1;
439 }
451 }
440 needle.start = PyBytes_AsString(key);
452 needle.start = PyBytes_AsString(key);
441 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
453 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
442 &linecmp);
454 &linecmp);
443 if (!hit || hit->deleted) {
455 if (!hit || hit->deleted) {
444 PyErr_Format(PyExc_KeyError,
456 PyErr_Format(PyExc_KeyError,
445 "Tried to delete nonexistent manifest entry.");
457 "Tried to delete nonexistent manifest entry.");
446 return -1;
458 return -1;
447 }
459 }
448 self->dirty = true;
460 self->dirty = true;
449 hit->deleted = true;
461 hit->deleted = true;
450 self->livelines--;
462 self->livelines--;
451 return 0;
463 return 0;
452 }
464 }
453
465
454 /* Do a binary search for the insertion point for new, creating the
466 /* Do a binary search for the insertion point for new, creating the
455 * new entry if needed. */
467 * new entry if needed. */
456 static int internalsetitem(lazymanifest *self, line *new)
468 static int internalsetitem(lazymanifest *self, line *new)
457 {
469 {
458 int start = 0, end = self->numlines;
470 int start = 0, end = self->numlines;
459 while (start < end) {
471 while (start < end) {
460 int pos = start + (end - start) / 2;
472 int pos = start + (end - start) / 2;
461 int c = linecmp(new, self->lines + pos);
473 int c = linecmp(new, self->lines + pos);
462 if (c < 0)
474 if (c < 0)
463 end = pos;
475 end = pos;
464 else if (c > 0)
476 else if (c > 0)
465 start = pos + 1;
477 start = pos + 1;
466 else {
478 else {
467 if (self->lines[pos].deleted)
479 if (self->lines[pos].deleted)
468 self->livelines++;
480 self->livelines++;
469 if (self->lines[pos].from_malloc)
481 if (self->lines[pos].from_malloc)
470 free(self->lines[pos].start);
482 free(self->lines[pos].start);
471 start = pos;
483 start = pos;
472 goto finish;
484 goto finish;
473 }
485 }
474 }
486 }
475 /* being here means we need to do an insert */
487 /* being here means we need to do an insert */
476 if (!realloc_if_full(self)) {
488 if (!realloc_if_full(self)) {
477 PyErr_NoMemory();
489 PyErr_NoMemory();
478 return -1;
490 return -1;
479 }
491 }
480 memmove(self->lines + start + 1, self->lines + start,
492 memmove(self->lines + start + 1, self->lines + start,
481 (self->numlines - start) * sizeof(line));
493 (self->numlines - start) * sizeof(line));
482 self->numlines++;
494 self->numlines++;
483 self->livelines++;
495 self->livelines++;
484 finish:
496 finish:
485 self->lines[start] = *new;
497 self->lines[start] = *new;
486 self->dirty = true;
498 self->dirty = true;
487 return 0;
499 return 0;
488 }
500 }
489
501
490 static int lazymanifest_setitem(
502 static int lazymanifest_setitem(
491 lazymanifest *self, PyObject *key, PyObject *value)
503 lazymanifest *self, PyObject *key, PyObject *value)
492 {
504 {
493 char *path;
505 char *path;
494 Py_ssize_t plen;
506 Py_ssize_t plen;
495 PyObject *pyhash;
507 PyObject *pyhash;
496 Py_ssize_t hlen;
508 Py_ssize_t hlen;
497 char *hash;
509 char *hash;
498 PyObject *pyflags;
510 PyObject *pyflags;
499 char *flags;
511 char *flags;
500 Py_ssize_t flen;
512 Py_ssize_t flen;
501 size_t dlen;
513 size_t dlen;
502 char *dest;
514 char *dest;
503 int i;
515 int i;
504 line new;
516 line new;
505 if (!PyBytes_Check(key)) {
517 if (!PyBytes_Check(key)) {
506 PyErr_Format(PyExc_TypeError,
518 PyErr_Format(PyExc_TypeError,
507 "setitem: manifest keys must be a string.");
519 "setitem: manifest keys must be a string.");
508 return -1;
520 return -1;
509 }
521 }
510 if (!value) {
522 if (!value) {
511 return lazymanifest_delitem(self, key);
523 return lazymanifest_delitem(self, key);
512 }
524 }
513 if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) {
525 if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) {
514 PyErr_Format(PyExc_TypeError,
526 PyErr_Format(PyExc_TypeError,
515 "Manifest values must be a tuple of (node, flags).");
527 "Manifest values must be a tuple of (node, flags).");
516 return -1;
528 return -1;
517 }
529 }
518 if (PyBytes_AsStringAndSize(key, &path, &plen) == -1) {
530 if (PyBytes_AsStringAndSize(key, &path, &plen) == -1) {
519 return -1;
531 return -1;
520 }
532 }
521
533
522 pyhash = PyTuple_GetItem(value, 0);
534 pyhash = PyTuple_GetItem(value, 0);
523 if (!PyBytes_Check(pyhash)) {
535 if (!PyBytes_Check(pyhash)) {
524 PyErr_Format(PyExc_TypeError,
536 PyErr_Format(PyExc_TypeError,
525 "node must be a 20-byte string");
537 "node must be a 20-byte string");
526 return -1;
538 return -1;
527 }
539 }
528 hlen = PyBytes_Size(pyhash);
540 hlen = PyBytes_Size(pyhash);
529 /* Some parts of the codebase try and set 21 or 22
541 /* Some parts of the codebase try and set 21 or 22
530 * byte "hash" values in order to perturb things for
542 * byte "hash" values in order to perturb things for
531 * status. We have to preserve at least the 21st
543 * status. We have to preserve at least the 21st
532 * byte. Sigh. If there's a 22nd byte, we drop it on
544 * byte. Sigh. If there's a 22nd byte, we drop it on
533 * the floor, which works fine.
545 * the floor, which works fine.
534 */
546 */
535 if (hlen != 20 && hlen != 21 && hlen != 22) {
547 if (hlen != 20 && hlen != 21 && hlen != 22) {
536 PyErr_Format(PyExc_TypeError,
548 PyErr_Format(PyExc_TypeError,
537 "node must be a 20-byte string");
549 "node must be a 20-byte string");
538 return -1;
550 return -1;
539 }
551 }
540 hash = PyBytes_AsString(pyhash);
552 hash = PyBytes_AsString(pyhash);
541
553
542 pyflags = PyTuple_GetItem(value, 1);
554 pyflags = PyTuple_GetItem(value, 1);
543 if (!PyBytes_Check(pyflags) || PyBytes_Size(pyflags) > 1) {
555 if (!PyBytes_Check(pyflags) || PyBytes_Size(pyflags) > 1) {
544 PyErr_Format(PyExc_TypeError,
556 PyErr_Format(PyExc_TypeError,
545 "flags must a 0 or 1 byte string");
557 "flags must a 0 or 1 byte string");
546 return -1;
558 return -1;
547 }
559 }
548 if (PyBytes_AsStringAndSize(pyflags, &flags, &flen) == -1) {
560 if (PyBytes_AsStringAndSize(pyflags, &flags, &flen) == -1) {
549 return -1;
561 return -1;
550 }
562 }
551 /* one null byte and one newline */
563 /* one null byte and one newline */
552 dlen = plen + 41 + flen + 1;
564 dlen = plen + 41 + flen + 1;
553 dest = malloc(dlen);
565 dest = malloc(dlen);
554 if (!dest) {
566 if (!dest) {
555 PyErr_NoMemory();
567 PyErr_NoMemory();
556 return -1;
568 return -1;
557 }
569 }
558 memcpy(dest, path, plen + 1);
570 memcpy(dest, path, plen + 1);
559 for (i = 0; i < 20; i++) {
571 for (i = 0; i < 20; i++) {
560 /* Cast to unsigned, so it will not get sign-extended when promoted
572 /* Cast to unsigned, so it will not get sign-extended when promoted
561 * to int (as is done when passing to a variadic function)
573 * to int (as is done when passing to a variadic function)
562 */
574 */
563 sprintf(dest + plen + 1 + (i * 2), "%02x", (unsigned char)hash[i]);
575 sprintf(dest + plen + 1 + (i * 2), "%02x", (unsigned char)hash[i]);
564 }
576 }
565 memcpy(dest + plen + 41, flags, flen);
577 memcpy(dest + plen + 41, flags, flen);
566 dest[plen + 41 + flen] = '\n';
578 dest[plen + 41 + flen] = '\n';
567 new.start = dest;
579 new.start = dest;
568 new.len = dlen;
580 new.len = dlen;
569 new.hash_suffix = '\0';
581 new.hash_suffix = '\0';
570 if (hlen > 20) {
582 if (hlen > 20) {
571 new.hash_suffix = hash[20];
583 new.hash_suffix = hash[20];
572 }
584 }
573 new.from_malloc = true; /* is `start` a pointer we allocated? */
585 new.from_malloc = true; /* is `start` a pointer we allocated? */
574 new.deleted = false; /* is this entry deleted? */
586 new.deleted = false; /* is this entry deleted? */
575 if (internalsetitem(self, &new)) {
587 if (internalsetitem(self, &new)) {
576 return -1;
588 return -1;
577 }
589 }
578 return 0;
590 return 0;
579 }
591 }
580
592
581 static PyMappingMethods lazymanifest_mapping_methods = {
593 static PyMappingMethods lazymanifest_mapping_methods = {
582 (lenfunc)lazymanifest_size, /* mp_length */
594 (lenfunc)lazymanifest_size, /* mp_length */
583 (binaryfunc)lazymanifest_getitem, /* mp_subscript */
595 (binaryfunc)lazymanifest_getitem, /* mp_subscript */
584 (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */
596 (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */
585 };
597 };
586
598
587 /* sequence methods (important or __contains__ builds an iterator) */
599 /* sequence methods (important or __contains__ builds an iterator) */
588
600
589 static int lazymanifest_contains(lazymanifest *self, PyObject *key)
601 static int lazymanifest_contains(lazymanifest *self, PyObject *key)
590 {
602 {
591 line needle;
603 line needle;
592 line *hit;
604 line *hit;
593 if (!PyBytes_Check(key)) {
605 if (!PyBytes_Check(key)) {
594 /* Our keys are always strings, so if the contains
606 /* Our keys are always strings, so if the contains
595 * check is for a non-string, just return false. */
607 * check is for a non-string, just return false. */
596 return 0;
608 return 0;
597 }
609 }
598 needle.start = PyBytes_AsString(key);
610 needle.start = PyBytes_AsString(key);
599 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
611 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
600 &linecmp);
612 &linecmp);
601 if (!hit || hit->deleted) {
613 if (!hit || hit->deleted) {
602 return 0;
614 return 0;
603 }
615 }
604 return 1;
616 return 1;
605 }
617 }
606
618
607 static PySequenceMethods lazymanifest_seq_meths = {
619 static PySequenceMethods lazymanifest_seq_meths = {
608 (lenfunc)lazymanifest_size, /* sq_length */
620 (lenfunc)lazymanifest_size, /* sq_length */
609 0, /* sq_concat */
621 0, /* sq_concat */
610 0, /* sq_repeat */
622 0, /* sq_repeat */
611 0, /* sq_item */
623 0, /* sq_item */
612 0, /* sq_slice */
624 0, /* sq_slice */
613 0, /* sq_ass_item */
625 0, /* sq_ass_item */
614 0, /* sq_ass_slice */
626 0, /* sq_ass_slice */
615 (objobjproc)lazymanifest_contains, /* sq_contains */
627 (objobjproc)lazymanifest_contains, /* sq_contains */
616 0, /* sq_inplace_concat */
628 0, /* sq_inplace_concat */
617 0, /* sq_inplace_repeat */
629 0, /* sq_inplace_repeat */
618 };
630 };
619
631
620
632
621 /* Other methods (copy, diff, etc) */
633 /* Other methods (copy, diff, etc) */
622 static PyTypeObject lazymanifestType;
634 static PyTypeObject lazymanifestType;
623
635
624 /* If the manifest has changes, build the new manifest text and reindex it. */
636 /* If the manifest has changes, build the new manifest text and reindex it. */
625 static int compact(lazymanifest *self)
637 static int compact(lazymanifest *self)
626 {
638 {
627 int i;
639 int i;
628 ssize_t need = 0;
640 ssize_t need = 0;
629 char *data;
641 char *data;
630 line *src, *dst;
642 line *src, *dst;
631 PyObject *pydata;
643 PyObject *pydata;
632 if (!self->dirty)
644 if (!self->dirty)
633 return 0;
645 return 0;
634 for (i = 0; i < self->numlines; i++) {
646 for (i = 0; i < self->numlines; i++) {
635 if (!self->lines[i].deleted) {
647 if (!self->lines[i].deleted) {
636 need += self->lines[i].len;
648 need += self->lines[i].len;
637 }
649 }
638 }
650 }
639 pydata = PyBytes_FromStringAndSize(NULL, need);
651 pydata = PyBytes_FromStringAndSize(NULL, need);
640 if (!pydata)
652 if (!pydata)
641 return -1;
653 return -1;
642 data = PyBytes_AsString(pydata);
654 data = PyBytes_AsString(pydata);
643 if (!data) {
655 if (!data) {
644 return -1;
656 return -1;
645 }
657 }
646 src = self->lines;
658 src = self->lines;
647 dst = self->lines;
659 dst = self->lines;
648 for (i = 0; i < self->numlines; i++, src++) {
660 for (i = 0; i < self->numlines; i++, src++) {
649 char *tofree = NULL;
661 char *tofree = NULL;
650 if (src->from_malloc) {
662 if (src->from_malloc) {
651 tofree = src->start;
663 tofree = src->start;
652 }
664 }
653 if (!src->deleted) {
665 if (!src->deleted) {
654 memcpy(data, src->start, src->len);
666 memcpy(data, src->start, src->len);
655 *dst = *src;
667 *dst = *src;
656 dst->start = data;
668 dst->start = data;
657 dst->from_malloc = false;
669 dst->from_malloc = false;
658 data += dst->len;
670 data += dst->len;
659 dst++;
671 dst++;
660 }
672 }
661 free(tofree);
673 free(tofree);
662 }
674 }
663 Py_DECREF(self->pydata);
675 Py_DECREF(self->pydata);
664 self->pydata = pydata;
676 self->pydata = pydata;
665 self->numlines = self->livelines;
677 self->numlines = self->livelines;
666 self->dirty = false;
678 self->dirty = false;
667 return 0;
679 return 0;
668 }
680 }
669
681
670 static PyObject *lazymanifest_text(lazymanifest *self)
682 static PyObject *lazymanifest_text(lazymanifest *self)
671 {
683 {
672 if (compact(self) != 0) {
684 if (compact(self) != 0) {
673 PyErr_NoMemory();
685 PyErr_NoMemory();
674 return NULL;
686 return NULL;
675 }
687 }
676 Py_INCREF(self->pydata);
688 Py_INCREF(self->pydata);
677 return self->pydata;
689 return self->pydata;
678 }
690 }
679
691
680 static lazymanifest *lazymanifest_copy(lazymanifest *self)
692 static lazymanifest *lazymanifest_copy(lazymanifest *self)
681 {
693 {
682 lazymanifest *copy = NULL;
694 lazymanifest *copy = NULL;
683 if (compact(self) != 0) {
695 if (compact(self) != 0) {
684 goto nomem;
696 goto nomem;
685 }
697 }
686 copy = PyObject_New(lazymanifest, &lazymanifestType);
698 copy = PyObject_New(lazymanifest, &lazymanifestType);
687 if (!copy) {
699 if (!copy) {
688 goto nomem;
700 goto nomem;
689 }
701 }
690 lazymanifest_init_early(copy);
702 lazymanifest_init_early(copy);
691 copy->numlines = self->numlines;
703 copy->numlines = self->numlines;
692 copy->livelines = self->livelines;
704 copy->livelines = self->livelines;
693 copy->dirty = false;
705 copy->dirty = false;
694 copy->lines = malloc(self->maxlines *sizeof(line));
706 copy->lines = malloc(self->maxlines *sizeof(line));
695 if (!copy->lines) {
707 if (!copy->lines) {
696 goto nomem;
708 goto nomem;
697 }
709 }
698 memcpy(copy->lines, self->lines, self->numlines * sizeof(line));
710 memcpy(copy->lines, self->lines, self->numlines * sizeof(line));
699 copy->maxlines = self->maxlines;
711 copy->maxlines = self->maxlines;
700 copy->pydata = self->pydata;
712 copy->pydata = self->pydata;
701 Py_INCREF(copy->pydata);
713 Py_INCREF(copy->pydata);
702 return copy;
714 return copy;
703 nomem:
715 nomem:
704 PyErr_NoMemory();
716 PyErr_NoMemory();
705 Py_XDECREF(copy);
717 Py_XDECREF(copy);
706 return NULL;
718 return NULL;
707 }
719 }
708
720
709 static lazymanifest *lazymanifest_filtercopy(
721 static lazymanifest *lazymanifest_filtercopy(
710 lazymanifest *self, PyObject *matchfn)
722 lazymanifest *self, PyObject *matchfn)
711 {
723 {
712 lazymanifest *copy = NULL;
724 lazymanifest *copy = NULL;
713 int i;
725 int i;
714 if (!PyCallable_Check(matchfn)) {
726 if (!PyCallable_Check(matchfn)) {
715 PyErr_SetString(PyExc_TypeError, "matchfn must be callable");
727 PyErr_SetString(PyExc_TypeError, "matchfn must be callable");
716 return NULL;
728 return NULL;
717 }
729 }
718 /* compact ourselves first to avoid double-frees later when we
730 /* compact ourselves first to avoid double-frees later when we
719 * compact tmp so that it doesn't have random pointers to our
731 * compact tmp so that it doesn't have random pointers to our
720 * underlying from_malloc-data (self->pydata is safe) */
732 * underlying from_malloc-data (self->pydata is safe) */
721 if (compact(self) != 0) {
733 if (compact(self) != 0) {
722 goto nomem;
734 goto nomem;
723 }
735 }
724 copy = PyObject_New(lazymanifest, &lazymanifestType);
736 copy = PyObject_New(lazymanifest, &lazymanifestType);
725 if (!copy) {
737 if (!copy) {
726 goto nomem;
738 goto nomem;
727 }
739 }
728 lazymanifest_init_early(copy);
740 lazymanifest_init_early(copy);
729 copy->dirty = true;
741 copy->dirty = true;
730 copy->lines = malloc(self->maxlines * sizeof(line));
742 copy->lines = malloc(self->maxlines * sizeof(line));
731 if (!copy->lines) {
743 if (!copy->lines) {
732 goto nomem;
744 goto nomem;
733 }
745 }
734 copy->maxlines = self->maxlines;
746 copy->maxlines = self->maxlines;
735 copy->numlines = 0;
747 copy->numlines = 0;
736 copy->pydata = self->pydata;
748 copy->pydata = self->pydata;
737 Py_INCREF(copy->pydata);
749 Py_INCREF(copy->pydata);
738 for (i = 0; i < self->numlines; i++) {
750 for (i = 0; i < self->numlines; i++) {
739 PyObject *arglist = NULL, *result = NULL;
751 PyObject *arglist = NULL, *result = NULL;
740 arglist = Py_BuildValue(PY23("(s)", "(y)"),
752 arglist = Py_BuildValue(PY23("(s)", "(y)"),
741 self->lines[i].start);
753 self->lines[i].start);
742 if (!arglist) {
754 if (!arglist) {
743 goto bail;
755 goto bail;
744 }
756 }
745 result = PyObject_CallObject(matchfn, arglist);
757 result = PyObject_CallObject(matchfn, arglist);
746 Py_DECREF(arglist);
758 Py_DECREF(arglist);
747 /* if the callback raised an exception, just let it
759 /* if the callback raised an exception, just let it
748 * through and give up */
760 * through and give up */
749 if (!result) {
761 if (!result) {
750 goto bail;
762 goto bail;
751 }
763 }
752 if (PyObject_IsTrue(result)) {
764 if (PyObject_IsTrue(result)) {
753 assert(!(self->lines[i].from_malloc));
765 assert(!(self->lines[i].from_malloc));
754 copy->lines[copy->numlines++] = self->lines[i];
766 copy->lines[copy->numlines++] = self->lines[i];
755 }
767 }
756 Py_DECREF(result);
768 Py_DECREF(result);
757 }
769 }
758 copy->livelines = copy->numlines;
770 copy->livelines = copy->numlines;
759 return copy;
771 return copy;
760 nomem:
772 nomem:
761 PyErr_NoMemory();
773 PyErr_NoMemory();
762 bail:
774 bail:
763 Py_XDECREF(copy);
775 Py_XDECREF(copy);
764 return NULL;
776 return NULL;
765 }
777 }
766
778
767 static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args)
779 static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args)
768 {
780 {
769 lazymanifest *other;
781 lazymanifest *other;
770 PyObject *pyclean = NULL;
782 PyObject *pyclean = NULL;
771 bool listclean;
783 bool listclean;
772 PyObject *emptyTup = NULL, *ret = NULL;
784 PyObject *emptyTup = NULL, *ret = NULL;
773 PyObject *es;
785 PyObject *es;
774 int sneedle = 0, oneedle = 0;
786 int sneedle = 0, oneedle = 0;
775 if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) {
787 if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) {
776 return NULL;
788 return NULL;
777 }
789 }
778 listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean);
790 listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean);
779 es = PyBytes_FromString("");
791 es = PyBytes_FromString("");
780 if (!es) {
792 if (!es) {
781 goto nomem;
793 goto nomem;
782 }
794 }
783 emptyTup = PyTuple_Pack(2, Py_None, es);
795 emptyTup = PyTuple_Pack(2, Py_None, es);
784 Py_DECREF(es);
796 Py_DECREF(es);
785 if (!emptyTup) {
797 if (!emptyTup) {
786 goto nomem;
798 goto nomem;
787 }
799 }
788 ret = PyDict_New();
800 ret = PyDict_New();
789 if (!ret) {
801 if (!ret) {
790 goto nomem;
802 goto nomem;
791 }
803 }
792 while (sneedle != self->numlines || oneedle != other->numlines) {
804 while (sneedle != self->numlines || oneedle != other->numlines) {
793 line *left = self->lines + sneedle;
805 line *left = self->lines + sneedle;
794 line *right = other->lines + oneedle;
806 line *right = other->lines + oneedle;
795 int result;
807 int result;
796 PyObject *key;
808 PyObject *key;
797 PyObject *outer;
809 PyObject *outer;
798 /* If we're looking at a deleted entry and it's not
810 /* If we're looking at a deleted entry and it's not
799 * the end of the manifest, just skip it. */
811 * the end of the manifest, just skip it. */
800 if (sneedle < self->numlines && left->deleted) {
812 if (sneedle < self->numlines && left->deleted) {
801 sneedle++;
813 sneedle++;
802 continue;
814 continue;
803 }
815 }
804 if (oneedle < other->numlines && right->deleted) {
816 if (oneedle < other->numlines && right->deleted) {
805 oneedle++;
817 oneedle++;
806 continue;
818 continue;
807 }
819 }
808 /* if we're at the end of either manifest, then we
820 /* if we're at the end of either manifest, then we
809 * know the remaining items are adds so we can skip
821 * know the remaining items are adds so we can skip
810 * the strcmp. */
822 * the strcmp. */
811 if (sneedle == self->numlines) {
823 if (sneedle == self->numlines) {
812 result = 1;
824 result = 1;
813 } else if (oneedle == other->numlines) {
825 } else if (oneedle == other->numlines) {
814 result = -1;
826 result = -1;
815 } else {
827 } else {
816 result = linecmp(left, right);
828 result = linecmp(left, right);
817 }
829 }
818 key = result <= 0 ?
830 key = result <= 0 ?
819 PyBytes_FromString(left->start) :
831 PyBytes_FromString(left->start) :
820 PyBytes_FromString(right->start);
832 PyBytes_FromString(right->start);
821 if (!key)
833 if (!key)
822 goto nomem;
834 goto nomem;
823 if (result < 0) {
835 if (result < 0) {
824 PyObject *l = hashflags(left);
836 PyObject *l = hashflags(left);
825 if (!l) {
837 if (!l) {
826 goto nomem;
838 goto nomem;
827 }
839 }
828 outer = PyTuple_Pack(2, l, emptyTup);
840 outer = PyTuple_Pack(2, l, emptyTup);
829 Py_DECREF(l);
841 Py_DECREF(l);
830 if (!outer) {
842 if (!outer) {
831 goto nomem;
843 goto nomem;
832 }
844 }
833 PyDict_SetItem(ret, key, outer);
845 PyDict_SetItem(ret, key, outer);
834 Py_DECREF(outer);
846 Py_DECREF(outer);
835 sneedle++;
847 sneedle++;
836 } else if (result > 0) {
848 } else if (result > 0) {
837 PyObject *r = hashflags(right);
849 PyObject *r = hashflags(right);
838 if (!r) {
850 if (!r) {
839 goto nomem;
851 goto nomem;
840 }
852 }
841 outer = PyTuple_Pack(2, emptyTup, r);
853 outer = PyTuple_Pack(2, emptyTup, r);
842 Py_DECREF(r);
854 Py_DECREF(r);
843 if (!outer) {
855 if (!outer) {
844 goto nomem;
856 goto nomem;
845 }
857 }
846 PyDict_SetItem(ret, key, outer);
858 PyDict_SetItem(ret, key, outer);
847 Py_DECREF(outer);
859 Py_DECREF(outer);
848 oneedle++;
860 oneedle++;
849 } else {
861 } else {
850 /* file exists in both manifests */
862 /* file exists in both manifests */
851 if (left->len != right->len
863 if (left->len != right->len
852 || memcmp(left->start, right->start, left->len)
864 || memcmp(left->start, right->start, left->len)
853 || left->hash_suffix != right->hash_suffix) {
865 || left->hash_suffix != right->hash_suffix) {
854 PyObject *l = hashflags(left);
866 PyObject *l = hashflags(left);
855 PyObject *r;
867 PyObject *r;
856 if (!l) {
868 if (!l) {
857 goto nomem;
869 goto nomem;
858 }
870 }
859 r = hashflags(right);
871 r = hashflags(right);
860 if (!r) {
872 if (!r) {
861 Py_DECREF(l);
873 Py_DECREF(l);
862 goto nomem;
874 goto nomem;
863 }
875 }
864 outer = PyTuple_Pack(2, l, r);
876 outer = PyTuple_Pack(2, l, r);
865 Py_DECREF(l);
877 Py_DECREF(l);
866 Py_DECREF(r);
878 Py_DECREF(r);
867 if (!outer) {
879 if (!outer) {
868 goto nomem;
880 goto nomem;
869 }
881 }
870 PyDict_SetItem(ret, key, outer);
882 PyDict_SetItem(ret, key, outer);
871 Py_DECREF(outer);
883 Py_DECREF(outer);
872 } else if (listclean) {
884 } else if (listclean) {
873 PyDict_SetItem(ret, key, Py_None);
885 PyDict_SetItem(ret, key, Py_None);
874 }
886 }
875 sneedle++;
887 sneedle++;
876 oneedle++;
888 oneedle++;
877 }
889 }
878 Py_DECREF(key);
890 Py_DECREF(key);
879 }
891 }
880 Py_DECREF(emptyTup);
892 Py_DECREF(emptyTup);
881 return ret;
893 return ret;
882 nomem:
894 nomem:
883 PyErr_NoMemory();
895 PyErr_NoMemory();
884 Py_XDECREF(ret);
896 Py_XDECREF(ret);
885 Py_XDECREF(emptyTup);
897 Py_XDECREF(emptyTup);
886 return NULL;
898 return NULL;
887 }
899 }
888
900
889 static PyMethodDef lazymanifest_methods[] = {
901 static PyMethodDef lazymanifest_methods[] = {
890 {"iterkeys", (PyCFunction)lazymanifest_getkeysiter, METH_NOARGS,
902 {"iterkeys", (PyCFunction)lazymanifest_getkeysiter, METH_NOARGS,
891 "Iterate over file names in this lazymanifest."},
903 "Iterate over file names in this lazymanifest."},
892 {"iterentries", (PyCFunction)lazymanifest_getentriesiter, METH_NOARGS,
904 {"iterentries", (PyCFunction)lazymanifest_getentriesiter, METH_NOARGS,
893 "Iterate over (path, nodeid, flags) tuples in this lazymanifest."},
905 "Iterate over (path, nodeid, flags) tuples in this lazymanifest."},
894 {"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS,
906 {"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS,
895 "Make a copy of this lazymanifest."},
907 "Make a copy of this lazymanifest."},
896 {"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O,
908 {"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O,
897 "Make a copy of this manifest filtered by matchfn."},
909 "Make a copy of this manifest filtered by matchfn."},
898 {"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS,
910 {"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS,
899 "Compare this lazymanifest to another one."},
911 "Compare this lazymanifest to another one."},
900 {"text", (PyCFunction)lazymanifest_text, METH_NOARGS,
912 {"text", (PyCFunction)lazymanifest_text, METH_NOARGS,
901 "Encode this manifest to text."},
913 "Encode this manifest to text."},
902 {NULL},
914 {NULL},
903 };
915 };
904
916
905 #ifdef IS_PY3K
917 #ifdef IS_PY3K
906 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT
918 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT
907 #else
919 #else
908 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN
920 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN
909 #endif
921 #endif
910
922
911 static PyTypeObject lazymanifestType = {
923 static PyTypeObject lazymanifestType = {
912 PyVarObject_HEAD_INIT(NULL, 0) /* header */
924 PyVarObject_HEAD_INIT(NULL, 0) /* header */
913 "parsers.lazymanifest", /* tp_name */
925 "parsers.lazymanifest", /* tp_name */
914 sizeof(lazymanifest), /* tp_basicsize */
926 sizeof(lazymanifest), /* tp_basicsize */
915 0, /* tp_itemsize */
927 0, /* tp_itemsize */
916 (destructor)lazymanifest_dealloc, /* tp_dealloc */
928 (destructor)lazymanifest_dealloc, /* tp_dealloc */
917 0, /* tp_print */
929 0, /* tp_print */
918 0, /* tp_getattr */
930 0, /* tp_getattr */
919 0, /* tp_setattr */
931 0, /* tp_setattr */
920 0, /* tp_compare */
932 0, /* tp_compare */
921 0, /* tp_repr */
933 0, /* tp_repr */
922 0, /* tp_as_number */
934 0, /* tp_as_number */
923 &lazymanifest_seq_meths, /* tp_as_sequence */
935 &lazymanifest_seq_meths, /* tp_as_sequence */
924 &lazymanifest_mapping_methods, /* tp_as_mapping */
936 &lazymanifest_mapping_methods, /* tp_as_mapping */
925 0, /* tp_hash */
937 0, /* tp_hash */
926 0, /* tp_call */
938 0, /* tp_call */
927 0, /* tp_str */
939 0, /* tp_str */
928 0, /* tp_getattro */
940 0, /* tp_getattro */
929 0, /* tp_setattro */
941 0, /* tp_setattro */
930 0, /* tp_as_buffer */
942 0, /* tp_as_buffer */
931 LAZYMANIFEST_TPFLAGS, /* tp_flags */
943 LAZYMANIFEST_TPFLAGS, /* tp_flags */
932 "TODO(augie)", /* tp_doc */
944 "TODO(augie)", /* tp_doc */
933 0, /* tp_traverse */
945 0, /* tp_traverse */
934 0, /* tp_clear */
946 0, /* tp_clear */
935 0, /* tp_richcompare */
947 0, /* tp_richcompare */
936 0, /* tp_weaklistoffset */
948 0, /* tp_weaklistoffset */
937 (getiterfunc)lazymanifest_getkeysiter, /* tp_iter */
949 (getiterfunc)lazymanifest_getkeysiter, /* tp_iter */
938 0, /* tp_iternext */
950 0, /* tp_iternext */
939 lazymanifest_methods, /* tp_methods */
951 lazymanifest_methods, /* tp_methods */
940 0, /* tp_members */
952 0, /* tp_members */
941 0, /* tp_getset */
953 0, /* tp_getset */
942 0, /* tp_base */
954 0, /* tp_base */
943 0, /* tp_dict */
955 0, /* tp_dict */
944 0, /* tp_descr_get */
956 0, /* tp_descr_get */
945 0, /* tp_descr_set */
957 0, /* tp_descr_set */
946 0, /* tp_dictoffset */
958 0, /* tp_dictoffset */
947 (initproc)lazymanifest_init, /* tp_init */
959 (initproc)lazymanifest_init, /* tp_init */
948 0, /* tp_alloc */
960 0, /* tp_alloc */
949 };
961 };
950
962
951 void manifest_module_init(PyObject * mod)
963 void manifest_module_init(PyObject * mod)
952 {
964 {
953 lazymanifestType.tp_new = PyType_GenericNew;
965 lazymanifestType.tp_new = PyType_GenericNew;
954 if (PyType_Ready(&lazymanifestType) < 0)
966 if (PyType_Ready(&lazymanifestType) < 0)
955 return;
967 return;
956 Py_INCREF(&lazymanifestType);
968 Py_INCREF(&lazymanifestType);
957
969
958 PyModule_AddObject(mod, "lazymanifest",
970 PyModule_AddObject(mod, "lazymanifest",
959 (PyObject *)&lazymanifestType);
971 (PyObject *)&lazymanifestType);
960 }
972 }
@@ -1,422 +1,446
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import binascii
3 import binascii
4 import itertools
4 import itertools
5 import silenttestrunner
5 import silenttestrunner
6 import unittest
6 import unittest
7 import zlib
7
8
8 from mercurial import (
9 from mercurial import (
9 manifest as manifestmod,
10 manifest as manifestmod,
10 match as matchmod,
11 match as matchmod,
11 )
12 )
12
13
13 EMTPY_MANIFEST = b''
14 EMTPY_MANIFEST = b''
14
15
15 HASH_1 = b'1' * 40
16 HASH_1 = b'1' * 40
16 BIN_HASH_1 = binascii.unhexlify(HASH_1)
17 BIN_HASH_1 = binascii.unhexlify(HASH_1)
17 HASH_2 = b'f' * 40
18 HASH_2 = b'f' * 40
18 BIN_HASH_2 = binascii.unhexlify(HASH_2)
19 BIN_HASH_2 = binascii.unhexlify(HASH_2)
19 HASH_3 = b'1234567890abcdef0987654321deadbeef0fcafe'
20 HASH_3 = b'1234567890abcdef0987654321deadbeef0fcafe'
20 BIN_HASH_3 = binascii.unhexlify(HASH_3)
21 BIN_HASH_3 = binascii.unhexlify(HASH_3)
21 A_SHORT_MANIFEST = (
22 A_SHORT_MANIFEST = (
22 b'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
23 b'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
23 b'foo\0%(hash1)s%(flag1)s\n'
24 b'foo\0%(hash1)s%(flag1)s\n'
24 ) % {b'hash1': HASH_1,
25 ) % {b'hash1': HASH_1,
25 b'flag1': b'',
26 b'flag1': b'',
26 b'hash2': HASH_2,
27 b'hash2': HASH_2,
27 b'flag2': b'l',
28 b'flag2': b'l',
28 }
29 }
29
30
30 A_DEEPER_MANIFEST = (
31 A_DEEPER_MANIFEST = (
31 b'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
32 b'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
32 b'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
33 b'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
33 b'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
34 b'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
34 b'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
35 b'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
35 b'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
36 b'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
36 b'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
37 b'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
37 b'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
38 b'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
38 b'a/b/dog.py\0%(hash3)s%(flag1)s\n'
39 b'a/b/dog.py\0%(hash3)s%(flag1)s\n'
39 b'a/b/fish.py\0%(hash2)s%(flag1)s\n'
40 b'a/b/fish.py\0%(hash2)s%(flag1)s\n'
40 b'a/c/london.py\0%(hash3)s%(flag2)s\n'
41 b'a/c/london.py\0%(hash3)s%(flag2)s\n'
41 b'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
42 b'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
42 b'a/c/paris.py\0%(hash2)s%(flag1)s\n'
43 b'a/c/paris.py\0%(hash2)s%(flag1)s\n'
43 b'a/d/apple.py\0%(hash3)s%(flag1)s\n'
44 b'a/d/apple.py\0%(hash3)s%(flag1)s\n'
44 b'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
45 b'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
45 b'a/green.py\0%(hash1)s%(flag2)s\n'
46 b'a/green.py\0%(hash1)s%(flag2)s\n'
46 b'a/purple.py\0%(hash2)s%(flag1)s\n'
47 b'a/purple.py\0%(hash2)s%(flag1)s\n'
47 b'app.py\0%(hash3)s%(flag1)s\n'
48 b'app.py\0%(hash3)s%(flag1)s\n'
48 b'readme.txt\0%(hash2)s%(flag1)s\n'
49 b'readme.txt\0%(hash2)s%(flag1)s\n'
49 ) % {b'hash1': HASH_1,
50 ) % {b'hash1': HASH_1,
50 b'flag1': b'',
51 b'flag1': b'',
51 b'hash2': HASH_2,
52 b'hash2': HASH_2,
52 b'flag2': b'l',
53 b'flag2': b'l',
53 b'hash3': HASH_3,
54 b'hash3': HASH_3,
54 }
55 }
55
56
56 HUGE_MANIFEST_ENTRIES = 200001
57 HUGE_MANIFEST_ENTRIES = 200001
57
58
58 izip = getattr(itertools, 'izip', zip)
59 izip = getattr(itertools, 'izip', zip)
59 if 'xrange' not in globals():
60 if 'xrange' not in globals():
60 xrange = range
61 xrange = range
61
62
62 A_HUGE_MANIFEST = b''.join(sorted(
63 A_HUGE_MANIFEST = b''.join(sorted(
63 b'file%d\0%s%s\n' % (i, h, f) for i, h, f in
64 b'file%d\0%s%s\n' % (i, h, f) for i, h, f in
64 izip(xrange(200001),
65 izip(xrange(200001),
65 itertools.cycle((HASH_1, HASH_2)),
66 itertools.cycle((HASH_1, HASH_2)),
66 itertools.cycle((b'', b'x', b'l')))))
67 itertools.cycle((b'', b'x', b'l')))))
67
68
68 class basemanifesttests(object):
69 class basemanifesttests(object):
69 def parsemanifest(self, text):
70 def parsemanifest(self, text):
70 raise NotImplementedError('parsemanifest not implemented by test case')
71 raise NotImplementedError('parsemanifest not implemented by test case')
71
72
72 def testEmptyManifest(self):
73 def testEmptyManifest(self):
73 m = self.parsemanifest(EMTPY_MANIFEST)
74 m = self.parsemanifest(EMTPY_MANIFEST)
74 self.assertEqual(0, len(m))
75 self.assertEqual(0, len(m))
75 self.assertEqual([], list(m))
76 self.assertEqual([], list(m))
76
77
77 def testManifest(self):
78 def testManifest(self):
78 m = self.parsemanifest(A_SHORT_MANIFEST)
79 m = self.parsemanifest(A_SHORT_MANIFEST)
79 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m))
80 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m))
80 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py'])
81 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py'])
81 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
82 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
82 self.assertEqual(BIN_HASH_1, m[b'foo'])
83 self.assertEqual(BIN_HASH_1, m[b'foo'])
83 self.assertEqual(b'', m.flags(b'foo'))
84 self.assertEqual(b'', m.flags(b'foo'))
84 with self.assertRaises(KeyError):
85 with self.assertRaises(KeyError):
85 m[b'wat']
86 m[b'wat']
86
87
87 def testSetItem(self):
88 def testSetItem(self):
88 want = BIN_HASH_1
89 want = BIN_HASH_1
89
90
90 m = self.parsemanifest(EMTPY_MANIFEST)
91 m = self.parsemanifest(EMTPY_MANIFEST)
91 m[b'a'] = want
92 m[b'a'] = want
92 self.assertIn(b'a', m)
93 self.assertIn(b'a', m)
93 self.assertEqual(want, m[b'a'])
94 self.assertEqual(want, m[b'a'])
94 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text())
95 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text())
95
96
96 m = self.parsemanifest(A_SHORT_MANIFEST)
97 m = self.parsemanifest(A_SHORT_MANIFEST)
97 m[b'a'] = want
98 m[b'a'] = want
98 self.assertEqual(want, m[b'a'])
99 self.assertEqual(want, m[b'a'])
99 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST,
100 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST,
100 m.text())
101 m.text())
101
102
102 def testSetFlag(self):
103 def testSetFlag(self):
103 want = b'x'
104 want = b'x'
104
105
105 m = self.parsemanifest(EMTPY_MANIFEST)
106 m = self.parsemanifest(EMTPY_MANIFEST)
106 # first add a file; a file-less flag makes no sense
107 # first add a file; a file-less flag makes no sense
107 m[b'a'] = BIN_HASH_1
108 m[b'a'] = BIN_HASH_1
108 m.setflag(b'a', want)
109 m.setflag(b'a', want)
109 self.assertEqual(want, m.flags(b'a'))
110 self.assertEqual(want, m.flags(b'a'))
110 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text())
111 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text())
111
112
112 m = self.parsemanifest(A_SHORT_MANIFEST)
113 m = self.parsemanifest(A_SHORT_MANIFEST)
113 # first add a file; a file-less flag makes no sense
114 # first add a file; a file-less flag makes no sense
114 m[b'a'] = BIN_HASH_1
115 m[b'a'] = BIN_HASH_1
115 m.setflag(b'a', want)
116 m.setflag(b'a', want)
116 self.assertEqual(want, m.flags(b'a'))
117 self.assertEqual(want, m.flags(b'a'))
117 self.assertEqual(b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST,
118 self.assertEqual(b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST,
118 m.text())
119 m.text())
119
120
120 def testCopy(self):
121 def testCopy(self):
121 m = self.parsemanifest(A_SHORT_MANIFEST)
122 m = self.parsemanifest(A_SHORT_MANIFEST)
122 m[b'a'] = BIN_HASH_1
123 m[b'a'] = BIN_HASH_1
123 m2 = m.copy()
124 m2 = m.copy()
124 del m
125 del m
125 del m2 # make sure we don't double free() anything
126 del m2 # make sure we don't double free() anything
126
127
127 def testCompaction(self):
128 def testCompaction(self):
128 unhex = binascii.unhexlify
129 unhex = binascii.unhexlify
129 h1, h2 = unhex(HASH_1), unhex(HASH_2)
130 h1, h2 = unhex(HASH_1), unhex(HASH_2)
130 m = self.parsemanifest(A_SHORT_MANIFEST)
131 m = self.parsemanifest(A_SHORT_MANIFEST)
131 m[b'alpha'] = h1
132 m[b'alpha'] = h1
132 m[b'beta'] = h2
133 m[b'beta'] = h2
133 del m[b'foo']
134 del m[b'foo']
134 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
135 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
135 HASH_1, HASH_2, HASH_2)
136 HASH_1, HASH_2, HASH_2)
136 self.assertEqual(want, m.text())
137 self.assertEqual(want, m.text())
137 self.assertEqual(3, len(m))
138 self.assertEqual(3, len(m))
138 self.assertEqual([b'alpha', b'bar/baz/qux.py', b'beta'], list(m))
139 self.assertEqual([b'alpha', b'bar/baz/qux.py', b'beta'], list(m))
139 self.assertEqual(h1, m[b'alpha'])
140 self.assertEqual(h1, m[b'alpha'])
140 self.assertEqual(h2, m[b'bar/baz/qux.py'])
141 self.assertEqual(h2, m[b'bar/baz/qux.py'])
141 self.assertEqual(h2, m[b'beta'])
142 self.assertEqual(h2, m[b'beta'])
142 self.assertEqual(b'', m.flags(b'alpha'))
143 self.assertEqual(b'', m.flags(b'alpha'))
143 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
144 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
144 self.assertEqual(b'', m.flags(b'beta'))
145 self.assertEqual(b'', m.flags(b'beta'))
145 with self.assertRaises(KeyError):
146 with self.assertRaises(KeyError):
146 m[b'foo']
147 m[b'foo']
147
148
148 def testSetGetNodeSuffix(self):
149 def testSetGetNodeSuffix(self):
149 clean = self.parsemanifest(A_SHORT_MANIFEST)
150 clean = self.parsemanifest(A_SHORT_MANIFEST)
150 m = self.parsemanifest(A_SHORT_MANIFEST)
151 m = self.parsemanifest(A_SHORT_MANIFEST)
151 h = m[b'foo']
152 h = m[b'foo']
152 f = m.flags(b'foo')
153 f = m.flags(b'foo')
153 want = h + b'a'
154 want = h + b'a'
154 # Merge code wants to set 21-byte fake hashes at times
155 # Merge code wants to set 21-byte fake hashes at times
155 m[b'foo'] = want
156 m[b'foo'] = want
156 self.assertEqual(want, m[b'foo'])
157 self.assertEqual(want, m[b'foo'])
157 self.assertEqual([(b'bar/baz/qux.py', BIN_HASH_2),
158 self.assertEqual([(b'bar/baz/qux.py', BIN_HASH_2),
158 (b'foo', BIN_HASH_1 + b'a')],
159 (b'foo', BIN_HASH_1 + b'a')],
159 list(m.items()))
160 list(m.items()))
160 # Sometimes it even tries a 22-byte fake hash, but we can
161 # Sometimes it even tries a 22-byte fake hash, but we can
161 # return 21 and it'll work out
162 # return 21 and it'll work out
162 m[b'foo'] = want + b'+'
163 m[b'foo'] = want + b'+'
163 self.assertEqual(want, m[b'foo'])
164 self.assertEqual(want, m[b'foo'])
164 # make sure the suffix survives a copy
165 # make sure the suffix survives a copy
165 match = matchmod.match(b'', b'', [b're:foo'])
166 match = matchmod.match(b'', b'', [b're:foo'])
166 m2 = m.matches(match)
167 m2 = m.matches(match)
167 self.assertEqual(want, m2[b'foo'])
168 self.assertEqual(want, m2[b'foo'])
168 self.assertEqual(1, len(m2))
169 self.assertEqual(1, len(m2))
169 m2 = m.copy()
170 m2 = m.copy()
170 self.assertEqual(want, m2[b'foo'])
171 self.assertEqual(want, m2[b'foo'])
171 # suffix with iteration
172 # suffix with iteration
172 self.assertEqual([(b'bar/baz/qux.py', BIN_HASH_2),
173 self.assertEqual([(b'bar/baz/qux.py', BIN_HASH_2),
173 (b'foo', want)],
174 (b'foo', want)],
174 list(m.items()))
175 list(m.items()))
175
176
176 # shows up in diff
177 # shows up in diff
177 self.assertEqual({b'foo': ((want, f), (h, b''))}, m.diff(clean))
178 self.assertEqual({b'foo': ((want, f), (h, b''))}, m.diff(clean))
178 self.assertEqual({b'foo': ((h, b''), (want, f))}, clean.diff(m))
179 self.assertEqual({b'foo': ((h, b''), (want, f))}, clean.diff(m))
179
180
180 def testMatchException(self):
181 def testMatchException(self):
181 m = self.parsemanifest(A_SHORT_MANIFEST)
182 m = self.parsemanifest(A_SHORT_MANIFEST)
182 match = matchmod.match(b'', b'', [b're:.*'])
183 match = matchmod.match(b'', b'', [b're:.*'])
183 def filt(path):
184 def filt(path):
184 if path == b'foo':
185 if path == b'foo':
185 assert False
186 assert False
186 return True
187 return True
187 match.matchfn = filt
188 match.matchfn = filt
188 with self.assertRaises(AssertionError):
189 with self.assertRaises(AssertionError):
189 m.matches(match)
190 m.matches(match)
190
191
191 def testRemoveItem(self):
192 def testRemoveItem(self):
192 m = self.parsemanifest(A_SHORT_MANIFEST)
193 m = self.parsemanifest(A_SHORT_MANIFEST)
193 del m[b'foo']
194 del m[b'foo']
194 with self.assertRaises(KeyError):
195 with self.assertRaises(KeyError):
195 m[b'foo']
196 m[b'foo']
196 self.assertEqual(1, len(m))
197 self.assertEqual(1, len(m))
197 self.assertEqual(1, len(list(m)))
198 self.assertEqual(1, len(list(m)))
198 # now restore and make sure everything works right
199 # now restore and make sure everything works right
199 m[b'foo'] = b'a' * 20
200 m[b'foo'] = b'a' * 20
200 self.assertEqual(2, len(m))
201 self.assertEqual(2, len(m))
201 self.assertEqual(2, len(list(m)))
202 self.assertEqual(2, len(list(m)))
202
203
203 def testManifestDiff(self):
204 def testManifestDiff(self):
204 MISSING = (None, b'')
205 MISSING = (None, b'')
205 addl = b'z-only-in-left\0' + HASH_1 + b'\n'
206 addl = b'z-only-in-left\0' + HASH_1 + b'\n'
206 addr = b'z-only-in-right\0' + HASH_2 + b'x\n'
207 addr = b'z-only-in-right\0' + HASH_2 + b'x\n'
207 left = self.parsemanifest(
208 left = self.parsemanifest(
208 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl)
209 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl)
209 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
210 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
210 want = {
211 want = {
211 b'foo': ((BIN_HASH_3, b'x'),
212 b'foo': ((BIN_HASH_3, b'x'),
212 (BIN_HASH_1, b'')),
213 (BIN_HASH_1, b'')),
213 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
214 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
214 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')),
215 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')),
215 }
216 }
216 self.assertEqual(want, left.diff(right))
217 self.assertEqual(want, left.diff(right))
217
218
218 want = {
219 want = {
219 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')),
220 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')),
220 b'foo': (MISSING, (BIN_HASH_3, b'x')),
221 b'foo': (MISSING, (BIN_HASH_3, b'x')),
221 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')),
222 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')),
222 }
223 }
223 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
224 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
224
225
225 want = {
226 want = {
226 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING),
227 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING),
227 b'foo': ((BIN_HASH_3, b'x'), MISSING),
228 b'foo': ((BIN_HASH_3, b'x'), MISSING),
228 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
229 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
229 }
230 }
230 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
231 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
231 copy = right.copy()
232 copy = right.copy()
232 del copy[b'z-only-in-right']
233 del copy[b'z-only-in-right']
233 del right[b'foo']
234 del right[b'foo']
234 want = {
235 want = {
235 b'foo': (MISSING, (BIN_HASH_1, b'')),
236 b'foo': (MISSING, (BIN_HASH_1, b'')),
236 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING),
237 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING),
237 }
238 }
238 self.assertEqual(want, right.diff(copy))
239 self.assertEqual(want, right.diff(copy))
239
240
240 short = self.parsemanifest(A_SHORT_MANIFEST)
241 short = self.parsemanifest(A_SHORT_MANIFEST)
241 pruned = short.copy()
242 pruned = short.copy()
242 del pruned[b'foo']
243 del pruned[b'foo']
243 want = {
244 want = {
244 b'foo': ((BIN_HASH_1, b''), MISSING),
245 b'foo': ((BIN_HASH_1, b''), MISSING),
245 }
246 }
246 self.assertEqual(want, short.diff(pruned))
247 self.assertEqual(want, short.diff(pruned))
247 want = {
248 want = {
248 b'foo': (MISSING, (BIN_HASH_1, b'')),
249 b'foo': (MISSING, (BIN_HASH_1, b'')),
249 }
250 }
250 self.assertEqual(want, pruned.diff(short))
251 self.assertEqual(want, pruned.diff(short))
251 want = {
252 want = {
252 b'bar/baz/qux.py': None,
253 b'bar/baz/qux.py': None,
253 b'foo': (MISSING, (BIN_HASH_1, b'')),
254 b'foo': (MISSING, (BIN_HASH_1, b'')),
254 }
255 }
255 self.assertEqual(want, pruned.diff(short, clean=True))
256 self.assertEqual(want, pruned.diff(short, clean=True))
256
257
257 def testReversedLines(self):
258 def testReversedLines(self):
258 backwards = b''.join(
259 backwards = b''.join(
259 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l)
260 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l)
260 try:
261 try:
261 self.parsemanifest(backwards)
262 self.parsemanifest(backwards)
262 self.fail('Should have raised ValueError')
263 self.fail('Should have raised ValueError')
263 except ValueError as v:
264 except ValueError as v:
264 self.assertIn('Manifest lines not in sorted order.', str(v))
265 self.assertIn('Manifest lines not in sorted order.', str(v))
265
266
266 def testNoTerminalNewline(self):
267 def testNoTerminalNewline(self):
267 try:
268 try:
268 self.parsemanifest(A_SHORT_MANIFEST + b'wat')
269 self.parsemanifest(A_SHORT_MANIFEST + b'wat')
269 self.fail('Should have raised ValueError')
270 self.fail('Should have raised ValueError')
270 except ValueError as v:
271 except ValueError as v:
271 self.assertIn('Manifest did not end in a newline.', str(v))
272 self.assertIn('Manifest did not end in a newline.', str(v))
272
273
273 def testNoNewLineAtAll(self):
274 def testNoNewLineAtAll(self):
274 try:
275 try:
275 self.parsemanifest(b'wat')
276 self.parsemanifest(b'wat')
276 self.fail('Should have raised ValueError')
277 self.fail('Should have raised ValueError')
277 except ValueError as v:
278 except ValueError as v:
278 self.assertIn('Manifest did not end in a newline.', str(v))
279 self.assertIn('Manifest did not end in a newline.', str(v))
279
280
280 def testHugeManifest(self):
281 def testHugeManifest(self):
281 m = self.parsemanifest(A_HUGE_MANIFEST)
282 m = self.parsemanifest(A_HUGE_MANIFEST)
282 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
283 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
283 self.assertEqual(len(m), len(list(m)))
284 self.assertEqual(len(m), len(list(m)))
284
285
285 def testMatchesMetadata(self):
286 def testMatchesMetadata(self):
286 '''Tests matches() for a few specific files to make sure that both
287 '''Tests matches() for a few specific files to make sure that both
287 the set of files as well as their flags and nodeids are correct in
288 the set of files as well as their flags and nodeids are correct in
288 the resulting manifest.'''
289 the resulting manifest.'''
289 m = self.parsemanifest(A_HUGE_MANIFEST)
290 m = self.parsemanifest(A_HUGE_MANIFEST)
290
291
291 match = matchmod.match(b'/', b'',
292 match = matchmod.match(b'/', b'',
292 [b'file1', b'file200', b'file300'], exact=True)
293 [b'file1', b'file200', b'file300'], exact=True)
293 m2 = m.matches(match)
294 m2 = m.matches(match)
294
295
295 w = (b'file1\0%sx\n'
296 w = (b'file1\0%sx\n'
296 b'file200\0%sl\n'
297 b'file200\0%sl\n'
297 b'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
298 b'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
298 self.assertEqual(w, m2.text())
299 self.assertEqual(w, m2.text())
299
300
300 def testMatchesNonexistentFile(self):
301 def testMatchesNonexistentFile(self):
301 '''Tests matches() for a small set of specific files, including one
302 '''Tests matches() for a small set of specific files, including one
302 nonexistent file to make sure in only matches against existing files.
303 nonexistent file to make sure in only matches against existing files.
303 '''
304 '''
304 m = self.parsemanifest(A_DEEPER_MANIFEST)
305 m = self.parsemanifest(A_DEEPER_MANIFEST)
305
306
306 match = matchmod.match(b'/', b'',
307 match = matchmod.match(b'/', b'',
307 [b'a/b/c/bar.txt', b'a/b/d/qux.py',
308 [b'a/b/c/bar.txt', b'a/b/d/qux.py',
308 b'readme.txt', b'nonexistent'],
309 b'readme.txt', b'nonexistent'],
309 exact=True)
310 exact=True)
310 m2 = m.matches(match)
311 m2 = m.matches(match)
311
312
312 self.assertEqual(
313 self.assertEqual(
313 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt'],
314 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt'],
314 m2.keys())
315 m2.keys())
315
316
316 def testMatchesNonexistentDirectory(self):
317 def testMatchesNonexistentDirectory(self):
317 '''Tests matches() for a relpath match on a directory that doesn't
318 '''Tests matches() for a relpath match on a directory that doesn't
318 actually exist.'''
319 actually exist.'''
319 m = self.parsemanifest(A_DEEPER_MANIFEST)
320 m = self.parsemanifest(A_DEEPER_MANIFEST)
320
321
321 match = matchmod.match(b'/', b'', [b'a/f'], default=b'relpath')
322 match = matchmod.match(b'/', b'', [b'a/f'], default=b'relpath')
322 m2 = m.matches(match)
323 m2 = m.matches(match)
323
324
324 self.assertEqual([], m2.keys())
325 self.assertEqual([], m2.keys())
325
326
326 def testMatchesExactLarge(self):
327 def testMatchesExactLarge(self):
327 '''Tests matches() for files matching a large list of exact files.
328 '''Tests matches() for files matching a large list of exact files.
328 '''
329 '''
329 m = self.parsemanifest(A_HUGE_MANIFEST)
330 m = self.parsemanifest(A_HUGE_MANIFEST)
330
331
331 flist = m.keys()[80:300]
332 flist = m.keys()[80:300]
332 match = matchmod.match(b'/', b'', flist, exact=True)
333 match = matchmod.match(b'/', b'', flist, exact=True)
333 m2 = m.matches(match)
334 m2 = m.matches(match)
334
335
335 self.assertEqual(flist, m2.keys())
336 self.assertEqual(flist, m2.keys())
336
337
337 def testMatchesFull(self):
338 def testMatchesFull(self):
338 '''Tests matches() for what should be a full match.'''
339 '''Tests matches() for what should be a full match.'''
339 m = self.parsemanifest(A_DEEPER_MANIFEST)
340 m = self.parsemanifest(A_DEEPER_MANIFEST)
340
341
341 match = matchmod.match(b'/', b'', [b''])
342 match = matchmod.match(b'/', b'', [b''])
342 m2 = m.matches(match)
343 m2 = m.matches(match)
343
344
344 self.assertEqual(m.keys(), m2.keys())
345 self.assertEqual(m.keys(), m2.keys())
345
346
346 def testMatchesDirectory(self):
347 def testMatchesDirectory(self):
347 '''Tests matches() on a relpath match on a directory, which should
348 '''Tests matches() on a relpath match on a directory, which should
348 match against all files within said directory.'''
349 match against all files within said directory.'''
349 m = self.parsemanifest(A_DEEPER_MANIFEST)
350 m = self.parsemanifest(A_DEEPER_MANIFEST)
350
351
351 match = matchmod.match(b'/', b'', [b'a/b'], default=b'relpath')
352 match = matchmod.match(b'/', b'', [b'a/b'], default=b'relpath')
352 m2 = m.matches(match)
353 m2 = m.matches(match)
353
354
354 self.assertEqual([
355 self.assertEqual([
355 b'a/b/c/bar.py', b'a/b/c/bar.txt', b'a/b/c/foo.py',
356 b'a/b/c/bar.py', b'a/b/c/bar.txt', b'a/b/c/foo.py',
356 b'a/b/c/foo.txt',
357 b'a/b/c/foo.txt',
357 b'a/b/d/baz.py', b'a/b/d/qux.py', b'a/b/d/ten.txt', b'a/b/dog.py',
358 b'a/b/d/baz.py', b'a/b/d/qux.py', b'a/b/d/ten.txt', b'a/b/dog.py',
358 b'a/b/fish.py'], m2.keys())
359 b'a/b/fish.py'], m2.keys())
359
360
360 def testMatchesExactPath(self):
361 def testMatchesExactPath(self):
361 '''Tests matches() on an exact match on a directory, which should
362 '''Tests matches() on an exact match on a directory, which should
362 result in an empty manifest because you can't perform an exact match
363 result in an empty manifest because you can't perform an exact match
363 against a directory.'''
364 against a directory.'''
364 m = self.parsemanifest(A_DEEPER_MANIFEST)
365 m = self.parsemanifest(A_DEEPER_MANIFEST)
365
366
366 match = matchmod.match(b'/', b'', [b'a/b'], exact=True)
367 match = matchmod.match(b'/', b'', [b'a/b'], exact=True)
367 m2 = m.matches(match)
368 m2 = m.matches(match)
368
369
369 self.assertEqual([], m2.keys())
370 self.assertEqual([], m2.keys())
370
371
371 def testMatchesCwd(self):
372 def testMatchesCwd(self):
372 '''Tests matches() on a relpath match with the current directory ('.')
373 '''Tests matches() on a relpath match with the current directory ('.')
373 when not in the root directory.'''
374 when not in the root directory.'''
374 m = self.parsemanifest(A_DEEPER_MANIFEST)
375 m = self.parsemanifest(A_DEEPER_MANIFEST)
375
376
376 match = matchmod.match(b'/', b'a/b', [b'.'], default=b'relpath')
377 match = matchmod.match(b'/', b'a/b', [b'.'], default=b'relpath')
377 m2 = m.matches(match)
378 m2 = m.matches(match)
378
379
379 self.assertEqual([
380 self.assertEqual([
380 b'a/b/c/bar.py', b'a/b/c/bar.txt', b'a/b/c/foo.py',
381 b'a/b/c/bar.py', b'a/b/c/bar.txt', b'a/b/c/foo.py',
381 b'a/b/c/foo.txt', b'a/b/d/baz.py', b'a/b/d/qux.py',
382 b'a/b/c/foo.txt', b'a/b/d/baz.py', b'a/b/d/qux.py',
382 b'a/b/d/ten.txt', b'a/b/dog.py', b'a/b/fish.py'], m2.keys())
383 b'a/b/d/ten.txt', b'a/b/dog.py', b'a/b/fish.py'], m2.keys())
383
384
384 def testMatchesWithPattern(self):
385 def testMatchesWithPattern(self):
385 '''Tests matches() for files matching a pattern that reside
386 '''Tests matches() for files matching a pattern that reside
386 deeper than the specified directory.'''
387 deeper than the specified directory.'''
387 m = self.parsemanifest(A_DEEPER_MANIFEST)
388 m = self.parsemanifest(A_DEEPER_MANIFEST)
388
389
389 match = matchmod.match(b'/', b'', [b'a/b/*/*.txt'])
390 match = matchmod.match(b'/', b'', [b'a/b/*/*.txt'])
390 m2 = m.matches(match)
391 m2 = m.matches(match)
391
392
392 self.assertEqual(
393 self.assertEqual(
393 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'],
394 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'],
394 m2.keys())
395 m2.keys())
395
396
396 class testmanifestdict(unittest.TestCase, basemanifesttests):
397 class testmanifestdict(unittest.TestCase, basemanifesttests):
397 def parsemanifest(self, text):
398 def parsemanifest(self, text):
398 return manifestmod.manifestdict(text)
399 return manifestmod.manifestdict(text)
399
400
401 def testObviouslyBogusManifest(self):
402 # This is a 163k manifest that came from oss-fuzz. It was a
403 # timeout there, but when run normally it doesn't seem to
404 # present any particular slowness.
405 data = zlib.decompress(
406 'x\x9c\xed\xce;\n\x83\x00\x10\x04\xd0\x8deNa\x93~\xf1\x03\xc9q\xf4'
407 '\x14\xeaU\xbdB\xda\xd4\xe6Cj\xc1FA\xde+\x86\xe9f\xa2\xfci\xbb\xfb'
408 '\xa3\xef\xea\xba\xca\x7fk\x86q\x9a\xc6\xc8\xcc&\xb3\xcf\xf8\xb8|#'
409 '\x8a9\x00\xd8\xe6v\xf4\x01N\xe1\n\x00\x00\x00\x00\x00\x00\x00\x00'
410 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
411 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
412 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
413 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
414 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
415 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
416 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
417 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
418 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
419 '\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17'
420 '\xac\xbe')
421 with self.assertRaises(ValueError):
422 self.parsemanifest(data)
423
400 class testtreemanifest(unittest.TestCase, basemanifesttests):
424 class testtreemanifest(unittest.TestCase, basemanifesttests):
401 def parsemanifest(self, text):
425 def parsemanifest(self, text):
402 return manifestmod.treemanifest(b'', text)
426 return manifestmod.treemanifest(b'', text)
403
427
404 def testWalkSubtrees(self):
428 def testWalkSubtrees(self):
405 m = self.parsemanifest(A_DEEPER_MANIFEST)
429 m = self.parsemanifest(A_DEEPER_MANIFEST)
406
430
407 dirs = [s._dir for s in m.walksubtrees()]
431 dirs = [s._dir for s in m.walksubtrees()]
408 self.assertEqual(
432 self.assertEqual(
409 sorted([
433 sorted([
410 b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/']),
434 b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/']),
411 sorted(dirs)
435 sorted(dirs)
412 )
436 )
413
437
414 match = matchmod.match(b'/', b'', [b'path:a/b/'])
438 match = matchmod.match(b'/', b'', [b'path:a/b/'])
415 dirs = [s._dir for s in m.walksubtrees(matcher=match)]
439 dirs = [s._dir for s in m.walksubtrees(matcher=match)]
416 self.assertEqual(
440 self.assertEqual(
417 sorted([b'a/b/', b'a/b/c/', b'a/b/d/']),
441 sorted([b'a/b/', b'a/b/c/', b'a/b/d/']),
418 sorted(dirs)
442 sorted(dirs)
419 )
443 )
420
444
421 if __name__ == '__main__':
445 if __name__ == '__main__':
422 silenttestrunner.main(__name__)
446 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now