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