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