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