##// END OF EJS Templates
dirstate-item: introduce a `p1_tracked` property...
marmoute -
r48955:21542d4c default
parent child Browse files
Show More
@@ -1,1228 +1,1238 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 PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
48 48 PyObject *kwds)
49 49 {
50 50 /* We do all the initialization here and not a tp_init function because
51 51 * dirstate_item is immutable. */
52 52 dirstateItemObject *t;
53 53 int wc_tracked;
54 54 int p1_tracked;
55 55 int p2_info;
56 56 int has_meaningful_data;
57 57 int has_meaningful_mtime;
58 58 int mode;
59 59 int size;
60 60 int mtime;
61 61 PyObject *parentfiledata;
62 62 static char *keywords_name[] = {
63 63 "wc_tracked",
64 64 "p1_tracked",
65 65 "p2_info",
66 66 "has_meaningful_data",
67 67 "has_meaningful_mtime",
68 68 "parentfiledata",
69 69 NULL,
70 70 };
71 71 wc_tracked = 0;
72 72 p1_tracked = 0;
73 73 p2_info = 0;
74 74 has_meaningful_mtime = 1;
75 75 has_meaningful_data = 1;
76 76 parentfiledata = Py_None;
77 77 if (!PyArg_ParseTupleAndKeywords(
78 78 args, kwds, "|iiiiiO", keywords_name, &wc_tracked, &p1_tracked,
79 79 &p2_info, &has_meaningful_data, &has_meaningful_mtime,
80 80 &parentfiledata)) {
81 81 return NULL;
82 82 }
83 83 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
84 84 if (!t) {
85 85 return NULL;
86 86 }
87 87
88 88 t->flags = 0;
89 89 if (wc_tracked) {
90 90 t->flags |= dirstate_flag_wc_tracked;
91 91 }
92 92 if (p1_tracked) {
93 93 t->flags |= dirstate_flag_p1_tracked;
94 94 }
95 95 if (p2_info) {
96 96 t->flags |= dirstate_flag_p2_info;
97 97 }
98 98
99 99 if (parentfiledata != Py_None) {
100 100 if (!PyTuple_CheckExact(parentfiledata)) {
101 101 PyErr_SetString(
102 102 PyExc_TypeError,
103 103 "parentfiledata should be a Tuple or None");
104 104 return NULL;
105 105 }
106 106 mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
107 107 size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
108 108 mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
109 109 } else {
110 110 has_meaningful_data = 0;
111 111 has_meaningful_mtime = 0;
112 112 }
113 113 if (has_meaningful_data) {
114 114 t->flags |= dirstate_flag_has_meaningful_data;
115 115 t->mode = mode;
116 116 t->size = size;
117 117 } else {
118 118 t->mode = 0;
119 119 t->size = 0;
120 120 }
121 121 if (has_meaningful_mtime) {
122 122 t->flags |= dirstate_flag_has_meaningful_mtime;
123 123 t->mtime = mtime;
124 124 } else {
125 125 t->mtime = 0;
126 126 }
127 127 return (PyObject *)t;
128 128 }
129 129
130 130 static void dirstate_item_dealloc(PyObject *o)
131 131 {
132 132 PyObject_Del(o);
133 133 }
134 134
135 135 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
136 136 {
137 137 return (self->flags & dirstate_flag_wc_tracked);
138 138 }
139 139
140 140 static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self)
141 141 {
142 142 const unsigned char mask = dirstate_flag_wc_tracked |
143 143 dirstate_flag_p1_tracked |
144 144 dirstate_flag_p2_info;
145 145 return (self->flags & mask);
146 146 }
147 147
148 148 static inline bool dirstate_item_c_added(dirstateItemObject *self)
149 149 {
150 150 const unsigned char mask =
151 151 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
152 152 dirstate_flag_p2_info);
153 153 const unsigned char target = dirstate_flag_wc_tracked;
154 154 return (self->flags & mask) == target;
155 155 }
156 156
157 157 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
158 158 {
159 159 if (self->flags & dirstate_flag_wc_tracked) {
160 160 return false;
161 161 }
162 162 return (self->flags &
163 163 (dirstate_flag_p1_tracked | dirstate_flag_p2_info));
164 164 }
165 165
166 166 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
167 167 {
168 168 return ((self->flags & dirstate_flag_wc_tracked) &&
169 169 (self->flags & dirstate_flag_p1_tracked) &&
170 170 (self->flags & dirstate_flag_p2_info));
171 171 }
172 172
173 173 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
174 174 {
175 175 return ((self->flags & dirstate_flag_wc_tracked) &&
176 176 !(self->flags & dirstate_flag_p1_tracked) &&
177 177 (self->flags & dirstate_flag_p2_info));
178 178 }
179 179
180 180 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
181 181 {
182 182 if (dirstate_item_c_removed(self)) {
183 183 return 'r';
184 184 } else if (dirstate_item_c_merged(self)) {
185 185 return 'm';
186 186 } else if (dirstate_item_c_added(self)) {
187 187 return 'a';
188 188 } else {
189 189 return 'n';
190 190 }
191 191 }
192 192
193 193 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
194 194 {
195 195 if (self->flags & dirstate_flag_has_meaningful_data) {
196 196 return self->mode;
197 197 } else {
198 198 return 0;
199 199 }
200 200 }
201 201
202 202 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
203 203 {
204 204 if (!(self->flags & dirstate_flag_wc_tracked) &&
205 205 (self->flags & dirstate_flag_p2_info)) {
206 206 if (self->flags & dirstate_flag_p1_tracked) {
207 207 return dirstate_v1_nonnormal;
208 208 } else {
209 209 return dirstate_v1_from_p2;
210 210 }
211 211 } else if (dirstate_item_c_removed(self)) {
212 212 return 0;
213 213 } else if (self->flags & dirstate_flag_p2_info) {
214 214 return dirstate_v1_from_p2;
215 215 } else if (dirstate_item_c_added(self)) {
216 216 return dirstate_v1_nonnormal;
217 217 } else if (self->flags & dirstate_flag_has_meaningful_data) {
218 218 return self->size;
219 219 } else {
220 220 return dirstate_v1_nonnormal;
221 221 }
222 222 }
223 223
224 224 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
225 225 {
226 226 if (dirstate_item_c_removed(self)) {
227 227 return 0;
228 228 } else if (!(self->flags & dirstate_flag_has_meaningful_mtime) ||
229 229 !(self->flags & dirstate_flag_p1_tracked) ||
230 230 !(self->flags & dirstate_flag_wc_tracked) ||
231 231 (self->flags & dirstate_flag_p2_info)) {
232 232 return ambiguous_time;
233 233 } else {
234 234 return self->mtime;
235 235 }
236 236 }
237 237
238 238 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
239 239 {
240 240 char state = dirstate_item_c_v1_state(self);
241 241 return PyBytes_FromStringAndSize(&state, 1);
242 242 };
243 243
244 244 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
245 245 {
246 246 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
247 247 };
248 248
249 249 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
250 250 {
251 251 return PyInt_FromLong(dirstate_item_c_v1_size(self));
252 252 };
253 253
254 254 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
255 255 {
256 256 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
257 257 };
258 258
259 259 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
260 260 PyObject *value)
261 261 {
262 262 long now;
263 263 if (!pylong_to_long(value, &now)) {
264 264 return NULL;
265 265 }
266 266 if (dirstate_item_c_v1_state(self) == 'n' &&
267 267 dirstate_item_c_v1_mtime(self) == now) {
268 268 Py_RETURN_TRUE;
269 269 } else {
270 270 Py_RETURN_FALSE;
271 271 }
272 272 };
273 273
274 274 /* This will never change since it's bound to V1
275 275 */
276 276 static inline dirstateItemObject *
277 277 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
278 278 {
279 279 dirstateItemObject *t =
280 280 PyObject_New(dirstateItemObject, &dirstateItemType);
281 281 if (!t) {
282 282 return NULL;
283 283 }
284 284 t->flags = 0;
285 285 t->mode = 0;
286 286 t->size = 0;
287 287 t->mtime = 0;
288 288
289 289 if (state == 'm') {
290 290 t->flags = (dirstate_flag_wc_tracked |
291 291 dirstate_flag_p1_tracked | dirstate_flag_p2_info);
292 292 } else if (state == 'a') {
293 293 t->flags = dirstate_flag_wc_tracked;
294 294 } else if (state == 'r') {
295 295 if (size == dirstate_v1_nonnormal) {
296 296 t->flags =
297 297 dirstate_flag_p1_tracked | dirstate_flag_p2_info;
298 298 } else if (size == dirstate_v1_from_p2) {
299 299 t->flags = dirstate_flag_p2_info;
300 300 } else {
301 301 t->flags = dirstate_flag_p1_tracked;
302 302 }
303 303 } else if (state == 'n') {
304 304 if (size == dirstate_v1_from_p2) {
305 305 t->flags =
306 306 dirstate_flag_wc_tracked | dirstate_flag_p2_info;
307 307 } else if (size == dirstate_v1_nonnormal) {
308 308 t->flags =
309 309 dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
310 310 } else if (mtime == ambiguous_time) {
311 311 t->flags = (dirstate_flag_wc_tracked |
312 312 dirstate_flag_p1_tracked |
313 313 dirstate_flag_has_meaningful_data);
314 314 t->mode = mode;
315 315 t->size = size;
316 316 } else {
317 317 t->flags = (dirstate_flag_wc_tracked |
318 318 dirstate_flag_p1_tracked |
319 319 dirstate_flag_has_meaningful_data |
320 320 dirstate_flag_has_meaningful_mtime);
321 321 t->mode = mode;
322 322 t->size = size;
323 323 t->mtime = mtime;
324 324 }
325 325 } else {
326 326 PyErr_Format(PyExc_RuntimeError,
327 327 "unknown state: `%c` (%d, %d, %d)", state, mode,
328 328 size, mtime, NULL);
329 329 Py_DECREF(t);
330 330 return NULL;
331 331 }
332 332
333 333 return t;
334 334 }
335 335
336 336 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
337 337 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
338 338 PyObject *args)
339 339 {
340 340 /* We do all the initialization here and not a tp_init function because
341 341 * dirstate_item is immutable. */
342 342 char state;
343 343 int size, mode, mtime;
344 344 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
345 345 return NULL;
346 346 }
347 347 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
348 348 };
349 349
350 350 /* constructor to help legacy API to build a new "added" item
351 351
352 352 Should eventually be removed */
353 353 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
354 354 {
355 355 dirstateItemObject *t;
356 356 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
357 357 if (!t) {
358 358 return NULL;
359 359 }
360 360 t->flags = dirstate_flag_wc_tracked;
361 361 t->mode = 0;
362 362 t->size = 0;
363 363 t->mtime = 0;
364 364 return (PyObject *)t;
365 365 };
366 366
367 367 /* constructor to help legacy API to build a new "merged" item
368 368
369 369 Should eventually be removed */
370 370 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
371 371 {
372 372 dirstateItemObject *t;
373 373 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
374 374 if (!t) {
375 375 return NULL;
376 376 }
377 377 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
378 378 dirstate_flag_p2_info);
379 379 t->mode = 0;
380 380 t->size = 0;
381 381 t->mtime = 0;
382 382 return (PyObject *)t;
383 383 };
384 384
385 385 /* constructor to help legacy API to build a new "from_p2" item
386 386
387 387 Should eventually be removed */
388 388 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
389 389 {
390 390 /* We do all the initialization here and not a tp_init function because
391 391 * dirstate_item is immutable. */
392 392 dirstateItemObject *t;
393 393 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
394 394 if (!t) {
395 395 return NULL;
396 396 }
397 397 t->flags = dirstate_flag_wc_tracked | dirstate_flag_p2_info;
398 398 t->mode = 0;
399 399 t->size = 0;
400 400 t->mtime = 0;
401 401 return (PyObject *)t;
402 402 };
403 403
404 404 /* constructor to help legacy API to build a new "possibly" item
405 405
406 406 Should eventually be removed */
407 407 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
408 408 {
409 409 /* We do all the initialization here and not a tp_init function because
410 410 * dirstate_item is immutable. */
411 411 dirstateItemObject *t;
412 412 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
413 413 if (!t) {
414 414 return NULL;
415 415 }
416 416 t->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
417 417 t->mode = 0;
418 418 t->size = 0;
419 419 t->mtime = 0;
420 420 return (PyObject *)t;
421 421 };
422 422
423 423 /* constructor to help legacy API to build a new "normal" item
424 424
425 425 Should eventually be removed */
426 426 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
427 427 {
428 428 /* We do all the initialization here and not a tp_init function because
429 429 * dirstate_item is immutable. */
430 430 dirstateItemObject *t;
431 431 int size, mode, mtime;
432 432 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
433 433 return NULL;
434 434 }
435 435
436 436 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
437 437 if (!t) {
438 438 return NULL;
439 439 }
440 440 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked);
441 441 t->mode = mode;
442 442 t->size = size;
443 443 t->mtime = mtime;
444 444 return (PyObject *)t;
445 445 };
446 446
447 447 /* This means the next status call will have to actually check its content
448 448 to make sure it is correct. */
449 449 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
450 450 {
451 451 self->flags &= ~dirstate_flag_has_meaningful_mtime;
452 452 Py_RETURN_NONE;
453 453 }
454 454
455 455 /* See docstring of the python implementation for details */
456 456 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
457 457 PyObject *args)
458 458 {
459 459 int size, mode, mtime;
460 460 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
461 461 return NULL;
462 462 }
463 463 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
464 464 dirstate_flag_has_meaningful_data |
465 465 dirstate_flag_has_meaningful_mtime;
466 466 self->mode = mode;
467 467 self->size = size;
468 468 self->mtime = mtime;
469 469 Py_RETURN_NONE;
470 470 }
471 471
472 472 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
473 473 {
474 474 self->flags |= dirstate_flag_wc_tracked;
475 475 self->flags &= ~dirstate_flag_has_meaningful_mtime;
476 476 Py_RETURN_NONE;
477 477 }
478 478
479 479 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
480 480 {
481 481 self->flags &= ~dirstate_flag_wc_tracked;
482 482 self->mode = 0;
483 483 self->mtime = 0;
484 484 self->size = 0;
485 485 Py_RETURN_NONE;
486 486 }
487 487
488 488 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
489 489 {
490 490 if (self->flags & dirstate_flag_p2_info) {
491 491 self->flags &= ~(dirstate_flag_p2_info |
492 492 dirstate_flag_has_meaningful_data |
493 493 dirstate_flag_has_meaningful_mtime);
494 494 self->mode = 0;
495 495 self->mtime = 0;
496 496 self->size = 0;
497 497 }
498 498 Py_RETURN_NONE;
499 499 }
500 500 static PyMethodDef dirstate_item_methods[] = {
501 501 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
502 502 "return a \"state\" suitable for v1 serialization"},
503 503 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
504 504 "return a \"mode\" suitable for v1 serialization"},
505 505 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
506 506 "return a \"size\" suitable for v1 serialization"},
507 507 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
508 508 "return a \"mtime\" suitable for v1 serialization"},
509 509 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
510 510 "True if the stored mtime would be ambiguous with the current time"},
511 511 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
512 512 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
513 513 {"new_added", (PyCFunction)dirstate_item_new_added,
514 514 METH_NOARGS | METH_CLASS,
515 515 "constructor to help legacy API to build a new \"added\" item"},
516 516 {"new_merged", (PyCFunction)dirstate_item_new_merged,
517 517 METH_NOARGS | METH_CLASS,
518 518 "constructor to help legacy API to build a new \"merged\" item"},
519 519 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
520 520 METH_NOARGS | METH_CLASS,
521 521 "constructor to help legacy API to build a new \"from_p2\" item"},
522 522 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
523 523 METH_NOARGS | METH_CLASS,
524 524 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
525 525 {"new_normal", (PyCFunction)dirstate_item_new_normal,
526 526 METH_VARARGS | METH_CLASS,
527 527 "constructor to help legacy API to build a new \"normal\" item"},
528 528 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
529 529 METH_NOARGS, "mark a file as \"possibly dirty\""},
530 530 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
531 531 "mark a file as \"clean\""},
532 532 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
533 533 "mark a file as \"tracked\""},
534 534 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
535 535 "mark a file as \"untracked\""},
536 536 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
537 537 "remove all \"merge-only\" from a DirstateItem"},
538 538 {NULL} /* Sentinel */
539 539 };
540 540
541 541 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
542 542 {
543 543 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
544 544 };
545 545
546 546 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
547 547 {
548 548 return PyInt_FromLong(dirstate_item_c_v1_size(self));
549 549 };
550 550
551 551 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
552 552 {
553 553 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
554 554 };
555 555
556 556 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
557 557 {
558 558 char state = dirstate_item_c_v1_state(self);
559 559 return PyBytes_FromStringAndSize(&state, 1);
560 560 };
561 561
562 562 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
563 563 {
564 564 if (dirstate_item_c_tracked(self)) {
565 565 Py_RETURN_TRUE;
566 566 } else {
567 567 Py_RETURN_FALSE;
568 568 }
569 569 };
570 static PyObject *dirstate_item_get_p1_tracked(dirstateItemObject *self)
571 {
572 if (self->flags & dirstate_flag_p1_tracked) {
573 Py_RETURN_TRUE;
574 } else {
575 Py_RETURN_FALSE;
576 }
577 };
570 578
571 579 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
572 580 {
573 581 if (dirstate_item_c_added(self)) {
574 582 Py_RETURN_TRUE;
575 583 } else {
576 584 Py_RETURN_FALSE;
577 585 }
578 586 };
579 587
580 588 static PyObject *dirstate_item_get_p2_info(dirstateItemObject *self)
581 589 {
582 590 if (self->flags & dirstate_flag_wc_tracked &&
583 591 self->flags & dirstate_flag_p2_info) {
584 592 Py_RETURN_TRUE;
585 593 } else {
586 594 Py_RETURN_FALSE;
587 595 }
588 596 };
589 597
590 598 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
591 599 {
592 600 if (dirstate_item_c_merged(self)) {
593 601 Py_RETURN_TRUE;
594 602 } else {
595 603 Py_RETURN_FALSE;
596 604 }
597 605 };
598 606
599 607 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
600 608 {
601 609 if (dirstate_item_c_from_p2(self)) {
602 610 Py_RETURN_TRUE;
603 611 } else {
604 612 Py_RETURN_FALSE;
605 613 }
606 614 };
607 615
608 616 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
609 617 {
610 618 if (!(self->flags & dirstate_flag_wc_tracked)) {
611 619 Py_RETURN_FALSE;
612 620 } else if (!(self->flags & dirstate_flag_p1_tracked)) {
613 621 Py_RETURN_FALSE;
614 622 } else if (self->flags & dirstate_flag_p2_info) {
615 623 Py_RETURN_FALSE;
616 624 } else {
617 625 Py_RETURN_TRUE;
618 626 }
619 627 };
620 628
621 629 static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self)
622 630 {
623 631 if (dirstate_item_c_any_tracked(self)) {
624 632 Py_RETURN_TRUE;
625 633 } else {
626 634 Py_RETURN_FALSE;
627 635 }
628 636 };
629 637
630 638 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
631 639 {
632 640 if (dirstate_item_c_removed(self)) {
633 641 Py_RETURN_TRUE;
634 642 } else {
635 643 Py_RETURN_FALSE;
636 644 }
637 645 };
638 646
639 647 static PyGetSetDef dirstate_item_getset[] = {
640 648 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
641 649 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
642 650 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
643 651 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
644 652 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
653 {"p1_tracked", (getter)dirstate_item_get_p1_tracked, NULL, "p1_tracked",
654 NULL},
645 655 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
646 656 {"p2_info", (getter)dirstate_item_get_p2_info, NULL, "p2_info", NULL},
647 657 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
648 658 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
649 659 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
650 660 NULL},
651 661 {"any_tracked", (getter)dirstate_item_get_any_tracked, NULL, "any_tracked",
652 662 NULL},
653 663 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
654 664 {NULL} /* Sentinel */
655 665 };
656 666
657 667 PyTypeObject dirstateItemType = {
658 668 PyVarObject_HEAD_INIT(NULL, 0) /* header */
659 669 "dirstate_tuple", /* tp_name */
660 670 sizeof(dirstateItemObject), /* tp_basicsize */
661 671 0, /* tp_itemsize */
662 672 (destructor)dirstate_item_dealloc, /* tp_dealloc */
663 673 0, /* tp_print */
664 674 0, /* tp_getattr */
665 675 0, /* tp_setattr */
666 676 0, /* tp_compare */
667 677 0, /* tp_repr */
668 678 0, /* tp_as_number */
669 679 0, /* tp_as_sequence */
670 680 0, /* tp_as_mapping */
671 681 0, /* tp_hash */
672 682 0, /* tp_call */
673 683 0, /* tp_str */
674 684 0, /* tp_getattro */
675 685 0, /* tp_setattro */
676 686 0, /* tp_as_buffer */
677 687 Py_TPFLAGS_DEFAULT, /* tp_flags */
678 688 "dirstate tuple", /* tp_doc */
679 689 0, /* tp_traverse */
680 690 0, /* tp_clear */
681 691 0, /* tp_richcompare */
682 692 0, /* tp_weaklistoffset */
683 693 0, /* tp_iter */
684 694 0, /* tp_iternext */
685 695 dirstate_item_methods, /* tp_methods */
686 696 0, /* tp_members */
687 697 dirstate_item_getset, /* tp_getset */
688 698 0, /* tp_base */
689 699 0, /* tp_dict */
690 700 0, /* tp_descr_get */
691 701 0, /* tp_descr_set */
692 702 0, /* tp_dictoffset */
693 703 0, /* tp_init */
694 704 0, /* tp_alloc */
695 705 dirstate_item_new, /* tp_new */
696 706 };
697 707
698 708 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
699 709 {
700 710 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
701 711 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
702 712 char state, *cur, *str, *cpos;
703 713 int mode, size, mtime;
704 714 unsigned int flen, pos = 40;
705 715 Py_ssize_t len = 40;
706 716 Py_ssize_t readlen;
707 717
708 718 if (!PyArg_ParseTuple(
709 719 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
710 720 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
711 721 goto quit;
712 722 }
713 723
714 724 len = readlen;
715 725
716 726 /* read parents */
717 727 if (len < 40) {
718 728 PyErr_SetString(PyExc_ValueError,
719 729 "too little data for parents");
720 730 goto quit;
721 731 }
722 732
723 733 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
724 734 str + 20, (Py_ssize_t)20);
725 735 if (!parents) {
726 736 goto quit;
727 737 }
728 738
729 739 /* read filenames */
730 740 while (pos >= 40 && pos < len) {
731 741 if (pos + 17 > len) {
732 742 PyErr_SetString(PyExc_ValueError,
733 743 "overflow in dirstate");
734 744 goto quit;
735 745 }
736 746 cur = str + pos;
737 747 /* unpack header */
738 748 state = *cur;
739 749 mode = getbe32(cur + 1);
740 750 size = getbe32(cur + 5);
741 751 mtime = getbe32(cur + 9);
742 752 flen = getbe32(cur + 13);
743 753 pos += 17;
744 754 cur += 17;
745 755 if (flen > len - pos) {
746 756 PyErr_SetString(PyExc_ValueError,
747 757 "overflow in dirstate");
748 758 goto quit;
749 759 }
750 760
751 761 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
752 762 size, mtime);
753 763 if (!entry)
754 764 goto quit;
755 765 cpos = memchr(cur, 0, flen);
756 766 if (cpos) {
757 767 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
758 768 cname = PyBytes_FromStringAndSize(
759 769 cpos + 1, flen - (cpos - cur) - 1);
760 770 if (!fname || !cname ||
761 771 PyDict_SetItem(cmap, fname, cname) == -1 ||
762 772 PyDict_SetItem(dmap, fname, entry) == -1) {
763 773 goto quit;
764 774 }
765 775 Py_DECREF(cname);
766 776 } else {
767 777 fname = PyBytes_FromStringAndSize(cur, flen);
768 778 if (!fname ||
769 779 PyDict_SetItem(dmap, fname, entry) == -1) {
770 780 goto quit;
771 781 }
772 782 }
773 783 Py_DECREF(fname);
774 784 Py_DECREF(entry);
775 785 fname = cname = entry = NULL;
776 786 pos += flen;
777 787 }
778 788
779 789 ret = parents;
780 790 Py_INCREF(ret);
781 791 quit:
782 792 Py_XDECREF(fname);
783 793 Py_XDECREF(cname);
784 794 Py_XDECREF(entry);
785 795 Py_XDECREF(parents);
786 796 return ret;
787 797 }
788 798
789 799 /*
790 800 * Efficiently pack a dirstate object into its on-disk format.
791 801 */
792 802 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
793 803 {
794 804 PyObject *packobj = NULL;
795 805 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
796 806 Py_ssize_t nbytes, pos, l;
797 807 PyObject *k, *v = NULL, *pn;
798 808 char *p, *s;
799 809 int now;
800 810
801 811 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
802 812 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
803 813 &now)) {
804 814 return NULL;
805 815 }
806 816
807 817 if (PyTuple_Size(pl) != 2) {
808 818 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
809 819 return NULL;
810 820 }
811 821
812 822 /* Figure out how much we need to allocate. */
813 823 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
814 824 PyObject *c;
815 825 if (!PyBytes_Check(k)) {
816 826 PyErr_SetString(PyExc_TypeError, "expected string key");
817 827 goto bail;
818 828 }
819 829 nbytes += PyBytes_GET_SIZE(k) + 17;
820 830 c = PyDict_GetItem(copymap, k);
821 831 if (c) {
822 832 if (!PyBytes_Check(c)) {
823 833 PyErr_SetString(PyExc_TypeError,
824 834 "expected string key");
825 835 goto bail;
826 836 }
827 837 nbytes += PyBytes_GET_SIZE(c) + 1;
828 838 }
829 839 }
830 840
831 841 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
832 842 if (packobj == NULL) {
833 843 goto bail;
834 844 }
835 845
836 846 p = PyBytes_AS_STRING(packobj);
837 847
838 848 pn = PyTuple_GET_ITEM(pl, 0);
839 849 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
840 850 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
841 851 goto bail;
842 852 }
843 853 memcpy(p, s, l);
844 854 p += 20;
845 855 pn = PyTuple_GET_ITEM(pl, 1);
846 856 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
847 857 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
848 858 goto bail;
849 859 }
850 860 memcpy(p, s, l);
851 861 p += 20;
852 862
853 863 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
854 864 dirstateItemObject *tuple;
855 865 char state;
856 866 int mode, size, mtime;
857 867 Py_ssize_t len, l;
858 868 PyObject *o;
859 869 char *t;
860 870
861 871 if (!dirstate_tuple_check(v)) {
862 872 PyErr_SetString(PyExc_TypeError,
863 873 "expected a dirstate tuple");
864 874 goto bail;
865 875 }
866 876 tuple = (dirstateItemObject *)v;
867 877
868 878 state = dirstate_item_c_v1_state(tuple);
869 879 mode = dirstate_item_c_v1_mode(tuple);
870 880 size = dirstate_item_c_v1_size(tuple);
871 881 mtime = dirstate_item_c_v1_mtime(tuple);
872 882 if (state == 'n' && mtime == now) {
873 883 /* See pure/parsers.py:pack_dirstate for why we do
874 884 * this. */
875 885 mtime = -1;
876 886 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
877 887 state, mode, size, mtime);
878 888 if (!mtime_unset) {
879 889 goto bail;
880 890 }
881 891 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
882 892 goto bail;
883 893 }
884 894 Py_DECREF(mtime_unset);
885 895 mtime_unset = NULL;
886 896 }
887 897 *p++ = state;
888 898 putbe32((uint32_t)mode, p);
889 899 putbe32((uint32_t)size, p + 4);
890 900 putbe32((uint32_t)mtime, p + 8);
891 901 t = p + 12;
892 902 p += 16;
893 903 len = PyBytes_GET_SIZE(k);
894 904 memcpy(p, PyBytes_AS_STRING(k), len);
895 905 p += len;
896 906 o = PyDict_GetItem(copymap, k);
897 907 if (o) {
898 908 *p++ = '\0';
899 909 l = PyBytes_GET_SIZE(o);
900 910 memcpy(p, PyBytes_AS_STRING(o), l);
901 911 p += l;
902 912 len += l + 1;
903 913 }
904 914 putbe32((uint32_t)len, t);
905 915 }
906 916
907 917 pos = p - PyBytes_AS_STRING(packobj);
908 918 if (pos != nbytes) {
909 919 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
910 920 (long)pos, (long)nbytes);
911 921 goto bail;
912 922 }
913 923
914 924 return packobj;
915 925 bail:
916 926 Py_XDECREF(mtime_unset);
917 927 Py_XDECREF(packobj);
918 928 Py_XDECREF(v);
919 929 return NULL;
920 930 }
921 931
922 932 #define BUMPED_FIX 1
923 933 #define USING_SHA_256 2
924 934 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
925 935
926 936 static PyObject *readshas(const char *source, unsigned char num,
927 937 Py_ssize_t hashwidth)
928 938 {
929 939 int i;
930 940 PyObject *list = PyTuple_New(num);
931 941 if (list == NULL) {
932 942 return NULL;
933 943 }
934 944 for (i = 0; i < num; i++) {
935 945 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
936 946 if (hash == NULL) {
937 947 Py_DECREF(list);
938 948 return NULL;
939 949 }
940 950 PyTuple_SET_ITEM(list, i, hash);
941 951 source += hashwidth;
942 952 }
943 953 return list;
944 954 }
945 955
946 956 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
947 957 uint32_t *msize)
948 958 {
949 959 const char *data = databegin;
950 960 const char *meta;
951 961
952 962 double mtime;
953 963 int16_t tz;
954 964 uint16_t flags;
955 965 unsigned char nsuccs, nparents, nmetadata;
956 966 Py_ssize_t hashwidth = 20;
957 967
958 968 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
959 969 PyObject *metadata = NULL, *ret = NULL;
960 970 int i;
961 971
962 972 if (data + FM1_HEADER_SIZE > dataend) {
963 973 goto overflow;
964 974 }
965 975
966 976 *msize = getbe32(data);
967 977 data += 4;
968 978 mtime = getbefloat64(data);
969 979 data += 8;
970 980 tz = getbeint16(data);
971 981 data += 2;
972 982 flags = getbeuint16(data);
973 983 data += 2;
974 984
975 985 if (flags & USING_SHA_256) {
976 986 hashwidth = 32;
977 987 }
978 988
979 989 nsuccs = (unsigned char)(*data++);
980 990 nparents = (unsigned char)(*data++);
981 991 nmetadata = (unsigned char)(*data++);
982 992
983 993 if (databegin + *msize > dataend) {
984 994 goto overflow;
985 995 }
986 996 dataend = databegin + *msize; /* narrow down to marker size */
987 997
988 998 if (data + hashwidth > dataend) {
989 999 goto overflow;
990 1000 }
991 1001 prec = PyBytes_FromStringAndSize(data, hashwidth);
992 1002 data += hashwidth;
993 1003 if (prec == NULL) {
994 1004 goto bail;
995 1005 }
996 1006
997 1007 if (data + nsuccs * hashwidth > dataend) {
998 1008 goto overflow;
999 1009 }
1000 1010 succs = readshas(data, nsuccs, hashwidth);
1001 1011 if (succs == NULL) {
1002 1012 goto bail;
1003 1013 }
1004 1014 data += nsuccs * hashwidth;
1005 1015
1006 1016 if (nparents == 1 || nparents == 2) {
1007 1017 if (data + nparents * hashwidth > dataend) {
1008 1018 goto overflow;
1009 1019 }
1010 1020 parents = readshas(data, nparents, hashwidth);
1011 1021 if (parents == NULL) {
1012 1022 goto bail;
1013 1023 }
1014 1024 data += nparents * hashwidth;
1015 1025 } else {
1016 1026 parents = Py_None;
1017 1027 Py_INCREF(parents);
1018 1028 }
1019 1029
1020 1030 if (data + 2 * nmetadata > dataend) {
1021 1031 goto overflow;
1022 1032 }
1023 1033 meta = data + (2 * nmetadata);
1024 1034 metadata = PyTuple_New(nmetadata);
1025 1035 if (metadata == NULL) {
1026 1036 goto bail;
1027 1037 }
1028 1038 for (i = 0; i < nmetadata; i++) {
1029 1039 PyObject *tmp, *left = NULL, *right = NULL;
1030 1040 Py_ssize_t leftsize = (unsigned char)(*data++);
1031 1041 Py_ssize_t rightsize = (unsigned char)(*data++);
1032 1042 if (meta + leftsize + rightsize > dataend) {
1033 1043 goto overflow;
1034 1044 }
1035 1045 left = PyBytes_FromStringAndSize(meta, leftsize);
1036 1046 meta += leftsize;
1037 1047 right = PyBytes_FromStringAndSize(meta, rightsize);
1038 1048 meta += rightsize;
1039 1049 tmp = PyTuple_New(2);
1040 1050 if (!left || !right || !tmp) {
1041 1051 Py_XDECREF(left);
1042 1052 Py_XDECREF(right);
1043 1053 Py_XDECREF(tmp);
1044 1054 goto bail;
1045 1055 }
1046 1056 PyTuple_SET_ITEM(tmp, 0, left);
1047 1057 PyTuple_SET_ITEM(tmp, 1, right);
1048 1058 PyTuple_SET_ITEM(metadata, i, tmp);
1049 1059 }
1050 1060 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1051 1061 (int)tz * 60, parents);
1052 1062 goto bail; /* return successfully */
1053 1063
1054 1064 overflow:
1055 1065 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1056 1066 bail:
1057 1067 Py_XDECREF(prec);
1058 1068 Py_XDECREF(succs);
1059 1069 Py_XDECREF(metadata);
1060 1070 Py_XDECREF(parents);
1061 1071 return ret;
1062 1072 }
1063 1073
1064 1074 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1065 1075 {
1066 1076 const char *data, *dataend;
1067 1077 Py_ssize_t datalen, offset, stop;
1068 1078 PyObject *markers = NULL;
1069 1079
1070 1080 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1071 1081 &offset, &stop)) {
1072 1082 return NULL;
1073 1083 }
1074 1084 if (offset < 0) {
1075 1085 PyErr_SetString(PyExc_ValueError,
1076 1086 "invalid negative offset in fm1readmarkers");
1077 1087 return NULL;
1078 1088 }
1079 1089 if (stop > datalen) {
1080 1090 PyErr_SetString(
1081 1091 PyExc_ValueError,
1082 1092 "stop longer than data length in fm1readmarkers");
1083 1093 return NULL;
1084 1094 }
1085 1095 dataend = data + datalen;
1086 1096 data += offset;
1087 1097 markers = PyList_New(0);
1088 1098 if (!markers) {
1089 1099 return NULL;
1090 1100 }
1091 1101 while (offset < stop) {
1092 1102 uint32_t msize;
1093 1103 int error;
1094 1104 PyObject *record = fm1readmarker(data, dataend, &msize);
1095 1105 if (!record) {
1096 1106 goto bail;
1097 1107 }
1098 1108 error = PyList_Append(markers, record);
1099 1109 Py_DECREF(record);
1100 1110 if (error) {
1101 1111 goto bail;
1102 1112 }
1103 1113 data += msize;
1104 1114 offset += msize;
1105 1115 }
1106 1116 return markers;
1107 1117 bail:
1108 1118 Py_DECREF(markers);
1109 1119 return NULL;
1110 1120 }
1111 1121
1112 1122 static char parsers_doc[] = "Efficient content parsing.";
1113 1123
1114 1124 PyObject *encodedir(PyObject *self, PyObject *args);
1115 1125 PyObject *pathencode(PyObject *self, PyObject *args);
1116 1126 PyObject *lowerencode(PyObject *self, PyObject *args);
1117 1127 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1118 1128
1119 1129 static PyMethodDef methods[] = {
1120 1130 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1121 1131 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1122 1132 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1123 1133 "parse a revlog index\n"},
1124 1134 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1125 1135 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1126 1136 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1127 1137 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1128 1138 "construct a dict with an expected size\n"},
1129 1139 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1130 1140 "make file foldmap\n"},
1131 1141 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1132 1142 "escape a UTF-8 byte string to JSON (fast path)\n"},
1133 1143 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1134 1144 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1135 1145 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1136 1146 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1137 1147 "parse v1 obsolete markers\n"},
1138 1148 {NULL, NULL}};
1139 1149
1140 1150 void dirs_module_init(PyObject *mod);
1141 1151 void manifest_module_init(PyObject *mod);
1142 1152 void revlog_module_init(PyObject *mod);
1143 1153
1144 1154 static const int version = 20;
1145 1155
1146 1156 static void module_init(PyObject *mod)
1147 1157 {
1148 1158 PyModule_AddIntConstant(mod, "version", version);
1149 1159
1150 1160 /* This module constant has two purposes. First, it lets us unit test
1151 1161 * the ImportError raised without hard-coding any error text. This
1152 1162 * means we can change the text in the future without breaking tests,
1153 1163 * even across changesets without a recompile. Second, its presence
1154 1164 * can be used to determine whether the version-checking logic is
1155 1165 * present, which also helps in testing across changesets without a
1156 1166 * recompile. Note that this means the pure-Python version of parsers
1157 1167 * should not have this module constant. */
1158 1168 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1159 1169
1160 1170 dirs_module_init(mod);
1161 1171 manifest_module_init(mod);
1162 1172 revlog_module_init(mod);
1163 1173
1164 1174 if (PyType_Ready(&dirstateItemType) < 0) {
1165 1175 return;
1166 1176 }
1167 1177 Py_INCREF(&dirstateItemType);
1168 1178 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1169 1179 }
1170 1180
1171 1181 static int check_python_version(void)
1172 1182 {
1173 1183 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1174 1184 long hexversion;
1175 1185 if (!sys) {
1176 1186 return -1;
1177 1187 }
1178 1188 ver = PyObject_GetAttrString(sys, "hexversion");
1179 1189 Py_DECREF(sys);
1180 1190 if (!ver) {
1181 1191 return -1;
1182 1192 }
1183 1193 hexversion = PyInt_AsLong(ver);
1184 1194 Py_DECREF(ver);
1185 1195 /* sys.hexversion is a 32-bit number by default, so the -1 case
1186 1196 * should only occur in unusual circumstances (e.g. if sys.hexversion
1187 1197 * is manually set to an invalid value). */
1188 1198 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1189 1199 PyErr_Format(PyExc_ImportError,
1190 1200 "%s: The Mercurial extension "
1191 1201 "modules were compiled with Python " PY_VERSION
1192 1202 ", but "
1193 1203 "Mercurial is currently using Python with "
1194 1204 "sys.hexversion=%ld: "
1195 1205 "Python %s\n at: %s",
1196 1206 versionerrortext, hexversion, Py_GetVersion(),
1197 1207 Py_GetProgramFullPath());
1198 1208 return -1;
1199 1209 }
1200 1210 return 0;
1201 1211 }
1202 1212
1203 1213 #ifdef IS_PY3K
1204 1214 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1205 1215 parsers_doc, -1, methods};
1206 1216
1207 1217 PyMODINIT_FUNC PyInit_parsers(void)
1208 1218 {
1209 1219 PyObject *mod;
1210 1220
1211 1221 if (check_python_version() == -1)
1212 1222 return NULL;
1213 1223 mod = PyModule_Create(&parsers_module);
1214 1224 module_init(mod);
1215 1225 return mod;
1216 1226 }
1217 1227 #else
1218 1228 PyMODINIT_FUNC initparsers(void)
1219 1229 {
1220 1230 PyObject *mod;
1221 1231
1222 1232 if (check_python_version() == -1) {
1223 1233 return;
1224 1234 }
1225 1235 mod = Py_InitModule3("parsers", methods, parsers_doc);
1226 1236 module_init(mod);
1227 1237 }
1228 1238 #endif
@@ -1,792 +1,797 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 hold multiple attributes
52 52
53 53 # about file tracking
54 54 - wc_tracked: is the file tracked by the working copy
55 55 - p1_tracked: is the file tracked in working copy first parent
56 56 - p2_info: the file has been involved in some merge operation. Either
57 57 because it was actually merged, or because the p2 version was
58 58 ahead, or because some renamed moved it there. In either case
59 59 `hg status` will want it displayed as modified.
60 60
61 61 # about the file state expected from p1 manifest:
62 62 - mode: the file mode in p1
63 63 - size: the file size in p1
64 64
65 65 These value can be set to None, which mean we don't have a meaningful value
66 66 to compare with. Either because we don't really care about them as there
67 67 `status` is known without having to look at the disk or because we don't
68 68 know these right now and a full comparison will be needed to find out if
69 69 the file is clean.
70 70
71 71 # about the file state on disk last time we saw it:
72 72 - mtime: the last known clean mtime for the file.
73 73
74 74 This value can be set to None if no cachable state exist. Either because we
75 75 do not care (see previous section) or because we could not cache something
76 76 yet.
77 77 """
78 78
79 79 _wc_tracked = attr.ib()
80 80 _p1_tracked = attr.ib()
81 81 _p2_info = attr.ib()
82 82 _mode = attr.ib()
83 83 _size = attr.ib()
84 84 _mtime = attr.ib()
85 85
86 86 def __init__(
87 87 self,
88 88 wc_tracked=False,
89 89 p1_tracked=False,
90 90 p2_info=False,
91 91 has_meaningful_data=True,
92 92 has_meaningful_mtime=True,
93 93 parentfiledata=None,
94 94 ):
95 95 self._wc_tracked = wc_tracked
96 96 self._p1_tracked = p1_tracked
97 97 self._p2_info = p2_info
98 98
99 99 self._mode = None
100 100 self._size = None
101 101 self._mtime = None
102 102 if parentfiledata is None:
103 103 has_meaningful_mtime = False
104 104 has_meaningful_data = False
105 105 if has_meaningful_data:
106 106 self._mode = parentfiledata[0]
107 107 self._size = parentfiledata[1]
108 108 if has_meaningful_mtime:
109 109 self._mtime = parentfiledata[2]
110 110
111 111 @classmethod
112 112 def new_added(cls):
113 113 """constructor to help legacy API to build a new "added" item
114 114
115 115 Should eventually be removed
116 116 """
117 117 return cls(wc_tracked=True)
118 118
119 119 @classmethod
120 120 def new_merged(cls):
121 121 """constructor to help legacy API to build a new "merged" item
122 122
123 123 Should eventually be removed
124 124 """
125 125 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
126 126
127 127 @classmethod
128 128 def new_from_p2(cls):
129 129 """constructor to help legacy API to build a new "from_p2" item
130 130
131 131 Should eventually be removed
132 132 """
133 133 return cls(wc_tracked=True, p2_info=True)
134 134
135 135 @classmethod
136 136 def new_possibly_dirty(cls):
137 137 """constructor to help legacy API to build a new "possibly_dirty" item
138 138
139 139 Should eventually be removed
140 140 """
141 141 return cls(wc_tracked=True, p1_tracked=True)
142 142
143 143 @classmethod
144 144 def new_normal(cls, mode, size, mtime):
145 145 """constructor to help legacy API to build a new "normal" item
146 146
147 147 Should eventually be removed
148 148 """
149 149 assert size != FROM_P2
150 150 assert size != NONNORMAL
151 151 return cls(
152 152 wc_tracked=True,
153 153 p1_tracked=True,
154 154 parentfiledata=(mode, size, mtime),
155 155 )
156 156
157 157 @classmethod
158 158 def from_v1_data(cls, state, mode, size, mtime):
159 159 """Build a new DirstateItem object from V1 data
160 160
161 161 Since the dirstate-v1 format is frozen, the signature of this function
162 162 is not expected to change, unlike the __init__ one.
163 163 """
164 164 if state == b'm':
165 165 return cls.new_merged()
166 166 elif state == b'a':
167 167 return cls.new_added()
168 168 elif state == b'r':
169 169 if size == NONNORMAL:
170 170 p1_tracked = True
171 171 p2_info = True
172 172 elif size == FROM_P2:
173 173 p1_tracked = False
174 174 p2_info = True
175 175 else:
176 176 p1_tracked = True
177 177 p2_info = False
178 178 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
179 179 elif state == b'n':
180 180 if size == FROM_P2:
181 181 return cls.new_from_p2()
182 182 elif size == NONNORMAL:
183 183 return cls.new_possibly_dirty()
184 184 elif mtime == AMBIGUOUS_TIME:
185 185 instance = cls.new_normal(mode, size, 42)
186 186 instance._mtime = None
187 187 return instance
188 188 else:
189 189 return cls.new_normal(mode, size, mtime)
190 190 else:
191 191 raise RuntimeError(b'unknown state: %s' % state)
192 192
193 193 def set_possibly_dirty(self):
194 194 """Mark a file as "possibly dirty"
195 195
196 196 This means the next status call will have to actually check its content
197 197 to make sure it is correct.
198 198 """
199 199 self._mtime = None
200 200
201 201 def set_clean(self, mode, size, mtime):
202 202 """mark a file as "clean" cancelling potential "possibly dirty call"
203 203
204 204 Note: this function is a descendant of `dirstate.normal` and is
205 205 currently expected to be call on "normal" entry only. There are not
206 206 reason for this to not change in the future as long as the ccode is
207 207 updated to preserve the proper state of the non-normal files.
208 208 """
209 209 self._wc_tracked = True
210 210 self._p1_tracked = True
211 211 self._mode = mode
212 212 self._size = size
213 213 self._mtime = mtime
214 214
215 215 def set_tracked(self):
216 216 """mark a file as tracked in the working copy
217 217
218 218 This will ultimately be called by command like `hg add`.
219 219 """
220 220 self._wc_tracked = True
221 221 # `set_tracked` is replacing various `normallookup` call. So we mark
222 222 # the files as needing lookup
223 223 #
224 224 # Consider dropping this in the future in favor of something less broad.
225 225 self._mtime = None
226 226
227 227 def set_untracked(self):
228 228 """mark a file as untracked in the working copy
229 229
230 230 This will ultimately be called by command like `hg remove`.
231 231 """
232 232 self._wc_tracked = False
233 233 self._mode = None
234 234 self._size = None
235 235 self._mtime = None
236 236
237 237 def drop_merge_data(self):
238 238 """remove all "merge-only" from a DirstateItem
239 239
240 240 This is to be call by the dirstatemap code when the second parent is dropped
241 241 """
242 242 if self._p2_info:
243 243 self._p2_info = False
244 244 self._mode = None
245 245 self._size = None
246 246 self._mtime = None
247 247
248 248 @property
249 249 def mode(self):
250 250 return self.v1_mode()
251 251
252 252 @property
253 253 def size(self):
254 254 return self.v1_size()
255 255
256 256 @property
257 257 def mtime(self):
258 258 return self.v1_mtime()
259 259
260 260 @property
261 261 def state(self):
262 262 """
263 263 States are:
264 264 n normal
265 265 m needs merging
266 266 r marked for removal
267 267 a marked for addition
268 268
269 269 XXX This "state" is a bit obscure and mostly a direct expression of the
270 270 dirstatev1 format. It would make sense to ultimately deprecate it in
271 271 favor of the more "semantic" attributes.
272 272 """
273 273 if not self.any_tracked:
274 274 return b'?'
275 275 return self.v1_state()
276 276
277 277 @property
278 278 def tracked(self):
279 279 """True is the file is tracked in the working copy"""
280 280 return self._wc_tracked
281 281
282 282 @property
283 283 def any_tracked(self):
284 284 """True is the file is tracked anywhere (wc or parents)"""
285 285 return self._wc_tracked or self._p1_tracked or self._p2_info
286 286
287 287 @property
288 288 def added(self):
289 289 """True if the file has been added"""
290 290 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
291 291
292 292 @property
293 293 def maybe_clean(self):
294 294 """True if the file has a chance to be in the "clean" state"""
295 295 if not self._wc_tracked:
296 296 return False
297 297 elif not self._p1_tracked:
298 298 return False
299 299 elif self._p2_info:
300 300 return False
301 301 return True
302 302
303 303 @property
304 def p1_tracked(self):
305 """True if the file is tracked in the first parent manifest"""
306 return self._p1_tracked
307
308 @property
304 309 def p2_info(self):
305 310 """True if the file needed to merge or apply any input from p2
306 311
307 312 See the class documentation for details.
308 313 """
309 314 return self._wc_tracked and self._p2_info
310 315
311 316 @property
312 317 def merged(self):
313 318 """True if the file has been merged
314 319
315 320 Should only be set if a merge is in progress in the dirstate
316 321 """
317 322 return self._wc_tracked and self._p1_tracked and self._p2_info
318 323
319 324 @property
320 325 def from_p2(self):
321 326 """True if the file have been fetched from p2 during the current merge
322 327
323 328 This is only True is the file is currently tracked.
324 329
325 330 Should only be set if a merge is in progress in the dirstate
326 331 """
327 332 return self._wc_tracked and (not self._p1_tracked) and self._p2_info
328 333
329 334 @property
330 335 def removed(self):
331 336 """True if the file has been removed"""
332 337 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
333 338
334 339 def v1_state(self):
335 340 """return a "state" suitable for v1 serialization"""
336 341 if not self.any_tracked:
337 342 # the object has no state to record, this is -currently-
338 343 # unsupported
339 344 raise RuntimeError('untracked item')
340 345 elif self.removed:
341 346 return b'r'
342 347 elif self.merged:
343 348 return b'm'
344 349 elif self.added:
345 350 return b'a'
346 351 else:
347 352 return b'n'
348 353
349 354 def v1_mode(self):
350 355 """return a "mode" suitable for v1 serialization"""
351 356 return self._mode if self._mode is not None else 0
352 357
353 358 def v1_size(self):
354 359 """return a "size" suitable for v1 serialization"""
355 360 if not self.any_tracked:
356 361 # the object has no state to record, this is -currently-
357 362 # unsupported
358 363 raise RuntimeError('untracked item')
359 364 elif self.removed and self._p1_tracked and self._p2_info:
360 365 return NONNORMAL
361 366 elif self.removed and self._p2_info:
362 367 return FROM_P2
363 368 elif self.removed:
364 369 return 0
365 370 elif self.merged:
366 371 return FROM_P2
367 372 elif self.added:
368 373 return NONNORMAL
369 374 elif self.from_p2:
370 375 return FROM_P2
371 376 elif self._size is None:
372 377 return NONNORMAL
373 378 else:
374 379 return self._size
375 380
376 381 def v1_mtime(self):
377 382 """return a "mtime" suitable for v1 serialization"""
378 383 if not self.any_tracked:
379 384 # the object has no state to record, this is -currently-
380 385 # unsupported
381 386 raise RuntimeError('untracked item')
382 387 elif self.removed:
383 388 return 0
384 389 elif self._mtime is None:
385 390 return AMBIGUOUS_TIME
386 391 elif self._p2_info:
387 392 return AMBIGUOUS_TIME
388 393 elif not self._p1_tracked:
389 394 return AMBIGUOUS_TIME
390 395 else:
391 396 return self._mtime
392 397
393 398 def need_delay(self, now):
394 399 """True if the stored mtime would be ambiguous with the current time"""
395 400 return self.v1_state() == b'n' and self.v1_mtime() == now
396 401
397 402
398 403 def gettype(q):
399 404 return int(q & 0xFFFF)
400 405
401 406
402 407 class BaseIndexObject(object):
403 408 # Can I be passed to an algorithme implemented in Rust ?
404 409 rust_ext_compat = 0
405 410 # Format of an index entry according to Python's `struct` language
406 411 index_format = revlog_constants.INDEX_ENTRY_V1
407 412 # Size of a C unsigned long long int, platform independent
408 413 big_int_size = struct.calcsize(b'>Q')
409 414 # Size of a C long int, platform independent
410 415 int_size = struct.calcsize(b'>i')
411 416 # An empty index entry, used as a default value to be overridden, or nullrev
412 417 null_item = (
413 418 0,
414 419 0,
415 420 0,
416 421 -1,
417 422 -1,
418 423 -1,
419 424 -1,
420 425 sha1nodeconstants.nullid,
421 426 0,
422 427 0,
423 428 revlog_constants.COMP_MODE_INLINE,
424 429 revlog_constants.COMP_MODE_INLINE,
425 430 )
426 431
427 432 @util.propertycache
428 433 def entry_size(self):
429 434 return self.index_format.size
430 435
431 436 @property
432 437 def nodemap(self):
433 438 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
434 439 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
435 440 return self._nodemap
436 441
437 442 @util.propertycache
438 443 def _nodemap(self):
439 444 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
440 445 for r in range(0, len(self)):
441 446 n = self[r][7]
442 447 nodemap[n] = r
443 448 return nodemap
444 449
445 450 def has_node(self, node):
446 451 """return True if the node exist in the index"""
447 452 return node in self._nodemap
448 453
449 454 def rev(self, node):
450 455 """return a revision for a node
451 456
452 457 If the node is unknown, raise a RevlogError"""
453 458 return self._nodemap[node]
454 459
455 460 def get_rev(self, node):
456 461 """return a revision for a node
457 462
458 463 If the node is unknown, return None"""
459 464 return self._nodemap.get(node)
460 465
461 466 def _stripnodes(self, start):
462 467 if '_nodemap' in vars(self):
463 468 for r in range(start, len(self)):
464 469 n = self[r][7]
465 470 del self._nodemap[n]
466 471
467 472 def clearcaches(self):
468 473 self.__dict__.pop('_nodemap', None)
469 474
470 475 def __len__(self):
471 476 return self._lgt + len(self._extra)
472 477
473 478 def append(self, tup):
474 479 if '_nodemap' in vars(self):
475 480 self._nodemap[tup[7]] = len(self)
476 481 data = self._pack_entry(len(self), tup)
477 482 self._extra.append(data)
478 483
479 484 def _pack_entry(self, rev, entry):
480 485 assert entry[8] == 0
481 486 assert entry[9] == 0
482 487 return self.index_format.pack(*entry[:8])
483 488
484 489 def _check_index(self, i):
485 490 if not isinstance(i, int):
486 491 raise TypeError(b"expecting int indexes")
487 492 if i < 0 or i >= len(self):
488 493 raise IndexError
489 494
490 495 def __getitem__(self, i):
491 496 if i == -1:
492 497 return self.null_item
493 498 self._check_index(i)
494 499 if i >= self._lgt:
495 500 data = self._extra[i - self._lgt]
496 501 else:
497 502 index = self._calculate_index(i)
498 503 data = self._data[index : index + self.entry_size]
499 504 r = self._unpack_entry(i, data)
500 505 if self._lgt and i == 0:
501 506 offset = revlogutils.offset_type(0, gettype(r[0]))
502 507 r = (offset,) + r[1:]
503 508 return r
504 509
505 510 def _unpack_entry(self, rev, data):
506 511 r = self.index_format.unpack(data)
507 512 r = r + (
508 513 0,
509 514 0,
510 515 revlog_constants.COMP_MODE_INLINE,
511 516 revlog_constants.COMP_MODE_INLINE,
512 517 )
513 518 return r
514 519
515 520 def pack_header(self, header):
516 521 """pack header information as binary"""
517 522 v_fmt = revlog_constants.INDEX_HEADER
518 523 return v_fmt.pack(header)
519 524
520 525 def entry_binary(self, rev):
521 526 """return the raw binary string representing a revision"""
522 527 entry = self[rev]
523 528 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
524 529 if rev == 0:
525 530 p = p[revlog_constants.INDEX_HEADER.size :]
526 531 return p
527 532
528 533
529 534 class IndexObject(BaseIndexObject):
530 535 def __init__(self, data):
531 536 assert len(data) % self.entry_size == 0, (
532 537 len(data),
533 538 self.entry_size,
534 539 len(data) % self.entry_size,
535 540 )
536 541 self._data = data
537 542 self._lgt = len(data) // self.entry_size
538 543 self._extra = []
539 544
540 545 def _calculate_index(self, i):
541 546 return i * self.entry_size
542 547
543 548 def __delitem__(self, i):
544 549 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
545 550 raise ValueError(b"deleting slices only supports a:-1 with step 1")
546 551 i = i.start
547 552 self._check_index(i)
548 553 self._stripnodes(i)
549 554 if i < self._lgt:
550 555 self._data = self._data[: i * self.entry_size]
551 556 self._lgt = i
552 557 self._extra = []
553 558 else:
554 559 self._extra = self._extra[: i - self._lgt]
555 560
556 561
557 562 class PersistentNodeMapIndexObject(IndexObject):
558 563 """a Debug oriented class to test persistent nodemap
559 564
560 565 We need a simple python object to test API and higher level behavior. See
561 566 the Rust implementation for more serious usage. This should be used only
562 567 through the dedicated `devel.persistent-nodemap` config.
563 568 """
564 569
565 570 def nodemap_data_all(self):
566 571 """Return bytes containing a full serialization of a nodemap
567 572
568 573 The nodemap should be valid for the full set of revisions in the
569 574 index."""
570 575 return nodemaputil.persistent_data(self)
571 576
572 577 def nodemap_data_incremental(self):
573 578 """Return bytes containing a incremental update to persistent nodemap
574 579
575 580 This containst the data for an append-only update of the data provided
576 581 in the last call to `update_nodemap_data`.
577 582 """
578 583 if self._nm_root is None:
579 584 return None
580 585 docket = self._nm_docket
581 586 changed, data = nodemaputil.update_persistent_data(
582 587 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
583 588 )
584 589
585 590 self._nm_root = self._nm_max_idx = self._nm_docket = None
586 591 return docket, changed, data
587 592
588 593 def update_nodemap_data(self, docket, nm_data):
589 594 """provide full block of persisted binary data for a nodemap
590 595
591 596 The data are expected to come from disk. See `nodemap_data_all` for a
592 597 produceur of such data."""
593 598 if nm_data is not None:
594 599 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
595 600 if self._nm_root:
596 601 self._nm_docket = docket
597 602 else:
598 603 self._nm_root = self._nm_max_idx = self._nm_docket = None
599 604
600 605
601 606 class InlinedIndexObject(BaseIndexObject):
602 607 def __init__(self, data, inline=0):
603 608 self._data = data
604 609 self._lgt = self._inline_scan(None)
605 610 self._inline_scan(self._lgt)
606 611 self._extra = []
607 612
608 613 def _inline_scan(self, lgt):
609 614 off = 0
610 615 if lgt is not None:
611 616 self._offsets = [0] * lgt
612 617 count = 0
613 618 while off <= len(self._data) - self.entry_size:
614 619 start = off + self.big_int_size
615 620 (s,) = struct.unpack(
616 621 b'>i',
617 622 self._data[start : start + self.int_size],
618 623 )
619 624 if lgt is not None:
620 625 self._offsets[count] = off
621 626 count += 1
622 627 off += self.entry_size + s
623 628 if off != len(self._data):
624 629 raise ValueError(b"corrupted data")
625 630 return count
626 631
627 632 def __delitem__(self, i):
628 633 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
629 634 raise ValueError(b"deleting slices only supports a:-1 with step 1")
630 635 i = i.start
631 636 self._check_index(i)
632 637 self._stripnodes(i)
633 638 if i < self._lgt:
634 639 self._offsets = self._offsets[:i]
635 640 self._lgt = i
636 641 self._extra = []
637 642 else:
638 643 self._extra = self._extra[: i - self._lgt]
639 644
640 645 def _calculate_index(self, i):
641 646 return self._offsets[i]
642 647
643 648
644 649 def parse_index2(data, inline, revlogv2=False):
645 650 if not inline:
646 651 cls = IndexObject2 if revlogv2 else IndexObject
647 652 return cls(data), None
648 653 cls = InlinedIndexObject
649 654 return cls(data, inline), (0, data)
650 655
651 656
652 657 def parse_index_cl_v2(data):
653 658 return IndexChangelogV2(data), None
654 659
655 660
656 661 class IndexObject2(IndexObject):
657 662 index_format = revlog_constants.INDEX_ENTRY_V2
658 663
659 664 def replace_sidedata_info(
660 665 self,
661 666 rev,
662 667 sidedata_offset,
663 668 sidedata_length,
664 669 offset_flags,
665 670 compression_mode,
666 671 ):
667 672 """
668 673 Replace an existing index entry's sidedata offset and length with new
669 674 ones.
670 675 This cannot be used outside of the context of sidedata rewriting,
671 676 inside the transaction that creates the revision `rev`.
672 677 """
673 678 if rev < 0:
674 679 raise KeyError
675 680 self._check_index(rev)
676 681 if rev < self._lgt:
677 682 msg = b"cannot rewrite entries outside of this transaction"
678 683 raise KeyError(msg)
679 684 else:
680 685 entry = list(self[rev])
681 686 entry[0] = offset_flags
682 687 entry[8] = sidedata_offset
683 688 entry[9] = sidedata_length
684 689 entry[11] = compression_mode
685 690 entry = tuple(entry)
686 691 new = self._pack_entry(rev, entry)
687 692 self._extra[rev - self._lgt] = new
688 693
689 694 def _unpack_entry(self, rev, data):
690 695 data = self.index_format.unpack(data)
691 696 entry = data[:10]
692 697 data_comp = data[10] & 3
693 698 sidedata_comp = (data[10] & (3 << 2)) >> 2
694 699 return entry + (data_comp, sidedata_comp)
695 700
696 701 def _pack_entry(self, rev, entry):
697 702 data = entry[:10]
698 703 data_comp = entry[10] & 3
699 704 sidedata_comp = (entry[11] & 3) << 2
700 705 data += (data_comp | sidedata_comp,)
701 706
702 707 return self.index_format.pack(*data)
703 708
704 709 def entry_binary(self, rev):
705 710 """return the raw binary string representing a revision"""
706 711 entry = self[rev]
707 712 return self._pack_entry(rev, entry)
708 713
709 714 def pack_header(self, header):
710 715 """pack header information as binary"""
711 716 msg = 'version header should go in the docket, not the index: %d'
712 717 msg %= header
713 718 raise error.ProgrammingError(msg)
714 719
715 720
716 721 class IndexChangelogV2(IndexObject2):
717 722 index_format = revlog_constants.INDEX_ENTRY_CL_V2
718 723
719 724 def _unpack_entry(self, rev, data, r=True):
720 725 items = self.index_format.unpack(data)
721 726 entry = items[:3] + (rev, rev) + items[3:8]
722 727 data_comp = items[8] & 3
723 728 sidedata_comp = (items[8] >> 2) & 3
724 729 return entry + (data_comp, sidedata_comp)
725 730
726 731 def _pack_entry(self, rev, entry):
727 732 assert entry[3] == rev, entry[3]
728 733 assert entry[4] == rev, entry[4]
729 734 data = entry[:3] + entry[5:10]
730 735 data_comp = entry[10] & 3
731 736 sidedata_comp = (entry[11] & 3) << 2
732 737 data += (data_comp | sidedata_comp,)
733 738 return self.index_format.pack(*data)
734 739
735 740
736 741 def parse_index_devel_nodemap(data, inline):
737 742 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
738 743 return PersistentNodeMapIndexObject(data), None
739 744
740 745
741 746 def parse_dirstate(dmap, copymap, st):
742 747 parents = [st[:20], st[20:40]]
743 748 # dereference fields so they will be local in loop
744 749 format = b">cllll"
745 750 e_size = struct.calcsize(format)
746 751 pos1 = 40
747 752 l = len(st)
748 753
749 754 # the inner loop
750 755 while pos1 < l:
751 756 pos2 = pos1 + e_size
752 757 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
753 758 pos1 = pos2 + e[4]
754 759 f = st[pos2:pos1]
755 760 if b'\0' in f:
756 761 f, c = f.split(b'\0')
757 762 copymap[f] = c
758 763 dmap[f] = DirstateItem.from_v1_data(*e[:4])
759 764 return parents
760 765
761 766
762 767 def pack_dirstate(dmap, copymap, pl, now):
763 768 now = int(now)
764 769 cs = stringio()
765 770 write = cs.write
766 771 write(b"".join(pl))
767 772 for f, e in pycompat.iteritems(dmap):
768 773 if e.need_delay(now):
769 774 # The file was last modified "simultaneously" with the current
770 775 # write to dirstate (i.e. within the same second for file-
771 776 # systems with a granularity of 1 sec). This commonly happens
772 777 # for at least a couple of files on 'update'.
773 778 # The user could change the file without changing its size
774 779 # within the same second. Invalidate the file's mtime in
775 780 # dirstate, forcing future 'status' calls to compare the
776 781 # contents of the file if the size is the same. This prevents
777 782 # mistakenly treating such files as clean.
778 783 e.set_possibly_dirty()
779 784
780 785 if f in copymap:
781 786 f = b"%s\0%s" % (f, copymap[f])
782 787 e = _pack(
783 788 b">cllll",
784 789 e.v1_state(),
785 790 e.v1_mode(),
786 791 e.v1_size(),
787 792 e.v1_mtime(),
788 793 len(f),
789 794 )
790 795 write(e)
791 796 write(f)
792 797 return cs.getvalue()
@@ -1,433 +1,437 b''
1 1 use crate::errors::HgError;
2 2 use bitflags::bitflags;
3 3 use std::convert::TryFrom;
4 4
5 5 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
6 6 pub enum EntryState {
7 7 Normal,
8 8 Added,
9 9 Removed,
10 10 Merged,
11 11 }
12 12
13 13 /// The C implementation uses all signed types. This will be an issue
14 14 /// either when 4GB+ source files are commonplace or in 2038, whichever
15 15 /// comes first.
16 16 #[derive(Debug, PartialEq, Copy, Clone)]
17 17 pub struct DirstateEntry {
18 18 pub(crate) flags: Flags,
19 19 mode_size: Option<(i32, i32)>,
20 20 mtime: Option<i32>,
21 21 }
22 22
23 23 bitflags! {
24 24 pub(crate) struct Flags: u8 {
25 25 const WDIR_TRACKED = 1 << 0;
26 26 const P1_TRACKED = 1 << 1;
27 27 const P2_INFO = 1 << 2;
28 28 }
29 29 }
30 30
31 31 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
32 32
33 33 pub const MTIME_UNSET: i32 = -1;
34 34
35 35 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
36 36 /// other parent. This allows revert to pick the right status back during a
37 37 /// merge.
38 38 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
39 39 /// A special value used for internal representation of special case in
40 40 /// dirstate v1 format.
41 41 pub const SIZE_NON_NORMAL: i32 = -1;
42 42
43 43 impl DirstateEntry {
44 44 pub fn from_v2_data(
45 45 wdir_tracked: bool,
46 46 p1_tracked: bool,
47 47 p2_info: bool,
48 48 mode_size: Option<(i32, i32)>,
49 49 mtime: Option<i32>,
50 50 ) -> Self {
51 51 let mut flags = Flags::empty();
52 52 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
53 53 flags.set(Flags::P1_TRACKED, p1_tracked);
54 54 flags.set(Flags::P2_INFO, p2_info);
55 55 Self {
56 56 flags,
57 57 mode_size,
58 58 mtime,
59 59 }
60 60 }
61 61
62 62 pub fn from_v1_data(
63 63 state: EntryState,
64 64 mode: i32,
65 65 size: i32,
66 66 mtime: i32,
67 67 ) -> Self {
68 68 match state {
69 69 EntryState::Normal => {
70 70 if size == SIZE_FROM_OTHER_PARENT {
71 71 Self::new_from_p2()
72 72 } else if size == SIZE_NON_NORMAL {
73 73 Self::new_possibly_dirty()
74 74 } else if mtime == MTIME_UNSET {
75 75 Self {
76 76 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
77 77 mode_size: Some((mode, size)),
78 78 mtime: None,
79 79 }
80 80 } else {
81 81 Self::new_normal(mode, size, mtime)
82 82 }
83 83 }
84 84 EntryState::Added => Self::new_added(),
85 85 EntryState::Removed => Self {
86 86 flags: if size == SIZE_NON_NORMAL {
87 87 Flags::P1_TRACKED | Flags::P2_INFO
88 88 } else if size == SIZE_FROM_OTHER_PARENT {
89 89 // We don’t know if P1_TRACKED should be set (file history)
90 90 Flags::P2_INFO
91 91 } else {
92 92 Flags::P1_TRACKED
93 93 },
94 94 mode_size: None,
95 95 mtime: None,
96 96 },
97 97 EntryState::Merged => Self::new_merged(),
98 98 }
99 99 }
100 100
101 101 pub fn new_from_p2() -> Self {
102 102 Self {
103 103 // might be missing P1_TRACKED
104 104 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
105 105 mode_size: None,
106 106 mtime: None,
107 107 }
108 108 }
109 109
110 110 pub fn new_possibly_dirty() -> Self {
111 111 Self {
112 112 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
113 113 mode_size: None,
114 114 mtime: None,
115 115 }
116 116 }
117 117
118 118 pub fn new_added() -> Self {
119 119 Self {
120 120 flags: Flags::WDIR_TRACKED,
121 121 mode_size: None,
122 122 mtime: None,
123 123 }
124 124 }
125 125
126 126 pub fn new_merged() -> Self {
127 127 Self {
128 128 flags: Flags::WDIR_TRACKED
129 129 | Flags::P1_TRACKED // might not be true because of rename ?
130 130 | Flags::P2_INFO, // might not be true because of rename ?
131 131 mode_size: None,
132 132 mtime: None,
133 133 }
134 134 }
135 135
136 136 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
137 137 Self {
138 138 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
139 139 mode_size: Some((mode, size)),
140 140 mtime: Some(mtime),
141 141 }
142 142 }
143 143
144 144 /// Creates a new entry in "removed" state.
145 145 ///
146 146 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
147 147 /// `SIZE_FROM_OTHER_PARENT`
148 148 pub fn new_removed(size: i32) -> Self {
149 149 Self::from_v1_data(EntryState::Removed, 0, size, 0)
150 150 }
151 151
152 152 pub fn tracked(&self) -> bool {
153 153 self.flags.contains(Flags::WDIR_TRACKED)
154 154 }
155 155
156 pub fn p1_tracked(&self) -> bool {
157 self.flags.contains(Flags::P1_TRACKED)
158 }
159
156 160 fn in_either_parent(&self) -> bool {
157 161 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
158 162 }
159 163
160 164 pub fn removed(&self) -> bool {
161 165 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
162 166 }
163 167
164 168 pub fn p2_info(&self) -> bool {
165 169 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
166 170 }
167 171
168 172 pub fn merged(&self) -> bool {
169 173 self.flags
170 174 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
171 175 }
172 176
173 177 pub fn added(&self) -> bool {
174 178 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
175 179 }
176 180
177 181 pub fn from_p2(&self) -> bool {
178 182 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
179 183 && !self.flags.contains(Flags::P1_TRACKED)
180 184 }
181 185
182 186 pub fn maybe_clean(&self) -> bool {
183 187 if !self.flags.contains(Flags::WDIR_TRACKED) {
184 188 false
185 189 } else if !self.flags.contains(Flags::P1_TRACKED) {
186 190 false
187 191 } else if self.flags.contains(Flags::P2_INFO) {
188 192 false
189 193 } else {
190 194 true
191 195 }
192 196 }
193 197
194 198 pub fn any_tracked(&self) -> bool {
195 199 self.flags.intersects(
196 200 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
197 201 )
198 202 }
199 203
200 204 /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)`
201 205 pub(crate) fn v2_data(
202 206 &self,
203 207 ) -> (bool, bool, bool, Option<(i32, i32)>, Option<i32>) {
204 208 if !self.any_tracked() {
205 209 // TODO: return an Option instead?
206 210 panic!("Accessing v1_state of an untracked DirstateEntry")
207 211 }
208 212 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
209 213 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
210 214 let p2_info = self.flags.contains(Flags::P2_INFO);
211 215 let mode_size = self.mode_size;
212 216 let mtime = self.mtime;
213 217 (wdir_tracked, p1_tracked, p2_info, mode_size, mtime)
214 218 }
215 219
216 220 fn v1_state(&self) -> EntryState {
217 221 if !self.any_tracked() {
218 222 // TODO: return an Option instead?
219 223 panic!("Accessing v1_state of an untracked DirstateEntry")
220 224 }
221 225 if self.removed() {
222 226 EntryState::Removed
223 227 } else if self.merged() {
224 228 EntryState::Merged
225 229 } else if self.added() {
226 230 EntryState::Added
227 231 } else {
228 232 EntryState::Normal
229 233 }
230 234 }
231 235
232 236 fn v1_mode(&self) -> i32 {
233 237 if let Some((mode, _size)) = self.mode_size {
234 238 mode
235 239 } else {
236 240 0
237 241 }
238 242 }
239 243
240 244 fn v1_size(&self) -> i32 {
241 245 if !self.any_tracked() {
242 246 // TODO: return an Option instead?
243 247 panic!("Accessing v1_size of an untracked DirstateEntry")
244 248 }
245 249 if self.removed()
246 250 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
247 251 {
248 252 SIZE_NON_NORMAL
249 253 } else if self.removed() && self.flags.contains(Flags::P2_INFO) {
250 254 SIZE_FROM_OTHER_PARENT
251 255 } else if self.removed() {
252 256 0
253 257 } else if self.merged() {
254 258 SIZE_FROM_OTHER_PARENT
255 259 } else if self.added() {
256 260 SIZE_NON_NORMAL
257 261 } else if self.from_p2() {
258 262 SIZE_FROM_OTHER_PARENT
259 263 } else if let Some((_mode, size)) = self.mode_size {
260 264 size
261 265 } else {
262 266 SIZE_NON_NORMAL
263 267 }
264 268 }
265 269
266 270 fn v1_mtime(&self) -> i32 {
267 271 if !self.any_tracked() {
268 272 // TODO: return an Option instead?
269 273 panic!("Accessing v1_mtime of an untracked DirstateEntry")
270 274 }
271 275 if self.removed() {
272 276 0
273 277 } else if self.flags.contains(Flags::P2_INFO) {
274 278 MTIME_UNSET
275 279 } else if !self.flags.contains(Flags::P1_TRACKED) {
276 280 MTIME_UNSET
277 281 } else {
278 282 self.mtime.unwrap_or(MTIME_UNSET)
279 283 }
280 284 }
281 285
282 286 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
283 287 pub fn state(&self) -> EntryState {
284 288 self.v1_state()
285 289 }
286 290
287 291 // TODO: return Option?
288 292 pub fn mode(&self) -> i32 {
289 293 self.v1_mode()
290 294 }
291 295
292 296 // TODO: return Option?
293 297 pub fn size(&self) -> i32 {
294 298 self.v1_size()
295 299 }
296 300
297 301 // TODO: return Option?
298 302 pub fn mtime(&self) -> i32 {
299 303 self.v1_mtime()
300 304 }
301 305
302 306 pub fn drop_merge_data(&mut self) {
303 307 if self.flags.contains(Flags::P2_INFO) {
304 308 self.flags.remove(Flags::P2_INFO);
305 309 self.mode_size = None;
306 310 self.mtime = None;
307 311 }
308 312 }
309 313
310 314 pub fn set_possibly_dirty(&mut self) {
311 315 self.mtime = None
312 316 }
313 317
314 318 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
315 319 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
316 320 self.mode_size = Some((mode, size));
317 321 self.mtime = Some(mtime);
318 322 }
319 323
320 324 pub fn set_tracked(&mut self) {
321 325 self.flags.insert(Flags::WDIR_TRACKED);
322 326 // `set_tracked` is replacing various `normallookup` call. So we mark
323 327 // the files as needing lookup
324 328 //
325 329 // Consider dropping this in the future in favor of something less
326 330 // broad.
327 331 self.mtime = None;
328 332 }
329 333
330 334 pub fn set_untracked(&mut self) {
331 335 self.flags.remove(Flags::WDIR_TRACKED);
332 336 self.mode_size = None;
333 337 self.mtime = None;
334 338 }
335 339
336 340 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
337 341 /// in the dirstate-v1 format.
338 342 ///
339 343 /// This includes marker values such as `mtime == -1`. In the future we may
340 344 /// want to not represent these cases that way in memory, but serialization
341 345 /// will need to keep the same format.
342 346 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
343 347 (
344 348 self.v1_state().into(),
345 349 self.v1_mode(),
346 350 self.v1_size(),
347 351 self.v1_mtime(),
348 352 )
349 353 }
350 354
351 355 pub(crate) fn is_from_other_parent(&self) -> bool {
352 356 self.state() == EntryState::Normal
353 357 && self.size() == SIZE_FROM_OTHER_PARENT
354 358 }
355 359
356 360 // TODO: other platforms
357 361 #[cfg(unix)]
358 362 pub fn mode_changed(
359 363 &self,
360 364 filesystem_metadata: &std::fs::Metadata,
361 365 ) -> bool {
362 366 use std::os::unix::fs::MetadataExt;
363 367 const EXEC_BIT_MASK: u32 = 0o100;
364 368 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
365 369 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
366 370 dirstate_exec_bit != fs_exec_bit
367 371 }
368 372
369 373 /// Returns a `(state, mode, size, mtime)` tuple as for
370 374 /// `DirstateMapMethods::debug_iter`.
371 375 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
372 376 (self.state().into(), self.mode(), self.size(), self.mtime())
373 377 }
374 378
375 379 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
376 380 self.state() == EntryState::Normal && self.mtime() == now
377 381 }
378 382
379 383 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
380 384 let ambiguous = self.mtime_is_ambiguous(now);
381 385 if ambiguous {
382 386 // The file was last modified "simultaneously" with the current
383 387 // write to dirstate (i.e. within the same second for file-
384 388 // systems with a granularity of 1 sec). This commonly happens
385 389 // for at least a couple of files on 'update'.
386 390 // The user could change the file without changing its size
387 391 // within the same second. Invalidate the file's mtime in
388 392 // dirstate, forcing future 'status' calls to compare the
389 393 // contents of the file if the size is the same. This prevents
390 394 // mistakenly treating such files as clean.
391 395 self.set_possibly_dirty()
392 396 }
393 397 ambiguous
394 398 }
395 399 }
396 400
397 401 impl EntryState {
398 402 pub fn is_tracked(self) -> bool {
399 403 use EntryState::*;
400 404 match self {
401 405 Normal | Added | Merged => true,
402 406 Removed => false,
403 407 }
404 408 }
405 409 }
406 410
407 411 impl TryFrom<u8> for EntryState {
408 412 type Error = HgError;
409 413
410 414 fn try_from(value: u8) -> Result<Self, Self::Error> {
411 415 match value {
412 416 b'n' => Ok(EntryState::Normal),
413 417 b'a' => Ok(EntryState::Added),
414 418 b'r' => Ok(EntryState::Removed),
415 419 b'm' => Ok(EntryState::Merged),
416 420 _ => Err(HgError::CorruptedRepository(format!(
417 421 "Incorrect dirstate entry state {}",
418 422 value
419 423 ))),
420 424 }
421 425 }
422 426 }
423 427
424 428 impl Into<u8> for EntryState {
425 429 fn into(self) -> u8 {
426 430 match self {
427 431 EntryState::Normal => b'n',
428 432 EntryState::Added => b'a',
429 433 EntryState::Removed => b'r',
430 434 EntryState::Merged => b'm',
431 435 }
432 436 }
433 437 }
@@ -1,225 +1,230 b''
1 1 use cpython::exc;
2 2 use cpython::PyBytes;
3 3 use cpython::PyErr;
4 4 use cpython::PyNone;
5 5 use cpython::PyObject;
6 6 use cpython::PyResult;
7 7 use cpython::Python;
8 8 use cpython::PythonObject;
9 9 use hg::dirstate::DirstateEntry;
10 10 use hg::dirstate::EntryState;
11 11 use std::cell::Cell;
12 12 use std::convert::TryFrom;
13 13
14 14 py_class!(pub class DirstateItem |py| {
15 15 data entry: Cell<DirstateEntry>;
16 16
17 17 def __new__(
18 18 _cls,
19 19 wc_tracked: bool = false,
20 20 p1_tracked: bool = false,
21 21 p2_info: bool = false,
22 22 has_meaningful_data: bool = true,
23 23 has_meaningful_mtime: bool = true,
24 24 parentfiledata: Option<(i32, i32, i32)> = None,
25 25
26 26 ) -> PyResult<DirstateItem> {
27 27 let mut mode_size_opt = None;
28 28 let mut mtime_opt = None;
29 29 if let Some((mode, size, mtime)) = parentfiledata {
30 30 if has_meaningful_data {
31 31 mode_size_opt = Some((mode, size))
32 32 }
33 33 if has_meaningful_mtime {
34 34 mtime_opt = Some(mtime)
35 35 }
36 36 }
37 37 let entry = DirstateEntry::from_v2_data(
38 38 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
39 39 );
40 40 DirstateItem::create_instance(py, Cell::new(entry))
41 41 }
42 42
43 43 @property
44 44 def state(&self) -> PyResult<PyBytes> {
45 45 let state_byte: u8 = self.entry(py).get().state().into();
46 46 Ok(PyBytes::new(py, &[state_byte]))
47 47 }
48 48
49 49 @property
50 50 def mode(&self) -> PyResult<i32> {
51 51 Ok(self.entry(py).get().mode())
52 52 }
53 53
54 54 @property
55 55 def size(&self) -> PyResult<i32> {
56 56 Ok(self.entry(py).get().size())
57 57 }
58 58
59 59 @property
60 60 def mtime(&self) -> PyResult<i32> {
61 61 Ok(self.entry(py).get().mtime())
62 62 }
63 63
64 64 @property
65 65 def tracked(&self) -> PyResult<bool> {
66 66 Ok(self.entry(py).get().tracked())
67 67 }
68 68
69 69 @property
70 def p1_tracked(&self) -> PyResult<bool> {
71 Ok(self.entry(py).get().p1_tracked())
72 }
73
74 @property
70 75 def added(&self) -> PyResult<bool> {
71 76 Ok(self.entry(py).get().added())
72 77 }
73 78
74 79
75 80 @property
76 81 def p2_info(&self) -> PyResult<bool> {
77 82 Ok(self.entry(py).get().p2_info())
78 83 }
79 84
80 85 @property
81 86 def merged(&self) -> PyResult<bool> {
82 87 Ok(self.entry(py).get().merged())
83 88 }
84 89
85 90 @property
86 91 def removed(&self) -> PyResult<bool> {
87 92 Ok(self.entry(py).get().removed())
88 93 }
89 94
90 95 @property
91 96 def from_p2(&self) -> PyResult<bool> {
92 97 Ok(self.entry(py).get().from_p2())
93 98 }
94 99
95 100 @property
96 101 def maybe_clean(&self) -> PyResult<bool> {
97 102 Ok(self.entry(py).get().maybe_clean())
98 103 }
99 104
100 105 @property
101 106 def any_tracked(&self) -> PyResult<bool> {
102 107 Ok(self.entry(py).get().any_tracked())
103 108 }
104 109
105 110 def v1_state(&self) -> PyResult<PyBytes> {
106 111 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
107 112 let state_byte: u8 = state.into();
108 113 Ok(PyBytes::new(py, &[state_byte]))
109 114 }
110 115
111 116 def v1_mode(&self) -> PyResult<i32> {
112 117 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
113 118 Ok(mode)
114 119 }
115 120
116 121 def v1_size(&self) -> PyResult<i32> {
117 122 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
118 123 Ok(size)
119 124 }
120 125
121 126 def v1_mtime(&self) -> PyResult<i32> {
122 127 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
123 128 Ok(mtime)
124 129 }
125 130
126 131 def need_delay(&self, now: i32) -> PyResult<bool> {
127 132 Ok(self.entry(py).get().mtime_is_ambiguous(now))
128 133 }
129 134
130 135 @classmethod
131 136 def from_v1_data(
132 137 _cls,
133 138 state: PyBytes,
134 139 mode: i32,
135 140 size: i32,
136 141 mtime: i32,
137 142 ) -> PyResult<Self> {
138 143 let state = <[u8; 1]>::try_from(state.data(py))
139 144 .ok()
140 145 .and_then(|state| EntryState::try_from(state[0]).ok())
141 146 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
142 147 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
143 148 DirstateItem::create_instance(py, Cell::new(entry))
144 149 }
145 150
146 151 @classmethod
147 152 def new_added(_cls) -> PyResult<Self> {
148 153 let entry = DirstateEntry::new_added();
149 154 DirstateItem::create_instance(py, Cell::new(entry))
150 155 }
151 156
152 157 @classmethod
153 158 def new_merged(_cls) -> PyResult<Self> {
154 159 let entry = DirstateEntry::new_merged();
155 160 DirstateItem::create_instance(py, Cell::new(entry))
156 161 }
157 162
158 163 @classmethod
159 164 def new_from_p2(_cls) -> PyResult<Self> {
160 165 let entry = DirstateEntry::new_from_p2();
161 166 DirstateItem::create_instance(py, Cell::new(entry))
162 167 }
163 168
164 169 @classmethod
165 170 def new_possibly_dirty(_cls) -> PyResult<Self> {
166 171 let entry = DirstateEntry::new_possibly_dirty();
167 172 DirstateItem::create_instance(py, Cell::new(entry))
168 173 }
169 174
170 175 @classmethod
171 176 def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult<Self> {
172 177 let entry = DirstateEntry::new_normal(mode, size, mtime);
173 178 DirstateItem::create_instance(py, Cell::new(entry))
174 179 }
175 180
176 181 def drop_merge_data(&self) -> PyResult<PyNone> {
177 182 self.update(py, |entry| entry.drop_merge_data());
178 183 Ok(PyNone)
179 184 }
180 185
181 186 def set_clean(
182 187 &self,
183 188 mode: i32,
184 189 size: i32,
185 190 mtime: i32,
186 191 ) -> PyResult<PyNone> {
187 192 self.update(py, |entry| entry.set_clean(mode, size, mtime));
188 193 Ok(PyNone)
189 194 }
190 195
191 196 def set_possibly_dirty(&self) -> PyResult<PyNone> {
192 197 self.update(py, |entry| entry.set_possibly_dirty());
193 198 Ok(PyNone)
194 199 }
195 200
196 201 def set_tracked(&self) -> PyResult<PyNone> {
197 202 self.update(py, |entry| entry.set_tracked());
198 203 Ok(PyNone)
199 204 }
200 205
201 206 def set_untracked(&self) -> PyResult<PyNone> {
202 207 self.update(py, |entry| entry.set_untracked());
203 208 Ok(PyNone)
204 209 }
205 210 });
206 211
207 212 impl DirstateItem {
208 213 pub fn new_as_pyobject(
209 214 py: Python<'_>,
210 215 entry: DirstateEntry,
211 216 ) -> PyResult<PyObject> {
212 217 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
213 218 }
214 219
215 220 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
216 221 self.entry(py).get()
217 222 }
218 223
219 224 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
220 225 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
221 226 let mut entry = self.entry(py).get();
222 227 f(&mut entry);
223 228 self.entry(py).set(entry)
224 229 }
225 230 }
General Comments 0
You need to be logged in to leave comments. Login now