##// END OF EJS Templates
dirstate-item: add a `set_possibly_dirty` method...
marmoute -
r48466:e53256a9 default
parent child Browse files
Show More
@@ -1,957 +1,968 b''
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 12 #include <ctype.h>
13 13 #include <stddef.h>
14 14 #include <string.h>
15 15
16 16 #include "bitmanipulation.h"
17 17 #include "charencode.h"
18 18 #include "util.h"
19 19
20 20 #ifdef IS_PY3K
21 21 /* The mapping of Python types is meant to be temporary to get Python
22 22 * 3 to compile. We should remove this once Python 3 support is fully
23 23 * supported and proper types are used in the extensions themselves. */
24 24 #define PyInt_Check PyLong_Check
25 25 #define PyInt_FromLong PyLong_FromLong
26 26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 27 #define PyInt_AsLong PyLong_AsLong
28 28 #endif
29 29
30 30 static const char *const versionerrortext = "Python minor version mismatch";
31 31
32 32 static const int dirstate_v1_from_p2 = -2;
33 33 static const int dirstate_v1_nonnormal = -1;
34 static const int ambiguous_time = -1;
34 35
35 36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
36 37 {
37 38 Py_ssize_t expected_size;
38 39
39 40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
40 41 return NULL;
41 42 }
42 43
43 44 return _dict_new_presized(expected_size);
44 45 }
45 46
46 47 static inline dirstateItemObject *make_dirstate_item(char state, int mode,
47 48 int size, int mtime)
48 49 {
49 50 dirstateItemObject *t =
50 51 PyObject_New(dirstateItemObject, &dirstateItemType);
51 52 if (!t) {
52 53 return NULL;
53 54 }
54 55 t->state = state;
55 56 t->mode = mode;
56 57 t->size = size;
57 58 t->mtime = mtime;
58 59 return t;
59 60 }
60 61
61 62 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
62 63 PyObject *kwds)
63 64 {
64 65 /* We do all the initialization here and not a tp_init function because
65 66 * dirstate_item is immutable. */
66 67 dirstateItemObject *t;
67 68 char state;
68 69 int size, mode, mtime;
69 70 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
70 71 return NULL;
71 72 }
72 73
73 74 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
74 75 if (!t) {
75 76 return NULL;
76 77 }
77 78 t->state = state;
78 79 t->mode = mode;
79 80 t->size = size;
80 81 t->mtime = mtime;
81 82
82 83 return (PyObject *)t;
83 84 }
84 85
85 86 static void dirstate_item_dealloc(PyObject *o)
86 87 {
87 88 PyObject_Del(o);
88 89 }
89 90
90 91 static Py_ssize_t dirstate_item_length(PyObject *o)
91 92 {
92 93 return 4;
93 94 }
94 95
95 96 static PyObject *dirstate_item_item(PyObject *o, Py_ssize_t i)
96 97 {
97 98 dirstateItemObject *t = (dirstateItemObject *)o;
98 99 switch (i) {
99 100 case 0:
100 101 return PyBytes_FromStringAndSize(&t->state, 1);
101 102 case 1:
102 103 return PyInt_FromLong(t->mode);
103 104 case 2:
104 105 return PyInt_FromLong(t->size);
105 106 case 3:
106 107 return PyInt_FromLong(t->mtime);
107 108 default:
108 109 PyErr_SetString(PyExc_IndexError, "index out of range");
109 110 return NULL;
110 111 }
111 112 }
112 113
113 114 static PySequenceMethods dirstate_item_sq = {
114 115 dirstate_item_length, /* sq_length */
115 116 0, /* sq_concat */
116 117 0, /* sq_repeat */
117 118 dirstate_item_item, /* sq_item */
118 119 0, /* sq_ass_item */
119 120 0, /* sq_contains */
120 121 0, /* sq_inplace_concat */
121 122 0 /* sq_inplace_repeat */
122 123 };
123 124
124 125 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
125 126 {
126 127 return PyBytes_FromStringAndSize(&self->state, 1);
127 128 };
128 129
129 130 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
130 131 {
131 132 return PyInt_FromLong(self->mode);
132 133 };
133 134
134 135 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
135 136 {
136 137 return PyInt_FromLong(self->size);
137 138 };
138 139
139 140 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
140 141 {
141 142 return PyInt_FromLong(self->mtime);
142 143 };
143 144
144 145 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
145 146 PyObject *value)
146 147 {
147 148 long now;
148 149 if (!pylong_to_long(value, &now)) {
149 150 return NULL;
150 151 }
151 152 if (self->state == 'n' && self->mtime == now) {
152 153 Py_RETURN_TRUE;
153 154 } else {
154 155 Py_RETURN_FALSE;
155 156 }
156 157 };
157 158
158 159 /* This will never change since it's bound to V1, unlike `make_dirstate_item`
159 160 */
160 161 static inline dirstateItemObject *
161 162 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
162 163 {
163 164 dirstateItemObject *t =
164 165 PyObject_New(dirstateItemObject, &dirstateItemType);
165 166 if (!t) {
166 167 return NULL;
167 168 }
168 169 t->state = state;
169 170 t->mode = mode;
170 171 t->size = size;
171 172 t->mtime = mtime;
172 173 return t;
173 174 }
174 175
175 176 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
176 177 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
177 178 PyObject *args)
178 179 {
179 180 /* We do all the initialization here and not a tp_init function because
180 181 * dirstate_item is immutable. */
181 182 dirstateItemObject *t;
182 183 char state;
183 184 int size, mode, mtime;
184 185 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
185 186 return NULL;
186 187 }
187 188
188 189 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
189 190 if (!t) {
190 191 return NULL;
191 192 }
192 193 t->state = state;
193 194 t->mode = mode;
194 195 t->size = size;
195 196 t->mtime = mtime;
196 197
197 198 return (PyObject *)t;
198 199 };
199 200
201 /* This means the next status call will have to actually check its content
202 to make sure it is correct. */
203 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
204 {
205 self->mtime = ambiguous_time;
206 Py_RETURN_NONE;
207 }
208
200 209 static PyMethodDef dirstate_item_methods[] = {
201 210 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
202 211 "return a \"state\" suitable for v1 serialization"},
203 212 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
204 213 "return a \"mode\" suitable for v1 serialization"},
205 214 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
206 215 "return a \"size\" suitable for v1 serialization"},
207 216 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
208 217 "return a \"mtime\" suitable for v1 serialization"},
209 218 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
210 219 "True if the stored mtime would be ambiguous with the current time"},
211 220 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth, METH_O,
212 221 "build a new DirstateItem object from V1 data"},
222 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
223 METH_NOARGS, "mark a file as \"possibly dirty\""},
213 224 {NULL} /* Sentinel */
214 225 };
215 226
216 227 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
217 228 {
218 229 return PyInt_FromLong(self->mode);
219 230 };
220 231
221 232 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
222 233 {
223 234 return PyInt_FromLong(self->size);
224 235 };
225 236
226 237 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
227 238 {
228 239 return PyInt_FromLong(self->mtime);
229 240 };
230 241
231 242 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
232 243 {
233 244 return PyBytes_FromStringAndSize(&self->state, 1);
234 245 };
235 246
236 247 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
237 248 {
238 249 if (self->state == 'a' || self->state == 'm' || self->state == 'n') {
239 250 Py_RETURN_TRUE;
240 251 } else {
241 252 Py_RETURN_FALSE;
242 253 }
243 254 };
244 255
245 256 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
246 257 {
247 258 if (self->state == 'a') {
248 259 Py_RETURN_TRUE;
249 260 } else {
250 261 Py_RETURN_FALSE;
251 262 }
252 263 };
253 264
254 265 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
255 266 {
256 267 if (self->state == 'm') {
257 268 Py_RETURN_TRUE;
258 269 } else {
259 270 Py_RETURN_FALSE;
260 271 }
261 272 };
262 273
263 274 static PyObject *dirstate_item_get_merged_removed(dirstateItemObject *self)
264 275 {
265 276 if (self->state == 'r' && self->size == dirstate_v1_nonnormal) {
266 277 Py_RETURN_TRUE;
267 278 } else {
268 279 Py_RETURN_FALSE;
269 280 }
270 281 };
271 282
272 283 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
273 284 {
274 285 if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
275 286 Py_RETURN_TRUE;
276 287 } else {
277 288 Py_RETURN_FALSE;
278 289 }
279 290 };
280 291
281 292 static PyObject *dirstate_item_get_from_p2_removed(dirstateItemObject *self)
282 293 {
283 294 if (self->state == 'r' && self->size == dirstate_v1_from_p2) {
284 295 Py_RETURN_TRUE;
285 296 } else {
286 297 Py_RETURN_FALSE;
287 298 }
288 299 };
289 300
290 301 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
291 302 {
292 303 if (self->state == 'r') {
293 304 Py_RETURN_TRUE;
294 305 } else {
295 306 Py_RETURN_FALSE;
296 307 }
297 308 };
298 309
299 310 static PyGetSetDef dirstate_item_getset[] = {
300 311 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
301 312 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
302 313 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
303 314 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
304 315 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
305 316 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
306 317 {"merged_removed", (getter)dirstate_item_get_merged_removed, NULL,
307 318 "merged_removed", NULL},
308 319 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
309 320 {"from_p2_removed", (getter)dirstate_item_get_from_p2_removed, NULL,
310 321 "from_p2_removed", NULL},
311 322 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
312 323 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
313 324 {NULL} /* Sentinel */
314 325 };
315 326
316 327 PyTypeObject dirstateItemType = {
317 328 PyVarObject_HEAD_INIT(NULL, 0) /* header */
318 329 "dirstate_tuple", /* tp_name */
319 330 sizeof(dirstateItemObject), /* tp_basicsize */
320 331 0, /* tp_itemsize */
321 332 (destructor)dirstate_item_dealloc, /* tp_dealloc */
322 333 0, /* tp_print */
323 334 0, /* tp_getattr */
324 335 0, /* tp_setattr */
325 336 0, /* tp_compare */
326 337 0, /* tp_repr */
327 338 0, /* tp_as_number */
328 339 &dirstate_item_sq, /* tp_as_sequence */
329 340 0, /* tp_as_mapping */
330 341 0, /* tp_hash */
331 342 0, /* tp_call */
332 343 0, /* tp_str */
333 344 0, /* tp_getattro */
334 345 0, /* tp_setattro */
335 346 0, /* tp_as_buffer */
336 347 Py_TPFLAGS_DEFAULT, /* tp_flags */
337 348 "dirstate tuple", /* tp_doc */
338 349 0, /* tp_traverse */
339 350 0, /* tp_clear */
340 351 0, /* tp_richcompare */
341 352 0, /* tp_weaklistoffset */
342 353 0, /* tp_iter */
343 354 0, /* tp_iternext */
344 355 dirstate_item_methods, /* tp_methods */
345 356 0, /* tp_members */
346 357 dirstate_item_getset, /* tp_getset */
347 358 0, /* tp_base */
348 359 0, /* tp_dict */
349 360 0, /* tp_descr_get */
350 361 0, /* tp_descr_set */
351 362 0, /* tp_dictoffset */
352 363 0, /* tp_init */
353 364 0, /* tp_alloc */
354 365 dirstate_item_new, /* tp_new */
355 366 };
356 367
357 368 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
358 369 {
359 370 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
360 371 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
361 372 char state, *cur, *str, *cpos;
362 373 int mode, size, mtime;
363 374 unsigned int flen, pos = 40;
364 375 Py_ssize_t len = 40;
365 376 Py_ssize_t readlen;
366 377
367 378 if (!PyArg_ParseTuple(
368 379 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
369 380 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
370 381 goto quit;
371 382 }
372 383
373 384 len = readlen;
374 385
375 386 /* read parents */
376 387 if (len < 40) {
377 388 PyErr_SetString(PyExc_ValueError,
378 389 "too little data for parents");
379 390 goto quit;
380 391 }
381 392
382 393 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
383 394 str + 20, (Py_ssize_t)20);
384 395 if (!parents) {
385 396 goto quit;
386 397 }
387 398
388 399 /* read filenames */
389 400 while (pos >= 40 && pos < len) {
390 401 if (pos + 17 > len) {
391 402 PyErr_SetString(PyExc_ValueError,
392 403 "overflow in dirstate");
393 404 goto quit;
394 405 }
395 406 cur = str + pos;
396 407 /* unpack header */
397 408 state = *cur;
398 409 mode = getbe32(cur + 1);
399 410 size = getbe32(cur + 5);
400 411 mtime = getbe32(cur + 9);
401 412 flen = getbe32(cur + 13);
402 413 pos += 17;
403 414 cur += 17;
404 415 if (flen > len - pos) {
405 416 PyErr_SetString(PyExc_ValueError,
406 417 "overflow in dirstate");
407 418 goto quit;
408 419 }
409 420
410 421 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
411 422 size, mtime);
412 423 cpos = memchr(cur, 0, flen);
413 424 if (cpos) {
414 425 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
415 426 cname = PyBytes_FromStringAndSize(
416 427 cpos + 1, flen - (cpos - cur) - 1);
417 428 if (!fname || !cname ||
418 429 PyDict_SetItem(cmap, fname, cname) == -1 ||
419 430 PyDict_SetItem(dmap, fname, entry) == -1) {
420 431 goto quit;
421 432 }
422 433 Py_DECREF(cname);
423 434 } else {
424 435 fname = PyBytes_FromStringAndSize(cur, flen);
425 436 if (!fname ||
426 437 PyDict_SetItem(dmap, fname, entry) == -1) {
427 438 goto quit;
428 439 }
429 440 }
430 441 Py_DECREF(fname);
431 442 Py_DECREF(entry);
432 443 fname = cname = entry = NULL;
433 444 pos += flen;
434 445 }
435 446
436 447 ret = parents;
437 448 Py_INCREF(ret);
438 449 quit:
439 450 Py_XDECREF(fname);
440 451 Py_XDECREF(cname);
441 452 Py_XDECREF(entry);
442 453 Py_XDECREF(parents);
443 454 return ret;
444 455 }
445 456
446 457 /*
447 458 * Build a set of non-normal and other parent entries from the dirstate dmap
448 459 */
449 460 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
450 461 {
451 462 PyObject *dmap, *fname, *v;
452 463 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
453 464 Py_ssize_t pos;
454 465
455 466 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
456 467 &dmap)) {
457 468 goto bail;
458 469 }
459 470
460 471 nonnset = PySet_New(NULL);
461 472 if (nonnset == NULL) {
462 473 goto bail;
463 474 }
464 475
465 476 otherpset = PySet_New(NULL);
466 477 if (otherpset == NULL) {
467 478 goto bail;
468 479 }
469 480
470 481 pos = 0;
471 482 while (PyDict_Next(dmap, &pos, &fname, &v)) {
472 483 dirstateItemObject *t;
473 484 if (!dirstate_tuple_check(v)) {
474 485 PyErr_SetString(PyExc_TypeError,
475 486 "expected a dirstate tuple");
476 487 goto bail;
477 488 }
478 489 t = (dirstateItemObject *)v;
479 490
480 491 if (t->state == 'n' && t->size == -2) {
481 492 if (PySet_Add(otherpset, fname) == -1) {
482 493 goto bail;
483 494 }
484 495 }
485 496
486 497 if (t->state == 'n' && t->mtime != -1) {
487 498 continue;
488 499 }
489 500 if (PySet_Add(nonnset, fname) == -1) {
490 501 goto bail;
491 502 }
492 503 }
493 504
494 505 result = Py_BuildValue("(OO)", nonnset, otherpset);
495 506 if (result == NULL) {
496 507 goto bail;
497 508 }
498 509 Py_DECREF(nonnset);
499 510 Py_DECREF(otherpset);
500 511 return result;
501 512 bail:
502 513 Py_XDECREF(nonnset);
503 514 Py_XDECREF(otherpset);
504 515 Py_XDECREF(result);
505 516 return NULL;
506 517 }
507 518
508 519 /*
509 520 * Efficiently pack a dirstate object into its on-disk format.
510 521 */
511 522 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
512 523 {
513 524 PyObject *packobj = NULL;
514 525 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
515 526 Py_ssize_t nbytes, pos, l;
516 527 PyObject *k, *v = NULL, *pn;
517 528 char *p, *s;
518 529 int now;
519 530
520 531 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
521 532 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
522 533 &now)) {
523 534 return NULL;
524 535 }
525 536
526 537 if (PyTuple_Size(pl) != 2) {
527 538 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
528 539 return NULL;
529 540 }
530 541
531 542 /* Figure out how much we need to allocate. */
532 543 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
533 544 PyObject *c;
534 545 if (!PyBytes_Check(k)) {
535 546 PyErr_SetString(PyExc_TypeError, "expected string key");
536 547 goto bail;
537 548 }
538 549 nbytes += PyBytes_GET_SIZE(k) + 17;
539 550 c = PyDict_GetItem(copymap, k);
540 551 if (c) {
541 552 if (!PyBytes_Check(c)) {
542 553 PyErr_SetString(PyExc_TypeError,
543 554 "expected string key");
544 555 goto bail;
545 556 }
546 557 nbytes += PyBytes_GET_SIZE(c) + 1;
547 558 }
548 559 }
549 560
550 561 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
551 562 if (packobj == NULL) {
552 563 goto bail;
553 564 }
554 565
555 566 p = PyBytes_AS_STRING(packobj);
556 567
557 568 pn = PyTuple_GET_ITEM(pl, 0);
558 569 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
559 570 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
560 571 goto bail;
561 572 }
562 573 memcpy(p, s, l);
563 574 p += 20;
564 575 pn = PyTuple_GET_ITEM(pl, 1);
565 576 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
566 577 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
567 578 goto bail;
568 579 }
569 580 memcpy(p, s, l);
570 581 p += 20;
571 582
572 583 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
573 584 dirstateItemObject *tuple;
574 585 char state;
575 586 int mode, size, mtime;
576 587 Py_ssize_t len, l;
577 588 PyObject *o;
578 589 char *t;
579 590
580 591 if (!dirstate_tuple_check(v)) {
581 592 PyErr_SetString(PyExc_TypeError,
582 593 "expected a dirstate tuple");
583 594 goto bail;
584 595 }
585 596 tuple = (dirstateItemObject *)v;
586 597
587 598 state = tuple->state;
588 599 mode = tuple->mode;
589 600 size = tuple->size;
590 601 mtime = tuple->mtime;
591 602 if (state == 'n' && mtime == now) {
592 603 /* See pure/parsers.py:pack_dirstate for why we do
593 604 * this. */
594 605 mtime = -1;
595 606 mtime_unset = (PyObject *)make_dirstate_item(
596 607 state, mode, size, mtime);
597 608 if (!mtime_unset) {
598 609 goto bail;
599 610 }
600 611 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
601 612 goto bail;
602 613 }
603 614 Py_DECREF(mtime_unset);
604 615 mtime_unset = NULL;
605 616 }
606 617 *p++ = state;
607 618 putbe32((uint32_t)mode, p);
608 619 putbe32((uint32_t)size, p + 4);
609 620 putbe32((uint32_t)mtime, p + 8);
610 621 t = p + 12;
611 622 p += 16;
612 623 len = PyBytes_GET_SIZE(k);
613 624 memcpy(p, PyBytes_AS_STRING(k), len);
614 625 p += len;
615 626 o = PyDict_GetItem(copymap, k);
616 627 if (o) {
617 628 *p++ = '\0';
618 629 l = PyBytes_GET_SIZE(o);
619 630 memcpy(p, PyBytes_AS_STRING(o), l);
620 631 p += l;
621 632 len += l + 1;
622 633 }
623 634 putbe32((uint32_t)len, t);
624 635 }
625 636
626 637 pos = p - PyBytes_AS_STRING(packobj);
627 638 if (pos != nbytes) {
628 639 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
629 640 (long)pos, (long)nbytes);
630 641 goto bail;
631 642 }
632 643
633 644 return packobj;
634 645 bail:
635 646 Py_XDECREF(mtime_unset);
636 647 Py_XDECREF(packobj);
637 648 Py_XDECREF(v);
638 649 return NULL;
639 650 }
640 651
641 652 #define BUMPED_FIX 1
642 653 #define USING_SHA_256 2
643 654 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
644 655
645 656 static PyObject *readshas(const char *source, unsigned char num,
646 657 Py_ssize_t hashwidth)
647 658 {
648 659 int i;
649 660 PyObject *list = PyTuple_New(num);
650 661 if (list == NULL) {
651 662 return NULL;
652 663 }
653 664 for (i = 0; i < num; i++) {
654 665 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
655 666 if (hash == NULL) {
656 667 Py_DECREF(list);
657 668 return NULL;
658 669 }
659 670 PyTuple_SET_ITEM(list, i, hash);
660 671 source += hashwidth;
661 672 }
662 673 return list;
663 674 }
664 675
665 676 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
666 677 uint32_t *msize)
667 678 {
668 679 const char *data = databegin;
669 680 const char *meta;
670 681
671 682 double mtime;
672 683 int16_t tz;
673 684 uint16_t flags;
674 685 unsigned char nsuccs, nparents, nmetadata;
675 686 Py_ssize_t hashwidth = 20;
676 687
677 688 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
678 689 PyObject *metadata = NULL, *ret = NULL;
679 690 int i;
680 691
681 692 if (data + FM1_HEADER_SIZE > dataend) {
682 693 goto overflow;
683 694 }
684 695
685 696 *msize = getbe32(data);
686 697 data += 4;
687 698 mtime = getbefloat64(data);
688 699 data += 8;
689 700 tz = getbeint16(data);
690 701 data += 2;
691 702 flags = getbeuint16(data);
692 703 data += 2;
693 704
694 705 if (flags & USING_SHA_256) {
695 706 hashwidth = 32;
696 707 }
697 708
698 709 nsuccs = (unsigned char)(*data++);
699 710 nparents = (unsigned char)(*data++);
700 711 nmetadata = (unsigned char)(*data++);
701 712
702 713 if (databegin + *msize > dataend) {
703 714 goto overflow;
704 715 }
705 716 dataend = databegin + *msize; /* narrow down to marker size */
706 717
707 718 if (data + hashwidth > dataend) {
708 719 goto overflow;
709 720 }
710 721 prec = PyBytes_FromStringAndSize(data, hashwidth);
711 722 data += hashwidth;
712 723 if (prec == NULL) {
713 724 goto bail;
714 725 }
715 726
716 727 if (data + nsuccs * hashwidth > dataend) {
717 728 goto overflow;
718 729 }
719 730 succs = readshas(data, nsuccs, hashwidth);
720 731 if (succs == NULL) {
721 732 goto bail;
722 733 }
723 734 data += nsuccs * hashwidth;
724 735
725 736 if (nparents == 1 || nparents == 2) {
726 737 if (data + nparents * hashwidth > dataend) {
727 738 goto overflow;
728 739 }
729 740 parents = readshas(data, nparents, hashwidth);
730 741 if (parents == NULL) {
731 742 goto bail;
732 743 }
733 744 data += nparents * hashwidth;
734 745 } else {
735 746 parents = Py_None;
736 747 Py_INCREF(parents);
737 748 }
738 749
739 750 if (data + 2 * nmetadata > dataend) {
740 751 goto overflow;
741 752 }
742 753 meta = data + (2 * nmetadata);
743 754 metadata = PyTuple_New(nmetadata);
744 755 if (metadata == NULL) {
745 756 goto bail;
746 757 }
747 758 for (i = 0; i < nmetadata; i++) {
748 759 PyObject *tmp, *left = NULL, *right = NULL;
749 760 Py_ssize_t leftsize = (unsigned char)(*data++);
750 761 Py_ssize_t rightsize = (unsigned char)(*data++);
751 762 if (meta + leftsize + rightsize > dataend) {
752 763 goto overflow;
753 764 }
754 765 left = PyBytes_FromStringAndSize(meta, leftsize);
755 766 meta += leftsize;
756 767 right = PyBytes_FromStringAndSize(meta, rightsize);
757 768 meta += rightsize;
758 769 tmp = PyTuple_New(2);
759 770 if (!left || !right || !tmp) {
760 771 Py_XDECREF(left);
761 772 Py_XDECREF(right);
762 773 Py_XDECREF(tmp);
763 774 goto bail;
764 775 }
765 776 PyTuple_SET_ITEM(tmp, 0, left);
766 777 PyTuple_SET_ITEM(tmp, 1, right);
767 778 PyTuple_SET_ITEM(metadata, i, tmp);
768 779 }
769 780 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
770 781 (int)tz * 60, parents);
771 782 goto bail; /* return successfully */
772 783
773 784 overflow:
774 785 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
775 786 bail:
776 787 Py_XDECREF(prec);
777 788 Py_XDECREF(succs);
778 789 Py_XDECREF(metadata);
779 790 Py_XDECREF(parents);
780 791 return ret;
781 792 }
782 793
783 794 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
784 795 {
785 796 const char *data, *dataend;
786 797 Py_ssize_t datalen, offset, stop;
787 798 PyObject *markers = NULL;
788 799
789 800 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
790 801 &offset, &stop)) {
791 802 return NULL;
792 803 }
793 804 if (offset < 0) {
794 805 PyErr_SetString(PyExc_ValueError,
795 806 "invalid negative offset in fm1readmarkers");
796 807 return NULL;
797 808 }
798 809 if (stop > datalen) {
799 810 PyErr_SetString(
800 811 PyExc_ValueError,
801 812 "stop longer than data length in fm1readmarkers");
802 813 return NULL;
803 814 }
804 815 dataend = data + datalen;
805 816 data += offset;
806 817 markers = PyList_New(0);
807 818 if (!markers) {
808 819 return NULL;
809 820 }
810 821 while (offset < stop) {
811 822 uint32_t msize;
812 823 int error;
813 824 PyObject *record = fm1readmarker(data, dataend, &msize);
814 825 if (!record) {
815 826 goto bail;
816 827 }
817 828 error = PyList_Append(markers, record);
818 829 Py_DECREF(record);
819 830 if (error) {
820 831 goto bail;
821 832 }
822 833 data += msize;
823 834 offset += msize;
824 835 }
825 836 return markers;
826 837 bail:
827 838 Py_DECREF(markers);
828 839 return NULL;
829 840 }
830 841
831 842 static char parsers_doc[] = "Efficient content parsing.";
832 843
833 844 PyObject *encodedir(PyObject *self, PyObject *args);
834 845 PyObject *pathencode(PyObject *self, PyObject *args);
835 846 PyObject *lowerencode(PyObject *self, PyObject *args);
836 847 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
837 848
838 849 static PyMethodDef methods[] = {
839 850 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
840 851 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
841 852 "create a set containing non-normal and other parent entries of given "
842 853 "dirstate\n"},
843 854 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
844 855 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
845 856 "parse a revlog index\n"},
846 857 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
847 858 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
848 859 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
849 860 {"dict_new_presized", dict_new_presized, METH_VARARGS,
850 861 "construct a dict with an expected size\n"},
851 862 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
852 863 "make file foldmap\n"},
853 864 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
854 865 "escape a UTF-8 byte string to JSON (fast path)\n"},
855 866 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
856 867 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
857 868 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
858 869 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
859 870 "parse v1 obsolete markers\n"},
860 871 {NULL, NULL}};
861 872
862 873 void dirs_module_init(PyObject *mod);
863 874 void manifest_module_init(PyObject *mod);
864 875 void revlog_module_init(PyObject *mod);
865 876
866 877 static const int version = 20;
867 878
868 879 static void module_init(PyObject *mod)
869 880 {
870 881 PyObject *capsule = NULL;
871 882 PyModule_AddIntConstant(mod, "version", version);
872 883
873 884 /* This module constant has two purposes. First, it lets us unit test
874 885 * the ImportError raised without hard-coding any error text. This
875 886 * means we can change the text in the future without breaking tests,
876 887 * even across changesets without a recompile. Second, its presence
877 888 * can be used to determine whether the version-checking logic is
878 889 * present, which also helps in testing across changesets without a
879 890 * recompile. Note that this means the pure-Python version of parsers
880 891 * should not have this module constant. */
881 892 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
882 893
883 894 dirs_module_init(mod);
884 895 manifest_module_init(mod);
885 896 revlog_module_init(mod);
886 897
887 898 capsule = PyCapsule_New(
888 899 make_dirstate_item,
889 900 "mercurial.cext.parsers.make_dirstate_item_CAPI", NULL);
890 901 if (capsule != NULL)
891 902 PyModule_AddObject(mod, "make_dirstate_item_CAPI", capsule);
892 903
893 904 if (PyType_Ready(&dirstateItemType) < 0) {
894 905 return;
895 906 }
896 907 Py_INCREF(&dirstateItemType);
897 908 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
898 909 }
899 910
900 911 static int check_python_version(void)
901 912 {
902 913 PyObject *sys = PyImport_ImportModule("sys"), *ver;
903 914 long hexversion;
904 915 if (!sys) {
905 916 return -1;
906 917 }
907 918 ver = PyObject_GetAttrString(sys, "hexversion");
908 919 Py_DECREF(sys);
909 920 if (!ver) {
910 921 return -1;
911 922 }
912 923 hexversion = PyInt_AsLong(ver);
913 924 Py_DECREF(ver);
914 925 /* sys.hexversion is a 32-bit number by default, so the -1 case
915 926 * should only occur in unusual circumstances (e.g. if sys.hexversion
916 927 * is manually set to an invalid value). */
917 928 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
918 929 PyErr_Format(PyExc_ImportError,
919 930 "%s: The Mercurial extension "
920 931 "modules were compiled with Python " PY_VERSION
921 932 ", but "
922 933 "Mercurial is currently using Python with "
923 934 "sys.hexversion=%ld: "
924 935 "Python %s\n at: %s",
925 936 versionerrortext, hexversion, Py_GetVersion(),
926 937 Py_GetProgramFullPath());
927 938 return -1;
928 939 }
929 940 return 0;
930 941 }
931 942
932 943 #ifdef IS_PY3K
933 944 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
934 945 parsers_doc, -1, methods};
935 946
936 947 PyMODINIT_FUNC PyInit_parsers(void)
937 948 {
938 949 PyObject *mod;
939 950
940 951 if (check_python_version() == -1)
941 952 return NULL;
942 953 mod = PyModule_Create(&parsers_module);
943 954 module_init(mod);
944 955 return mod;
945 956 }
946 957 #else
947 958 PyMODINIT_FUNC initparsers(void)
948 959 {
949 960 PyObject *mod;
950 961
951 962 if (check_python_version() == -1) {
952 963 return;
953 964 }
954 965 mod = Py_InitModule3("parsers", methods, parsers_doc);
955 966 module_init(mod);
956 967 }
957 968 #endif
@@ -1,598 +1,606 b''
1 1 # parsers.py - Python implementation of parsers.c
2 2 #
3 3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import struct
11 11 import zlib
12 12
13 13 from ..node import (
14 14 nullrev,
15 15 sha1nodeconstants,
16 16 )
17 17 from ..thirdparty import attr
18 18 from .. import (
19 19 error,
20 20 pycompat,
21 21 revlogutils,
22 22 util,
23 23 )
24 24
25 25 from ..revlogutils import nodemap as nodemaputil
26 26 from ..revlogutils import constants as revlog_constants
27 27
28 28 stringio = pycompat.bytesio
29 29
30 30
31 31 _pack = struct.pack
32 32 _unpack = struct.unpack
33 33 _compress = zlib.compress
34 34 _decompress = zlib.decompress
35 35
36 36
37 37 # a special value used internally for `size` if the file come from the other parent
38 38 FROM_P2 = -2
39 39
40 40 # a special value used internally for `size` if the file is modified/merged/added
41 41 NONNORMAL = -1
42 42
43 43 # a special value used internally for `time` if the time is ambigeous
44 44 AMBIGUOUS_TIME = -1
45 45
46 46
47 47 @attr.s(slots=True, init=False)
48 48 class DirstateItem(object):
49 49 """represent a dirstate entry
50 50
51 51 It contains:
52 52
53 53 - state (one of 'n', 'a', 'r', 'm')
54 54 - mode,
55 55 - size,
56 56 - mtime,
57 57 """
58 58
59 59 _state = attr.ib()
60 60 _mode = attr.ib()
61 61 _size = attr.ib()
62 62 _mtime = attr.ib()
63 63
64 64 def __init__(self, state, mode, size, mtime):
65 65 self._state = state
66 66 self._mode = mode
67 67 self._size = size
68 68 self._mtime = mtime
69 69
70 70 @classmethod
71 71 def from_v1_data(cls, state, mode, size, mtime):
72 72 """Build a new DirstateItem object from V1 data
73 73
74 74 Since the dirstate-v1 format is frozen, the signature of this function
75 75 is not expected to change, unlike the __init__ one.
76 76 """
77 77 return cls(
78 78 state=state,
79 79 mode=mode,
80 80 size=size,
81 81 mtime=mtime,
82 82 )
83 83
84 def set_possibly_dirty(self):
85 """Mark a file as "possibly dirty"
86
87 This means the next status call will have to actually check its content
88 to make sure it is correct.
89 """
90 self._mtime = AMBIGUOUS_TIME
91
84 92 def __getitem__(self, idx):
85 93 if idx == 0 or idx == -4:
86 94 msg = b"do not use item[x], use item.state"
87 95 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
88 96 return self._state
89 97 elif idx == 1 or idx == -3:
90 98 msg = b"do not use item[x], use item.mode"
91 99 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
92 100 return self._mode
93 101 elif idx == 2 or idx == -2:
94 102 msg = b"do not use item[x], use item.size"
95 103 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
96 104 return self._size
97 105 elif idx == 3 or idx == -1:
98 106 msg = b"do not use item[x], use item.mtime"
99 107 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
100 108 return self._mtime
101 109 else:
102 110 raise IndexError(idx)
103 111
104 112 @property
105 113 def mode(self):
106 114 return self._mode
107 115
108 116 @property
109 117 def size(self):
110 118 return self._size
111 119
112 120 @property
113 121 def mtime(self):
114 122 return self._mtime
115 123
116 124 @property
117 125 def state(self):
118 126 """
119 127 States are:
120 128 n normal
121 129 m needs merging
122 130 r marked for removal
123 131 a marked for addition
124 132
125 133 XXX This "state" is a bit obscure and mostly a direct expression of the
126 134 dirstatev1 format. It would make sense to ultimately deprecate it in
127 135 favor of the more "semantic" attributes.
128 136 """
129 137 return self._state
130 138
131 139 @property
132 140 def tracked(self):
133 141 """True is the file is tracked in the working copy"""
134 142 return self._state in b"nma"
135 143
136 144 @property
137 145 def added(self):
138 146 """True if the file has been added"""
139 147 return self._state == b'a'
140 148
141 149 @property
142 150 def merged(self):
143 151 """True if the file has been merged
144 152
145 153 Should only be set if a merge is in progress in the dirstate
146 154 """
147 155 return self._state == b'm'
148 156
149 157 @property
150 158 def from_p2(self):
151 159 """True if the file have been fetched from p2 during the current merge
152 160
153 161 This is only True is the file is currently tracked.
154 162
155 163 Should only be set if a merge is in progress in the dirstate
156 164 """
157 165 return self._state == b'n' and self._size == FROM_P2
158 166
159 167 @property
160 168 def from_p2_removed(self):
161 169 """True if the file has been removed, but was "from_p2" initially
162 170
163 171 This property seems like an abstraction leakage and should probably be
164 172 dealt in this class (or maybe the dirstatemap) directly.
165 173 """
166 174 return self._state == b'r' and self._size == FROM_P2
167 175
168 176 @property
169 177 def removed(self):
170 178 """True if the file has been removed"""
171 179 return self._state == b'r'
172 180
173 181 @property
174 182 def merged_removed(self):
175 183 """True if the file has been removed, but was "merged" initially
176 184
177 185 This property seems like an abstraction leakage and should probably be
178 186 dealt in this class (or maybe the dirstatemap) directly.
179 187 """
180 188 return self._state == b'r' and self._size == NONNORMAL
181 189
182 190 def v1_state(self):
183 191 """return a "state" suitable for v1 serialization"""
184 192 return self._state
185 193
186 194 def v1_mode(self):
187 195 """return a "mode" suitable for v1 serialization"""
188 196 return self._mode
189 197
190 198 def v1_size(self):
191 199 """return a "size" suitable for v1 serialization"""
192 200 return self._size
193 201
194 202 def v1_mtime(self):
195 203 """return a "mtime" suitable for v1 serialization"""
196 204 return self._mtime
197 205
198 206 def need_delay(self, now):
199 207 """True if the stored mtime would be ambiguous with the current time"""
200 208 return self._state == b'n' and self._mtime == now
201 209
202 210
203 211 def gettype(q):
204 212 return int(q & 0xFFFF)
205 213
206 214
207 215 class BaseIndexObject(object):
208 216 # Can I be passed to an algorithme implemented in Rust ?
209 217 rust_ext_compat = 0
210 218 # Format of an index entry according to Python's `struct` language
211 219 index_format = revlog_constants.INDEX_ENTRY_V1
212 220 # Size of a C unsigned long long int, platform independent
213 221 big_int_size = struct.calcsize(b'>Q')
214 222 # Size of a C long int, platform independent
215 223 int_size = struct.calcsize(b'>i')
216 224 # An empty index entry, used as a default value to be overridden, or nullrev
217 225 null_item = (
218 226 0,
219 227 0,
220 228 0,
221 229 -1,
222 230 -1,
223 231 -1,
224 232 -1,
225 233 sha1nodeconstants.nullid,
226 234 0,
227 235 0,
228 236 revlog_constants.COMP_MODE_INLINE,
229 237 revlog_constants.COMP_MODE_INLINE,
230 238 )
231 239
232 240 @util.propertycache
233 241 def entry_size(self):
234 242 return self.index_format.size
235 243
236 244 @property
237 245 def nodemap(self):
238 246 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
239 247 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
240 248 return self._nodemap
241 249
242 250 @util.propertycache
243 251 def _nodemap(self):
244 252 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
245 253 for r in range(0, len(self)):
246 254 n = self[r][7]
247 255 nodemap[n] = r
248 256 return nodemap
249 257
250 258 def has_node(self, node):
251 259 """return True if the node exist in the index"""
252 260 return node in self._nodemap
253 261
254 262 def rev(self, node):
255 263 """return a revision for a node
256 264
257 265 If the node is unknown, raise a RevlogError"""
258 266 return self._nodemap[node]
259 267
260 268 def get_rev(self, node):
261 269 """return a revision for a node
262 270
263 271 If the node is unknown, return None"""
264 272 return self._nodemap.get(node)
265 273
266 274 def _stripnodes(self, start):
267 275 if '_nodemap' in vars(self):
268 276 for r in range(start, len(self)):
269 277 n = self[r][7]
270 278 del self._nodemap[n]
271 279
272 280 def clearcaches(self):
273 281 self.__dict__.pop('_nodemap', None)
274 282
275 283 def __len__(self):
276 284 return self._lgt + len(self._extra)
277 285
278 286 def append(self, tup):
279 287 if '_nodemap' in vars(self):
280 288 self._nodemap[tup[7]] = len(self)
281 289 data = self._pack_entry(len(self), tup)
282 290 self._extra.append(data)
283 291
284 292 def _pack_entry(self, rev, entry):
285 293 assert entry[8] == 0
286 294 assert entry[9] == 0
287 295 return self.index_format.pack(*entry[:8])
288 296
289 297 def _check_index(self, i):
290 298 if not isinstance(i, int):
291 299 raise TypeError(b"expecting int indexes")
292 300 if i < 0 or i >= len(self):
293 301 raise IndexError
294 302
295 303 def __getitem__(self, i):
296 304 if i == -1:
297 305 return self.null_item
298 306 self._check_index(i)
299 307 if i >= self._lgt:
300 308 data = self._extra[i - self._lgt]
301 309 else:
302 310 index = self._calculate_index(i)
303 311 data = self._data[index : index + self.entry_size]
304 312 r = self._unpack_entry(i, data)
305 313 if self._lgt and i == 0:
306 314 offset = revlogutils.offset_type(0, gettype(r[0]))
307 315 r = (offset,) + r[1:]
308 316 return r
309 317
310 318 def _unpack_entry(self, rev, data):
311 319 r = self.index_format.unpack(data)
312 320 r = r + (
313 321 0,
314 322 0,
315 323 revlog_constants.COMP_MODE_INLINE,
316 324 revlog_constants.COMP_MODE_INLINE,
317 325 )
318 326 return r
319 327
320 328 def pack_header(self, header):
321 329 """pack header information as binary"""
322 330 v_fmt = revlog_constants.INDEX_HEADER
323 331 return v_fmt.pack(header)
324 332
325 333 def entry_binary(self, rev):
326 334 """return the raw binary string representing a revision"""
327 335 entry = self[rev]
328 336 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
329 337 if rev == 0:
330 338 p = p[revlog_constants.INDEX_HEADER.size :]
331 339 return p
332 340
333 341
334 342 class IndexObject(BaseIndexObject):
335 343 def __init__(self, data):
336 344 assert len(data) % self.entry_size == 0, (
337 345 len(data),
338 346 self.entry_size,
339 347 len(data) % self.entry_size,
340 348 )
341 349 self._data = data
342 350 self._lgt = len(data) // self.entry_size
343 351 self._extra = []
344 352
345 353 def _calculate_index(self, i):
346 354 return i * self.entry_size
347 355
348 356 def __delitem__(self, i):
349 357 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
350 358 raise ValueError(b"deleting slices only supports a:-1 with step 1")
351 359 i = i.start
352 360 self._check_index(i)
353 361 self._stripnodes(i)
354 362 if i < self._lgt:
355 363 self._data = self._data[: i * self.entry_size]
356 364 self._lgt = i
357 365 self._extra = []
358 366 else:
359 367 self._extra = self._extra[: i - self._lgt]
360 368
361 369
362 370 class PersistentNodeMapIndexObject(IndexObject):
363 371 """a Debug oriented class to test persistent nodemap
364 372
365 373 We need a simple python object to test API and higher level behavior. See
366 374 the Rust implementation for more serious usage. This should be used only
367 375 through the dedicated `devel.persistent-nodemap` config.
368 376 """
369 377
370 378 def nodemap_data_all(self):
371 379 """Return bytes containing a full serialization of a nodemap
372 380
373 381 The nodemap should be valid for the full set of revisions in the
374 382 index."""
375 383 return nodemaputil.persistent_data(self)
376 384
377 385 def nodemap_data_incremental(self):
378 386 """Return bytes containing a incremental update to persistent nodemap
379 387
380 388 This containst the data for an append-only update of the data provided
381 389 in the last call to `update_nodemap_data`.
382 390 """
383 391 if self._nm_root is None:
384 392 return None
385 393 docket = self._nm_docket
386 394 changed, data = nodemaputil.update_persistent_data(
387 395 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
388 396 )
389 397
390 398 self._nm_root = self._nm_max_idx = self._nm_docket = None
391 399 return docket, changed, data
392 400
393 401 def update_nodemap_data(self, docket, nm_data):
394 402 """provide full block of persisted binary data for a nodemap
395 403
396 404 The data are expected to come from disk. See `nodemap_data_all` for a
397 405 produceur of such data."""
398 406 if nm_data is not None:
399 407 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
400 408 if self._nm_root:
401 409 self._nm_docket = docket
402 410 else:
403 411 self._nm_root = self._nm_max_idx = self._nm_docket = None
404 412
405 413
406 414 class InlinedIndexObject(BaseIndexObject):
407 415 def __init__(self, data, inline=0):
408 416 self._data = data
409 417 self._lgt = self._inline_scan(None)
410 418 self._inline_scan(self._lgt)
411 419 self._extra = []
412 420
413 421 def _inline_scan(self, lgt):
414 422 off = 0
415 423 if lgt is not None:
416 424 self._offsets = [0] * lgt
417 425 count = 0
418 426 while off <= len(self._data) - self.entry_size:
419 427 start = off + self.big_int_size
420 428 (s,) = struct.unpack(
421 429 b'>i',
422 430 self._data[start : start + self.int_size],
423 431 )
424 432 if lgt is not None:
425 433 self._offsets[count] = off
426 434 count += 1
427 435 off += self.entry_size + s
428 436 if off != len(self._data):
429 437 raise ValueError(b"corrupted data")
430 438 return count
431 439
432 440 def __delitem__(self, i):
433 441 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
434 442 raise ValueError(b"deleting slices only supports a:-1 with step 1")
435 443 i = i.start
436 444 self._check_index(i)
437 445 self._stripnodes(i)
438 446 if i < self._lgt:
439 447 self._offsets = self._offsets[:i]
440 448 self._lgt = i
441 449 self._extra = []
442 450 else:
443 451 self._extra = self._extra[: i - self._lgt]
444 452
445 453 def _calculate_index(self, i):
446 454 return self._offsets[i]
447 455
448 456
449 457 def parse_index2(data, inline, revlogv2=False):
450 458 if not inline:
451 459 cls = IndexObject2 if revlogv2 else IndexObject
452 460 return cls(data), None
453 461 cls = InlinedIndexObject
454 462 return cls(data, inline), (0, data)
455 463
456 464
457 465 def parse_index_cl_v2(data):
458 466 return IndexChangelogV2(data), None
459 467
460 468
461 469 class IndexObject2(IndexObject):
462 470 index_format = revlog_constants.INDEX_ENTRY_V2
463 471
464 472 def replace_sidedata_info(
465 473 self,
466 474 rev,
467 475 sidedata_offset,
468 476 sidedata_length,
469 477 offset_flags,
470 478 compression_mode,
471 479 ):
472 480 """
473 481 Replace an existing index entry's sidedata offset and length with new
474 482 ones.
475 483 This cannot be used outside of the context of sidedata rewriting,
476 484 inside the transaction that creates the revision `rev`.
477 485 """
478 486 if rev < 0:
479 487 raise KeyError
480 488 self._check_index(rev)
481 489 if rev < self._lgt:
482 490 msg = b"cannot rewrite entries outside of this transaction"
483 491 raise KeyError(msg)
484 492 else:
485 493 entry = list(self[rev])
486 494 entry[0] = offset_flags
487 495 entry[8] = sidedata_offset
488 496 entry[9] = sidedata_length
489 497 entry[11] = compression_mode
490 498 entry = tuple(entry)
491 499 new = self._pack_entry(rev, entry)
492 500 self._extra[rev - self._lgt] = new
493 501
494 502 def _unpack_entry(self, rev, data):
495 503 data = self.index_format.unpack(data)
496 504 entry = data[:10]
497 505 data_comp = data[10] & 3
498 506 sidedata_comp = (data[10] & (3 << 2)) >> 2
499 507 return entry + (data_comp, sidedata_comp)
500 508
501 509 def _pack_entry(self, rev, entry):
502 510 data = entry[:10]
503 511 data_comp = entry[10] & 3
504 512 sidedata_comp = (entry[11] & 3) << 2
505 513 data += (data_comp | sidedata_comp,)
506 514
507 515 return self.index_format.pack(*data)
508 516
509 517 def entry_binary(self, rev):
510 518 """return the raw binary string representing a revision"""
511 519 entry = self[rev]
512 520 return self._pack_entry(rev, entry)
513 521
514 522 def pack_header(self, header):
515 523 """pack header information as binary"""
516 524 msg = 'version header should go in the docket, not the index: %d'
517 525 msg %= header
518 526 raise error.ProgrammingError(msg)
519 527
520 528
521 529 class IndexChangelogV2(IndexObject2):
522 530 index_format = revlog_constants.INDEX_ENTRY_CL_V2
523 531
524 532 def _unpack_entry(self, rev, data, r=True):
525 533 items = self.index_format.unpack(data)
526 534 entry = items[:3] + (rev, rev) + items[3:8]
527 535 data_comp = items[8] & 3
528 536 sidedata_comp = (items[8] >> 2) & 3
529 537 return entry + (data_comp, sidedata_comp)
530 538
531 539 def _pack_entry(self, rev, entry):
532 540 assert entry[3] == rev, entry[3]
533 541 assert entry[4] == rev, entry[4]
534 542 data = entry[:3] + entry[5:10]
535 543 data_comp = entry[10] & 3
536 544 sidedata_comp = (entry[11] & 3) << 2
537 545 data += (data_comp | sidedata_comp,)
538 546 return self.index_format.pack(*data)
539 547
540 548
541 549 def parse_index_devel_nodemap(data, inline):
542 550 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
543 551 return PersistentNodeMapIndexObject(data), None
544 552
545 553
546 554 def parse_dirstate(dmap, copymap, st):
547 555 parents = [st[:20], st[20:40]]
548 556 # dereference fields so they will be local in loop
549 557 format = b">cllll"
550 558 e_size = struct.calcsize(format)
551 559 pos1 = 40
552 560 l = len(st)
553 561
554 562 # the inner loop
555 563 while pos1 < l:
556 564 pos2 = pos1 + e_size
557 565 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
558 566 pos1 = pos2 + e[4]
559 567 f = st[pos2:pos1]
560 568 if b'\0' in f:
561 569 f, c = f.split(b'\0')
562 570 copymap[f] = c
563 571 dmap[f] = DirstateItem.from_v1_data(*e[:4])
564 572 return parents
565 573
566 574
567 575 def pack_dirstate(dmap, copymap, pl, now):
568 576 now = int(now)
569 577 cs = stringio()
570 578 write = cs.write
571 579 write(b"".join(pl))
572 580 for f, e in pycompat.iteritems(dmap):
573 581 if e.need_delay(now):
574 582 # The file was last modified "simultaneously" with the current
575 583 # write to dirstate (i.e. within the same second for file-
576 584 # systems with a granularity of 1 sec). This commonly happens
577 585 # for at least a couple of files on 'update'.
578 586 # The user could change the file without changing its size
579 587 # within the same second. Invalidate the file's mtime in
580 588 # dirstate, forcing future 'status' calls to compare the
581 589 # contents of the file if the size is the same. This prevents
582 590 # mistakenly treating such files as clean.
583 591 e = DirstateItem(e.state, e.mode, e.size, AMBIGUOUS_TIME)
584 592 dmap[f] = e
585 593
586 594 if f in copymap:
587 595 f = b"%s\0%s" % (f, copymap[f])
588 596 e = _pack(
589 597 b">cllll",
590 598 e.v1_state(),
591 599 e.v1_mode(),
592 600 e.v1_size(),
593 601 e.v1_mtime(),
594 602 len(f),
595 603 )
596 604 write(e)
597 605 write(f)
598 606 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now