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