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