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