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