##// END OF EJS Templates
dirstate-item: add dedicated "legacy" constructor for `addfile` case...
marmoute -
r48716:c0d6a59a default
parent child Browse files
Show More
@@ -1,1083 +1,1194 b''
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 12 #include <ctype.h>
13 13 #include <stddef.h>
14 14 #include <string.h>
15 15
16 16 #include "bitmanipulation.h"
17 17 #include "charencode.h"
18 18 #include "util.h"
19 19
20 20 #ifdef IS_PY3K
21 21 /* The mapping of Python types is meant to be temporary to get Python
22 22 * 3 to compile. We should remove this once Python 3 support is fully
23 23 * supported and proper types are used in the extensions themselves. */
24 24 #define PyInt_Check PyLong_Check
25 25 #define PyInt_FromLong PyLong_FromLong
26 26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 27 #define PyInt_AsLong PyLong_AsLong
28 28 #endif
29 29
30 30 static const char *const versionerrortext = "Python minor version mismatch";
31 31
32 32 static const int dirstate_v1_from_p2 = -2;
33 33 static const int dirstate_v1_nonnormal = -1;
34 34 static const int ambiguous_time = -1;
35 35
36 36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
37 37 {
38 38 Py_ssize_t expected_size;
39 39
40 40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
41 41 return NULL;
42 42 }
43 43
44 44 return _dict_new_presized(expected_size);
45 45 }
46 46
47 47 static inline dirstateItemObject *make_dirstate_item(char state, int mode,
48 48 int size, int mtime)
49 49 {
50 50 dirstateItemObject *t =
51 51 PyObject_New(dirstateItemObject, &dirstateItemType);
52 52 if (!t) {
53 53 return NULL;
54 54 }
55 55 t->state = state;
56 56 t->mode = mode;
57 57 t->size = size;
58 58 t->mtime = mtime;
59 59 return t;
60 60 }
61 61
62 62 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
63 63 PyObject *kwds)
64 64 {
65 65 /* We do all the initialization here and not a tp_init function because
66 66 * dirstate_item is immutable. */
67 67 dirstateItemObject *t;
68 68 int wc_tracked;
69 69 int p1_tracked;
70 70 int p2_tracked;
71 71 int merged;
72 72 int clean_p1;
73 73 int clean_p2;
74 74 int possibly_dirty;
75 75 PyObject *parentfiledata;
76 76 static char *keywords_name[] = {
77 77 "wc_tracked", "p1_tracked", "p2_tracked",
78 78 "merged", "clean_p1", "clean_p2",
79 79 "possibly_dirty", "parentfiledata", NULL,
80 80 };
81 81 wc_tracked = 0;
82 82 p1_tracked = 0;
83 83 p2_tracked = 0;
84 84 merged = 0;
85 85 clean_p1 = 0;
86 86 clean_p2 = 0;
87 87 possibly_dirty = 0;
88 88 parentfiledata = Py_None;
89 89 if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiiiiiiO", keywords_name,
90 90 &wc_tracked, &p1_tracked, &p2_tracked,
91 91 &merged, &clean_p1, &clean_p2,
92 92 &possibly_dirty, &parentfiledata
93 93
94 94 )) {
95 95 return NULL;
96 96 }
97 97 if (merged && (clean_p1 || clean_p2)) {
98 98 PyErr_SetString(PyExc_RuntimeError,
99 99 "`merged` argument incompatible with "
100 100 "`clean_p1`/`clean_p2`");
101 101 return NULL;
102 102 }
103 103 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
104 104 if (!t) {
105 105 return NULL;
106 106 }
107 107 t->state = 'r';
108 108 t->mode = 0;
109 109 t->size = dirstate_v1_nonnormal;
110 110 t->mtime = ambiguous_time;
111 111 if (!(p1_tracked || p2_tracked || wc_tracked)) {
112 112 /* Nothing special to do, file is untracked */
113 113 } else if (merged) {
114 114 t->state = 'm';
115 115 t->size = dirstate_v1_from_p2;
116 116 t->mtime = ambiguous_time;
117 117 } else if (!(p1_tracked || p2_tracked) && wc_tracked) {
118 118 t->state = 'a';
119 119 t->size = dirstate_v1_nonnormal;
120 120 t->mtime = ambiguous_time;
121 121 } else if ((p1_tracked || p2_tracked) && !wc_tracked) {
122 122 t->state = 'r';
123 123 t->size = 0;
124 124 t->mtime = 0;
125 125 } else if (clean_p2 && wc_tracked) {
126 126 t->state = 'n';
127 127 t->size = dirstate_v1_from_p2;
128 128 t->mtime = ambiguous_time;
129 129 } else if (!p1_tracked && p2_tracked && wc_tracked) {
130 130 t->state = 'n';
131 131 t->size = dirstate_v1_from_p2;
132 132 t->mtime = ambiguous_time;
133 133 } else if (possibly_dirty) {
134 134 t->state = 'n';
135 135 t->size = dirstate_v1_nonnormal;
136 136 t->mtime = ambiguous_time;
137 137 } else if (wc_tracked) {
138 138 /* this is a "normal" file */
139 139 if (parentfiledata == Py_None) {
140 140 PyErr_SetString(
141 141 PyExc_RuntimeError,
142 142 "failed to pass parentfiledata for a normal file");
143 143 return NULL;
144 144 }
145 145 if (!PyTuple_CheckExact(parentfiledata)) {
146 146 PyErr_SetString(
147 147 PyExc_TypeError,
148 148 "parentfiledata should be a Tuple or None");
149 149 return NULL;
150 150 }
151 151 t->state = 'n';
152 152 t->mode =
153 153 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
154 154 t->size =
155 155 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
156 156 t->mtime =
157 157 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
158 158 } else {
159 159 PyErr_SetString(PyExc_RuntimeError, "unreachable");
160 160 return NULL;
161 161 }
162 162 return (PyObject *)t;
163 163 }
164 164
165 165 static void dirstate_item_dealloc(PyObject *o)
166 166 {
167 167 PyObject_Del(o);
168 168 }
169 169
170 170 static Py_ssize_t dirstate_item_length(PyObject *o)
171 171 {
172 172 return 4;
173 173 }
174 174
175 175 static PyObject *dirstate_item_item(PyObject *o, Py_ssize_t i)
176 176 {
177 177 dirstateItemObject *t = (dirstateItemObject *)o;
178 178 switch (i) {
179 179 case 0:
180 180 return PyBytes_FromStringAndSize(&t->state, 1);
181 181 case 1:
182 182 return PyInt_FromLong(t->mode);
183 183 case 2:
184 184 return PyInt_FromLong(t->size);
185 185 case 3:
186 186 return PyInt_FromLong(t->mtime);
187 187 default:
188 188 PyErr_SetString(PyExc_IndexError, "index out of range");
189 189 return NULL;
190 190 }
191 191 }
192 192
193 193 static PySequenceMethods dirstate_item_sq = {
194 194 dirstate_item_length, /* sq_length */
195 195 0, /* sq_concat */
196 196 0, /* sq_repeat */
197 197 dirstate_item_item, /* sq_item */
198 198 0, /* sq_ass_item */
199 199 0, /* sq_contains */
200 200 0, /* sq_inplace_concat */
201 201 0 /* sq_inplace_repeat */
202 202 };
203 203
204 204 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
205 205 {
206 206 return PyBytes_FromStringAndSize(&self->state, 1);
207 207 };
208 208
209 209 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
210 210 {
211 211 return PyInt_FromLong(self->mode);
212 212 };
213 213
214 214 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
215 215 {
216 216 return PyInt_FromLong(self->size);
217 217 };
218 218
219 219 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
220 220 {
221 221 return PyInt_FromLong(self->mtime);
222 222 };
223 223
224 224 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
225 225 PyObject *value)
226 226 {
227 227 long now;
228 228 if (!pylong_to_long(value, &now)) {
229 229 return NULL;
230 230 }
231 231 if (self->state == 'n' && self->mtime == now) {
232 232 Py_RETURN_TRUE;
233 233 } else {
234 234 Py_RETURN_FALSE;
235 235 }
236 236 };
237 237
238 238 /* This will never change since it's bound to V1, unlike `make_dirstate_item`
239 239 */
240 240 static inline dirstateItemObject *
241 241 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
242 242 {
243 243 dirstateItemObject *t =
244 244 PyObject_New(dirstateItemObject, &dirstateItemType);
245 245 if (!t) {
246 246 return NULL;
247 247 }
248 248 t->state = state;
249 249 t->mode = mode;
250 250 t->size = size;
251 251 t->mtime = mtime;
252 252 return t;
253 253 }
254 254
255 255 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
256 256 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
257 257 PyObject *args)
258 258 {
259 259 /* We do all the initialization here and not a tp_init function because
260 260 * dirstate_item is immutable. */
261 261 dirstateItemObject *t;
262 262 char state;
263 263 int size, mode, mtime;
264 264 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
265 265 return NULL;
266 266 }
267 267
268 268 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
269 269 if (!t) {
270 270 return NULL;
271 271 }
272 272 t->state = state;
273 273 t->mode = mode;
274 274 t->size = size;
275 275 t->mtime = mtime;
276 276
277 277 return (PyObject *)t;
278 278 };
279 279
280 /* constructor to help legacy API to build a new "added" item
281
282 Should eventually be removed */
283 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
284 {
285 dirstateItemObject *t;
286 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
287 if (!t) {
288 return NULL;
289 }
290 t->state = 'a';
291 t->mode = 0;
292 t->size = dirstate_v1_nonnormal;
293 t->mtime = ambiguous_time;
294 return (PyObject *)t;
295 };
296
297 /* constructor to help legacy API to build a new "merged" item
298
299 Should eventually be removed */
300 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
301 {
302 dirstateItemObject *t;
303 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
304 if (!t) {
305 return NULL;
306 }
307 t->state = 'm';
308 t->mode = 0;
309 t->size = dirstate_v1_from_p2;
310 t->mtime = ambiguous_time;
311 return (PyObject *)t;
312 };
313
314 /* constructor to help legacy API to build a new "from_p2" item
315
316 Should eventually be removed */
317 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
318 {
319 /* We do all the initialization here and not a tp_init function because
320 * dirstate_item is immutable. */
321 dirstateItemObject *t;
322 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
323 if (!t) {
324 return NULL;
325 }
326 t->state = 'n';
327 t->mode = 0;
328 t->size = dirstate_v1_from_p2;
329 t->mtime = ambiguous_time;
330 return (PyObject *)t;
331 };
332
333 /* constructor to help legacy API to build a new "possibly" item
334
335 Should eventually be removed */
336 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
337 {
338 /* We do all the initialization here and not a tp_init function because
339 * dirstate_item is immutable. */
340 dirstateItemObject *t;
341 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
342 if (!t) {
343 return NULL;
344 }
345 t->state = 'n';
346 t->mode = 0;
347 t->size = dirstate_v1_nonnormal;
348 t->mtime = ambiguous_time;
349 return (PyObject *)t;
350 };
351
352 /* constructor to help legacy API to build a new "normal" item
353
354 Should eventually be removed */
355 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
356 {
357 /* We do all the initialization here and not a tp_init function because
358 * dirstate_item is immutable. */
359 dirstateItemObject *t;
360 int size, mode, mtime;
361 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
362 return NULL;
363 }
364
365 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
366 if (!t) {
367 return NULL;
368 }
369 t->state = 'n';
370 t->mode = mode;
371 t->size = size;
372 t->mtime = mtime;
373 return (PyObject *)t;
374 };
375
280 376 /* This means the next status call will have to actually check its content
281 377 to make sure it is correct. */
282 378 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
283 379 {
284 380 self->mtime = ambiguous_time;
285 381 Py_RETURN_NONE;
286 382 }
287 383
288 384 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
289 385 {
290 386 if (self->state == 'm') {
291 387 self->size = dirstate_v1_nonnormal;
292 388 } else if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
293 389 self->size = dirstate_v1_from_p2;
294 390 } else {
295 391 self->size = 0;
296 392 }
297 393 self->state = 'r';
298 394 self->mode = 0;
299 395 self->mtime = 0;
300 396 Py_RETURN_NONE;
301 397 }
302 398
303 399 static PyMethodDef dirstate_item_methods[] = {
304 400 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
305 401 "return a \"state\" suitable for v1 serialization"},
306 402 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
307 403 "return a \"mode\" suitable for v1 serialization"},
308 404 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
309 405 "return a \"size\" suitable for v1 serialization"},
310 406 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
311 407 "return a \"mtime\" suitable for v1 serialization"},
312 408 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
313 409 "True if the stored mtime would be ambiguous with the current time"},
314 410 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
315 411 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
412 {"new_added", (PyCFunction)dirstate_item_new_added,
413 METH_NOARGS | METH_CLASS,
414 "constructor to help legacy API to build a new \"added\" item"},
415 {"new_merged", (PyCFunction)dirstate_item_new_merged,
416 METH_NOARGS | METH_CLASS,
417 "constructor to help legacy API to build a new \"merged\" item"},
418 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
419 METH_NOARGS | METH_CLASS,
420 "constructor to help legacy API to build a new \"from_p2\" item"},
421 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
422 METH_NOARGS | METH_CLASS,
423 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
424 {"new_normal", (PyCFunction)dirstate_item_new_normal,
425 METH_VARARGS | METH_CLASS,
426 "constructor to help legacy API to build a new \"normal\" item"},
316 427 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
317 428 METH_NOARGS, "mark a file as \"possibly dirty\""},
318 429 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
319 430 "mark a file as \"untracked\""},
320 431 {NULL} /* Sentinel */
321 432 };
322 433
323 434 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
324 435 {
325 436 return PyInt_FromLong(self->mode);
326 437 };
327 438
328 439 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
329 440 {
330 441 return PyInt_FromLong(self->size);
331 442 };
332 443
333 444 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
334 445 {
335 446 return PyInt_FromLong(self->mtime);
336 447 };
337 448
338 449 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
339 450 {
340 451 return PyBytes_FromStringAndSize(&self->state, 1);
341 452 };
342 453
343 454 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
344 455 {
345 456 if (self->state == 'a' || self->state == 'm' || self->state == 'n') {
346 457 Py_RETURN_TRUE;
347 458 } else {
348 459 Py_RETURN_FALSE;
349 460 }
350 461 };
351 462
352 463 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
353 464 {
354 465 if (self->state == 'a') {
355 466 Py_RETURN_TRUE;
356 467 } else {
357 468 Py_RETURN_FALSE;
358 469 }
359 470 };
360 471
361 472 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
362 473 {
363 474 if (self->state == 'm') {
364 475 Py_RETURN_TRUE;
365 476 } else {
366 477 Py_RETURN_FALSE;
367 478 }
368 479 };
369 480
370 481 static PyObject *dirstate_item_get_merged_removed(dirstateItemObject *self)
371 482 {
372 483 if (self->state == 'r' && self->size == dirstate_v1_nonnormal) {
373 484 Py_RETURN_TRUE;
374 485 } else {
375 486 Py_RETURN_FALSE;
376 487 }
377 488 };
378 489
379 490 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
380 491 {
381 492 if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
382 493 Py_RETURN_TRUE;
383 494 } else {
384 495 Py_RETURN_FALSE;
385 496 }
386 497 };
387 498
388 499 static PyObject *dirstate_item_get_from_p2_removed(dirstateItemObject *self)
389 500 {
390 501 if (self->state == 'r' && self->size == dirstate_v1_from_p2) {
391 502 Py_RETURN_TRUE;
392 503 } else {
393 504 Py_RETURN_FALSE;
394 505 }
395 506 };
396 507
397 508 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
398 509 {
399 510 if (self->state == 'r') {
400 511 Py_RETURN_TRUE;
401 512 } else {
402 513 Py_RETURN_FALSE;
403 514 }
404 515 };
405 516
406 517 static PyObject *dm_nonnormal(dirstateItemObject *self)
407 518 {
408 519 if (self->state != 'n' || self->mtime == ambiguous_time) {
409 520 Py_RETURN_TRUE;
410 521 } else {
411 522 Py_RETURN_FALSE;
412 523 }
413 524 };
414 525 static PyObject *dm_otherparent(dirstateItemObject *self)
415 526 {
416 527 if (self->size == dirstate_v1_from_p2) {
417 528 Py_RETURN_TRUE;
418 529 } else {
419 530 Py_RETURN_FALSE;
420 531 }
421 532 };
422 533
423 534 static PyGetSetDef dirstate_item_getset[] = {
424 535 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
425 536 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
426 537 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
427 538 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
428 539 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
429 540 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
430 541 {"merged_removed", (getter)dirstate_item_get_merged_removed, NULL,
431 542 "merged_removed", NULL},
432 543 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
433 544 {"from_p2_removed", (getter)dirstate_item_get_from_p2_removed, NULL,
434 545 "from_p2_removed", NULL},
435 546 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
436 547 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
437 548 {"dm_nonnormal", (getter)dm_nonnormal, NULL, "dm_nonnormal", NULL},
438 549 {"dm_otherparent", (getter)dm_otherparent, NULL, "dm_otherparent", NULL},
439 550 {NULL} /* Sentinel */
440 551 };
441 552
442 553 PyTypeObject dirstateItemType = {
443 554 PyVarObject_HEAD_INIT(NULL, 0) /* header */
444 555 "dirstate_tuple", /* tp_name */
445 556 sizeof(dirstateItemObject), /* tp_basicsize */
446 557 0, /* tp_itemsize */
447 558 (destructor)dirstate_item_dealloc, /* tp_dealloc */
448 559 0, /* tp_print */
449 560 0, /* tp_getattr */
450 561 0, /* tp_setattr */
451 562 0, /* tp_compare */
452 563 0, /* tp_repr */
453 564 0, /* tp_as_number */
454 565 &dirstate_item_sq, /* tp_as_sequence */
455 566 0, /* tp_as_mapping */
456 567 0, /* tp_hash */
457 568 0, /* tp_call */
458 569 0, /* tp_str */
459 570 0, /* tp_getattro */
460 571 0, /* tp_setattro */
461 572 0, /* tp_as_buffer */
462 573 Py_TPFLAGS_DEFAULT, /* tp_flags */
463 574 "dirstate tuple", /* tp_doc */
464 575 0, /* tp_traverse */
465 576 0, /* tp_clear */
466 577 0, /* tp_richcompare */
467 578 0, /* tp_weaklistoffset */
468 579 0, /* tp_iter */
469 580 0, /* tp_iternext */
470 581 dirstate_item_methods, /* tp_methods */
471 582 0, /* tp_members */
472 583 dirstate_item_getset, /* tp_getset */
473 584 0, /* tp_base */
474 585 0, /* tp_dict */
475 586 0, /* tp_descr_get */
476 587 0, /* tp_descr_set */
477 588 0, /* tp_dictoffset */
478 589 0, /* tp_init */
479 590 0, /* tp_alloc */
480 591 dirstate_item_new, /* tp_new */
481 592 };
482 593
483 594 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
484 595 {
485 596 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
486 597 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
487 598 char state, *cur, *str, *cpos;
488 599 int mode, size, mtime;
489 600 unsigned int flen, pos = 40;
490 601 Py_ssize_t len = 40;
491 602 Py_ssize_t readlen;
492 603
493 604 if (!PyArg_ParseTuple(
494 605 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
495 606 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
496 607 goto quit;
497 608 }
498 609
499 610 len = readlen;
500 611
501 612 /* read parents */
502 613 if (len < 40) {
503 614 PyErr_SetString(PyExc_ValueError,
504 615 "too little data for parents");
505 616 goto quit;
506 617 }
507 618
508 619 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
509 620 str + 20, (Py_ssize_t)20);
510 621 if (!parents) {
511 622 goto quit;
512 623 }
513 624
514 625 /* read filenames */
515 626 while (pos >= 40 && pos < len) {
516 627 if (pos + 17 > len) {
517 628 PyErr_SetString(PyExc_ValueError,
518 629 "overflow in dirstate");
519 630 goto quit;
520 631 }
521 632 cur = str + pos;
522 633 /* unpack header */
523 634 state = *cur;
524 635 mode = getbe32(cur + 1);
525 636 size = getbe32(cur + 5);
526 637 mtime = getbe32(cur + 9);
527 638 flen = getbe32(cur + 13);
528 639 pos += 17;
529 640 cur += 17;
530 641 if (flen > len - pos) {
531 642 PyErr_SetString(PyExc_ValueError,
532 643 "overflow in dirstate");
533 644 goto quit;
534 645 }
535 646
536 647 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
537 648 size, mtime);
538 649 cpos = memchr(cur, 0, flen);
539 650 if (cpos) {
540 651 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
541 652 cname = PyBytes_FromStringAndSize(
542 653 cpos + 1, flen - (cpos - cur) - 1);
543 654 if (!fname || !cname ||
544 655 PyDict_SetItem(cmap, fname, cname) == -1 ||
545 656 PyDict_SetItem(dmap, fname, entry) == -1) {
546 657 goto quit;
547 658 }
548 659 Py_DECREF(cname);
549 660 } else {
550 661 fname = PyBytes_FromStringAndSize(cur, flen);
551 662 if (!fname ||
552 663 PyDict_SetItem(dmap, fname, entry) == -1) {
553 664 goto quit;
554 665 }
555 666 }
556 667 Py_DECREF(fname);
557 668 Py_DECREF(entry);
558 669 fname = cname = entry = NULL;
559 670 pos += flen;
560 671 }
561 672
562 673 ret = parents;
563 674 Py_INCREF(ret);
564 675 quit:
565 676 Py_XDECREF(fname);
566 677 Py_XDECREF(cname);
567 678 Py_XDECREF(entry);
568 679 Py_XDECREF(parents);
569 680 return ret;
570 681 }
571 682
572 683 /*
573 684 * Build a set of non-normal and other parent entries from the dirstate dmap
574 685 */
575 686 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
576 687 {
577 688 PyObject *dmap, *fname, *v;
578 689 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
579 690 Py_ssize_t pos;
580 691
581 692 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
582 693 &dmap)) {
583 694 goto bail;
584 695 }
585 696
586 697 nonnset = PySet_New(NULL);
587 698 if (nonnset == NULL) {
588 699 goto bail;
589 700 }
590 701
591 702 otherpset = PySet_New(NULL);
592 703 if (otherpset == NULL) {
593 704 goto bail;
594 705 }
595 706
596 707 pos = 0;
597 708 while (PyDict_Next(dmap, &pos, &fname, &v)) {
598 709 dirstateItemObject *t;
599 710 if (!dirstate_tuple_check(v)) {
600 711 PyErr_SetString(PyExc_TypeError,
601 712 "expected a dirstate tuple");
602 713 goto bail;
603 714 }
604 715 t = (dirstateItemObject *)v;
605 716
606 717 if (t->state == 'n' && t->size == -2) {
607 718 if (PySet_Add(otherpset, fname) == -1) {
608 719 goto bail;
609 720 }
610 721 }
611 722
612 723 if (t->state == 'n' && t->mtime != -1) {
613 724 continue;
614 725 }
615 726 if (PySet_Add(nonnset, fname) == -1) {
616 727 goto bail;
617 728 }
618 729 }
619 730
620 731 result = Py_BuildValue("(OO)", nonnset, otherpset);
621 732 if (result == NULL) {
622 733 goto bail;
623 734 }
624 735 Py_DECREF(nonnset);
625 736 Py_DECREF(otherpset);
626 737 return result;
627 738 bail:
628 739 Py_XDECREF(nonnset);
629 740 Py_XDECREF(otherpset);
630 741 Py_XDECREF(result);
631 742 return NULL;
632 743 }
633 744
634 745 /*
635 746 * Efficiently pack a dirstate object into its on-disk format.
636 747 */
637 748 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
638 749 {
639 750 PyObject *packobj = NULL;
640 751 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
641 752 Py_ssize_t nbytes, pos, l;
642 753 PyObject *k, *v = NULL, *pn;
643 754 char *p, *s;
644 755 int now;
645 756
646 757 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
647 758 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
648 759 &now)) {
649 760 return NULL;
650 761 }
651 762
652 763 if (PyTuple_Size(pl) != 2) {
653 764 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
654 765 return NULL;
655 766 }
656 767
657 768 /* Figure out how much we need to allocate. */
658 769 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
659 770 PyObject *c;
660 771 if (!PyBytes_Check(k)) {
661 772 PyErr_SetString(PyExc_TypeError, "expected string key");
662 773 goto bail;
663 774 }
664 775 nbytes += PyBytes_GET_SIZE(k) + 17;
665 776 c = PyDict_GetItem(copymap, k);
666 777 if (c) {
667 778 if (!PyBytes_Check(c)) {
668 779 PyErr_SetString(PyExc_TypeError,
669 780 "expected string key");
670 781 goto bail;
671 782 }
672 783 nbytes += PyBytes_GET_SIZE(c) + 1;
673 784 }
674 785 }
675 786
676 787 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
677 788 if (packobj == NULL) {
678 789 goto bail;
679 790 }
680 791
681 792 p = PyBytes_AS_STRING(packobj);
682 793
683 794 pn = PyTuple_GET_ITEM(pl, 0);
684 795 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
685 796 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
686 797 goto bail;
687 798 }
688 799 memcpy(p, s, l);
689 800 p += 20;
690 801 pn = PyTuple_GET_ITEM(pl, 1);
691 802 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
692 803 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
693 804 goto bail;
694 805 }
695 806 memcpy(p, s, l);
696 807 p += 20;
697 808
698 809 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
699 810 dirstateItemObject *tuple;
700 811 char state;
701 812 int mode, size, mtime;
702 813 Py_ssize_t len, l;
703 814 PyObject *o;
704 815 char *t;
705 816
706 817 if (!dirstate_tuple_check(v)) {
707 818 PyErr_SetString(PyExc_TypeError,
708 819 "expected a dirstate tuple");
709 820 goto bail;
710 821 }
711 822 tuple = (dirstateItemObject *)v;
712 823
713 824 state = tuple->state;
714 825 mode = tuple->mode;
715 826 size = tuple->size;
716 827 mtime = tuple->mtime;
717 828 if (state == 'n' && mtime == now) {
718 829 /* See pure/parsers.py:pack_dirstate for why we do
719 830 * this. */
720 831 mtime = -1;
721 832 mtime_unset = (PyObject *)make_dirstate_item(
722 833 state, mode, size, mtime);
723 834 if (!mtime_unset) {
724 835 goto bail;
725 836 }
726 837 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
727 838 goto bail;
728 839 }
729 840 Py_DECREF(mtime_unset);
730 841 mtime_unset = NULL;
731 842 }
732 843 *p++ = state;
733 844 putbe32((uint32_t)mode, p);
734 845 putbe32((uint32_t)size, p + 4);
735 846 putbe32((uint32_t)mtime, p + 8);
736 847 t = p + 12;
737 848 p += 16;
738 849 len = PyBytes_GET_SIZE(k);
739 850 memcpy(p, PyBytes_AS_STRING(k), len);
740 851 p += len;
741 852 o = PyDict_GetItem(copymap, k);
742 853 if (o) {
743 854 *p++ = '\0';
744 855 l = PyBytes_GET_SIZE(o);
745 856 memcpy(p, PyBytes_AS_STRING(o), l);
746 857 p += l;
747 858 len += l + 1;
748 859 }
749 860 putbe32((uint32_t)len, t);
750 861 }
751 862
752 863 pos = p - PyBytes_AS_STRING(packobj);
753 864 if (pos != nbytes) {
754 865 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
755 866 (long)pos, (long)nbytes);
756 867 goto bail;
757 868 }
758 869
759 870 return packobj;
760 871 bail:
761 872 Py_XDECREF(mtime_unset);
762 873 Py_XDECREF(packobj);
763 874 Py_XDECREF(v);
764 875 return NULL;
765 876 }
766 877
767 878 #define BUMPED_FIX 1
768 879 #define USING_SHA_256 2
769 880 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
770 881
771 882 static PyObject *readshas(const char *source, unsigned char num,
772 883 Py_ssize_t hashwidth)
773 884 {
774 885 int i;
775 886 PyObject *list = PyTuple_New(num);
776 887 if (list == NULL) {
777 888 return NULL;
778 889 }
779 890 for (i = 0; i < num; i++) {
780 891 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
781 892 if (hash == NULL) {
782 893 Py_DECREF(list);
783 894 return NULL;
784 895 }
785 896 PyTuple_SET_ITEM(list, i, hash);
786 897 source += hashwidth;
787 898 }
788 899 return list;
789 900 }
790 901
791 902 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
792 903 uint32_t *msize)
793 904 {
794 905 const char *data = databegin;
795 906 const char *meta;
796 907
797 908 double mtime;
798 909 int16_t tz;
799 910 uint16_t flags;
800 911 unsigned char nsuccs, nparents, nmetadata;
801 912 Py_ssize_t hashwidth = 20;
802 913
803 914 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
804 915 PyObject *metadata = NULL, *ret = NULL;
805 916 int i;
806 917
807 918 if (data + FM1_HEADER_SIZE > dataend) {
808 919 goto overflow;
809 920 }
810 921
811 922 *msize = getbe32(data);
812 923 data += 4;
813 924 mtime = getbefloat64(data);
814 925 data += 8;
815 926 tz = getbeint16(data);
816 927 data += 2;
817 928 flags = getbeuint16(data);
818 929 data += 2;
819 930
820 931 if (flags & USING_SHA_256) {
821 932 hashwidth = 32;
822 933 }
823 934
824 935 nsuccs = (unsigned char)(*data++);
825 936 nparents = (unsigned char)(*data++);
826 937 nmetadata = (unsigned char)(*data++);
827 938
828 939 if (databegin + *msize > dataend) {
829 940 goto overflow;
830 941 }
831 942 dataend = databegin + *msize; /* narrow down to marker size */
832 943
833 944 if (data + hashwidth > dataend) {
834 945 goto overflow;
835 946 }
836 947 prec = PyBytes_FromStringAndSize(data, hashwidth);
837 948 data += hashwidth;
838 949 if (prec == NULL) {
839 950 goto bail;
840 951 }
841 952
842 953 if (data + nsuccs * hashwidth > dataend) {
843 954 goto overflow;
844 955 }
845 956 succs = readshas(data, nsuccs, hashwidth);
846 957 if (succs == NULL) {
847 958 goto bail;
848 959 }
849 960 data += nsuccs * hashwidth;
850 961
851 962 if (nparents == 1 || nparents == 2) {
852 963 if (data + nparents * hashwidth > dataend) {
853 964 goto overflow;
854 965 }
855 966 parents = readshas(data, nparents, hashwidth);
856 967 if (parents == NULL) {
857 968 goto bail;
858 969 }
859 970 data += nparents * hashwidth;
860 971 } else {
861 972 parents = Py_None;
862 973 Py_INCREF(parents);
863 974 }
864 975
865 976 if (data + 2 * nmetadata > dataend) {
866 977 goto overflow;
867 978 }
868 979 meta = data + (2 * nmetadata);
869 980 metadata = PyTuple_New(nmetadata);
870 981 if (metadata == NULL) {
871 982 goto bail;
872 983 }
873 984 for (i = 0; i < nmetadata; i++) {
874 985 PyObject *tmp, *left = NULL, *right = NULL;
875 986 Py_ssize_t leftsize = (unsigned char)(*data++);
876 987 Py_ssize_t rightsize = (unsigned char)(*data++);
877 988 if (meta + leftsize + rightsize > dataend) {
878 989 goto overflow;
879 990 }
880 991 left = PyBytes_FromStringAndSize(meta, leftsize);
881 992 meta += leftsize;
882 993 right = PyBytes_FromStringAndSize(meta, rightsize);
883 994 meta += rightsize;
884 995 tmp = PyTuple_New(2);
885 996 if (!left || !right || !tmp) {
886 997 Py_XDECREF(left);
887 998 Py_XDECREF(right);
888 999 Py_XDECREF(tmp);
889 1000 goto bail;
890 1001 }
891 1002 PyTuple_SET_ITEM(tmp, 0, left);
892 1003 PyTuple_SET_ITEM(tmp, 1, right);
893 1004 PyTuple_SET_ITEM(metadata, i, tmp);
894 1005 }
895 1006 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
896 1007 (int)tz * 60, parents);
897 1008 goto bail; /* return successfully */
898 1009
899 1010 overflow:
900 1011 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
901 1012 bail:
902 1013 Py_XDECREF(prec);
903 1014 Py_XDECREF(succs);
904 1015 Py_XDECREF(metadata);
905 1016 Py_XDECREF(parents);
906 1017 return ret;
907 1018 }
908 1019
909 1020 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
910 1021 {
911 1022 const char *data, *dataend;
912 1023 Py_ssize_t datalen, offset, stop;
913 1024 PyObject *markers = NULL;
914 1025
915 1026 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
916 1027 &offset, &stop)) {
917 1028 return NULL;
918 1029 }
919 1030 if (offset < 0) {
920 1031 PyErr_SetString(PyExc_ValueError,
921 1032 "invalid negative offset in fm1readmarkers");
922 1033 return NULL;
923 1034 }
924 1035 if (stop > datalen) {
925 1036 PyErr_SetString(
926 1037 PyExc_ValueError,
927 1038 "stop longer than data length in fm1readmarkers");
928 1039 return NULL;
929 1040 }
930 1041 dataend = data + datalen;
931 1042 data += offset;
932 1043 markers = PyList_New(0);
933 1044 if (!markers) {
934 1045 return NULL;
935 1046 }
936 1047 while (offset < stop) {
937 1048 uint32_t msize;
938 1049 int error;
939 1050 PyObject *record = fm1readmarker(data, dataend, &msize);
940 1051 if (!record) {
941 1052 goto bail;
942 1053 }
943 1054 error = PyList_Append(markers, record);
944 1055 Py_DECREF(record);
945 1056 if (error) {
946 1057 goto bail;
947 1058 }
948 1059 data += msize;
949 1060 offset += msize;
950 1061 }
951 1062 return markers;
952 1063 bail:
953 1064 Py_DECREF(markers);
954 1065 return NULL;
955 1066 }
956 1067
957 1068 static char parsers_doc[] = "Efficient content parsing.";
958 1069
959 1070 PyObject *encodedir(PyObject *self, PyObject *args);
960 1071 PyObject *pathencode(PyObject *self, PyObject *args);
961 1072 PyObject *lowerencode(PyObject *self, PyObject *args);
962 1073 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
963 1074
964 1075 static PyMethodDef methods[] = {
965 1076 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
966 1077 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
967 1078 "create a set containing non-normal and other parent entries of given "
968 1079 "dirstate\n"},
969 1080 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
970 1081 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
971 1082 "parse a revlog index\n"},
972 1083 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
973 1084 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
974 1085 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
975 1086 {"dict_new_presized", dict_new_presized, METH_VARARGS,
976 1087 "construct a dict with an expected size\n"},
977 1088 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
978 1089 "make file foldmap\n"},
979 1090 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
980 1091 "escape a UTF-8 byte string to JSON (fast path)\n"},
981 1092 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
982 1093 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
983 1094 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
984 1095 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
985 1096 "parse v1 obsolete markers\n"},
986 1097 {NULL, NULL}};
987 1098
988 1099 void dirs_module_init(PyObject *mod);
989 1100 void manifest_module_init(PyObject *mod);
990 1101 void revlog_module_init(PyObject *mod);
991 1102
992 1103 static const int version = 20;
993 1104
994 1105 static void module_init(PyObject *mod)
995 1106 {
996 1107 PyObject *capsule = NULL;
997 1108 PyModule_AddIntConstant(mod, "version", version);
998 1109
999 1110 /* This module constant has two purposes. First, it lets us unit test
1000 1111 * the ImportError raised without hard-coding any error text. This
1001 1112 * means we can change the text in the future without breaking tests,
1002 1113 * even across changesets without a recompile. Second, its presence
1003 1114 * can be used to determine whether the version-checking logic is
1004 1115 * present, which also helps in testing across changesets without a
1005 1116 * recompile. Note that this means the pure-Python version of parsers
1006 1117 * should not have this module constant. */
1007 1118 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1008 1119
1009 1120 dirs_module_init(mod);
1010 1121 manifest_module_init(mod);
1011 1122 revlog_module_init(mod);
1012 1123
1013 1124 capsule = PyCapsule_New(
1014 1125 make_dirstate_item,
1015 1126 "mercurial.cext.parsers.make_dirstate_item_CAPI", NULL);
1016 1127 if (capsule != NULL)
1017 1128 PyModule_AddObject(mod, "make_dirstate_item_CAPI", capsule);
1018 1129
1019 1130 if (PyType_Ready(&dirstateItemType) < 0) {
1020 1131 return;
1021 1132 }
1022 1133 Py_INCREF(&dirstateItemType);
1023 1134 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1024 1135 }
1025 1136
1026 1137 static int check_python_version(void)
1027 1138 {
1028 1139 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1029 1140 long hexversion;
1030 1141 if (!sys) {
1031 1142 return -1;
1032 1143 }
1033 1144 ver = PyObject_GetAttrString(sys, "hexversion");
1034 1145 Py_DECREF(sys);
1035 1146 if (!ver) {
1036 1147 return -1;
1037 1148 }
1038 1149 hexversion = PyInt_AsLong(ver);
1039 1150 Py_DECREF(ver);
1040 1151 /* sys.hexversion is a 32-bit number by default, so the -1 case
1041 1152 * should only occur in unusual circumstances (e.g. if sys.hexversion
1042 1153 * is manually set to an invalid value). */
1043 1154 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1044 1155 PyErr_Format(PyExc_ImportError,
1045 1156 "%s: The Mercurial extension "
1046 1157 "modules were compiled with Python " PY_VERSION
1047 1158 ", but "
1048 1159 "Mercurial is currently using Python with "
1049 1160 "sys.hexversion=%ld: "
1050 1161 "Python %s\n at: %s",
1051 1162 versionerrortext, hexversion, Py_GetVersion(),
1052 1163 Py_GetProgramFullPath());
1053 1164 return -1;
1054 1165 }
1055 1166 return 0;
1056 1167 }
1057 1168
1058 1169 #ifdef IS_PY3K
1059 1170 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1060 1171 parsers_doc, -1, methods};
1061 1172
1062 1173 PyMODINIT_FUNC PyInit_parsers(void)
1063 1174 {
1064 1175 PyObject *mod;
1065 1176
1066 1177 if (check_python_version() == -1)
1067 1178 return NULL;
1068 1179 mod = PyModule_Create(&parsers_module);
1069 1180 module_init(mod);
1070 1181 return mod;
1071 1182 }
1072 1183 #else
1073 1184 PyMODINIT_FUNC initparsers(void)
1074 1185 {
1075 1186 PyObject *mod;
1076 1187
1077 1188 if (check_python_version() == -1) {
1078 1189 return;
1079 1190 }
1080 1191 mod = Py_InitModule3("parsers", methods, parsers_doc);
1081 1192 module_init(mod);
1082 1193 }
1083 1194 #endif
@@ -1,930 +1,916 b''
1 1 # dirstatemap.py
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 from __future__ import absolute_import
7 7
8 8 import errno
9 9
10 10 from .i18n import _
11 11
12 12 from . import (
13 13 error,
14 14 pathutil,
15 15 policy,
16 16 pycompat,
17 17 txnutil,
18 18 util,
19 19 )
20 20
21 21 from .dirstateutils import (
22 22 docket as docketmod,
23 23 )
24 24
25 25 parsers = policy.importmod('parsers')
26 26 rustmod = policy.importrust('dirstate')
27 27
28 28 propertycache = util.propertycache
29 29
30 30 DirstateItem = parsers.DirstateItem
31 31
32 32
33 33 # a special value used internally for `size` if the file come from the other parent
34 34 FROM_P2 = -2
35 35
36 36 # a special value used internally for `size` if the file is modified/merged/added
37 37 NONNORMAL = -1
38 38
39 39 # a special value used internally for `time` if the time is ambigeous
40 40 AMBIGUOUS_TIME = -1
41 41
42 42 rangemask = 0x7FFFFFFF
43 43
44 44
45 45 class dirstatemap(object):
46 46 """Map encapsulating the dirstate's contents.
47 47
48 48 The dirstate contains the following state:
49 49
50 50 - `identity` is the identity of the dirstate file, which can be used to
51 51 detect when changes have occurred to the dirstate file.
52 52
53 53 - `parents` is a pair containing the parents of the working copy. The
54 54 parents are updated by calling `setparents`.
55 55
56 56 - the state map maps filenames to tuples of (state, mode, size, mtime),
57 57 where state is a single character representing 'normal', 'added',
58 58 'removed', or 'merged'. It is read by treating the dirstate as a
59 59 dict. File state is updated by calling the `addfile`, `removefile` and
60 60 `dropfile` methods.
61 61
62 62 - `copymap` maps destination filenames to their source filename.
63 63
64 64 The dirstate also provides the following views onto the state:
65 65
66 66 - `nonnormalset` is a set of the filenames that have state other
67 67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
68 68
69 69 - `otherparentset` is a set of the filenames that are marked as coming
70 70 from the second parent when the dirstate is currently being merged.
71 71
72 72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
73 73 form that they appear as in the dirstate.
74 74
75 75 - `dirfoldmap` is a dict mapping normalized directory names to the
76 76 denormalized form that they appear as in the dirstate.
77 77 """
78 78
79 79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
80 80 self._ui = ui
81 81 self._opener = opener
82 82 self._root = root
83 83 self._filename = b'dirstate'
84 84 self._nodelen = 20
85 85 self._nodeconstants = nodeconstants
86 86 assert (
87 87 not use_dirstate_v2
88 88 ), "should have detected unsupported requirement"
89 89
90 90 self._parents = None
91 91 self._dirtyparents = False
92 92
93 93 # for consistent view between _pl() and _read() invocations
94 94 self._pendingmode = None
95 95
96 96 @propertycache
97 97 def _map(self):
98 98 self._map = {}
99 99 self.read()
100 100 return self._map
101 101
102 102 @propertycache
103 103 def copymap(self):
104 104 self.copymap = {}
105 105 self._map
106 106 return self.copymap
107 107
108 108 def clear(self):
109 109 self._map.clear()
110 110 self.copymap.clear()
111 111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
112 112 util.clearcachedproperty(self, b"_dirs")
113 113 util.clearcachedproperty(self, b"_alldirs")
114 114 util.clearcachedproperty(self, b"filefoldmap")
115 115 util.clearcachedproperty(self, b"dirfoldmap")
116 116 util.clearcachedproperty(self, b"nonnormalset")
117 117 util.clearcachedproperty(self, b"otherparentset")
118 118
119 119 def items(self):
120 120 return pycompat.iteritems(self._map)
121 121
122 122 # forward for python2,3 compat
123 123 iteritems = items
124 124
125 125 debug_iter = items
126 126
127 127 def __len__(self):
128 128 return len(self._map)
129 129
130 130 def __iter__(self):
131 131 return iter(self._map)
132 132
133 133 def get(self, key, default=None):
134 134 return self._map.get(key, default)
135 135
136 136 def __contains__(self, key):
137 137 return key in self._map
138 138
139 139 def __getitem__(self, key):
140 140 return self._map[key]
141 141
142 142 def keys(self):
143 143 return self._map.keys()
144 144
145 145 def preload(self):
146 146 """Loads the underlying data, if it's not already loaded"""
147 147 self._map
148 148
149 149 def _dirs_incr(self, filename, old_entry=None):
150 150 """incremente the dirstate counter if applicable"""
151 151 if (
152 152 old_entry is None or old_entry.removed
153 153 ) and "_dirs" in self.__dict__:
154 154 self._dirs.addpath(filename)
155 155 if old_entry is None and "_alldirs" in self.__dict__:
156 156 self._alldirs.addpath(filename)
157 157
158 158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
159 159 """decremente the dirstate counter if applicable"""
160 160 if old_entry is not None:
161 161 if "_dirs" in self.__dict__ and not old_entry.removed:
162 162 self._dirs.delpath(filename)
163 163 if "_alldirs" in self.__dict__ and not remove_variant:
164 164 self._alldirs.delpath(filename)
165 165 elif remove_variant and "_alldirs" in self.__dict__:
166 166 self._alldirs.addpath(filename)
167 167 if "filefoldmap" in self.__dict__:
168 168 normed = util.normcase(filename)
169 169 self.filefoldmap.pop(normed, None)
170 170
171 171 def set_possibly_dirty(self, filename):
172 172 """record that the current state of the file on disk is unknown"""
173 173 self[filename].set_possibly_dirty()
174 174
175 175 def addfile(
176 176 self,
177 177 f,
178 178 mode=0,
179 179 size=None,
180 180 mtime=None,
181 181 added=False,
182 182 merged=False,
183 183 from_p2=False,
184 184 possibly_dirty=False,
185 185 ):
186 186 """Add a tracked file to the dirstate."""
187 187 if added:
188 188 assert not merged
189 189 assert not possibly_dirty
190 190 assert not from_p2
191 state = b'a'
192 size = NONNORMAL
193 mtime = AMBIGUOUS_TIME
191 new_entry = DirstateItem.new_added()
194 192 elif merged:
195 193 assert not possibly_dirty
196 194 assert not from_p2
197 state = b'm'
198 size = FROM_P2
199 mtime = AMBIGUOUS_TIME
195 new_entry = DirstateItem.new_merged()
200 196 elif from_p2:
201 197 assert not possibly_dirty
202 state = b'n'
203 size = FROM_P2
204 mtime = AMBIGUOUS_TIME
198 new_entry = DirstateItem.new_from_p2()
205 199 elif possibly_dirty:
206 state = b'n'
207 size = NONNORMAL
208 mtime = AMBIGUOUS_TIME
200 new_entry = DirstateItem.new_possibly_dirty()
209 201 else:
210 assert size != FROM_P2
211 assert size != NONNORMAL
212 202 assert size is not None
213 203 assert mtime is not None
214
215 state = b'n'
216 204 size = size & rangemask
217 205 mtime = mtime & rangemask
218 assert state is not None
219 assert size is not None
220 assert mtime is not None
206 new_entry = DirstateItem.new_normal(mode, size, mtime)
221 207 old_entry = self.get(f)
222 208 self._dirs_incr(f, old_entry)
223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
224 if e.dm_nonnormal:
209 self._map[f] = new_entry
210 if new_entry.dm_nonnormal:
225 211 self.nonnormalset.add(f)
226 if e.dm_otherparent:
212 if new_entry.dm_otherparent:
227 213 self.otherparentset.add(f)
228 214
229 215 def reset_state(
230 216 self,
231 217 filename,
232 218 wc_tracked,
233 219 p1_tracked,
234 220 p2_tracked=False,
235 221 merged=False,
236 222 clean_p1=False,
237 223 clean_p2=False,
238 224 possibly_dirty=False,
239 225 parentfiledata=None,
240 226 ):
241 227 """Set a entry to a given state, diregarding all previous state
242 228
243 229 This is to be used by the part of the dirstate API dedicated to
244 230 adjusting the dirstate after a update/merge.
245 231
246 232 note: calling this might result to no entry existing at all if the
247 233 dirstate map does not see any point at having one for this file
248 234 anymore.
249 235 """
250 236 if merged and (clean_p1 or clean_p2):
251 237 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
252 238 raise error.ProgrammingError(msg)
253 239 # copy information are now outdated
254 240 # (maybe new information should be in directly passed to this function)
255 241 self.copymap.pop(filename, None)
256 242
257 243 if not (p1_tracked or p2_tracked or wc_tracked):
258 244 self.dropfile(filename)
259 245 return
260 246 elif merged:
261 247 # XXX might be merged and removed ?
262 248 entry = self.get(filename)
263 249 if entry is None or not entry.tracked:
264 250 # XXX mostly replicate dirstate.other parent. We should get
265 251 # the higher layer to pass us more reliable data where `merged`
266 252 # actually mean merged. Dropping this clause will show failure
267 253 # in `test-graft.t`
268 254 merged = False
269 255 clean_p2 = True
270 256 elif not (p1_tracked or p2_tracked) and wc_tracked:
271 257 pass # file is added, nothing special to adjust
272 258 elif (p1_tracked or p2_tracked) and not wc_tracked:
273 259 pass
274 260 elif clean_p2 and wc_tracked:
275 261 if p1_tracked or self.get(filename) is not None:
276 262 # XXX the `self.get` call is catching some case in
277 263 # `test-merge-remove.t` where the file is tracked in p1, the
278 264 # p1_tracked argument is False.
279 265 #
280 266 # In addition, this seems to be a case where the file is marked
281 267 # as merged without actually being the result of a merge
282 268 # action. So thing are not ideal here.
283 269 merged = True
284 270 clean_p2 = False
285 271 elif not p1_tracked and p2_tracked and wc_tracked:
286 272 clean_p2 = True
287 273 elif possibly_dirty:
288 274 pass
289 275 elif wc_tracked:
290 276 # this is a "normal" file
291 277 if parentfiledata is None:
292 278 msg = b'failed to pass parentfiledata for a normal file: %s'
293 279 msg %= filename
294 280 raise error.ProgrammingError(msg)
295 281 else:
296 282 assert False, 'unreachable'
297 283
298 284 old_entry = self._map.get(filename)
299 285 self._dirs_incr(filename, old_entry)
300 286 entry = DirstateItem(
301 287 wc_tracked=wc_tracked,
302 288 p1_tracked=p1_tracked,
303 289 p2_tracked=p2_tracked,
304 290 merged=merged,
305 291 clean_p1=clean_p1,
306 292 clean_p2=clean_p2,
307 293 possibly_dirty=possibly_dirty,
308 294 parentfiledata=parentfiledata,
309 295 )
310 296 if entry.dm_nonnormal:
311 297 self.nonnormalset.add(filename)
312 298 else:
313 299 self.nonnormalset.discard(filename)
314 300 if entry.dm_otherparent:
315 301 self.otherparentset.add(filename)
316 302 else:
317 303 self.otherparentset.discard(filename)
318 304 self._map[filename] = entry
319 305
320 306 def set_untracked(self, f):
321 307 """Mark a file as no longer tracked in the dirstate map"""
322 308 entry = self[f]
323 309 self._dirs_decr(f, old_entry=entry, remove_variant=True)
324 310 if entry.from_p2:
325 311 self.otherparentset.add(f)
326 312 elif not entry.merged:
327 313 self.copymap.pop(f, None)
328 314 entry.set_untracked()
329 315 self.nonnormalset.add(f)
330 316
331 317 def dropfile(self, f):
332 318 """
333 319 Remove a file from the dirstate. Returns True if the file was
334 320 previously recorded.
335 321 """
336 322 old_entry = self._map.pop(f, None)
337 323 self._dirs_decr(f, old_entry=old_entry)
338 324 self.nonnormalset.discard(f)
339 325 return old_entry is not None
340 326
341 327 def clearambiguoustimes(self, files, now):
342 328 for f in files:
343 329 e = self.get(f)
344 330 if e is not None and e.need_delay(now):
345 331 e.set_possibly_dirty()
346 332 self.nonnormalset.add(f)
347 333
348 334 def nonnormalentries(self):
349 335 '''Compute the nonnormal dirstate entries from the dmap'''
350 336 try:
351 337 return parsers.nonnormalotherparententries(self._map)
352 338 except AttributeError:
353 339 nonnorm = set()
354 340 otherparent = set()
355 341 for fname, e in pycompat.iteritems(self._map):
356 342 if e.dm_nonnormal:
357 343 nonnorm.add(fname)
358 344 if e.from_p2:
359 345 otherparent.add(fname)
360 346 return nonnorm, otherparent
361 347
362 348 @propertycache
363 349 def filefoldmap(self):
364 350 """Returns a dictionary mapping normalized case paths to their
365 351 non-normalized versions.
366 352 """
367 353 try:
368 354 makefilefoldmap = parsers.make_file_foldmap
369 355 except AttributeError:
370 356 pass
371 357 else:
372 358 return makefilefoldmap(
373 359 self._map, util.normcasespec, util.normcasefallback
374 360 )
375 361
376 362 f = {}
377 363 normcase = util.normcase
378 364 for name, s in pycompat.iteritems(self._map):
379 365 if not s.removed:
380 366 f[normcase(name)] = name
381 367 f[b'.'] = b'.' # prevents useless util.fspath() invocation
382 368 return f
383 369
384 370 def hastrackeddir(self, d):
385 371 """
386 372 Returns True if the dirstate contains a tracked (not removed) file
387 373 in this directory.
388 374 """
389 375 return d in self._dirs
390 376
391 377 def hasdir(self, d):
392 378 """
393 379 Returns True if the dirstate contains a file (tracked or removed)
394 380 in this directory.
395 381 """
396 382 return d in self._alldirs
397 383
398 384 @propertycache
399 385 def _dirs(self):
400 386 return pathutil.dirs(self._map, b'r')
401 387
402 388 @propertycache
403 389 def _alldirs(self):
404 390 return pathutil.dirs(self._map)
405 391
406 392 def _opendirstatefile(self):
407 393 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
408 394 if self._pendingmode is not None and self._pendingmode != mode:
409 395 fp.close()
410 396 raise error.Abort(
411 397 _(b'working directory state may be changed parallelly')
412 398 )
413 399 self._pendingmode = mode
414 400 return fp
415 401
416 402 def parents(self):
417 403 if not self._parents:
418 404 try:
419 405 fp = self._opendirstatefile()
420 406 st = fp.read(2 * self._nodelen)
421 407 fp.close()
422 408 except IOError as err:
423 409 if err.errno != errno.ENOENT:
424 410 raise
425 411 # File doesn't exist, so the current state is empty
426 412 st = b''
427 413
428 414 l = len(st)
429 415 if l == self._nodelen * 2:
430 416 self._parents = (
431 417 st[: self._nodelen],
432 418 st[self._nodelen : 2 * self._nodelen],
433 419 )
434 420 elif l == 0:
435 421 self._parents = (
436 422 self._nodeconstants.nullid,
437 423 self._nodeconstants.nullid,
438 424 )
439 425 else:
440 426 raise error.Abort(
441 427 _(b'working directory state appears damaged!')
442 428 )
443 429
444 430 return self._parents
445 431
446 432 def setparents(self, p1, p2):
447 433 self._parents = (p1, p2)
448 434 self._dirtyparents = True
449 435
450 436 def read(self):
451 437 # ignore HG_PENDING because identity is used only for writing
452 438 self.identity = util.filestat.frompath(
453 439 self._opener.join(self._filename)
454 440 )
455 441
456 442 try:
457 443 fp = self._opendirstatefile()
458 444 try:
459 445 st = fp.read()
460 446 finally:
461 447 fp.close()
462 448 except IOError as err:
463 449 if err.errno != errno.ENOENT:
464 450 raise
465 451 return
466 452 if not st:
467 453 return
468 454
469 455 if util.safehasattr(parsers, b'dict_new_presized'):
470 456 # Make an estimate of the number of files in the dirstate based on
471 457 # its size. This trades wasting some memory for avoiding costly
472 458 # resizes. Each entry have a prefix of 17 bytes followed by one or
473 459 # two path names. Studies on various large-scale real-world repositories
474 460 # found 54 bytes a reasonable upper limit for the average path names.
475 461 # Copy entries are ignored for the sake of this estimate.
476 462 self._map = parsers.dict_new_presized(len(st) // 71)
477 463
478 464 # Python's garbage collector triggers a GC each time a certain number
479 465 # of container objects (the number being defined by
480 466 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
481 467 # for each file in the dirstate. The C version then immediately marks
482 468 # them as not to be tracked by the collector. However, this has no
483 469 # effect on when GCs are triggered, only on what objects the GC looks
484 470 # into. This means that O(number of files) GCs are unavoidable.
485 471 # Depending on when in the process's lifetime the dirstate is parsed,
486 472 # this can get very expensive. As a workaround, disable GC while
487 473 # parsing the dirstate.
488 474 #
489 475 # (we cannot decorate the function directly since it is in a C module)
490 476 parse_dirstate = util.nogc(parsers.parse_dirstate)
491 477 p = parse_dirstate(self._map, self.copymap, st)
492 478 if not self._dirtyparents:
493 479 self.setparents(*p)
494 480
495 481 # Avoid excess attribute lookups by fast pathing certain checks
496 482 self.__contains__ = self._map.__contains__
497 483 self.__getitem__ = self._map.__getitem__
498 484 self.get = self._map.get
499 485
500 486 def write(self, _tr, st, now):
501 487 st.write(
502 488 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
503 489 )
504 490 st.close()
505 491 self._dirtyparents = False
506 492 self.nonnormalset, self.otherparentset = self.nonnormalentries()
507 493
508 494 @propertycache
509 495 def nonnormalset(self):
510 496 nonnorm, otherparents = self.nonnormalentries()
511 497 self.otherparentset = otherparents
512 498 return nonnorm
513 499
514 500 @propertycache
515 501 def otherparentset(self):
516 502 nonnorm, otherparents = self.nonnormalentries()
517 503 self.nonnormalset = nonnorm
518 504 return otherparents
519 505
520 506 def non_normal_or_other_parent_paths(self):
521 507 return self.nonnormalset.union(self.otherparentset)
522 508
523 509 @propertycache
524 510 def identity(self):
525 511 self._map
526 512 return self.identity
527 513
528 514 @propertycache
529 515 def dirfoldmap(self):
530 516 f = {}
531 517 normcase = util.normcase
532 518 for name in self._dirs:
533 519 f[normcase(name)] = name
534 520 return f
535 521
536 522
537 523 if rustmod is not None:
538 524
539 525 class dirstatemap(object):
540 526 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
541 527 self._use_dirstate_v2 = use_dirstate_v2
542 528 self._nodeconstants = nodeconstants
543 529 self._ui = ui
544 530 self._opener = opener
545 531 self._root = root
546 532 self._filename = b'dirstate'
547 533 self._nodelen = 20 # Also update Rust code when changing this!
548 534 self._parents = None
549 535 self._dirtyparents = False
550 536 self._docket = None
551 537
552 538 # for consistent view between _pl() and _read() invocations
553 539 self._pendingmode = None
554 540
555 541 self._use_dirstate_tree = self._ui.configbool(
556 542 b"experimental",
557 543 b"dirstate-tree.in-memory",
558 544 False,
559 545 )
560 546
561 547 def addfile(
562 548 self,
563 549 f,
564 550 mode=0,
565 551 size=None,
566 552 mtime=None,
567 553 added=False,
568 554 merged=False,
569 555 from_p2=False,
570 556 possibly_dirty=False,
571 557 ):
572 558 return self._rustmap.addfile(
573 559 f,
574 560 mode,
575 561 size,
576 562 mtime,
577 563 added,
578 564 merged,
579 565 from_p2,
580 566 possibly_dirty,
581 567 )
582 568
583 569 def reset_state(
584 570 self,
585 571 filename,
586 572 wc_tracked,
587 573 p1_tracked,
588 574 p2_tracked=False,
589 575 merged=False,
590 576 clean_p1=False,
591 577 clean_p2=False,
592 578 possibly_dirty=False,
593 579 parentfiledata=None,
594 580 ):
595 581 """Set a entry to a given state, disregarding all previous state
596 582
597 583 This is to be used by the part of the dirstate API dedicated to
598 584 adjusting the dirstate after a update/merge.
599 585
600 586 note: calling this might result to no entry existing at all if the
601 587 dirstate map does not see any point at having one for this file
602 588 anymore.
603 589 """
604 590 if merged and (clean_p1 or clean_p2):
605 591 msg = (
606 592 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
607 593 )
608 594 raise error.ProgrammingError(msg)
609 595 # copy information are now outdated
610 596 # (maybe new information should be in directly passed to this function)
611 597 self.copymap.pop(filename, None)
612 598
613 599 if not (p1_tracked or p2_tracked or wc_tracked):
614 600 self.dropfile(filename)
615 601 elif merged:
616 602 # XXX might be merged and removed ?
617 603 entry = self.get(filename)
618 604 if entry is not None and entry.tracked:
619 605 # XXX mostly replicate dirstate.other parent. We should get
620 606 # the higher layer to pass us more reliable data where `merged`
621 607 # actually mean merged. Dropping the else clause will show
622 608 # failure in `test-graft.t`
623 609 self.addfile(filename, merged=True)
624 610 else:
625 611 self.addfile(filename, from_p2=True)
626 612 elif not (p1_tracked or p2_tracked) and wc_tracked:
627 613 self.addfile(
628 614 filename, added=True, possibly_dirty=possibly_dirty
629 615 )
630 616 elif (p1_tracked or p2_tracked) and not wc_tracked:
631 617 # XXX might be merged and removed ?
632 618 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
633 619 self.nonnormalset.add(filename)
634 620 elif clean_p2 and wc_tracked:
635 621 if p1_tracked or self.get(filename) is not None:
636 622 # XXX the `self.get` call is catching some case in
637 623 # `test-merge-remove.t` where the file is tracked in p1, the
638 624 # p1_tracked argument is False.
639 625 #
640 626 # In addition, this seems to be a case where the file is marked
641 627 # as merged without actually being the result of a merge
642 628 # action. So thing are not ideal here.
643 629 self.addfile(filename, merged=True)
644 630 else:
645 631 self.addfile(filename, from_p2=True)
646 632 elif not p1_tracked and p2_tracked and wc_tracked:
647 633 self.addfile(
648 634 filename, from_p2=True, possibly_dirty=possibly_dirty
649 635 )
650 636 elif possibly_dirty:
651 637 self.addfile(filename, possibly_dirty=possibly_dirty)
652 638 elif wc_tracked:
653 639 # this is a "normal" file
654 640 if parentfiledata is None:
655 641 msg = b'failed to pass parentfiledata for a normal file: %s'
656 642 msg %= filename
657 643 raise error.ProgrammingError(msg)
658 644 mode, size, mtime = parentfiledata
659 645 self.addfile(filename, mode=mode, size=size, mtime=mtime)
660 646 self.nonnormalset.discard(filename)
661 647 else:
662 648 assert False, 'unreachable'
663 649
664 650 def set_untracked(self, f):
665 651 """Mark a file as no longer tracked in the dirstate map"""
666 652 # in merge is only trigger more logic, so it "fine" to pass it.
667 653 #
668 654 # the inner rust dirstate map code need to be adjusted once the API
669 655 # for dirstate/dirstatemap/DirstateItem is a bit more settled
670 656 self._rustmap.removefile(f, in_merge=True)
671 657
672 658 def removefile(self, *args, **kwargs):
673 659 return self._rustmap.removefile(*args, **kwargs)
674 660
675 661 def dropfile(self, *args, **kwargs):
676 662 return self._rustmap.dropfile(*args, **kwargs)
677 663
678 664 def clearambiguoustimes(self, *args, **kwargs):
679 665 return self._rustmap.clearambiguoustimes(*args, **kwargs)
680 666
681 667 def nonnormalentries(self):
682 668 return self._rustmap.nonnormalentries()
683 669
684 670 def get(self, *args, **kwargs):
685 671 return self._rustmap.get(*args, **kwargs)
686 672
687 673 @property
688 674 def copymap(self):
689 675 return self._rustmap.copymap()
690 676
691 677 def directories(self):
692 678 return self._rustmap.directories()
693 679
694 680 def debug_iter(self):
695 681 return self._rustmap.debug_iter()
696 682
697 683 def preload(self):
698 684 self._rustmap
699 685
700 686 def clear(self):
701 687 self._rustmap.clear()
702 688 self.setparents(
703 689 self._nodeconstants.nullid, self._nodeconstants.nullid
704 690 )
705 691 util.clearcachedproperty(self, b"_dirs")
706 692 util.clearcachedproperty(self, b"_alldirs")
707 693 util.clearcachedproperty(self, b"dirfoldmap")
708 694
709 695 def items(self):
710 696 return self._rustmap.items()
711 697
712 698 def keys(self):
713 699 return iter(self._rustmap)
714 700
715 701 def __contains__(self, key):
716 702 return key in self._rustmap
717 703
718 704 def __getitem__(self, item):
719 705 return self._rustmap[item]
720 706
721 707 def __len__(self):
722 708 return len(self._rustmap)
723 709
724 710 def __iter__(self):
725 711 return iter(self._rustmap)
726 712
727 713 # forward for python2,3 compat
728 714 iteritems = items
729 715
730 716 def _opendirstatefile(self):
731 717 fp, mode = txnutil.trypending(
732 718 self._root, self._opener, self._filename
733 719 )
734 720 if self._pendingmode is not None and self._pendingmode != mode:
735 721 fp.close()
736 722 raise error.Abort(
737 723 _(b'working directory state may be changed parallelly')
738 724 )
739 725 self._pendingmode = mode
740 726 return fp
741 727
742 728 def _readdirstatefile(self, size=-1):
743 729 try:
744 730 with self._opendirstatefile() as fp:
745 731 return fp.read(size)
746 732 except IOError as err:
747 733 if err.errno != errno.ENOENT:
748 734 raise
749 735 # File doesn't exist, so the current state is empty
750 736 return b''
751 737
752 738 def setparents(self, p1, p2):
753 739 self._parents = (p1, p2)
754 740 self._dirtyparents = True
755 741
756 742 def parents(self):
757 743 if not self._parents:
758 744 if self._use_dirstate_v2:
759 745 self._parents = self.docket.parents
760 746 else:
761 747 read_len = self._nodelen * 2
762 748 st = self._readdirstatefile(read_len)
763 749 l = len(st)
764 750 if l == read_len:
765 751 self._parents = (
766 752 st[: self._nodelen],
767 753 st[self._nodelen : 2 * self._nodelen],
768 754 )
769 755 elif l == 0:
770 756 self._parents = (
771 757 self._nodeconstants.nullid,
772 758 self._nodeconstants.nullid,
773 759 )
774 760 else:
775 761 raise error.Abort(
776 762 _(b'working directory state appears damaged!')
777 763 )
778 764
779 765 return self._parents
780 766
781 767 @property
782 768 def docket(self):
783 769 if not self._docket:
784 770 if not self._use_dirstate_v2:
785 771 raise error.ProgrammingError(
786 772 b'dirstate only has a docket in v2 format'
787 773 )
788 774 self._docket = docketmod.DirstateDocket.parse(
789 775 self._readdirstatefile(), self._nodeconstants
790 776 )
791 777 return self._docket
792 778
793 779 @propertycache
794 780 def _rustmap(self):
795 781 """
796 782 Fills the Dirstatemap when called.
797 783 """
798 784 # ignore HG_PENDING because identity is used only for writing
799 785 self.identity = util.filestat.frompath(
800 786 self._opener.join(self._filename)
801 787 )
802 788
803 789 if self._use_dirstate_v2:
804 790 if self.docket.uuid:
805 791 # TODO: use mmap when possible
806 792 data = self._opener.read(self.docket.data_filename())
807 793 else:
808 794 data = b''
809 795 self._rustmap = rustmod.DirstateMap.new_v2(
810 796 data, self.docket.data_size, self.docket.tree_metadata
811 797 )
812 798 parents = self.docket.parents
813 799 else:
814 800 self._rustmap, parents = rustmod.DirstateMap.new_v1(
815 801 self._use_dirstate_tree, self._readdirstatefile()
816 802 )
817 803
818 804 if parents and not self._dirtyparents:
819 805 self.setparents(*parents)
820 806
821 807 self.__contains__ = self._rustmap.__contains__
822 808 self.__getitem__ = self._rustmap.__getitem__
823 809 self.get = self._rustmap.get
824 810 return self._rustmap
825 811
826 812 def write(self, tr, st, now):
827 813 if not self._use_dirstate_v2:
828 814 p1, p2 = self.parents()
829 815 packed = self._rustmap.write_v1(p1, p2, now)
830 816 st.write(packed)
831 817 st.close()
832 818 self._dirtyparents = False
833 819 return
834 820
835 821 # We can only append to an existing data file if there is one
836 822 can_append = self.docket.uuid is not None
837 823 packed, meta, append = self._rustmap.write_v2(now, can_append)
838 824 if append:
839 825 docket = self.docket
840 826 data_filename = docket.data_filename()
841 827 if tr:
842 828 tr.add(data_filename, docket.data_size)
843 829 with self._opener(data_filename, b'r+b') as fp:
844 830 fp.seek(docket.data_size)
845 831 assert fp.tell() == docket.data_size
846 832 written = fp.write(packed)
847 833 if written is not None: # py2 may return None
848 834 assert written == len(packed), (written, len(packed))
849 835 docket.data_size += len(packed)
850 836 docket.parents = self.parents()
851 837 docket.tree_metadata = meta
852 838 st.write(docket.serialize())
853 839 st.close()
854 840 else:
855 841 old_docket = self.docket
856 842 new_docket = docketmod.DirstateDocket.with_new_uuid(
857 843 self.parents(), len(packed), meta
858 844 )
859 845 data_filename = new_docket.data_filename()
860 846 if tr:
861 847 tr.add(data_filename, 0)
862 848 self._opener.write(data_filename, packed)
863 849 # Write the new docket after the new data file has been
864 850 # written. Because `st` was opened with `atomictemp=True`,
865 851 # the actual `.hg/dirstate` file is only affected on close.
866 852 st.write(new_docket.serialize())
867 853 st.close()
868 854 # Remove the old data file after the new docket pointing to
869 855 # the new data file was written.
870 856 if old_docket.uuid:
871 857 data_filename = old_docket.data_filename()
872 858 unlink = lambda _tr=None: self._opener.unlink(data_filename)
873 859 if tr:
874 860 category = b"dirstate-v2-clean-" + old_docket.uuid
875 861 tr.addpostclose(category, unlink)
876 862 else:
877 863 unlink()
878 864 self._docket = new_docket
879 865 # Reload from the newly-written file
880 866 util.clearcachedproperty(self, b"_rustmap")
881 867 self._dirtyparents = False
882 868
883 869 @propertycache
884 870 def filefoldmap(self):
885 871 """Returns a dictionary mapping normalized case paths to their
886 872 non-normalized versions.
887 873 """
888 874 return self._rustmap.filefoldmapasdict()
889 875
890 876 def hastrackeddir(self, d):
891 877 return self._rustmap.hastrackeddir(d)
892 878
893 879 def hasdir(self, d):
894 880 return self._rustmap.hasdir(d)
895 881
896 882 @propertycache
897 883 def identity(self):
898 884 self._rustmap
899 885 return self.identity
900 886
901 887 @property
902 888 def nonnormalset(self):
903 889 nonnorm = self._rustmap.non_normal_entries()
904 890 return nonnorm
905 891
906 892 @propertycache
907 893 def otherparentset(self):
908 894 otherparents = self._rustmap.other_parent_entries()
909 895 return otherparents
910 896
911 897 def non_normal_or_other_parent_paths(self):
912 898 return self._rustmap.non_normal_or_other_parent_paths()
913 899
914 900 @propertycache
915 901 def dirfoldmap(self):
916 902 f = {}
917 903 normcase = util.normcase
918 904 for name in self._rustmap.tracked_dirs():
919 905 f[normcase(name)] = name
920 906 return f
921 907
922 908 def set_possibly_dirty(self, filename):
923 909 """record that the current state of the file on disk is unknown"""
924 910 entry = self[filename]
925 911 entry.set_possibly_dirty()
926 912 self._rustmap.set_v1(filename, entry)
927 913
928 914 def __setitem__(self, key, value):
929 915 assert isinstance(value, DirstateItem)
930 916 self._rustmap.set_v1(key, value)
@@ -1,688 +1,755 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 ..thirdparty import attr
18 18 from .. import (
19 19 error,
20 20 pycompat,
21 21 revlogutils,
22 22 util,
23 23 )
24 24
25 25 from ..revlogutils import nodemap as nodemaputil
26 26 from ..revlogutils import constants as revlog_constants
27 27
28 28 stringio = pycompat.bytesio
29 29
30 30
31 31 _pack = struct.pack
32 32 _unpack = struct.unpack
33 33 _compress = zlib.compress
34 34 _decompress = zlib.decompress
35 35
36 36
37 37 # a special value used internally for `size` if the file come from the other parent
38 38 FROM_P2 = -2
39 39
40 40 # a special value used internally for `size` if the file is modified/merged/added
41 41 NONNORMAL = -1
42 42
43 43 # a special value used internally for `time` if the time is ambigeous
44 44 AMBIGUOUS_TIME = -1
45 45
46 46
47 47 @attr.s(slots=True, init=False)
48 48 class DirstateItem(object):
49 49 """represent a dirstate entry
50 50
51 51 It contains:
52 52
53 53 - state (one of 'n', 'a', 'r', 'm')
54 54 - mode,
55 55 - size,
56 56 - mtime,
57 57 """
58 58
59 59 _state = attr.ib()
60 60 _mode = attr.ib()
61 61 _size = attr.ib()
62 62 _mtime = attr.ib()
63 63
64 64 def __init__(
65 65 self,
66 66 wc_tracked=False,
67 67 p1_tracked=False,
68 68 p2_tracked=False,
69 69 merged=False,
70 70 clean_p1=False,
71 71 clean_p2=False,
72 72 possibly_dirty=False,
73 73 parentfiledata=None,
74 74 ):
75 75 if merged and (clean_p1 or clean_p2):
76 76 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
77 77 raise error.ProgrammingError(msg)
78 78
79 79 self._state = None
80 80 self._mode = 0
81 81 self._size = NONNORMAL
82 82 self._mtime = AMBIGUOUS_TIME
83 83 if not (p1_tracked or p2_tracked or wc_tracked):
84 84 pass # the object has no state to record
85 85 elif merged:
86 86 self._state = b'm'
87 87 self._size = FROM_P2
88 88 self._mtime = AMBIGUOUS_TIME
89 89 elif not (p1_tracked or p2_tracked) and wc_tracked:
90 90 self._state = b'a'
91 91 self._size = NONNORMAL
92 92 self._mtime = AMBIGUOUS_TIME
93 93 elif (p1_tracked or p2_tracked) and not wc_tracked:
94 94 self._state = b'r'
95 95 self._size = 0
96 96 self._mtime = 0
97 97 elif clean_p2 and wc_tracked:
98 98 self._state = b'n'
99 99 self._size = FROM_P2
100 100 self._mtime = AMBIGUOUS_TIME
101 101 elif not p1_tracked and p2_tracked and wc_tracked:
102 102 self._state = b'n'
103 103 self._size = FROM_P2
104 104 self._mtime = AMBIGUOUS_TIME
105 105 elif possibly_dirty:
106 106 self._state = b'n'
107 107 self._size = NONNORMAL
108 108 self._mtime = AMBIGUOUS_TIME
109 109 elif wc_tracked:
110 110 # this is a "normal" file
111 111 if parentfiledata is None:
112 112 msg = b'failed to pass parentfiledata for a normal file'
113 113 raise error.ProgrammingError(msg)
114 114 self._state = b'n'
115 115 self._mode = parentfiledata[0]
116 116 self._size = parentfiledata[1]
117 117 self._mtime = parentfiledata[2]
118 118 else:
119 119 assert False, 'unreachable'
120 120
121 121 @classmethod
122 def new_added(cls):
123 """constructor to help legacy API to build a new "added" item
124
125 Should eventually be removed
126 """
127 instance = cls()
128 instance._state = b'a'
129 instance._mode = 0
130 instance._size = NONNORMAL
131 instance._mtime = AMBIGUOUS_TIME
132 return instance
133
134 @classmethod
135 def new_merged(cls):
136 """constructor to help legacy API to build a new "merged" item
137
138 Should eventually be removed
139 """
140 instance = cls()
141 instance._state = b'm'
142 instance._mode = 0
143 instance._size = FROM_P2
144 instance._mtime = AMBIGUOUS_TIME
145 return instance
146
147 @classmethod
148 def new_from_p2(cls):
149 """constructor to help legacy API to build a new "from_p2" item
150
151 Should eventually be removed
152 """
153 instance = cls()
154 instance._state = b'n'
155 instance._mode = 0
156 instance._size = FROM_P2
157 instance._mtime = AMBIGUOUS_TIME
158 return instance
159
160 @classmethod
161 def new_possibly_dirty(cls):
162 """constructor to help legacy API to build a new "possibly_dirty" item
163
164 Should eventually be removed
165 """
166 instance = cls()
167 instance._state = b'n'
168 instance._mode = 0
169 instance._size = NONNORMAL
170 instance._mtime = AMBIGUOUS_TIME
171 return instance
172
173 @classmethod
174 def new_normal(cls, mode, size, mtime):
175 """constructor to help legacy API to build a new "normal" item
176
177 Should eventually be removed
178 """
179 assert size != FROM_P2
180 assert size != NONNORMAL
181 instance = cls()
182 instance._state = b'n'
183 instance._mode = mode
184 instance._size = size
185 instance._mtime = mtime
186 return instance
187
188 @classmethod
122 189 def from_v1_data(cls, state, mode, size, mtime):
123 190 """Build a new DirstateItem object from V1 data
124 191
125 192 Since the dirstate-v1 format is frozen, the signature of this function
126 193 is not expected to change, unlike the __init__ one.
127 194 """
128 195 instance = cls()
129 196 instance._state = state
130 197 instance._mode = mode
131 198 instance._size = size
132 199 instance._mtime = mtime
133 200 return instance
134 201
135 202 def set_possibly_dirty(self):
136 203 """Mark a file as "possibly dirty"
137 204
138 205 This means the next status call will have to actually check its content
139 206 to make sure it is correct.
140 207 """
141 208 self._mtime = AMBIGUOUS_TIME
142 209
143 210 def set_untracked(self):
144 211 """mark a file as untracked in the working copy
145 212
146 213 This will ultimately be called by command like `hg remove`.
147 214 """
148 215 # backup the previous state (useful for merge)
149 216 size = 0
150 217 if self.merged: # merge
151 218 size = NONNORMAL
152 219 elif self.from_p2:
153 220 size = FROM_P2
154 221 self._state = b'r'
155 222 self._mode = 0
156 223 self._size = size
157 224 self._mtime = 0
158 225
159 226 def __getitem__(self, idx):
160 227 if idx == 0 or idx == -4:
161 228 msg = b"do not use item[x], use item.state"
162 229 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
163 230 return self._state
164 231 elif idx == 1 or idx == -3:
165 232 msg = b"do not use item[x], use item.mode"
166 233 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
167 234 return self._mode
168 235 elif idx == 2 or idx == -2:
169 236 msg = b"do not use item[x], use item.size"
170 237 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
171 238 return self._size
172 239 elif idx == 3 or idx == -1:
173 240 msg = b"do not use item[x], use item.mtime"
174 241 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
175 242 return self._mtime
176 243 else:
177 244 raise IndexError(idx)
178 245
179 246 @property
180 247 def mode(self):
181 248 return self._mode
182 249
183 250 @property
184 251 def size(self):
185 252 return self._size
186 253
187 254 @property
188 255 def mtime(self):
189 256 return self._mtime
190 257
191 258 @property
192 259 def state(self):
193 260 """
194 261 States are:
195 262 n normal
196 263 m needs merging
197 264 r marked for removal
198 265 a marked for addition
199 266
200 267 XXX This "state" is a bit obscure and mostly a direct expression of the
201 268 dirstatev1 format. It would make sense to ultimately deprecate it in
202 269 favor of the more "semantic" attributes.
203 270 """
204 271 return self._state
205 272
206 273 @property
207 274 def tracked(self):
208 275 """True is the file is tracked in the working copy"""
209 276 return self._state in b"nma"
210 277
211 278 @property
212 279 def added(self):
213 280 """True if the file has been added"""
214 281 return self._state == b'a'
215 282
216 283 @property
217 284 def merged(self):
218 285 """True if the file has been merged
219 286
220 287 Should only be set if a merge is in progress in the dirstate
221 288 """
222 289 return self._state == b'm'
223 290
224 291 @property
225 292 def from_p2(self):
226 293 """True if the file have been fetched from p2 during the current merge
227 294
228 295 This is only True is the file is currently tracked.
229 296
230 297 Should only be set if a merge is in progress in the dirstate
231 298 """
232 299 return self._state == b'n' and self._size == FROM_P2
233 300
234 301 @property
235 302 def from_p2_removed(self):
236 303 """True if the file has been removed, but was "from_p2" initially
237 304
238 305 This property seems like an abstraction leakage and should probably be
239 306 dealt in this class (or maybe the dirstatemap) directly.
240 307 """
241 308 return self._state == b'r' and self._size == FROM_P2
242 309
243 310 @property
244 311 def removed(self):
245 312 """True if the file has been removed"""
246 313 return self._state == b'r'
247 314
248 315 @property
249 316 def merged_removed(self):
250 317 """True if the file has been removed, but was "merged" initially
251 318
252 319 This property seems like an abstraction leakage and should probably be
253 320 dealt in this class (or maybe the dirstatemap) directly.
254 321 """
255 322 return self._state == b'r' and self._size == NONNORMAL
256 323
257 324 @property
258 325 def dm_nonnormal(self):
259 326 """True is the entry is non-normal in the dirstatemap sense
260 327
261 328 There is no reason for any code, but the dirstatemap one to use this.
262 329 """
263 330 return self.state != b'n' or self.mtime == AMBIGUOUS_TIME
264 331
265 332 @property
266 333 def dm_otherparent(self):
267 334 """True is the entry is `otherparent` in the dirstatemap sense
268 335
269 336 There is no reason for any code, but the dirstatemap one to use this.
270 337 """
271 338 return self._size == FROM_P2
272 339
273 340 def v1_state(self):
274 341 """return a "state" suitable for v1 serialization"""
275 342 return self._state
276 343
277 344 def v1_mode(self):
278 345 """return a "mode" suitable for v1 serialization"""
279 346 return self._mode
280 347
281 348 def v1_size(self):
282 349 """return a "size" suitable for v1 serialization"""
283 350 return self._size
284 351
285 352 def v1_mtime(self):
286 353 """return a "mtime" suitable for v1 serialization"""
287 354 return self._mtime
288 355
289 356 def need_delay(self, now):
290 357 """True if the stored mtime would be ambiguous with the current time"""
291 358 return self._state == b'n' and self._mtime == now
292 359
293 360
294 361 def gettype(q):
295 362 return int(q & 0xFFFF)
296 363
297 364
298 365 class BaseIndexObject(object):
299 366 # Can I be passed to an algorithme implemented in Rust ?
300 367 rust_ext_compat = 0
301 368 # Format of an index entry according to Python's `struct` language
302 369 index_format = revlog_constants.INDEX_ENTRY_V1
303 370 # Size of a C unsigned long long int, platform independent
304 371 big_int_size = struct.calcsize(b'>Q')
305 372 # Size of a C long int, platform independent
306 373 int_size = struct.calcsize(b'>i')
307 374 # An empty index entry, used as a default value to be overridden, or nullrev
308 375 null_item = (
309 376 0,
310 377 0,
311 378 0,
312 379 -1,
313 380 -1,
314 381 -1,
315 382 -1,
316 383 sha1nodeconstants.nullid,
317 384 0,
318 385 0,
319 386 revlog_constants.COMP_MODE_INLINE,
320 387 revlog_constants.COMP_MODE_INLINE,
321 388 )
322 389
323 390 @util.propertycache
324 391 def entry_size(self):
325 392 return self.index_format.size
326 393
327 394 @property
328 395 def nodemap(self):
329 396 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
330 397 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
331 398 return self._nodemap
332 399
333 400 @util.propertycache
334 401 def _nodemap(self):
335 402 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
336 403 for r in range(0, len(self)):
337 404 n = self[r][7]
338 405 nodemap[n] = r
339 406 return nodemap
340 407
341 408 def has_node(self, node):
342 409 """return True if the node exist in the index"""
343 410 return node in self._nodemap
344 411
345 412 def rev(self, node):
346 413 """return a revision for a node
347 414
348 415 If the node is unknown, raise a RevlogError"""
349 416 return self._nodemap[node]
350 417
351 418 def get_rev(self, node):
352 419 """return a revision for a node
353 420
354 421 If the node is unknown, return None"""
355 422 return self._nodemap.get(node)
356 423
357 424 def _stripnodes(self, start):
358 425 if '_nodemap' in vars(self):
359 426 for r in range(start, len(self)):
360 427 n = self[r][7]
361 428 del self._nodemap[n]
362 429
363 430 def clearcaches(self):
364 431 self.__dict__.pop('_nodemap', None)
365 432
366 433 def __len__(self):
367 434 return self._lgt + len(self._extra)
368 435
369 436 def append(self, tup):
370 437 if '_nodemap' in vars(self):
371 438 self._nodemap[tup[7]] = len(self)
372 439 data = self._pack_entry(len(self), tup)
373 440 self._extra.append(data)
374 441
375 442 def _pack_entry(self, rev, entry):
376 443 assert entry[8] == 0
377 444 assert entry[9] == 0
378 445 return self.index_format.pack(*entry[:8])
379 446
380 447 def _check_index(self, i):
381 448 if not isinstance(i, int):
382 449 raise TypeError(b"expecting int indexes")
383 450 if i < 0 or i >= len(self):
384 451 raise IndexError
385 452
386 453 def __getitem__(self, i):
387 454 if i == -1:
388 455 return self.null_item
389 456 self._check_index(i)
390 457 if i >= self._lgt:
391 458 data = self._extra[i - self._lgt]
392 459 else:
393 460 index = self._calculate_index(i)
394 461 data = self._data[index : index + self.entry_size]
395 462 r = self._unpack_entry(i, data)
396 463 if self._lgt and i == 0:
397 464 offset = revlogutils.offset_type(0, gettype(r[0]))
398 465 r = (offset,) + r[1:]
399 466 return r
400 467
401 468 def _unpack_entry(self, rev, data):
402 469 r = self.index_format.unpack(data)
403 470 r = r + (
404 471 0,
405 472 0,
406 473 revlog_constants.COMP_MODE_INLINE,
407 474 revlog_constants.COMP_MODE_INLINE,
408 475 )
409 476 return r
410 477
411 478 def pack_header(self, header):
412 479 """pack header information as binary"""
413 480 v_fmt = revlog_constants.INDEX_HEADER
414 481 return v_fmt.pack(header)
415 482
416 483 def entry_binary(self, rev):
417 484 """return the raw binary string representing a revision"""
418 485 entry = self[rev]
419 486 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
420 487 if rev == 0:
421 488 p = p[revlog_constants.INDEX_HEADER.size :]
422 489 return p
423 490
424 491
425 492 class IndexObject(BaseIndexObject):
426 493 def __init__(self, data):
427 494 assert len(data) % self.entry_size == 0, (
428 495 len(data),
429 496 self.entry_size,
430 497 len(data) % self.entry_size,
431 498 )
432 499 self._data = data
433 500 self._lgt = len(data) // self.entry_size
434 501 self._extra = []
435 502
436 503 def _calculate_index(self, i):
437 504 return i * self.entry_size
438 505
439 506 def __delitem__(self, i):
440 507 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
441 508 raise ValueError(b"deleting slices only supports a:-1 with step 1")
442 509 i = i.start
443 510 self._check_index(i)
444 511 self._stripnodes(i)
445 512 if i < self._lgt:
446 513 self._data = self._data[: i * self.entry_size]
447 514 self._lgt = i
448 515 self._extra = []
449 516 else:
450 517 self._extra = self._extra[: i - self._lgt]
451 518
452 519
453 520 class PersistentNodeMapIndexObject(IndexObject):
454 521 """a Debug oriented class to test persistent nodemap
455 522
456 523 We need a simple python object to test API and higher level behavior. See
457 524 the Rust implementation for more serious usage. This should be used only
458 525 through the dedicated `devel.persistent-nodemap` config.
459 526 """
460 527
461 528 def nodemap_data_all(self):
462 529 """Return bytes containing a full serialization of a nodemap
463 530
464 531 The nodemap should be valid for the full set of revisions in the
465 532 index."""
466 533 return nodemaputil.persistent_data(self)
467 534
468 535 def nodemap_data_incremental(self):
469 536 """Return bytes containing a incremental update to persistent nodemap
470 537
471 538 This containst the data for an append-only update of the data provided
472 539 in the last call to `update_nodemap_data`.
473 540 """
474 541 if self._nm_root is None:
475 542 return None
476 543 docket = self._nm_docket
477 544 changed, data = nodemaputil.update_persistent_data(
478 545 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
479 546 )
480 547
481 548 self._nm_root = self._nm_max_idx = self._nm_docket = None
482 549 return docket, changed, data
483 550
484 551 def update_nodemap_data(self, docket, nm_data):
485 552 """provide full block of persisted binary data for a nodemap
486 553
487 554 The data are expected to come from disk. See `nodemap_data_all` for a
488 555 produceur of such data."""
489 556 if nm_data is not None:
490 557 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
491 558 if self._nm_root:
492 559 self._nm_docket = docket
493 560 else:
494 561 self._nm_root = self._nm_max_idx = self._nm_docket = None
495 562
496 563
497 564 class InlinedIndexObject(BaseIndexObject):
498 565 def __init__(self, data, inline=0):
499 566 self._data = data
500 567 self._lgt = self._inline_scan(None)
501 568 self._inline_scan(self._lgt)
502 569 self._extra = []
503 570
504 571 def _inline_scan(self, lgt):
505 572 off = 0
506 573 if lgt is not None:
507 574 self._offsets = [0] * lgt
508 575 count = 0
509 576 while off <= len(self._data) - self.entry_size:
510 577 start = off + self.big_int_size
511 578 (s,) = struct.unpack(
512 579 b'>i',
513 580 self._data[start : start + self.int_size],
514 581 )
515 582 if lgt is not None:
516 583 self._offsets[count] = off
517 584 count += 1
518 585 off += self.entry_size + s
519 586 if off != len(self._data):
520 587 raise ValueError(b"corrupted data")
521 588 return count
522 589
523 590 def __delitem__(self, i):
524 591 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
525 592 raise ValueError(b"deleting slices only supports a:-1 with step 1")
526 593 i = i.start
527 594 self._check_index(i)
528 595 self._stripnodes(i)
529 596 if i < self._lgt:
530 597 self._offsets = self._offsets[:i]
531 598 self._lgt = i
532 599 self._extra = []
533 600 else:
534 601 self._extra = self._extra[: i - self._lgt]
535 602
536 603 def _calculate_index(self, i):
537 604 return self._offsets[i]
538 605
539 606
540 607 def parse_index2(data, inline, revlogv2=False):
541 608 if not inline:
542 609 cls = IndexObject2 if revlogv2 else IndexObject
543 610 return cls(data), None
544 611 cls = InlinedIndexObject
545 612 return cls(data, inline), (0, data)
546 613
547 614
548 615 def parse_index_cl_v2(data):
549 616 return IndexChangelogV2(data), None
550 617
551 618
552 619 class IndexObject2(IndexObject):
553 620 index_format = revlog_constants.INDEX_ENTRY_V2
554 621
555 622 def replace_sidedata_info(
556 623 self,
557 624 rev,
558 625 sidedata_offset,
559 626 sidedata_length,
560 627 offset_flags,
561 628 compression_mode,
562 629 ):
563 630 """
564 631 Replace an existing index entry's sidedata offset and length with new
565 632 ones.
566 633 This cannot be used outside of the context of sidedata rewriting,
567 634 inside the transaction that creates the revision `rev`.
568 635 """
569 636 if rev < 0:
570 637 raise KeyError
571 638 self._check_index(rev)
572 639 if rev < self._lgt:
573 640 msg = b"cannot rewrite entries outside of this transaction"
574 641 raise KeyError(msg)
575 642 else:
576 643 entry = list(self[rev])
577 644 entry[0] = offset_flags
578 645 entry[8] = sidedata_offset
579 646 entry[9] = sidedata_length
580 647 entry[11] = compression_mode
581 648 entry = tuple(entry)
582 649 new = self._pack_entry(rev, entry)
583 650 self._extra[rev - self._lgt] = new
584 651
585 652 def _unpack_entry(self, rev, data):
586 653 data = self.index_format.unpack(data)
587 654 entry = data[:10]
588 655 data_comp = data[10] & 3
589 656 sidedata_comp = (data[10] & (3 << 2)) >> 2
590 657 return entry + (data_comp, sidedata_comp)
591 658
592 659 def _pack_entry(self, rev, entry):
593 660 data = entry[:10]
594 661 data_comp = entry[10] & 3
595 662 sidedata_comp = (entry[11] & 3) << 2
596 663 data += (data_comp | sidedata_comp,)
597 664
598 665 return self.index_format.pack(*data)
599 666
600 667 def entry_binary(self, rev):
601 668 """return the raw binary string representing a revision"""
602 669 entry = self[rev]
603 670 return self._pack_entry(rev, entry)
604 671
605 672 def pack_header(self, header):
606 673 """pack header information as binary"""
607 674 msg = 'version header should go in the docket, not the index: %d'
608 675 msg %= header
609 676 raise error.ProgrammingError(msg)
610 677
611 678
612 679 class IndexChangelogV2(IndexObject2):
613 680 index_format = revlog_constants.INDEX_ENTRY_CL_V2
614 681
615 682 def _unpack_entry(self, rev, data, r=True):
616 683 items = self.index_format.unpack(data)
617 684 entry = items[:3] + (rev, rev) + items[3:8]
618 685 data_comp = items[8] & 3
619 686 sidedata_comp = (items[8] >> 2) & 3
620 687 return entry + (data_comp, sidedata_comp)
621 688
622 689 def _pack_entry(self, rev, entry):
623 690 assert entry[3] == rev, entry[3]
624 691 assert entry[4] == rev, entry[4]
625 692 data = entry[:3] + entry[5:10]
626 693 data_comp = entry[10] & 3
627 694 sidedata_comp = (entry[11] & 3) << 2
628 695 data += (data_comp | sidedata_comp,)
629 696 return self.index_format.pack(*data)
630 697
631 698
632 699 def parse_index_devel_nodemap(data, inline):
633 700 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
634 701 return PersistentNodeMapIndexObject(data), None
635 702
636 703
637 704 def parse_dirstate(dmap, copymap, st):
638 705 parents = [st[:20], st[20:40]]
639 706 # dereference fields so they will be local in loop
640 707 format = b">cllll"
641 708 e_size = struct.calcsize(format)
642 709 pos1 = 40
643 710 l = len(st)
644 711
645 712 # the inner loop
646 713 while pos1 < l:
647 714 pos2 = pos1 + e_size
648 715 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
649 716 pos1 = pos2 + e[4]
650 717 f = st[pos2:pos1]
651 718 if b'\0' in f:
652 719 f, c = f.split(b'\0')
653 720 copymap[f] = c
654 721 dmap[f] = DirstateItem.from_v1_data(*e[:4])
655 722 return parents
656 723
657 724
658 725 def pack_dirstate(dmap, copymap, pl, now):
659 726 now = int(now)
660 727 cs = stringio()
661 728 write = cs.write
662 729 write(b"".join(pl))
663 730 for f, e in pycompat.iteritems(dmap):
664 731 if e.need_delay(now):
665 732 # The file was last modified "simultaneously" with the current
666 733 # write to dirstate (i.e. within the same second for file-
667 734 # systems with a granularity of 1 sec). This commonly happens
668 735 # for at least a couple of files on 'update'.
669 736 # The user could change the file without changing its size
670 737 # within the same second. Invalidate the file's mtime in
671 738 # dirstate, forcing future 'status' calls to compare the
672 739 # contents of the file if the size is the same. This prevents
673 740 # mistakenly treating such files as clean.
674 741 e.set_possibly_dirty()
675 742
676 743 if f in copymap:
677 744 f = b"%s\0%s" % (f, copymap[f])
678 745 e = _pack(
679 746 b">cllll",
680 747 e.v1_state(),
681 748 e.v1_mode(),
682 749 e.v1_size(),
683 750 e.v1_mtime(),
684 751 len(f),
685 752 )
686 753 write(e)
687 754 write(f)
688 755 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now