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