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