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