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