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