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