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