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