##// END OF EJS Templates
dirstate-entry: add a `need_delay` method...
marmoute -
r48321:ccbabaee default
parent child Browse files
Show More
@@ -1,880 +1,896
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
35 35 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
36 36 {
37 37 Py_ssize_t expected_size;
38 38
39 39 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
40 40 return NULL;
41 41 }
42 42
43 43 return _dict_new_presized(expected_size);
44 44 }
45 45
46 46 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
47 47 int size, int mtime)
48 48 {
49 49 dirstateTupleObject *t =
50 50 PyObject_New(dirstateTupleObject, &dirstateTupleType);
51 51 if (!t) {
52 52 return NULL;
53 53 }
54 54 t->state = state;
55 55 t->mode = mode;
56 56 t->size = size;
57 57 t->mtime = mtime;
58 58 return t;
59 59 }
60 60
61 61 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
62 62 PyObject *kwds)
63 63 {
64 64 /* We do all the initialization here and not a tp_init function because
65 65 * dirstate_tuple is immutable. */
66 66 dirstateTupleObject *t;
67 67 char state;
68 68 int size, mode, mtime;
69 69 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
70 70 return NULL;
71 71 }
72 72
73 73 t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
74 74 if (!t) {
75 75 return NULL;
76 76 }
77 77 t->state = state;
78 78 t->mode = mode;
79 79 t->size = size;
80 80 t->mtime = mtime;
81 81
82 82 return (PyObject *)t;
83 83 }
84 84
85 85 static void dirstate_tuple_dealloc(PyObject *o)
86 86 {
87 87 PyObject_Del(o);
88 88 }
89 89
90 90 static Py_ssize_t dirstate_tuple_length(PyObject *o)
91 91 {
92 92 return 4;
93 93 }
94 94
95 95 static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
96 96 {
97 97 dirstateTupleObject *t = (dirstateTupleObject *)o;
98 98 switch (i) {
99 99 case 0:
100 100 return PyBytes_FromStringAndSize(&t->state, 1);
101 101 case 1:
102 102 return PyInt_FromLong(t->mode);
103 103 case 2:
104 104 return PyInt_FromLong(t->size);
105 105 case 3:
106 106 return PyInt_FromLong(t->mtime);
107 107 default:
108 108 PyErr_SetString(PyExc_IndexError, "index out of range");
109 109 return NULL;
110 110 }
111 111 }
112 112
113 113 static PySequenceMethods dirstate_tuple_sq = {
114 114 dirstate_tuple_length, /* sq_length */
115 115 0, /* sq_concat */
116 116 0, /* sq_repeat */
117 117 dirstate_tuple_item, /* sq_item */
118 118 0, /* sq_ass_item */
119 119 0, /* sq_contains */
120 120 0, /* sq_inplace_concat */
121 121 0 /* sq_inplace_repeat */
122 122 };
123 123
124 124 static PyObject *dirstatetuple_v1_state(dirstateTupleObject *self)
125 125 {
126 126 return PyBytes_FromStringAndSize(&self->state, 1);
127 127 };
128 128
129 129 static PyObject *dirstatetuple_v1_mode(dirstateTupleObject *self)
130 130 {
131 131 return PyInt_FromLong(self->mode);
132 132 };
133 133
134 134 static PyObject *dirstatetuple_v1_size(dirstateTupleObject *self)
135 135 {
136 136 return PyInt_FromLong(self->size);
137 137 };
138 138
139 139 static PyObject *dirstatetuple_v1_mtime(dirstateTupleObject *self)
140 140 {
141 141 return PyInt_FromLong(self->mtime);
142 142 };
143 143
144 static PyObject *dirstatetuple_need_delay(dirstateTupleObject *self,
145 PyObject *value)
146 {
147 long now;
148 if (!pylong_to_long(value, &now)) {
149 return NULL;
150 }
151 if (self->state == 'n' && self->mtime == now) {
152 Py_RETURN_TRUE;
153 } else {
154 Py_RETURN_FALSE;
155 }
156 };
157
144 158 static PyMethodDef dirstatetuple_methods[] = {
145 159 {"v1_state", (PyCFunction)dirstatetuple_v1_state, METH_NOARGS,
146 160 "return a \"state\" suitable for v1 serialization"},
147 161 {"v1_mode", (PyCFunction)dirstatetuple_v1_mode, METH_NOARGS,
148 162 "return a \"mode\" suitable for v1 serialization"},
149 163 {"v1_size", (PyCFunction)dirstatetuple_v1_size, METH_NOARGS,
150 164 "return a \"size\" suitable for v1 serialization"},
151 165 {"v1_mtime", (PyCFunction)dirstatetuple_v1_mtime, METH_NOARGS,
152 166 "return a \"mtime\" suitable for v1 serialization"},
167 {"need_delay", (PyCFunction)dirstatetuple_need_delay, METH_O,
168 "True if the stored mtime would be ambiguous with the current time"},
153 169 {NULL} /* Sentinel */
154 170 };
155 171
156 172 static PyObject *dirstatetuple_get_state(dirstateTupleObject *self)
157 173 {
158 174 return PyBytes_FromStringAndSize(&self->state, 1);
159 175 };
160 176
161 177 static PyObject *dirstatetuple_get_tracked(dirstateTupleObject *self)
162 178 {
163 179 if (self->state == 'a' || self->state == 'm' || self->state == 'n') {
164 180 Py_RETURN_TRUE;
165 181 } else {
166 182 Py_RETURN_FALSE;
167 183 }
168 184 };
169 185
170 186 static PyObject *dirstatetuple_get_added(dirstateTupleObject *self)
171 187 {
172 188 if (self->state == 'a') {
173 189 Py_RETURN_TRUE;
174 190 } else {
175 191 Py_RETURN_FALSE;
176 192 }
177 193 };
178 194
179 195 static PyObject *dirstatetuple_get_merged(dirstateTupleObject *self)
180 196 {
181 197 if (self->state == 'm') {
182 198 Py_RETURN_TRUE;
183 199 } else {
184 200 Py_RETURN_FALSE;
185 201 }
186 202 };
187 203
188 204 static PyObject *dirstatetuple_get_merged_removed(dirstateTupleObject *self)
189 205 {
190 206 if (self->state == 'r' && self->size == dirstate_v1_nonnormal) {
191 207 Py_RETURN_TRUE;
192 208 } else {
193 209 Py_RETURN_FALSE;
194 210 }
195 211 };
196 212
197 213 static PyObject *dirstatetuple_get_from_p2(dirstateTupleObject *self)
198 214 {
199 215 if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
200 216 Py_RETURN_TRUE;
201 217 } else {
202 218 Py_RETURN_FALSE;
203 219 }
204 220 };
205 221
206 222 static PyObject *dirstatetuple_get_from_p2_removed(dirstateTupleObject *self)
207 223 {
208 224 if (self->state == 'r' && self->size == dirstate_v1_from_p2) {
209 225 Py_RETURN_TRUE;
210 226 } else {
211 227 Py_RETURN_FALSE;
212 228 }
213 229 };
214 230
215 231 static PyObject *dirstatetuple_get_removed(dirstateTupleObject *self)
216 232 {
217 233 if (self->state == 'r') {
218 234 Py_RETURN_TRUE;
219 235 } else {
220 236 Py_RETURN_FALSE;
221 237 }
222 238 };
223 239
224 240 static PyGetSetDef dirstatetuple_getset[] = {
225 241 {"state", (getter)dirstatetuple_get_state, NULL, "state", NULL},
226 242 {"tracked", (getter)dirstatetuple_get_tracked, NULL, "tracked", NULL},
227 243 {"added", (getter)dirstatetuple_get_added, NULL, "added", NULL},
228 244 {"merged_removed", (getter)dirstatetuple_get_merged_removed, NULL,
229 245 "merged_removed", NULL},
230 246 {"merged", (getter)dirstatetuple_get_merged, NULL, "merged", NULL},
231 247 {"from_p2_removed", (getter)dirstatetuple_get_from_p2_removed, NULL,
232 248 "from_p2_removed", NULL},
233 249 {"from_p2", (getter)dirstatetuple_get_from_p2, NULL, "from_p2", NULL},
234 250 {"removed", (getter)dirstatetuple_get_removed, NULL, "removed", NULL},
235 251 {NULL} /* Sentinel */
236 252 };
237 253
238 254 PyTypeObject dirstateTupleType = {
239 255 PyVarObject_HEAD_INIT(NULL, 0) /* header */
240 256 "dirstate_tuple", /* tp_name */
241 257 sizeof(dirstateTupleObject), /* tp_basicsize */
242 258 0, /* tp_itemsize */
243 259 (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
244 260 0, /* tp_print */
245 261 0, /* tp_getattr */
246 262 0, /* tp_setattr */
247 263 0, /* tp_compare */
248 264 0, /* tp_repr */
249 265 0, /* tp_as_number */
250 266 &dirstate_tuple_sq, /* tp_as_sequence */
251 267 0, /* tp_as_mapping */
252 268 0, /* tp_hash */
253 269 0, /* tp_call */
254 270 0, /* tp_str */
255 271 0, /* tp_getattro */
256 272 0, /* tp_setattro */
257 273 0, /* tp_as_buffer */
258 274 Py_TPFLAGS_DEFAULT, /* tp_flags */
259 275 "dirstate tuple", /* tp_doc */
260 276 0, /* tp_traverse */
261 277 0, /* tp_clear */
262 278 0, /* tp_richcompare */
263 279 0, /* tp_weaklistoffset */
264 280 0, /* tp_iter */
265 281 0, /* tp_iternext */
266 282 dirstatetuple_methods, /* tp_methods */
267 283 0, /* tp_members */
268 284 dirstatetuple_getset, /* tp_getset */
269 285 0, /* tp_base */
270 286 0, /* tp_dict */
271 287 0, /* tp_descr_get */
272 288 0, /* tp_descr_set */
273 289 0, /* tp_dictoffset */
274 290 0, /* tp_init */
275 291 0, /* tp_alloc */
276 292 dirstate_tuple_new, /* tp_new */
277 293 };
278 294
279 295 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
280 296 {
281 297 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
282 298 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
283 299 char state, *cur, *str, *cpos;
284 300 int mode, size, mtime;
285 301 unsigned int flen, pos = 40;
286 302 Py_ssize_t len = 40;
287 303 Py_ssize_t readlen;
288 304
289 305 if (!PyArg_ParseTuple(
290 306 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
291 307 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
292 308 goto quit;
293 309 }
294 310
295 311 len = readlen;
296 312
297 313 /* read parents */
298 314 if (len < 40) {
299 315 PyErr_SetString(PyExc_ValueError,
300 316 "too little data for parents");
301 317 goto quit;
302 318 }
303 319
304 320 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
305 321 str + 20, (Py_ssize_t)20);
306 322 if (!parents) {
307 323 goto quit;
308 324 }
309 325
310 326 /* read filenames */
311 327 while (pos >= 40 && pos < len) {
312 328 if (pos + 17 > len) {
313 329 PyErr_SetString(PyExc_ValueError,
314 330 "overflow in dirstate");
315 331 goto quit;
316 332 }
317 333 cur = str + pos;
318 334 /* unpack header */
319 335 state = *cur;
320 336 mode = getbe32(cur + 1);
321 337 size = getbe32(cur + 5);
322 338 mtime = getbe32(cur + 9);
323 339 flen = getbe32(cur + 13);
324 340 pos += 17;
325 341 cur += 17;
326 342 if (flen > len - pos) {
327 343 PyErr_SetString(PyExc_ValueError,
328 344 "overflow in dirstate");
329 345 goto quit;
330 346 }
331 347
332 348 entry =
333 349 (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
334 350 cpos = memchr(cur, 0, flen);
335 351 if (cpos) {
336 352 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
337 353 cname = PyBytes_FromStringAndSize(
338 354 cpos + 1, flen - (cpos - cur) - 1);
339 355 if (!fname || !cname ||
340 356 PyDict_SetItem(cmap, fname, cname) == -1 ||
341 357 PyDict_SetItem(dmap, fname, entry) == -1) {
342 358 goto quit;
343 359 }
344 360 Py_DECREF(cname);
345 361 } else {
346 362 fname = PyBytes_FromStringAndSize(cur, flen);
347 363 if (!fname ||
348 364 PyDict_SetItem(dmap, fname, entry) == -1) {
349 365 goto quit;
350 366 }
351 367 }
352 368 Py_DECREF(fname);
353 369 Py_DECREF(entry);
354 370 fname = cname = entry = NULL;
355 371 pos += flen;
356 372 }
357 373
358 374 ret = parents;
359 375 Py_INCREF(ret);
360 376 quit:
361 377 Py_XDECREF(fname);
362 378 Py_XDECREF(cname);
363 379 Py_XDECREF(entry);
364 380 Py_XDECREF(parents);
365 381 return ret;
366 382 }
367 383
368 384 /*
369 385 * Build a set of non-normal and other parent entries from the dirstate dmap
370 386 */
371 387 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
372 388 {
373 389 PyObject *dmap, *fname, *v;
374 390 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
375 391 Py_ssize_t pos;
376 392
377 393 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
378 394 &dmap)) {
379 395 goto bail;
380 396 }
381 397
382 398 nonnset = PySet_New(NULL);
383 399 if (nonnset == NULL) {
384 400 goto bail;
385 401 }
386 402
387 403 otherpset = PySet_New(NULL);
388 404 if (otherpset == NULL) {
389 405 goto bail;
390 406 }
391 407
392 408 pos = 0;
393 409 while (PyDict_Next(dmap, &pos, &fname, &v)) {
394 410 dirstateTupleObject *t;
395 411 if (!dirstate_tuple_check(v)) {
396 412 PyErr_SetString(PyExc_TypeError,
397 413 "expected a dirstate tuple");
398 414 goto bail;
399 415 }
400 416 t = (dirstateTupleObject *)v;
401 417
402 418 if (t->state == 'n' && t->size == -2) {
403 419 if (PySet_Add(otherpset, fname) == -1) {
404 420 goto bail;
405 421 }
406 422 }
407 423
408 424 if (t->state == 'n' && t->mtime != -1) {
409 425 continue;
410 426 }
411 427 if (PySet_Add(nonnset, fname) == -1) {
412 428 goto bail;
413 429 }
414 430 }
415 431
416 432 result = Py_BuildValue("(OO)", nonnset, otherpset);
417 433 if (result == NULL) {
418 434 goto bail;
419 435 }
420 436 Py_DECREF(nonnset);
421 437 Py_DECREF(otherpset);
422 438 return result;
423 439 bail:
424 440 Py_XDECREF(nonnset);
425 441 Py_XDECREF(otherpset);
426 442 Py_XDECREF(result);
427 443 return NULL;
428 444 }
429 445
430 446 /*
431 447 * Efficiently pack a dirstate object into its on-disk format.
432 448 */
433 449 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
434 450 {
435 451 PyObject *packobj = NULL;
436 452 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
437 453 Py_ssize_t nbytes, pos, l;
438 454 PyObject *k, *v = NULL, *pn;
439 455 char *p, *s;
440 456 int now;
441 457
442 458 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
443 459 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
444 460 &now)) {
445 461 return NULL;
446 462 }
447 463
448 464 if (PyTuple_Size(pl) != 2) {
449 465 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
450 466 return NULL;
451 467 }
452 468
453 469 /* Figure out how much we need to allocate. */
454 470 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
455 471 PyObject *c;
456 472 if (!PyBytes_Check(k)) {
457 473 PyErr_SetString(PyExc_TypeError, "expected string key");
458 474 goto bail;
459 475 }
460 476 nbytes += PyBytes_GET_SIZE(k) + 17;
461 477 c = PyDict_GetItem(copymap, k);
462 478 if (c) {
463 479 if (!PyBytes_Check(c)) {
464 480 PyErr_SetString(PyExc_TypeError,
465 481 "expected string key");
466 482 goto bail;
467 483 }
468 484 nbytes += PyBytes_GET_SIZE(c) + 1;
469 485 }
470 486 }
471 487
472 488 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
473 489 if (packobj == NULL) {
474 490 goto bail;
475 491 }
476 492
477 493 p = PyBytes_AS_STRING(packobj);
478 494
479 495 pn = PyTuple_GET_ITEM(pl, 0);
480 496 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
481 497 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
482 498 goto bail;
483 499 }
484 500 memcpy(p, s, l);
485 501 p += 20;
486 502 pn = PyTuple_GET_ITEM(pl, 1);
487 503 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
488 504 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
489 505 goto bail;
490 506 }
491 507 memcpy(p, s, l);
492 508 p += 20;
493 509
494 510 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
495 511 dirstateTupleObject *tuple;
496 512 char state;
497 513 int mode, size, mtime;
498 514 Py_ssize_t len, l;
499 515 PyObject *o;
500 516 char *t;
501 517
502 518 if (!dirstate_tuple_check(v)) {
503 519 PyErr_SetString(PyExc_TypeError,
504 520 "expected a dirstate tuple");
505 521 goto bail;
506 522 }
507 523 tuple = (dirstateTupleObject *)v;
508 524
509 525 state = tuple->state;
510 526 mode = tuple->mode;
511 527 size = tuple->size;
512 528 mtime = tuple->mtime;
513 529 if (state == 'n' && mtime == now) {
514 530 /* See pure/parsers.py:pack_dirstate for why we do
515 531 * this. */
516 532 mtime = -1;
517 533 mtime_unset = (PyObject *)make_dirstate_tuple(
518 534 state, mode, size, mtime);
519 535 if (!mtime_unset) {
520 536 goto bail;
521 537 }
522 538 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
523 539 goto bail;
524 540 }
525 541 Py_DECREF(mtime_unset);
526 542 mtime_unset = NULL;
527 543 }
528 544 *p++ = state;
529 545 putbe32((uint32_t)mode, p);
530 546 putbe32((uint32_t)size, p + 4);
531 547 putbe32((uint32_t)mtime, p + 8);
532 548 t = p + 12;
533 549 p += 16;
534 550 len = PyBytes_GET_SIZE(k);
535 551 memcpy(p, PyBytes_AS_STRING(k), len);
536 552 p += len;
537 553 o = PyDict_GetItem(copymap, k);
538 554 if (o) {
539 555 *p++ = '\0';
540 556 l = PyBytes_GET_SIZE(o);
541 557 memcpy(p, PyBytes_AS_STRING(o), l);
542 558 p += l;
543 559 len += l + 1;
544 560 }
545 561 putbe32((uint32_t)len, t);
546 562 }
547 563
548 564 pos = p - PyBytes_AS_STRING(packobj);
549 565 if (pos != nbytes) {
550 566 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
551 567 (long)pos, (long)nbytes);
552 568 goto bail;
553 569 }
554 570
555 571 return packobj;
556 572 bail:
557 573 Py_XDECREF(mtime_unset);
558 574 Py_XDECREF(packobj);
559 575 Py_XDECREF(v);
560 576 return NULL;
561 577 }
562 578
563 579 #define BUMPED_FIX 1
564 580 #define USING_SHA_256 2
565 581 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
566 582
567 583 static PyObject *readshas(const char *source, unsigned char num,
568 584 Py_ssize_t hashwidth)
569 585 {
570 586 int i;
571 587 PyObject *list = PyTuple_New(num);
572 588 if (list == NULL) {
573 589 return NULL;
574 590 }
575 591 for (i = 0; i < num; i++) {
576 592 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
577 593 if (hash == NULL) {
578 594 Py_DECREF(list);
579 595 return NULL;
580 596 }
581 597 PyTuple_SET_ITEM(list, i, hash);
582 598 source += hashwidth;
583 599 }
584 600 return list;
585 601 }
586 602
587 603 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
588 604 uint32_t *msize)
589 605 {
590 606 const char *data = databegin;
591 607 const char *meta;
592 608
593 609 double mtime;
594 610 int16_t tz;
595 611 uint16_t flags;
596 612 unsigned char nsuccs, nparents, nmetadata;
597 613 Py_ssize_t hashwidth = 20;
598 614
599 615 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
600 616 PyObject *metadata = NULL, *ret = NULL;
601 617 int i;
602 618
603 619 if (data + FM1_HEADER_SIZE > dataend) {
604 620 goto overflow;
605 621 }
606 622
607 623 *msize = getbe32(data);
608 624 data += 4;
609 625 mtime = getbefloat64(data);
610 626 data += 8;
611 627 tz = getbeint16(data);
612 628 data += 2;
613 629 flags = getbeuint16(data);
614 630 data += 2;
615 631
616 632 if (flags & USING_SHA_256) {
617 633 hashwidth = 32;
618 634 }
619 635
620 636 nsuccs = (unsigned char)(*data++);
621 637 nparents = (unsigned char)(*data++);
622 638 nmetadata = (unsigned char)(*data++);
623 639
624 640 if (databegin + *msize > dataend) {
625 641 goto overflow;
626 642 }
627 643 dataend = databegin + *msize; /* narrow down to marker size */
628 644
629 645 if (data + hashwidth > dataend) {
630 646 goto overflow;
631 647 }
632 648 prec = PyBytes_FromStringAndSize(data, hashwidth);
633 649 data += hashwidth;
634 650 if (prec == NULL) {
635 651 goto bail;
636 652 }
637 653
638 654 if (data + nsuccs * hashwidth > dataend) {
639 655 goto overflow;
640 656 }
641 657 succs = readshas(data, nsuccs, hashwidth);
642 658 if (succs == NULL) {
643 659 goto bail;
644 660 }
645 661 data += nsuccs * hashwidth;
646 662
647 663 if (nparents == 1 || nparents == 2) {
648 664 if (data + nparents * hashwidth > dataend) {
649 665 goto overflow;
650 666 }
651 667 parents = readshas(data, nparents, hashwidth);
652 668 if (parents == NULL) {
653 669 goto bail;
654 670 }
655 671 data += nparents * hashwidth;
656 672 } else {
657 673 parents = Py_None;
658 674 Py_INCREF(parents);
659 675 }
660 676
661 677 if (data + 2 * nmetadata > dataend) {
662 678 goto overflow;
663 679 }
664 680 meta = data + (2 * nmetadata);
665 681 metadata = PyTuple_New(nmetadata);
666 682 if (metadata == NULL) {
667 683 goto bail;
668 684 }
669 685 for (i = 0; i < nmetadata; i++) {
670 686 PyObject *tmp, *left = NULL, *right = NULL;
671 687 Py_ssize_t leftsize = (unsigned char)(*data++);
672 688 Py_ssize_t rightsize = (unsigned char)(*data++);
673 689 if (meta + leftsize + rightsize > dataend) {
674 690 goto overflow;
675 691 }
676 692 left = PyBytes_FromStringAndSize(meta, leftsize);
677 693 meta += leftsize;
678 694 right = PyBytes_FromStringAndSize(meta, rightsize);
679 695 meta += rightsize;
680 696 tmp = PyTuple_New(2);
681 697 if (!left || !right || !tmp) {
682 698 Py_XDECREF(left);
683 699 Py_XDECREF(right);
684 700 Py_XDECREF(tmp);
685 701 goto bail;
686 702 }
687 703 PyTuple_SET_ITEM(tmp, 0, left);
688 704 PyTuple_SET_ITEM(tmp, 1, right);
689 705 PyTuple_SET_ITEM(metadata, i, tmp);
690 706 }
691 707 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
692 708 (int)tz * 60, parents);
693 709 goto bail; /* return successfully */
694 710
695 711 overflow:
696 712 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
697 713 bail:
698 714 Py_XDECREF(prec);
699 715 Py_XDECREF(succs);
700 716 Py_XDECREF(metadata);
701 717 Py_XDECREF(parents);
702 718 return ret;
703 719 }
704 720
705 721 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
706 722 {
707 723 const char *data, *dataend;
708 724 Py_ssize_t datalen, offset, stop;
709 725 PyObject *markers = NULL;
710 726
711 727 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
712 728 &offset, &stop)) {
713 729 return NULL;
714 730 }
715 731 if (offset < 0) {
716 732 PyErr_SetString(PyExc_ValueError,
717 733 "invalid negative offset in fm1readmarkers");
718 734 return NULL;
719 735 }
720 736 if (stop > datalen) {
721 737 PyErr_SetString(
722 738 PyExc_ValueError,
723 739 "stop longer than data length in fm1readmarkers");
724 740 return NULL;
725 741 }
726 742 dataend = data + datalen;
727 743 data += offset;
728 744 markers = PyList_New(0);
729 745 if (!markers) {
730 746 return NULL;
731 747 }
732 748 while (offset < stop) {
733 749 uint32_t msize;
734 750 int error;
735 751 PyObject *record = fm1readmarker(data, dataend, &msize);
736 752 if (!record) {
737 753 goto bail;
738 754 }
739 755 error = PyList_Append(markers, record);
740 756 Py_DECREF(record);
741 757 if (error) {
742 758 goto bail;
743 759 }
744 760 data += msize;
745 761 offset += msize;
746 762 }
747 763 return markers;
748 764 bail:
749 765 Py_DECREF(markers);
750 766 return NULL;
751 767 }
752 768
753 769 static char parsers_doc[] = "Efficient content parsing.";
754 770
755 771 PyObject *encodedir(PyObject *self, PyObject *args);
756 772 PyObject *pathencode(PyObject *self, PyObject *args);
757 773 PyObject *lowerencode(PyObject *self, PyObject *args);
758 774 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
759 775
760 776 static PyMethodDef methods[] = {
761 777 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
762 778 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
763 779 "create a set containing non-normal and other parent entries of given "
764 780 "dirstate\n"},
765 781 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
766 782 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
767 783 "parse a revlog index\n"},
768 784 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
769 785 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
770 786 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
771 787 {"dict_new_presized", dict_new_presized, METH_VARARGS,
772 788 "construct a dict with an expected size\n"},
773 789 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
774 790 "make file foldmap\n"},
775 791 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
776 792 "escape a UTF-8 byte string to JSON (fast path)\n"},
777 793 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
778 794 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
779 795 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
780 796 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
781 797 "parse v1 obsolete markers\n"},
782 798 {NULL, NULL}};
783 799
784 800 void dirs_module_init(PyObject *mod);
785 801 void manifest_module_init(PyObject *mod);
786 802 void revlog_module_init(PyObject *mod);
787 803
788 804 static const int version = 20;
789 805
790 806 static void module_init(PyObject *mod)
791 807 {
792 808 PyObject *capsule = NULL;
793 809 PyModule_AddIntConstant(mod, "version", version);
794 810
795 811 /* This module constant has two purposes. First, it lets us unit test
796 812 * the ImportError raised without hard-coding any error text. This
797 813 * means we can change the text in the future without breaking tests,
798 814 * even across changesets without a recompile. Second, its presence
799 815 * can be used to determine whether the version-checking logic is
800 816 * present, which also helps in testing across changesets without a
801 817 * recompile. Note that this means the pure-Python version of parsers
802 818 * should not have this module constant. */
803 819 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
804 820
805 821 dirs_module_init(mod);
806 822 manifest_module_init(mod);
807 823 revlog_module_init(mod);
808 824
809 825 capsule = PyCapsule_New(
810 826 make_dirstate_tuple,
811 827 "mercurial.cext.parsers.make_dirstate_tuple_CAPI", NULL);
812 828 if (capsule != NULL)
813 829 PyModule_AddObject(mod, "make_dirstate_tuple_CAPI", capsule);
814 830
815 831 if (PyType_Ready(&dirstateTupleType) < 0) {
816 832 return;
817 833 }
818 834 Py_INCREF(&dirstateTupleType);
819 835 PyModule_AddObject(mod, "dirstatetuple",
820 836 (PyObject *)&dirstateTupleType);
821 837 }
822 838
823 839 static int check_python_version(void)
824 840 {
825 841 PyObject *sys = PyImport_ImportModule("sys"), *ver;
826 842 long hexversion;
827 843 if (!sys) {
828 844 return -1;
829 845 }
830 846 ver = PyObject_GetAttrString(sys, "hexversion");
831 847 Py_DECREF(sys);
832 848 if (!ver) {
833 849 return -1;
834 850 }
835 851 hexversion = PyInt_AsLong(ver);
836 852 Py_DECREF(ver);
837 853 /* sys.hexversion is a 32-bit number by default, so the -1 case
838 854 * should only occur in unusual circumstances (e.g. if sys.hexversion
839 855 * is manually set to an invalid value). */
840 856 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
841 857 PyErr_Format(PyExc_ImportError,
842 858 "%s: The Mercurial extension "
843 859 "modules were compiled with Python " PY_VERSION
844 860 ", but "
845 861 "Mercurial is currently using Python with "
846 862 "sys.hexversion=%ld: "
847 863 "Python %s\n at: %s",
848 864 versionerrortext, hexversion, Py_GetVersion(),
849 865 Py_GetProgramFullPath());
850 866 return -1;
851 867 }
852 868 return 0;
853 869 }
854 870
855 871 #ifdef IS_PY3K
856 872 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
857 873 parsers_doc, -1, methods};
858 874
859 875 PyMODINIT_FUNC PyInit_parsers(void)
860 876 {
861 877 PyObject *mod;
862 878
863 879 if (check_python_version() == -1)
864 880 return NULL;
865 881 mod = PyModule_Create(&parsers_module);
866 882 module_init(mod);
867 883 return mod;
868 884 }
869 885 #else
870 886 PyMODINIT_FUNC initparsers(void)
871 887 {
872 888 PyObject *mod;
873 889
874 890 if (check_python_version() == -1) {
875 891 return;
876 892 }
877 893 mod = Py_InitModule3("parsers", methods, parsers_doc);
878 894 module_init(mod);
879 895 }
880 896 #endif
@@ -1,1437 +1,1437
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
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 collections
11 11 import contextlib
12 12 import errno
13 13 import os
14 14 import stat
15 15
16 16 from .i18n import _
17 17 from .pycompat import delattr
18 18
19 19 from hgdemandimport import tracing
20 20
21 21 from . import (
22 22 dirstatemap,
23 23 encoding,
24 24 error,
25 25 match as matchmod,
26 26 pathutil,
27 27 policy,
28 28 pycompat,
29 29 scmutil,
30 30 sparse,
31 31 util,
32 32 )
33 33
34 34 from .interfaces import (
35 35 dirstate as intdirstate,
36 36 util as interfaceutil,
37 37 )
38 38
39 39 parsers = policy.importmod('parsers')
40 40 rustmod = policy.importrust('dirstate')
41 41
42 42 SUPPORTS_DIRSTATE_V2 = rustmod is not None
43 43
44 44 propertycache = util.propertycache
45 45 filecache = scmutil.filecache
46 46 _rangemask = dirstatemap.rangemask
47 47
48 48 dirstatetuple = parsers.dirstatetuple
49 49
50 50
51 51 class repocache(filecache):
52 52 """filecache for files in .hg/"""
53 53
54 54 def join(self, obj, fname):
55 55 return obj._opener.join(fname)
56 56
57 57
58 58 class rootcache(filecache):
59 59 """filecache for files in the repository root"""
60 60
61 61 def join(self, obj, fname):
62 62 return obj._join(fname)
63 63
64 64
65 65 def _getfsnow(vfs):
66 66 '''Get "now" timestamp on filesystem'''
67 67 tmpfd, tmpname = vfs.mkstemp()
68 68 try:
69 69 return os.fstat(tmpfd)[stat.ST_MTIME]
70 70 finally:
71 71 os.close(tmpfd)
72 72 vfs.unlink(tmpname)
73 73
74 74
75 75 @interfaceutil.implementer(intdirstate.idirstate)
76 76 class dirstate(object):
77 77 def __init__(
78 78 self,
79 79 opener,
80 80 ui,
81 81 root,
82 82 validate,
83 83 sparsematchfn,
84 84 nodeconstants,
85 85 use_dirstate_v2,
86 86 ):
87 87 """Create a new dirstate object.
88 88
89 89 opener is an open()-like callable that can be used to open the
90 90 dirstate file; root is the root of the directory tracked by
91 91 the dirstate.
92 92 """
93 93 self._use_dirstate_v2 = use_dirstate_v2
94 94 self._nodeconstants = nodeconstants
95 95 self._opener = opener
96 96 self._validate = validate
97 97 self._root = root
98 98 self._sparsematchfn = sparsematchfn
99 99 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
100 100 # UNC path pointing to root share (issue4557)
101 101 self._rootdir = pathutil.normasprefix(root)
102 102 self._dirty = False
103 103 self._lastnormaltime = 0
104 104 self._ui = ui
105 105 self._filecache = {}
106 106 self._parentwriters = 0
107 107 self._filename = b'dirstate'
108 108 self._pendingfilename = b'%s.pending' % self._filename
109 109 self._plchangecallbacks = {}
110 110 self._origpl = None
111 111 self._updatedfiles = set()
112 112 self._mapcls = dirstatemap.dirstatemap
113 113 # Access and cache cwd early, so we don't access it for the first time
114 114 # after a working-copy update caused it to not exist (accessing it then
115 115 # raises an exception).
116 116 self._cwd
117 117
118 118 def prefetch_parents(self):
119 119 """make sure the parents are loaded
120 120
121 121 Used to avoid a race condition.
122 122 """
123 123 self._pl
124 124
125 125 @contextlib.contextmanager
126 126 def parentchange(self):
127 127 """Context manager for handling dirstate parents.
128 128
129 129 If an exception occurs in the scope of the context manager,
130 130 the incoherent dirstate won't be written when wlock is
131 131 released.
132 132 """
133 133 self._parentwriters += 1
134 134 yield
135 135 # Typically we want the "undo" step of a context manager in a
136 136 # finally block so it happens even when an exception
137 137 # occurs. In this case, however, we only want to decrement
138 138 # parentwriters if the code in the with statement exits
139 139 # normally, so we don't have a try/finally here on purpose.
140 140 self._parentwriters -= 1
141 141
142 142 def pendingparentchange(self):
143 143 """Returns true if the dirstate is in the middle of a set of changes
144 144 that modify the dirstate parent.
145 145 """
146 146 return self._parentwriters > 0
147 147
148 148 @propertycache
149 149 def _map(self):
150 150 """Return the dirstate contents (see documentation for dirstatemap)."""
151 151 self._map = self._mapcls(
152 152 self._ui,
153 153 self._opener,
154 154 self._root,
155 155 self._nodeconstants,
156 156 self._use_dirstate_v2,
157 157 )
158 158 return self._map
159 159
160 160 @property
161 161 def _sparsematcher(self):
162 162 """The matcher for the sparse checkout.
163 163
164 164 The working directory may not include every file from a manifest. The
165 165 matcher obtained by this property will match a path if it is to be
166 166 included in the working directory.
167 167 """
168 168 # TODO there is potential to cache this property. For now, the matcher
169 169 # is resolved on every access. (But the called function does use a
170 170 # cache to keep the lookup fast.)
171 171 return self._sparsematchfn()
172 172
173 173 @repocache(b'branch')
174 174 def _branch(self):
175 175 try:
176 176 return self._opener.read(b"branch").strip() or b"default"
177 177 except IOError as inst:
178 178 if inst.errno != errno.ENOENT:
179 179 raise
180 180 return b"default"
181 181
182 182 @property
183 183 def _pl(self):
184 184 return self._map.parents()
185 185
186 186 def hasdir(self, d):
187 187 return self._map.hastrackeddir(d)
188 188
189 189 @rootcache(b'.hgignore')
190 190 def _ignore(self):
191 191 files = self._ignorefiles()
192 192 if not files:
193 193 return matchmod.never()
194 194
195 195 pats = [b'include:%s' % f for f in files]
196 196 return matchmod.match(self._root, b'', [], pats, warn=self._ui.warn)
197 197
198 198 @propertycache
199 199 def _slash(self):
200 200 return self._ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/'
201 201
202 202 @propertycache
203 203 def _checklink(self):
204 204 return util.checklink(self._root)
205 205
206 206 @propertycache
207 207 def _checkexec(self):
208 208 return bool(util.checkexec(self._root))
209 209
210 210 @propertycache
211 211 def _checkcase(self):
212 212 return not util.fscasesensitive(self._join(b'.hg'))
213 213
214 214 def _join(self, f):
215 215 # much faster than os.path.join()
216 216 # it's safe because f is always a relative path
217 217 return self._rootdir + f
218 218
219 219 def flagfunc(self, buildfallback):
220 220 if self._checklink and self._checkexec:
221 221
222 222 def f(x):
223 223 try:
224 224 st = os.lstat(self._join(x))
225 225 if util.statislink(st):
226 226 return b'l'
227 227 if util.statisexec(st):
228 228 return b'x'
229 229 except OSError:
230 230 pass
231 231 return b''
232 232
233 233 return f
234 234
235 235 fallback = buildfallback()
236 236 if self._checklink:
237 237
238 238 def f(x):
239 239 if os.path.islink(self._join(x)):
240 240 return b'l'
241 241 if b'x' in fallback(x):
242 242 return b'x'
243 243 return b''
244 244
245 245 return f
246 246 if self._checkexec:
247 247
248 248 def f(x):
249 249 if b'l' in fallback(x):
250 250 return b'l'
251 251 if util.isexec(self._join(x)):
252 252 return b'x'
253 253 return b''
254 254
255 255 return f
256 256 else:
257 257 return fallback
258 258
259 259 @propertycache
260 260 def _cwd(self):
261 261 # internal config: ui.forcecwd
262 262 forcecwd = self._ui.config(b'ui', b'forcecwd')
263 263 if forcecwd:
264 264 return forcecwd
265 265 return encoding.getcwd()
266 266
267 267 def getcwd(self):
268 268 """Return the path from which a canonical path is calculated.
269 269
270 270 This path should be used to resolve file patterns or to convert
271 271 canonical paths back to file paths for display. It shouldn't be
272 272 used to get real file paths. Use vfs functions instead.
273 273 """
274 274 cwd = self._cwd
275 275 if cwd == self._root:
276 276 return b''
277 277 # self._root ends with a path separator if self._root is '/' or 'C:\'
278 278 rootsep = self._root
279 279 if not util.endswithsep(rootsep):
280 280 rootsep += pycompat.ossep
281 281 if cwd.startswith(rootsep):
282 282 return cwd[len(rootsep) :]
283 283 else:
284 284 # we're outside the repo. return an absolute path.
285 285 return cwd
286 286
287 287 def pathto(self, f, cwd=None):
288 288 if cwd is None:
289 289 cwd = self.getcwd()
290 290 path = util.pathto(self._root, cwd, f)
291 291 if self._slash:
292 292 return util.pconvert(path)
293 293 return path
294 294
295 295 def __getitem__(self, key):
296 296 """Return the current state of key (a filename) in the dirstate.
297 297
298 298 States are:
299 299 n normal
300 300 m needs merging
301 301 r marked for removal
302 302 a marked for addition
303 303 ? not tracked
304 304
305 305 XXX The "state" is a bit obscure to be in the "public" API. we should
306 306 consider migrating all user of this to going through the dirstate entry
307 307 instead.
308 308 """
309 309 entry = self._map.get(key)
310 310 if entry is not None:
311 311 return entry.state
312 312 return b'?'
313 313
314 314 def __contains__(self, key):
315 315 return key in self._map
316 316
317 317 def __iter__(self):
318 318 return iter(sorted(self._map))
319 319
320 320 def items(self):
321 321 return pycompat.iteritems(self._map)
322 322
323 323 iteritems = items
324 324
325 325 def directories(self):
326 326 return self._map.directories()
327 327
328 328 def parents(self):
329 329 return [self._validate(p) for p in self._pl]
330 330
331 331 def p1(self):
332 332 return self._validate(self._pl[0])
333 333
334 334 def p2(self):
335 335 return self._validate(self._pl[1])
336 336
337 337 @property
338 338 def in_merge(self):
339 339 """True if a merge is in progress"""
340 340 return self._pl[1] != self._nodeconstants.nullid
341 341
342 342 def branch(self):
343 343 return encoding.tolocal(self._branch)
344 344
345 345 def setparents(self, p1, p2=None):
346 346 """Set dirstate parents to p1 and p2.
347 347
348 348 When moving from two parents to one, "merged" entries a
349 349 adjusted to normal and previous copy records discarded and
350 350 returned by the call.
351 351
352 352 See localrepo.setparents()
353 353 """
354 354 if p2 is None:
355 355 p2 = self._nodeconstants.nullid
356 356 if self._parentwriters == 0:
357 357 raise ValueError(
358 358 b"cannot set dirstate parent outside of "
359 359 b"dirstate.parentchange context manager"
360 360 )
361 361
362 362 self._dirty = True
363 363 oldp2 = self._pl[1]
364 364 if self._origpl is None:
365 365 self._origpl = self._pl
366 366 self._map.setparents(p1, p2)
367 367 copies = {}
368 368 if (
369 369 oldp2 != self._nodeconstants.nullid
370 370 and p2 == self._nodeconstants.nullid
371 371 ):
372 372 candidatefiles = self._map.non_normal_or_other_parent_paths()
373 373
374 374 for f in candidatefiles:
375 375 s = self._map.get(f)
376 376 if s is None:
377 377 continue
378 378
379 379 # Discard "merged" markers when moving away from a merge state
380 380 if s.merged:
381 381 source = self._map.copymap.get(f)
382 382 if source:
383 383 copies[f] = source
384 384 self.normallookup(f)
385 385 # Also fix up otherparent markers
386 386 elif s.from_p2:
387 387 source = self._map.copymap.get(f)
388 388 if source:
389 389 copies[f] = source
390 390 self.add(f)
391 391 return copies
392 392
393 393 def setbranch(self, branch):
394 394 self.__class__._branch.set(self, encoding.fromlocal(branch))
395 395 f = self._opener(b'branch', b'w', atomictemp=True, checkambig=True)
396 396 try:
397 397 f.write(self._branch + b'\n')
398 398 f.close()
399 399
400 400 # make sure filecache has the correct stat info for _branch after
401 401 # replacing the underlying file
402 402 ce = self._filecache[b'_branch']
403 403 if ce:
404 404 ce.refresh()
405 405 except: # re-raises
406 406 f.discard()
407 407 raise
408 408
409 409 def invalidate(self):
410 410 """Causes the next access to reread the dirstate.
411 411
412 412 This is different from localrepo.invalidatedirstate() because it always
413 413 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
414 414 check whether the dirstate has changed before rereading it."""
415 415
416 416 for a in ("_map", "_branch", "_ignore"):
417 417 if a in self.__dict__:
418 418 delattr(self, a)
419 419 self._lastnormaltime = 0
420 420 self._dirty = False
421 421 self._updatedfiles.clear()
422 422 self._parentwriters = 0
423 423 self._origpl = None
424 424
425 425 def copy(self, source, dest):
426 426 """Mark dest as a copy of source. Unmark dest if source is None."""
427 427 if source == dest:
428 428 return
429 429 self._dirty = True
430 430 if source is not None:
431 431 self._map.copymap[dest] = source
432 432 self._updatedfiles.add(source)
433 433 self._updatedfiles.add(dest)
434 434 elif self._map.copymap.pop(dest, None):
435 435 self._updatedfiles.add(dest)
436 436
437 437 def copied(self, file):
438 438 return self._map.copymap.get(file, None)
439 439
440 440 def copies(self):
441 441 return self._map.copymap
442 442
443 443 def _addpath(
444 444 self,
445 445 f,
446 446 mode=0,
447 447 size=None,
448 448 mtime=None,
449 449 added=False,
450 450 merged=False,
451 451 from_p2=False,
452 452 possibly_dirty=False,
453 453 ):
454 454 entry = self._map.get(f)
455 455 if added or entry is not None and entry.removed:
456 456 scmutil.checkfilename(f)
457 457 if self._map.hastrackeddir(f):
458 458 msg = _(b'directory %r already in dirstate')
459 459 msg %= pycompat.bytestr(f)
460 460 raise error.Abort(msg)
461 461 # shadows
462 462 for d in pathutil.finddirs(f):
463 463 if self._map.hastrackeddir(d):
464 464 break
465 465 entry = self._map.get(d)
466 466 if entry is not None and not entry.removed:
467 467 msg = _(b'file %r in dirstate clashes with %r')
468 468 msg %= (pycompat.bytestr(d), pycompat.bytestr(f))
469 469 raise error.Abort(msg)
470 470 self._dirty = True
471 471 self._updatedfiles.add(f)
472 472 self._map.addfile(
473 473 f,
474 474 mode=mode,
475 475 size=size,
476 476 mtime=mtime,
477 477 added=added,
478 478 merged=merged,
479 479 from_p2=from_p2,
480 480 possibly_dirty=possibly_dirty,
481 481 )
482 482
483 483 def normal(self, f, parentfiledata=None):
484 484 """Mark a file normal and clean.
485 485
486 486 parentfiledata: (mode, size, mtime) of the clean file
487 487
488 488 parentfiledata should be computed from memory (for mode,
489 489 size), as or close as possible from the point where we
490 490 determined the file was clean, to limit the risk of the
491 491 file having been changed by an external process between the
492 492 moment where the file was determined to be clean and now."""
493 493 if parentfiledata:
494 494 (mode, size, mtime) = parentfiledata
495 495 else:
496 496 s = os.lstat(self._join(f))
497 497 mode = s.st_mode
498 498 size = s.st_size
499 499 mtime = s[stat.ST_MTIME]
500 500 self._addpath(f, mode=mode, size=size, mtime=mtime)
501 501 self._map.copymap.pop(f, None)
502 502 if f in self._map.nonnormalset:
503 503 self._map.nonnormalset.remove(f)
504 504 if mtime > self._lastnormaltime:
505 505 # Remember the most recent modification timeslot for status(),
506 506 # to make sure we won't miss future size-preserving file content
507 507 # modifications that happen within the same timeslot.
508 508 self._lastnormaltime = mtime
509 509
510 510 def normallookup(self, f):
511 511 '''Mark a file normal, but possibly dirty.'''
512 512 if self.in_merge:
513 513 # if there is a merge going on and the file was either
514 514 # "merged" or coming from other parent (-2) before
515 515 # being removed, restore that state.
516 516 entry = self._map.get(f)
517 517 if entry is not None:
518 518 # XXX this should probably be dealt with a a lower level
519 519 # (see `merged_removed` and `from_p2_removed`)
520 520 if entry.merged_removed or entry.from_p2_removed:
521 521 source = self._map.copymap.get(f)
522 522 if entry.merged_removed:
523 523 self.merge(f)
524 524 elif entry.from_p2_removed:
525 525 self.otherparent(f)
526 526 if source is not None:
527 527 self.copy(source, f)
528 528 return
529 529 elif entry.merged or entry.from_p2:
530 530 return
531 531 self._addpath(f, possibly_dirty=True)
532 532 self._map.copymap.pop(f, None)
533 533
534 534 def otherparent(self, f):
535 535 '''Mark as coming from the other parent, always dirty.'''
536 536 if not self.in_merge:
537 537 msg = _(b"setting %r to other parent only allowed in merges") % f
538 538 raise error.Abort(msg)
539 539 if f in self and self[f] == b'n':
540 540 # merge-like
541 541 self._addpath(f, merged=True)
542 542 else:
543 543 # add-like
544 544 self._addpath(f, from_p2=True)
545 545 self._map.copymap.pop(f, None)
546 546
547 547 def add(self, f):
548 548 '''Mark a file added.'''
549 549 self._addpath(f, added=True)
550 550 self._map.copymap.pop(f, None)
551 551
552 552 def remove(self, f):
553 553 '''Mark a file removed.'''
554 554 self._dirty = True
555 555 self._updatedfiles.add(f)
556 556 self._map.removefile(f, in_merge=self.in_merge)
557 557
558 558 def merge(self, f):
559 559 '''Mark a file merged.'''
560 560 if not self.in_merge:
561 561 return self.normallookup(f)
562 562 return self.otherparent(f)
563 563
564 564 def drop(self, f):
565 565 '''Drop a file from the dirstate'''
566 566 oldstate = self[f]
567 567 if self._map.dropfile(f, oldstate):
568 568 self._dirty = True
569 569 self._updatedfiles.add(f)
570 570 self._map.copymap.pop(f, None)
571 571
572 572 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
573 573 if exists is None:
574 574 exists = os.path.lexists(os.path.join(self._root, path))
575 575 if not exists:
576 576 # Maybe a path component exists
577 577 if not ignoremissing and b'/' in path:
578 578 d, f = path.rsplit(b'/', 1)
579 579 d = self._normalize(d, False, ignoremissing, None)
580 580 folded = d + b"/" + f
581 581 else:
582 582 # No path components, preserve original case
583 583 folded = path
584 584 else:
585 585 # recursively normalize leading directory components
586 586 # against dirstate
587 587 if b'/' in normed:
588 588 d, f = normed.rsplit(b'/', 1)
589 589 d = self._normalize(d, False, ignoremissing, True)
590 590 r = self._root + b"/" + d
591 591 folded = d + b"/" + util.fspath(f, r)
592 592 else:
593 593 folded = util.fspath(normed, self._root)
594 594 storemap[normed] = folded
595 595
596 596 return folded
597 597
598 598 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
599 599 normed = util.normcase(path)
600 600 folded = self._map.filefoldmap.get(normed, None)
601 601 if folded is None:
602 602 if isknown:
603 603 folded = path
604 604 else:
605 605 folded = self._discoverpath(
606 606 path, normed, ignoremissing, exists, self._map.filefoldmap
607 607 )
608 608 return folded
609 609
610 610 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
611 611 normed = util.normcase(path)
612 612 folded = self._map.filefoldmap.get(normed, None)
613 613 if folded is None:
614 614 folded = self._map.dirfoldmap.get(normed, None)
615 615 if folded is None:
616 616 if isknown:
617 617 folded = path
618 618 else:
619 619 # store discovered result in dirfoldmap so that future
620 620 # normalizefile calls don't start matching directories
621 621 folded = self._discoverpath(
622 622 path, normed, ignoremissing, exists, self._map.dirfoldmap
623 623 )
624 624 return folded
625 625
626 626 def normalize(self, path, isknown=False, ignoremissing=False):
627 627 """
628 628 normalize the case of a pathname when on a casefolding filesystem
629 629
630 630 isknown specifies whether the filename came from walking the
631 631 disk, to avoid extra filesystem access.
632 632
633 633 If ignoremissing is True, missing path are returned
634 634 unchanged. Otherwise, we try harder to normalize possibly
635 635 existing path components.
636 636
637 637 The normalized case is determined based on the following precedence:
638 638
639 639 - version of name already stored in the dirstate
640 640 - version of name stored on disk
641 641 - version provided via command arguments
642 642 """
643 643
644 644 if self._checkcase:
645 645 return self._normalize(path, isknown, ignoremissing)
646 646 return path
647 647
648 648 def clear(self):
649 649 self._map.clear()
650 650 self._lastnormaltime = 0
651 651 self._updatedfiles.clear()
652 652 self._dirty = True
653 653
654 654 def rebuild(self, parent, allfiles, changedfiles=None):
655 655 if changedfiles is None:
656 656 # Rebuild entire dirstate
657 657 to_lookup = allfiles
658 658 to_drop = []
659 659 lastnormaltime = self._lastnormaltime
660 660 self.clear()
661 661 self._lastnormaltime = lastnormaltime
662 662 elif len(changedfiles) < 10:
663 663 # Avoid turning allfiles into a set, which can be expensive if it's
664 664 # large.
665 665 to_lookup = []
666 666 to_drop = []
667 667 for f in changedfiles:
668 668 if f in allfiles:
669 669 to_lookup.append(f)
670 670 else:
671 671 to_drop.append(f)
672 672 else:
673 673 changedfilesset = set(changedfiles)
674 674 to_lookup = changedfilesset & set(allfiles)
675 675 to_drop = changedfilesset - to_lookup
676 676
677 677 if self._origpl is None:
678 678 self._origpl = self._pl
679 679 self._map.setparents(parent, self._nodeconstants.nullid)
680 680
681 681 for f in to_lookup:
682 682 self.normallookup(f)
683 683 for f in to_drop:
684 684 self.drop(f)
685 685
686 686 self._dirty = True
687 687
688 688 def identity(self):
689 689 """Return identity of dirstate itself to detect changing in storage
690 690
691 691 If identity of previous dirstate is equal to this, writing
692 692 changes based on the former dirstate out can keep consistency.
693 693 """
694 694 return self._map.identity
695 695
696 696 def write(self, tr):
697 697 if not self._dirty:
698 698 return
699 699
700 700 filename = self._filename
701 701 if tr:
702 702 # 'dirstate.write()' is not only for writing in-memory
703 703 # changes out, but also for dropping ambiguous timestamp.
704 704 # delayed writing re-raise "ambiguous timestamp issue".
705 705 # See also the wiki page below for detail:
706 706 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
707 707
708 708 # emulate dropping timestamp in 'parsers.pack_dirstate'
709 709 now = _getfsnow(self._opener)
710 710 self._map.clearambiguoustimes(self._updatedfiles, now)
711 711
712 712 # emulate that all 'dirstate.normal' results are written out
713 713 self._lastnormaltime = 0
714 714 self._updatedfiles.clear()
715 715
716 716 # delay writing in-memory changes out
717 717 tr.addfilegenerator(
718 718 b'dirstate',
719 719 (self._filename,),
720 720 self._writedirstate,
721 721 location=b'plain',
722 722 )
723 723 return
724 724
725 725 st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
726 726 self._writedirstate(st)
727 727
728 728 def addparentchangecallback(self, category, callback):
729 729 """add a callback to be called when the wd parents are changed
730 730
731 731 Callback will be called with the following arguments:
732 732 dirstate, (oldp1, oldp2), (newp1, newp2)
733 733
734 734 Category is a unique identifier to allow overwriting an old callback
735 735 with a newer callback.
736 736 """
737 737 self._plchangecallbacks[category] = callback
738 738
739 739 def _writedirstate(self, st):
740 740 # notify callbacks about parents change
741 741 if self._origpl is not None and self._origpl != self._pl:
742 742 for c, callback in sorted(
743 743 pycompat.iteritems(self._plchangecallbacks)
744 744 ):
745 745 callback(self, self._origpl, self._pl)
746 746 self._origpl = None
747 747 # use the modification time of the newly created temporary file as the
748 748 # filesystem's notion of 'now'
749 749 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
750 750
751 751 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
752 752 # timestamp of each entries in dirstate, because of 'now > mtime'
753 753 delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite')
754 754 if delaywrite > 0:
755 755 # do we have any files to delay for?
756 756 for f, e in pycompat.iteritems(self._map):
757 if e.state == b'n' and e[3] == now:
757 if e.need_delay(now):
758 758 import time # to avoid useless import
759 759
760 760 # rather than sleep n seconds, sleep until the next
761 761 # multiple of n seconds
762 762 clock = time.time()
763 763 start = int(clock) - (int(clock) % delaywrite)
764 764 end = start + delaywrite
765 765 time.sleep(end - clock)
766 766 now = end # trust our estimate that the end is near now
767 767 break
768 768
769 769 self._map.write(st, now)
770 770 self._lastnormaltime = 0
771 771 self._dirty = False
772 772
773 773 def _dirignore(self, f):
774 774 if self._ignore(f):
775 775 return True
776 776 for p in pathutil.finddirs(f):
777 777 if self._ignore(p):
778 778 return True
779 779 return False
780 780
781 781 def _ignorefiles(self):
782 782 files = []
783 783 if os.path.exists(self._join(b'.hgignore')):
784 784 files.append(self._join(b'.hgignore'))
785 785 for name, path in self._ui.configitems(b"ui"):
786 786 if name == b'ignore' or name.startswith(b'ignore.'):
787 787 # we need to use os.path.join here rather than self._join
788 788 # because path is arbitrary and user-specified
789 789 files.append(os.path.join(self._rootdir, util.expandpath(path)))
790 790 return files
791 791
792 792 def _ignorefileandline(self, f):
793 793 files = collections.deque(self._ignorefiles())
794 794 visited = set()
795 795 while files:
796 796 i = files.popleft()
797 797 patterns = matchmod.readpatternfile(
798 798 i, self._ui.warn, sourceinfo=True
799 799 )
800 800 for pattern, lineno, line in patterns:
801 801 kind, p = matchmod._patsplit(pattern, b'glob')
802 802 if kind == b"subinclude":
803 803 if p not in visited:
804 804 files.append(p)
805 805 continue
806 806 m = matchmod.match(
807 807 self._root, b'', [], [pattern], warn=self._ui.warn
808 808 )
809 809 if m(f):
810 810 return (i, lineno, line)
811 811 visited.add(i)
812 812 return (None, -1, b"")
813 813
814 814 def _walkexplicit(self, match, subrepos):
815 815 """Get stat data about the files explicitly specified by match.
816 816
817 817 Return a triple (results, dirsfound, dirsnotfound).
818 818 - results is a mapping from filename to stat result. It also contains
819 819 listings mapping subrepos and .hg to None.
820 820 - dirsfound is a list of files found to be directories.
821 821 - dirsnotfound is a list of files that the dirstate thinks are
822 822 directories and that were not found."""
823 823
824 824 def badtype(mode):
825 825 kind = _(b'unknown')
826 826 if stat.S_ISCHR(mode):
827 827 kind = _(b'character device')
828 828 elif stat.S_ISBLK(mode):
829 829 kind = _(b'block device')
830 830 elif stat.S_ISFIFO(mode):
831 831 kind = _(b'fifo')
832 832 elif stat.S_ISSOCK(mode):
833 833 kind = _(b'socket')
834 834 elif stat.S_ISDIR(mode):
835 835 kind = _(b'directory')
836 836 return _(b'unsupported file type (type is %s)') % kind
837 837
838 838 badfn = match.bad
839 839 dmap = self._map
840 840 lstat = os.lstat
841 841 getkind = stat.S_IFMT
842 842 dirkind = stat.S_IFDIR
843 843 regkind = stat.S_IFREG
844 844 lnkkind = stat.S_IFLNK
845 845 join = self._join
846 846 dirsfound = []
847 847 foundadd = dirsfound.append
848 848 dirsnotfound = []
849 849 notfoundadd = dirsnotfound.append
850 850
851 851 if not match.isexact() and self._checkcase:
852 852 normalize = self._normalize
853 853 else:
854 854 normalize = None
855 855
856 856 files = sorted(match.files())
857 857 subrepos.sort()
858 858 i, j = 0, 0
859 859 while i < len(files) and j < len(subrepos):
860 860 subpath = subrepos[j] + b"/"
861 861 if files[i] < subpath:
862 862 i += 1
863 863 continue
864 864 while i < len(files) and files[i].startswith(subpath):
865 865 del files[i]
866 866 j += 1
867 867
868 868 if not files or b'' in files:
869 869 files = [b'']
870 870 # constructing the foldmap is expensive, so don't do it for the
871 871 # common case where files is ['']
872 872 normalize = None
873 873 results = dict.fromkeys(subrepos)
874 874 results[b'.hg'] = None
875 875
876 876 for ff in files:
877 877 if normalize:
878 878 nf = normalize(ff, False, True)
879 879 else:
880 880 nf = ff
881 881 if nf in results:
882 882 continue
883 883
884 884 try:
885 885 st = lstat(join(nf))
886 886 kind = getkind(st.st_mode)
887 887 if kind == dirkind:
888 888 if nf in dmap:
889 889 # file replaced by dir on disk but still in dirstate
890 890 results[nf] = None
891 891 foundadd((nf, ff))
892 892 elif kind == regkind or kind == lnkkind:
893 893 results[nf] = st
894 894 else:
895 895 badfn(ff, badtype(kind))
896 896 if nf in dmap:
897 897 results[nf] = None
898 898 except OSError as inst: # nf not found on disk - it is dirstate only
899 899 if nf in dmap: # does it exactly match a missing file?
900 900 results[nf] = None
901 901 else: # does it match a missing directory?
902 902 if self._map.hasdir(nf):
903 903 notfoundadd(nf)
904 904 else:
905 905 badfn(ff, encoding.strtolocal(inst.strerror))
906 906
907 907 # match.files() may contain explicitly-specified paths that shouldn't
908 908 # be taken; drop them from the list of files found. dirsfound/notfound
909 909 # aren't filtered here because they will be tested later.
910 910 if match.anypats():
911 911 for f in list(results):
912 912 if f == b'.hg' or f in subrepos:
913 913 # keep sentinel to disable further out-of-repo walks
914 914 continue
915 915 if not match(f):
916 916 del results[f]
917 917
918 918 # Case insensitive filesystems cannot rely on lstat() failing to detect
919 919 # a case-only rename. Prune the stat object for any file that does not
920 920 # match the case in the filesystem, if there are multiple files that
921 921 # normalize to the same path.
922 922 if match.isexact() and self._checkcase:
923 923 normed = {}
924 924
925 925 for f, st in pycompat.iteritems(results):
926 926 if st is None:
927 927 continue
928 928
929 929 nc = util.normcase(f)
930 930 paths = normed.get(nc)
931 931
932 932 if paths is None:
933 933 paths = set()
934 934 normed[nc] = paths
935 935
936 936 paths.add(f)
937 937
938 938 for norm, paths in pycompat.iteritems(normed):
939 939 if len(paths) > 1:
940 940 for path in paths:
941 941 folded = self._discoverpath(
942 942 path, norm, True, None, self._map.dirfoldmap
943 943 )
944 944 if path != folded:
945 945 results[path] = None
946 946
947 947 return results, dirsfound, dirsnotfound
948 948
949 949 def walk(self, match, subrepos, unknown, ignored, full=True):
950 950 """
951 951 Walk recursively through the directory tree, finding all files
952 952 matched by match.
953 953
954 954 If full is False, maybe skip some known-clean files.
955 955
956 956 Return a dict mapping filename to stat-like object (either
957 957 mercurial.osutil.stat instance or return value of os.stat()).
958 958
959 959 """
960 960 # full is a flag that extensions that hook into walk can use -- this
961 961 # implementation doesn't use it at all. This satisfies the contract
962 962 # because we only guarantee a "maybe".
963 963
964 964 if ignored:
965 965 ignore = util.never
966 966 dirignore = util.never
967 967 elif unknown:
968 968 ignore = self._ignore
969 969 dirignore = self._dirignore
970 970 else:
971 971 # if not unknown and not ignored, drop dir recursion and step 2
972 972 ignore = util.always
973 973 dirignore = util.always
974 974
975 975 matchfn = match.matchfn
976 976 matchalways = match.always()
977 977 matchtdir = match.traversedir
978 978 dmap = self._map
979 979 listdir = util.listdir
980 980 lstat = os.lstat
981 981 dirkind = stat.S_IFDIR
982 982 regkind = stat.S_IFREG
983 983 lnkkind = stat.S_IFLNK
984 984 join = self._join
985 985
986 986 exact = skipstep3 = False
987 987 if match.isexact(): # match.exact
988 988 exact = True
989 989 dirignore = util.always # skip step 2
990 990 elif match.prefix(): # match.match, no patterns
991 991 skipstep3 = True
992 992
993 993 if not exact and self._checkcase:
994 994 normalize = self._normalize
995 995 normalizefile = self._normalizefile
996 996 skipstep3 = False
997 997 else:
998 998 normalize = self._normalize
999 999 normalizefile = None
1000 1000
1001 1001 # step 1: find all explicit files
1002 1002 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1003 1003 if matchtdir:
1004 1004 for d in work:
1005 1005 matchtdir(d[0])
1006 1006 for d in dirsnotfound:
1007 1007 matchtdir(d)
1008 1008
1009 1009 skipstep3 = skipstep3 and not (work or dirsnotfound)
1010 1010 work = [d for d in work if not dirignore(d[0])]
1011 1011
1012 1012 # step 2: visit subdirectories
1013 1013 def traverse(work, alreadynormed):
1014 1014 wadd = work.append
1015 1015 while work:
1016 1016 tracing.counter('dirstate.walk work', len(work))
1017 1017 nd = work.pop()
1018 1018 visitentries = match.visitchildrenset(nd)
1019 1019 if not visitentries:
1020 1020 continue
1021 1021 if visitentries == b'this' or visitentries == b'all':
1022 1022 visitentries = None
1023 1023 skip = None
1024 1024 if nd != b'':
1025 1025 skip = b'.hg'
1026 1026 try:
1027 1027 with tracing.log('dirstate.walk.traverse listdir %s', nd):
1028 1028 entries = listdir(join(nd), stat=True, skip=skip)
1029 1029 except OSError as inst:
1030 1030 if inst.errno in (errno.EACCES, errno.ENOENT):
1031 1031 match.bad(
1032 1032 self.pathto(nd), encoding.strtolocal(inst.strerror)
1033 1033 )
1034 1034 continue
1035 1035 raise
1036 1036 for f, kind, st in entries:
1037 1037 # Some matchers may return files in the visitentries set,
1038 1038 # instead of 'this', if the matcher explicitly mentions them
1039 1039 # and is not an exactmatcher. This is acceptable; we do not
1040 1040 # make any hard assumptions about file-or-directory below
1041 1041 # based on the presence of `f` in visitentries. If
1042 1042 # visitchildrenset returned a set, we can always skip the
1043 1043 # entries *not* in the set it provided regardless of whether
1044 1044 # they're actually a file or a directory.
1045 1045 if visitentries and f not in visitentries:
1046 1046 continue
1047 1047 if normalizefile:
1048 1048 # even though f might be a directory, we're only
1049 1049 # interested in comparing it to files currently in the
1050 1050 # dmap -- therefore normalizefile is enough
1051 1051 nf = normalizefile(
1052 1052 nd and (nd + b"/" + f) or f, True, True
1053 1053 )
1054 1054 else:
1055 1055 nf = nd and (nd + b"/" + f) or f
1056 1056 if nf not in results:
1057 1057 if kind == dirkind:
1058 1058 if not ignore(nf):
1059 1059 if matchtdir:
1060 1060 matchtdir(nf)
1061 1061 wadd(nf)
1062 1062 if nf in dmap and (matchalways or matchfn(nf)):
1063 1063 results[nf] = None
1064 1064 elif kind == regkind or kind == lnkkind:
1065 1065 if nf in dmap:
1066 1066 if matchalways or matchfn(nf):
1067 1067 results[nf] = st
1068 1068 elif (matchalways or matchfn(nf)) and not ignore(
1069 1069 nf
1070 1070 ):
1071 1071 # unknown file -- normalize if necessary
1072 1072 if not alreadynormed:
1073 1073 nf = normalize(nf, False, True)
1074 1074 results[nf] = st
1075 1075 elif nf in dmap and (matchalways or matchfn(nf)):
1076 1076 results[nf] = None
1077 1077
1078 1078 for nd, d in work:
1079 1079 # alreadynormed means that processwork doesn't have to do any
1080 1080 # expensive directory normalization
1081 1081 alreadynormed = not normalize or nd == d
1082 1082 traverse([d], alreadynormed)
1083 1083
1084 1084 for s in subrepos:
1085 1085 del results[s]
1086 1086 del results[b'.hg']
1087 1087
1088 1088 # step 3: visit remaining files from dmap
1089 1089 if not skipstep3 and not exact:
1090 1090 # If a dmap file is not in results yet, it was either
1091 1091 # a) not matching matchfn b) ignored, c) missing, or d) under a
1092 1092 # symlink directory.
1093 1093 if not results and matchalways:
1094 1094 visit = [f for f in dmap]
1095 1095 else:
1096 1096 visit = [f for f in dmap if f not in results and matchfn(f)]
1097 1097 visit.sort()
1098 1098
1099 1099 if unknown:
1100 1100 # unknown == True means we walked all dirs under the roots
1101 1101 # that wasn't ignored, and everything that matched was stat'ed
1102 1102 # and is already in results.
1103 1103 # The rest must thus be ignored or under a symlink.
1104 1104 audit_path = pathutil.pathauditor(self._root, cached=True)
1105 1105
1106 1106 for nf in iter(visit):
1107 1107 # If a stat for the same file was already added with a
1108 1108 # different case, don't add one for this, since that would
1109 1109 # make it appear as if the file exists under both names
1110 1110 # on disk.
1111 1111 if (
1112 1112 normalizefile
1113 1113 and normalizefile(nf, True, True) in results
1114 1114 ):
1115 1115 results[nf] = None
1116 1116 # Report ignored items in the dmap as long as they are not
1117 1117 # under a symlink directory.
1118 1118 elif audit_path.check(nf):
1119 1119 try:
1120 1120 results[nf] = lstat(join(nf))
1121 1121 # file was just ignored, no links, and exists
1122 1122 except OSError:
1123 1123 # file doesn't exist
1124 1124 results[nf] = None
1125 1125 else:
1126 1126 # It's either missing or under a symlink directory
1127 1127 # which we in this case report as missing
1128 1128 results[nf] = None
1129 1129 else:
1130 1130 # We may not have walked the full directory tree above,
1131 1131 # so stat and check everything we missed.
1132 1132 iv = iter(visit)
1133 1133 for st in util.statfiles([join(i) for i in visit]):
1134 1134 results[next(iv)] = st
1135 1135 return results
1136 1136
1137 1137 def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
1138 1138 # Force Rayon (Rust parallelism library) to respect the number of
1139 1139 # workers. This is a temporary workaround until Rust code knows
1140 1140 # how to read the config file.
1141 1141 numcpus = self._ui.configint(b"worker", b"numcpus")
1142 1142 if numcpus is not None:
1143 1143 encoding.environ.setdefault(b'RAYON_NUM_THREADS', b'%d' % numcpus)
1144 1144
1145 1145 workers_enabled = self._ui.configbool(b"worker", b"enabled", True)
1146 1146 if not workers_enabled:
1147 1147 encoding.environ[b"RAYON_NUM_THREADS"] = b"1"
1148 1148
1149 1149 (
1150 1150 lookup,
1151 1151 modified,
1152 1152 added,
1153 1153 removed,
1154 1154 deleted,
1155 1155 clean,
1156 1156 ignored,
1157 1157 unknown,
1158 1158 warnings,
1159 1159 bad,
1160 1160 traversed,
1161 1161 dirty,
1162 1162 ) = rustmod.status(
1163 1163 self._map._rustmap,
1164 1164 matcher,
1165 1165 self._rootdir,
1166 1166 self._ignorefiles(),
1167 1167 self._checkexec,
1168 1168 self._lastnormaltime,
1169 1169 bool(list_clean),
1170 1170 bool(list_ignored),
1171 1171 bool(list_unknown),
1172 1172 bool(matcher.traversedir),
1173 1173 )
1174 1174
1175 1175 self._dirty |= dirty
1176 1176
1177 1177 if matcher.traversedir:
1178 1178 for dir in traversed:
1179 1179 matcher.traversedir(dir)
1180 1180
1181 1181 if self._ui.warn:
1182 1182 for item in warnings:
1183 1183 if isinstance(item, tuple):
1184 1184 file_path, syntax = item
1185 1185 msg = _(b"%s: ignoring invalid syntax '%s'\n") % (
1186 1186 file_path,
1187 1187 syntax,
1188 1188 )
1189 1189 self._ui.warn(msg)
1190 1190 else:
1191 1191 msg = _(b"skipping unreadable pattern file '%s': %s\n")
1192 1192 self._ui.warn(
1193 1193 msg
1194 1194 % (
1195 1195 pathutil.canonpath(
1196 1196 self._rootdir, self._rootdir, item
1197 1197 ),
1198 1198 b"No such file or directory",
1199 1199 )
1200 1200 )
1201 1201
1202 1202 for (fn, message) in bad:
1203 1203 matcher.bad(fn, encoding.strtolocal(message))
1204 1204
1205 1205 status = scmutil.status(
1206 1206 modified=modified,
1207 1207 added=added,
1208 1208 removed=removed,
1209 1209 deleted=deleted,
1210 1210 unknown=unknown,
1211 1211 ignored=ignored,
1212 1212 clean=clean,
1213 1213 )
1214 1214 return (lookup, status)
1215 1215
1216 1216 def status(self, match, subrepos, ignored, clean, unknown):
1217 1217 """Determine the status of the working copy relative to the
1218 1218 dirstate and return a pair of (unsure, status), where status is of type
1219 1219 scmutil.status and:
1220 1220
1221 1221 unsure:
1222 1222 files that might have been modified since the dirstate was
1223 1223 written, but need to be read to be sure (size is the same
1224 1224 but mtime differs)
1225 1225 status.modified:
1226 1226 files that have definitely been modified since the dirstate
1227 1227 was written (different size or mode)
1228 1228 status.clean:
1229 1229 files that have definitely not been modified since the
1230 1230 dirstate was written
1231 1231 """
1232 1232 listignored, listclean, listunknown = ignored, clean, unknown
1233 1233 lookup, modified, added, unknown, ignored = [], [], [], [], []
1234 1234 removed, deleted, clean = [], [], []
1235 1235
1236 1236 dmap = self._map
1237 1237 dmap.preload()
1238 1238
1239 1239 use_rust = True
1240 1240
1241 1241 allowed_matchers = (
1242 1242 matchmod.alwaysmatcher,
1243 1243 matchmod.exactmatcher,
1244 1244 matchmod.includematcher,
1245 1245 )
1246 1246
1247 1247 if rustmod is None:
1248 1248 use_rust = False
1249 1249 elif self._checkcase:
1250 1250 # Case-insensitive filesystems are not handled yet
1251 1251 use_rust = False
1252 1252 elif subrepos:
1253 1253 use_rust = False
1254 1254 elif sparse.enabled:
1255 1255 use_rust = False
1256 1256 elif not isinstance(match, allowed_matchers):
1257 1257 # Some matchers have yet to be implemented
1258 1258 use_rust = False
1259 1259
1260 1260 if use_rust:
1261 1261 try:
1262 1262 return self._rust_status(
1263 1263 match, listclean, listignored, listunknown
1264 1264 )
1265 1265 except rustmod.FallbackError:
1266 1266 pass
1267 1267
1268 1268 def noop(f):
1269 1269 pass
1270 1270
1271 1271 dcontains = dmap.__contains__
1272 1272 dget = dmap.__getitem__
1273 1273 ladd = lookup.append # aka "unsure"
1274 1274 madd = modified.append
1275 1275 aadd = added.append
1276 1276 uadd = unknown.append if listunknown else noop
1277 1277 iadd = ignored.append if listignored else noop
1278 1278 radd = removed.append
1279 1279 dadd = deleted.append
1280 1280 cadd = clean.append if listclean else noop
1281 1281 mexact = match.exact
1282 1282 dirignore = self._dirignore
1283 1283 checkexec = self._checkexec
1284 1284 copymap = self._map.copymap
1285 1285 lastnormaltime = self._lastnormaltime
1286 1286
1287 1287 # We need to do full walks when either
1288 1288 # - we're listing all clean files, or
1289 1289 # - match.traversedir does something, because match.traversedir should
1290 1290 # be called for every dir in the working dir
1291 1291 full = listclean or match.traversedir is not None
1292 1292 for fn, st in pycompat.iteritems(
1293 1293 self.walk(match, subrepos, listunknown, listignored, full=full)
1294 1294 ):
1295 1295 if not dcontains(fn):
1296 1296 if (listignored or mexact(fn)) and dirignore(fn):
1297 1297 if listignored:
1298 1298 iadd(fn)
1299 1299 else:
1300 1300 uadd(fn)
1301 1301 continue
1302 1302
1303 1303 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1304 1304 # written like that for performance reasons. dmap[fn] is not a
1305 1305 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1306 1306 # opcode has fast paths when the value to be unpacked is a tuple or
1307 1307 # a list, but falls back to creating a full-fledged iterator in
1308 1308 # general. That is much slower than simply accessing and storing the
1309 1309 # tuple members one by one.
1310 1310 t = dget(fn)
1311 1311 state = t.state
1312 1312 mode = t[1]
1313 1313 size = t[2]
1314 1314 time = t[3]
1315 1315
1316 1316 if not st and t.tracked:
1317 1317 dadd(fn)
1318 1318 elif state == b'n':
1319 1319 if (
1320 1320 size >= 0
1321 1321 and (
1322 1322 (size != st.st_size and size != st.st_size & _rangemask)
1323 1323 or ((mode ^ st.st_mode) & 0o100 and checkexec)
1324 1324 )
1325 1325 or t.from_p2
1326 1326 or fn in copymap
1327 1327 ):
1328 1328 if stat.S_ISLNK(st.st_mode) and size != st.st_size:
1329 1329 # issue6456: Size returned may be longer due to
1330 1330 # encryption on EXT-4 fscrypt, undecided.
1331 1331 ladd(fn)
1332 1332 else:
1333 1333 madd(fn)
1334 1334 elif (
1335 1335 time != st[stat.ST_MTIME]
1336 1336 and time != st[stat.ST_MTIME] & _rangemask
1337 1337 ):
1338 1338 ladd(fn)
1339 1339 elif st[stat.ST_MTIME] == lastnormaltime:
1340 1340 # fn may have just been marked as normal and it may have
1341 1341 # changed in the same second without changing its size.
1342 1342 # This can happen if we quickly do multiple commits.
1343 1343 # Force lookup, so we don't miss such a racy file change.
1344 1344 ladd(fn)
1345 1345 elif listclean:
1346 1346 cadd(fn)
1347 1347 elif t.merged:
1348 1348 madd(fn)
1349 1349 elif t.added:
1350 1350 aadd(fn)
1351 1351 elif t.removed:
1352 1352 radd(fn)
1353 1353 status = scmutil.status(
1354 1354 modified, added, removed, deleted, unknown, ignored, clean
1355 1355 )
1356 1356 return (lookup, status)
1357 1357
1358 1358 def matches(self, match):
1359 1359 """
1360 1360 return files in the dirstate (in whatever state) filtered by match
1361 1361 """
1362 1362 dmap = self._map
1363 1363 if rustmod is not None:
1364 1364 dmap = self._map._rustmap
1365 1365
1366 1366 if match.always():
1367 1367 return dmap.keys()
1368 1368 files = match.files()
1369 1369 if match.isexact():
1370 1370 # fast path -- filter the other way around, since typically files is
1371 1371 # much smaller than dmap
1372 1372 return [f for f in files if f in dmap]
1373 1373 if match.prefix() and all(fn in dmap for fn in files):
1374 1374 # fast path -- all the values are known to be files, so just return
1375 1375 # that
1376 1376 return list(files)
1377 1377 return [f for f in dmap if match(f)]
1378 1378
1379 1379 def _actualfilename(self, tr):
1380 1380 if tr:
1381 1381 return self._pendingfilename
1382 1382 else:
1383 1383 return self._filename
1384 1384
1385 1385 def savebackup(self, tr, backupname):
1386 1386 '''Save current dirstate into backup file'''
1387 1387 filename = self._actualfilename(tr)
1388 1388 assert backupname != filename
1389 1389
1390 1390 # use '_writedirstate' instead of 'write' to write changes certainly,
1391 1391 # because the latter omits writing out if transaction is running.
1392 1392 # output file will be used to create backup of dirstate at this point.
1393 1393 if self._dirty or not self._opener.exists(filename):
1394 1394 self._writedirstate(
1395 1395 self._opener(filename, b"w", atomictemp=True, checkambig=True)
1396 1396 )
1397 1397
1398 1398 if tr:
1399 1399 # ensure that subsequent tr.writepending returns True for
1400 1400 # changes written out above, even if dirstate is never
1401 1401 # changed after this
1402 1402 tr.addfilegenerator(
1403 1403 b'dirstate',
1404 1404 (self._filename,),
1405 1405 self._writedirstate,
1406 1406 location=b'plain',
1407 1407 )
1408 1408
1409 1409 # ensure that pending file written above is unlinked at
1410 1410 # failure, even if tr.writepending isn't invoked until the
1411 1411 # end of this transaction
1412 1412 tr.registertmp(filename, location=b'plain')
1413 1413
1414 1414 self._opener.tryunlink(backupname)
1415 1415 # hardlink backup is okay because _writedirstate is always called
1416 1416 # with an "atomictemp=True" file.
1417 1417 util.copyfile(
1418 1418 self._opener.join(filename),
1419 1419 self._opener.join(backupname),
1420 1420 hardlink=True,
1421 1421 )
1422 1422
1423 1423 def restorebackup(self, tr, backupname):
1424 1424 '''Restore dirstate by backup file'''
1425 1425 # this "invalidate()" prevents "wlock.release()" from writing
1426 1426 # changes of dirstate out after restoring from backup file
1427 1427 self.invalidate()
1428 1428 filename = self._actualfilename(tr)
1429 1429 o = self._opener
1430 1430 if util.samefile(o.join(backupname), o.join(filename)):
1431 1431 o.unlink(backupname)
1432 1432 else:
1433 1433 o.rename(backupname, filename, checkambig=True)
1434 1434
1435 1435 def clearbackup(self, tr, backupname):
1436 1436 '''Clear backup file'''
1437 1437 self._opener.unlink(backupname)
@@ -1,552 +1,556
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 .. import (
18 18 error,
19 19 pycompat,
20 20 revlogutils,
21 21 util,
22 22 )
23 23
24 24 from ..revlogutils import nodemap as nodemaputil
25 25 from ..revlogutils import constants as revlog_constants
26 26
27 27 stringio = pycompat.bytesio
28 28
29 29
30 30 _pack = struct.pack
31 31 _unpack = struct.unpack
32 32 _compress = zlib.compress
33 33 _decompress = zlib.decompress
34 34
35 35
36 36 # a special value used internally for `size` if the file come from the other parent
37 37 FROM_P2 = -2
38 38
39 39 # a special value used internally for `size` if the file is modified/merged/added
40 40 NONNORMAL = -1
41 41
42 42
43 43 class dirstatetuple(object):
44 44 """represent a dirstate entry
45 45
46 46 It contains:
47 47
48 48 - state (one of 'n', 'a', 'r', 'm')
49 49 - mode,
50 50 - size,
51 51 - mtime,
52 52 """
53 53
54 54 __slot__ = ('_state', '_mode', '_size', '_mtime')
55 55
56 56 def __init__(self, state, mode, size, mtime):
57 57 self._state = state
58 58 self._mode = mode
59 59 self._size = size
60 60 self._mtime = mtime
61 61
62 62 def __getitem__(self, idx):
63 63 if idx == 0 or idx == -4:
64 64 return self._state
65 65 elif idx == 1 or idx == -3:
66 66 return self._mode
67 67 elif idx == 2 or idx == -2:
68 68 return self._size
69 69 elif idx == 3 or idx == -1:
70 70 return self._mtime
71 71 else:
72 72 raise IndexError(idx)
73 73
74 74 @property
75 75 def state(self):
76 76 """
77 77 States are:
78 78 n normal
79 79 m needs merging
80 80 r marked for removal
81 81 a marked for addition
82 82
83 83 XXX This "state" is a bit obscure and mostly a direct expression of the
84 84 dirstatev1 format. It would make sense to ultimately deprecate it in
85 85 favor of the more "semantic" attributes.
86 86 """
87 87 return self._state
88 88
89 89 @property
90 90 def tracked(self):
91 91 """True is the file is tracked in the working copy"""
92 92 return self._state in b"nma"
93 93
94 94 @property
95 95 def added(self):
96 96 """True if the file has been added"""
97 97 return self._state == b'a'
98 98
99 99 @property
100 100 def merged(self):
101 101 """True if the file has been merged
102 102
103 103 Should only be set if a merge is in progress in the dirstate
104 104 """
105 105 return self._state == b'm'
106 106
107 107 @property
108 108 def from_p2(self):
109 109 """True if the file have been fetched from p2 during the current merge
110 110
111 111 This is only True is the file is currently tracked.
112 112
113 113 Should only be set if a merge is in progress in the dirstate
114 114 """
115 115 return self._state == b'n' and self._size == FROM_P2
116 116
117 117 @property
118 118 def from_p2_removed(self):
119 119 """True if the file has been removed, but was "from_p2" initially
120 120
121 121 This property seems like an abstraction leakage and should probably be
122 122 dealt in this class (or maybe the dirstatemap) directly.
123 123 """
124 124 return self._state == b'r' and self._size == FROM_P2
125 125
126 126 @property
127 127 def removed(self):
128 128 """True if the file has been removed"""
129 129 return self._state == b'r'
130 130
131 131 @property
132 132 def merged_removed(self):
133 133 """True if the file has been removed, but was "merged" initially
134 134
135 135 This property seems like an abstraction leakage and should probably be
136 136 dealt in this class (or maybe the dirstatemap) directly.
137 137 """
138 138 return self._state == b'r' and self._size == NONNORMAL
139 139
140 140 def v1_state(self):
141 141 """return a "state" suitable for v1 serialization"""
142 142 return self._state
143 143
144 144 def v1_mode(self):
145 145 """return a "mode" suitable for v1 serialization"""
146 146 return self._mode
147 147
148 148 def v1_size(self):
149 149 """return a "size" suitable for v1 serialization"""
150 150 return self._size
151 151
152 152 def v1_mtime(self):
153 153 """return a "mtime" suitable for v1 serialization"""
154 154 return self._mtime
155 155
156 def need_delay(self, now):
157 """True if the stored mtime would be ambiguous with the current time"""
158 return self._state == b'n' and self._mtime == now
159
156 160
157 161 def gettype(q):
158 162 return int(q & 0xFFFF)
159 163
160 164
161 165 class BaseIndexObject(object):
162 166 # Can I be passed to an algorithme implemented in Rust ?
163 167 rust_ext_compat = 0
164 168 # Format of an index entry according to Python's `struct` language
165 169 index_format = revlog_constants.INDEX_ENTRY_V1
166 170 # Size of a C unsigned long long int, platform independent
167 171 big_int_size = struct.calcsize(b'>Q')
168 172 # Size of a C long int, platform independent
169 173 int_size = struct.calcsize(b'>i')
170 174 # An empty index entry, used as a default value to be overridden, or nullrev
171 175 null_item = (
172 176 0,
173 177 0,
174 178 0,
175 179 -1,
176 180 -1,
177 181 -1,
178 182 -1,
179 183 sha1nodeconstants.nullid,
180 184 0,
181 185 0,
182 186 revlog_constants.COMP_MODE_INLINE,
183 187 revlog_constants.COMP_MODE_INLINE,
184 188 )
185 189
186 190 @util.propertycache
187 191 def entry_size(self):
188 192 return self.index_format.size
189 193
190 194 @property
191 195 def nodemap(self):
192 196 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
193 197 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
194 198 return self._nodemap
195 199
196 200 @util.propertycache
197 201 def _nodemap(self):
198 202 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
199 203 for r in range(0, len(self)):
200 204 n = self[r][7]
201 205 nodemap[n] = r
202 206 return nodemap
203 207
204 208 def has_node(self, node):
205 209 """return True if the node exist in the index"""
206 210 return node in self._nodemap
207 211
208 212 def rev(self, node):
209 213 """return a revision for a node
210 214
211 215 If the node is unknown, raise a RevlogError"""
212 216 return self._nodemap[node]
213 217
214 218 def get_rev(self, node):
215 219 """return a revision for a node
216 220
217 221 If the node is unknown, return None"""
218 222 return self._nodemap.get(node)
219 223
220 224 def _stripnodes(self, start):
221 225 if '_nodemap' in vars(self):
222 226 for r in range(start, len(self)):
223 227 n = self[r][7]
224 228 del self._nodemap[n]
225 229
226 230 def clearcaches(self):
227 231 self.__dict__.pop('_nodemap', None)
228 232
229 233 def __len__(self):
230 234 return self._lgt + len(self._extra)
231 235
232 236 def append(self, tup):
233 237 if '_nodemap' in vars(self):
234 238 self._nodemap[tup[7]] = len(self)
235 239 data = self._pack_entry(len(self), tup)
236 240 self._extra.append(data)
237 241
238 242 def _pack_entry(self, rev, entry):
239 243 assert entry[8] == 0
240 244 assert entry[9] == 0
241 245 return self.index_format.pack(*entry[:8])
242 246
243 247 def _check_index(self, i):
244 248 if not isinstance(i, int):
245 249 raise TypeError(b"expecting int indexes")
246 250 if i < 0 or i >= len(self):
247 251 raise IndexError
248 252
249 253 def __getitem__(self, i):
250 254 if i == -1:
251 255 return self.null_item
252 256 self._check_index(i)
253 257 if i >= self._lgt:
254 258 data = self._extra[i - self._lgt]
255 259 else:
256 260 index = self._calculate_index(i)
257 261 data = self._data[index : index + self.entry_size]
258 262 r = self._unpack_entry(i, data)
259 263 if self._lgt and i == 0:
260 264 offset = revlogutils.offset_type(0, gettype(r[0]))
261 265 r = (offset,) + r[1:]
262 266 return r
263 267
264 268 def _unpack_entry(self, rev, data):
265 269 r = self.index_format.unpack(data)
266 270 r = r + (
267 271 0,
268 272 0,
269 273 revlog_constants.COMP_MODE_INLINE,
270 274 revlog_constants.COMP_MODE_INLINE,
271 275 )
272 276 return r
273 277
274 278 def pack_header(self, header):
275 279 """pack header information as binary"""
276 280 v_fmt = revlog_constants.INDEX_HEADER
277 281 return v_fmt.pack(header)
278 282
279 283 def entry_binary(self, rev):
280 284 """return the raw binary string representing a revision"""
281 285 entry = self[rev]
282 286 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
283 287 if rev == 0:
284 288 p = p[revlog_constants.INDEX_HEADER.size :]
285 289 return p
286 290
287 291
288 292 class IndexObject(BaseIndexObject):
289 293 def __init__(self, data):
290 294 assert len(data) % self.entry_size == 0, (
291 295 len(data),
292 296 self.entry_size,
293 297 len(data) % self.entry_size,
294 298 )
295 299 self._data = data
296 300 self._lgt = len(data) // self.entry_size
297 301 self._extra = []
298 302
299 303 def _calculate_index(self, i):
300 304 return i * self.entry_size
301 305
302 306 def __delitem__(self, i):
303 307 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
304 308 raise ValueError(b"deleting slices only supports a:-1 with step 1")
305 309 i = i.start
306 310 self._check_index(i)
307 311 self._stripnodes(i)
308 312 if i < self._lgt:
309 313 self._data = self._data[: i * self.entry_size]
310 314 self._lgt = i
311 315 self._extra = []
312 316 else:
313 317 self._extra = self._extra[: i - self._lgt]
314 318
315 319
316 320 class PersistentNodeMapIndexObject(IndexObject):
317 321 """a Debug oriented class to test persistent nodemap
318 322
319 323 We need a simple python object to test API and higher level behavior. See
320 324 the Rust implementation for more serious usage. This should be used only
321 325 through the dedicated `devel.persistent-nodemap` config.
322 326 """
323 327
324 328 def nodemap_data_all(self):
325 329 """Return bytes containing a full serialization of a nodemap
326 330
327 331 The nodemap should be valid for the full set of revisions in the
328 332 index."""
329 333 return nodemaputil.persistent_data(self)
330 334
331 335 def nodemap_data_incremental(self):
332 336 """Return bytes containing a incremental update to persistent nodemap
333 337
334 338 This containst the data for an append-only update of the data provided
335 339 in the last call to `update_nodemap_data`.
336 340 """
337 341 if self._nm_root is None:
338 342 return None
339 343 docket = self._nm_docket
340 344 changed, data = nodemaputil.update_persistent_data(
341 345 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
342 346 )
343 347
344 348 self._nm_root = self._nm_max_idx = self._nm_docket = None
345 349 return docket, changed, data
346 350
347 351 def update_nodemap_data(self, docket, nm_data):
348 352 """provide full block of persisted binary data for a nodemap
349 353
350 354 The data are expected to come from disk. See `nodemap_data_all` for a
351 355 produceur of such data."""
352 356 if nm_data is not None:
353 357 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
354 358 if self._nm_root:
355 359 self._nm_docket = docket
356 360 else:
357 361 self._nm_root = self._nm_max_idx = self._nm_docket = None
358 362
359 363
360 364 class InlinedIndexObject(BaseIndexObject):
361 365 def __init__(self, data, inline=0):
362 366 self._data = data
363 367 self._lgt = self._inline_scan(None)
364 368 self._inline_scan(self._lgt)
365 369 self._extra = []
366 370
367 371 def _inline_scan(self, lgt):
368 372 off = 0
369 373 if lgt is not None:
370 374 self._offsets = [0] * lgt
371 375 count = 0
372 376 while off <= len(self._data) - self.entry_size:
373 377 start = off + self.big_int_size
374 378 (s,) = struct.unpack(
375 379 b'>i',
376 380 self._data[start : start + self.int_size],
377 381 )
378 382 if lgt is not None:
379 383 self._offsets[count] = off
380 384 count += 1
381 385 off += self.entry_size + s
382 386 if off != len(self._data):
383 387 raise ValueError(b"corrupted data")
384 388 return count
385 389
386 390 def __delitem__(self, i):
387 391 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
388 392 raise ValueError(b"deleting slices only supports a:-1 with step 1")
389 393 i = i.start
390 394 self._check_index(i)
391 395 self._stripnodes(i)
392 396 if i < self._lgt:
393 397 self._offsets = self._offsets[:i]
394 398 self._lgt = i
395 399 self._extra = []
396 400 else:
397 401 self._extra = self._extra[: i - self._lgt]
398 402
399 403 def _calculate_index(self, i):
400 404 return self._offsets[i]
401 405
402 406
403 407 def parse_index2(data, inline, revlogv2=False):
404 408 if not inline:
405 409 cls = IndexObject2 if revlogv2 else IndexObject
406 410 return cls(data), None
407 411 cls = InlinedIndexObject
408 412 return cls(data, inline), (0, data)
409 413
410 414
411 415 def parse_index_cl_v2(data):
412 416 return IndexChangelogV2(data), None
413 417
414 418
415 419 class IndexObject2(IndexObject):
416 420 index_format = revlog_constants.INDEX_ENTRY_V2
417 421
418 422 def replace_sidedata_info(
419 423 self,
420 424 rev,
421 425 sidedata_offset,
422 426 sidedata_length,
423 427 offset_flags,
424 428 compression_mode,
425 429 ):
426 430 """
427 431 Replace an existing index entry's sidedata offset and length with new
428 432 ones.
429 433 This cannot be used outside of the context of sidedata rewriting,
430 434 inside the transaction that creates the revision `rev`.
431 435 """
432 436 if rev < 0:
433 437 raise KeyError
434 438 self._check_index(rev)
435 439 if rev < self._lgt:
436 440 msg = b"cannot rewrite entries outside of this transaction"
437 441 raise KeyError(msg)
438 442 else:
439 443 entry = list(self[rev])
440 444 entry[0] = offset_flags
441 445 entry[8] = sidedata_offset
442 446 entry[9] = sidedata_length
443 447 entry[11] = compression_mode
444 448 entry = tuple(entry)
445 449 new = self._pack_entry(rev, entry)
446 450 self._extra[rev - self._lgt] = new
447 451
448 452 def _unpack_entry(self, rev, data):
449 453 data = self.index_format.unpack(data)
450 454 entry = data[:10]
451 455 data_comp = data[10] & 3
452 456 sidedata_comp = (data[10] & (3 << 2)) >> 2
453 457 return entry + (data_comp, sidedata_comp)
454 458
455 459 def _pack_entry(self, rev, entry):
456 460 data = entry[:10]
457 461 data_comp = entry[10] & 3
458 462 sidedata_comp = (entry[11] & 3) << 2
459 463 data += (data_comp | sidedata_comp,)
460 464
461 465 return self.index_format.pack(*data)
462 466
463 467 def entry_binary(self, rev):
464 468 """return the raw binary string representing a revision"""
465 469 entry = self[rev]
466 470 return self._pack_entry(rev, entry)
467 471
468 472 def pack_header(self, header):
469 473 """pack header information as binary"""
470 474 msg = 'version header should go in the docket, not the index: %d'
471 475 msg %= header
472 476 raise error.ProgrammingError(msg)
473 477
474 478
475 479 class IndexChangelogV2(IndexObject2):
476 480 index_format = revlog_constants.INDEX_ENTRY_CL_V2
477 481
478 482 def _unpack_entry(self, rev, data, r=True):
479 483 items = self.index_format.unpack(data)
480 484 entry = items[:3] + (rev, rev) + items[3:8]
481 485 data_comp = items[8] & 3
482 486 sidedata_comp = (items[8] >> 2) & 3
483 487 return entry + (data_comp, sidedata_comp)
484 488
485 489 def _pack_entry(self, rev, entry):
486 490 assert entry[3] == rev, entry[3]
487 491 assert entry[4] == rev, entry[4]
488 492 data = entry[:3] + entry[5:10]
489 493 data_comp = entry[10] & 3
490 494 sidedata_comp = (entry[11] & 3) << 2
491 495 data += (data_comp | sidedata_comp,)
492 496 return self.index_format.pack(*data)
493 497
494 498
495 499 def parse_index_devel_nodemap(data, inline):
496 500 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
497 501 return PersistentNodeMapIndexObject(data), None
498 502
499 503
500 504 def parse_dirstate(dmap, copymap, st):
501 505 parents = [st[:20], st[20:40]]
502 506 # dereference fields so they will be local in loop
503 507 format = b">cllll"
504 508 e_size = struct.calcsize(format)
505 509 pos1 = 40
506 510 l = len(st)
507 511
508 512 # the inner loop
509 513 while pos1 < l:
510 514 pos2 = pos1 + e_size
511 515 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
512 516 pos1 = pos2 + e[4]
513 517 f = st[pos2:pos1]
514 518 if b'\0' in f:
515 519 f, c = f.split(b'\0')
516 520 copymap[f] = c
517 521 dmap[f] = dirstatetuple(*e[:4])
518 522 return parents
519 523
520 524
521 525 def pack_dirstate(dmap, copymap, pl, now):
522 526 now = int(now)
523 527 cs = stringio()
524 528 write = cs.write
525 529 write(b"".join(pl))
526 530 for f, e in pycompat.iteritems(dmap):
527 531 if e[0] == b'n' and e[3] == now:
528 532 # The file was last modified "simultaneously" with the current
529 533 # write to dirstate (i.e. within the same second for file-
530 534 # systems with a granularity of 1 sec). This commonly happens
531 535 # for at least a couple of files on 'update'.
532 536 # The user could change the file without changing its size
533 537 # within the same second. Invalidate the file's mtime in
534 538 # dirstate, forcing future 'status' calls to compare the
535 539 # contents of the file if the size is the same. This prevents
536 540 # mistakenly treating such files as clean.
537 541 e = dirstatetuple(e[0], e[1], e[2], -1)
538 542 dmap[f] = e
539 543
540 544 if f in copymap:
541 545 f = b"%s\0%s" % (f, copymap[f])
542 546 e = _pack(
543 547 b">cllll",
544 548 e.v1_state(),
545 549 e.v1_mode(),
546 550 e.v1_size(),
547 551 e.v1_mtime(),
548 552 len(f),
549 553 )
550 554 write(e)
551 555 write(f)
552 556 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now