##// END OF EJS Templates
dirstate-item: change the internal storage and constructor value...
marmoute -
r48950:38488d48 default
parent child Browse files
Show More
@@ -1,1247 +1,1217
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 12 #include <ctype.h>
13 13 #include <stddef.h>
14 14 #include <string.h>
15 15
16 16 #include "bitmanipulation.h"
17 17 #include "charencode.h"
18 18 #include "util.h"
19 19
20 20 #ifdef IS_PY3K
21 21 /* The mapping of Python types is meant to be temporary to get Python
22 22 * 3 to compile. We should remove this once Python 3 support is fully
23 23 * supported and proper types are used in the extensions themselves. */
24 24 #define PyInt_Check PyLong_Check
25 25 #define PyInt_FromLong PyLong_FromLong
26 26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 27 #define PyInt_AsLong PyLong_AsLong
28 28 #endif
29 29
30 30 static const char *const versionerrortext = "Python minor version mismatch";
31 31
32 32 static const int dirstate_v1_from_p2 = -2;
33 33 static const int dirstate_v1_nonnormal = -1;
34 34 static const int ambiguous_time = -1;
35 35
36 36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
37 37 {
38 38 Py_ssize_t expected_size;
39 39
40 40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
41 41 return NULL;
42 42 }
43 43
44 44 return _dict_new_presized(expected_size);
45 45 }
46 46
47 47 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
48 48 PyObject *kwds)
49 49 {
50 50 /* We do all the initialization here and not a tp_init function because
51 51 * dirstate_item is immutable. */
52 52 dirstateItemObject *t;
53 53 int wc_tracked;
54 54 int p1_tracked;
55 int p2_tracked;
56 int merged;
57 int clean_p1;
58 int clean_p2;
59 int possibly_dirty;
55 int p2_info;
56 int has_meaningful_data;
57 int has_meaningful_mtime;
58 int mode;
59 int size;
60 int mtime;
60 61 PyObject *parentfiledata;
61 62 static char *keywords_name[] = {
62 "wc_tracked", "p1_tracked", "p2_tracked",
63 "merged", "clean_p1", "clean_p2",
64 "possibly_dirty", "parentfiledata", NULL,
63 "wc_tracked",
64 "p1_tracked",
65 "p2_info",
66 "has_meaningful_data",
67 "has_meaningful_mtime",
68 "parentfiledata",
69 NULL,
65 70 };
66 71 wc_tracked = 0;
67 72 p1_tracked = 0;
68 p2_tracked = 0;
69 merged = 0;
70 clean_p1 = 0;
71 clean_p2 = 0;
72 possibly_dirty = 0;
73 p2_info = 0;
74 has_meaningful_mtime = 1;
75 has_meaningful_data = 1;
73 76 parentfiledata = Py_None;
74 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiiiO", keywords_name,
75 &wc_tracked, &p1_tracked, &p2_tracked,
76 &merged, &clean_p1, &clean_p2,
77 &possibly_dirty, &parentfiledata
78
79 )) {
80 return NULL;
81 }
82 if (merged && (clean_p1 || clean_p2)) {
83 PyErr_SetString(PyExc_RuntimeError,
84 "`merged` argument incompatible with "
85 "`clean_p1`/`clean_p2`");
77 if (!PyArg_ParseTupleAndKeywords(
78 args, kwds, "|iiiiiO", keywords_name, &wc_tracked, &p1_tracked,
79 &p2_info, &has_meaningful_data, &has_meaningful_mtime,
80 &parentfiledata)) {
86 81 return NULL;
87 82 }
88 83 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
89 84 if (!t) {
90 85 return NULL;
91 86 }
92 87
93 88 t->flags = 0;
94 89 if (wc_tracked) {
95 90 t->flags |= dirstate_flag_wc_tracked;
96 91 }
97 92 if (p1_tracked) {
98 93 t->flags |= dirstate_flag_p1_tracked;
99 94 }
100 if (p2_tracked) {
101 t->flags |= dirstate_flag_p2_tracked;
102 }
103 if (possibly_dirty) {
104 t->flags |= dirstate_flag_possibly_dirty;
105 }
106 if (merged) {
107 t->flags |= dirstate_flag_merged;
95 if (p2_info) {
96 t->flags |= dirstate_flag_p2_info;
108 97 }
109 if (clean_p1) {
110 t->flags |= dirstate_flag_clean_p1;
111 }
112 if (clean_p2) {
113 t->flags |= dirstate_flag_clean_p2;
114 }
115 t->mode = 0;
116 t->size = dirstate_v1_nonnormal;
117 t->mtime = ambiguous_time;
98
118 99 if (parentfiledata != Py_None) {
119 100 if (!PyTuple_CheckExact(parentfiledata)) {
120 101 PyErr_SetString(
121 102 PyExc_TypeError,
122 103 "parentfiledata should be a Tuple or None");
123 104 return NULL;
124 105 }
125 t->mode =
126 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
127 t->size =
128 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
129 t->mtime =
130 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
106 mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
107 size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
108 mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
109 } else {
110 has_meaningful_data = 0;
111 has_meaningful_mtime = 0;
112 }
113 if (has_meaningful_data) {
114 t->flags |= dirstate_flag_has_meaningful_data;
115 t->mode = mode;
116 t->size = size;
117 } else {
118 t->mode = 0;
119 t->size = 0;
120 }
121 if (has_meaningful_mtime) {
122 t->flags |= dirstate_flag_has_meaningful_mtime;
123 t->mtime = mtime;
124 } else {
125 t->mtime = 0;
131 126 }
132 127 return (PyObject *)t;
133 128 }
134 129
135 130 static void dirstate_item_dealloc(PyObject *o)
136 131 {
137 132 PyObject_Del(o);
138 133 }
139 134
140 135 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
141 136 {
142 137 return (self->flags & dirstate_flag_wc_tracked);
143 138 }
144 139
140 static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self)
141 {
142 const unsigned char mask = dirstate_flag_wc_tracked |
143 dirstate_flag_p1_tracked |
144 dirstate_flag_p2_info;
145 return (self->flags & mask);
146 }
147
145 148 static inline bool dirstate_item_c_added(dirstateItemObject *self)
146 149 {
147 unsigned char mask =
150 const unsigned char mask =
148 151 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
149 dirstate_flag_p2_tracked);
150 unsigned char target = dirstate_flag_wc_tracked;
152 dirstate_flag_p2_info);
153 const unsigned char target = dirstate_flag_wc_tracked;
151 154 return (self->flags & mask) == target;
152 155 }
153 156
154 157 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
155 158 {
156 159 if (self->flags & dirstate_flag_wc_tracked) {
157 160 return false;
158 161 }
159 162 return (self->flags &
160 (dirstate_flag_p1_tracked | dirstate_flag_p2_tracked));
163 (dirstate_flag_p1_tracked | dirstate_flag_p2_info));
161 164 }
162 165
163 166 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
164 167 {
165 168 return ((self->flags & dirstate_flag_wc_tracked) &&
166 (self->flags & dirstate_flag_merged));
169 (self->flags & dirstate_flag_p1_tracked) &&
170 (self->flags & dirstate_flag_p2_info));
167 171 }
168 172
169 173 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
170 174 {
171 if (!dirstate_item_c_tracked(self)) {
172 return false;
173 }
174 return (self->flags & dirstate_flag_clean_p2);
175 return ((self->flags & dirstate_flag_wc_tracked) &&
176 !(self->flags & dirstate_flag_p1_tracked) &&
177 (self->flags & dirstate_flag_p2_info));
175 178 }
176 179
177 180 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
178 181 {
179 182 if (dirstate_item_c_removed(self)) {
180 183 return 'r';
181 184 } else if (dirstate_item_c_merged(self)) {
182 185 return 'm';
183 186 } else if (dirstate_item_c_added(self)) {
184 187 return 'a';
185 188 } else {
186 189 return 'n';
187 190 }
188 191 }
189 192
190 193 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
191 194 {
195 if (self->flags & dirstate_flag_has_meaningful_data) {
192 196 return self->mode;
197 } else {
198 return 0;
199 }
193 200 }
194 201
195 202 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
196 203 {
197 if (dirstate_item_c_removed(self) &&
198 (self->flags & dirstate_flag_merged)) {
204 if (!(self->flags & dirstate_flag_wc_tracked) &&
205 (self->flags & dirstate_flag_p2_info)) {
206 if (self->flags & dirstate_flag_p1_tracked) {
199 207 return dirstate_v1_nonnormal;
200 } else if (dirstate_item_c_removed(self) &&
201 (self->flags & dirstate_flag_clean_p2)) {
208 } else {
202 209 return dirstate_v1_from_p2;
210 }
203 211 } else if (dirstate_item_c_removed(self)) {
204 212 return 0;
205 } else if (dirstate_item_c_merged(self)) {
213 } else if (self->flags & dirstate_flag_p2_info) {
206 214 return dirstate_v1_from_p2;
207 215 } else if (dirstate_item_c_added(self)) {
208 216 return dirstate_v1_nonnormal;
209 } else if (dirstate_item_c_from_p2(self)) {
210 return dirstate_v1_from_p2;
211 } else if (self->flags & dirstate_flag_possibly_dirty) {
212 return self->size; /* NON NORMAL ? */
217 } else if (self->flags & dirstate_flag_has_meaningful_data) {
218 return self->size;
213 219 } else {
214 return self->size;
220 return dirstate_v1_nonnormal;
215 221 }
216 222 }
217 223
218 224 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
219 225 {
220 226 if (dirstate_item_c_removed(self)) {
221 227 return 0;
222 } else if (self->flags & dirstate_flag_possibly_dirty) {
223 return ambiguous_time;
224 } else if (dirstate_item_c_merged(self)) {
225 return ambiguous_time;
226 } else if (dirstate_item_c_added(self)) {
227 return ambiguous_time;
228 } else if (dirstate_item_c_from_p2(self)) {
228 } else if (!(self->flags & dirstate_flag_has_meaningful_mtime) ||
229 !(self->flags & dirstate_flag_p1_tracked) ||
230 !(self->flags & dirstate_flag_wc_tracked) ||
231 (self->flags & dirstate_flag_p2_info)) {
229 232 return ambiguous_time;
230 233 } else {
231 234 return self->mtime;
232 235 }
233 236 }
234 237
235 238 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
236 239 {
237 240 char state = dirstate_item_c_v1_state(self);
238 241 return PyBytes_FromStringAndSize(&state, 1);
239 242 };
240 243
241 244 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
242 245 {
243 246 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
244 247 };
245 248
246 249 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
247 250 {
248 251 return PyInt_FromLong(dirstate_item_c_v1_size(self));
249 252 };
250 253
251 254 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
252 255 {
253 256 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
254 257 };
255 258
256 259 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
257 260 PyObject *value)
258 261 {
259 262 long now;
260 263 if (!pylong_to_long(value, &now)) {
261 264 return NULL;
262 265 }
263 266 if (dirstate_item_c_v1_state(self) == 'n' &&
264 267 dirstate_item_c_v1_mtime(self) == now) {
265 268 Py_RETURN_TRUE;
266 269 } else {
267 270 Py_RETURN_FALSE;
268 271 }
269 272 };
270 273
271 274 /* This will never change since it's bound to V1
272 275 */
273 276 static inline dirstateItemObject *
274 277 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
275 278 {
276 279 dirstateItemObject *t =
277 280 PyObject_New(dirstateItemObject, &dirstateItemType);
278 281 if (!t) {
279 282 return NULL;
280 283 }
281
282 if (state == 'm') {
283 t->flags =
284 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
285 dirstate_flag_p2_tracked | dirstate_flag_merged);
286 t->mode = 0;
287 t->size = dirstate_v1_from_p2;
288 t->mtime = ambiguous_time;
289 } else if (state == 'a') {
290 t->flags = dirstate_flag_wc_tracked;
291 t->mode = 0;
292 t->size = dirstate_v1_nonnormal;
293 t->mtime = ambiguous_time;
294 } else if (state == 'r') {
284 t->flags = 0;
295 285 t->mode = 0;
296 286 t->size = 0;
297 287 t->mtime = 0;
288
289 if (state == 'm') {
290 t->flags = (dirstate_flag_wc_tracked |
291 dirstate_flag_p1_tracked | dirstate_flag_p2_info);
292 } else if (state == 'a') {
293 t->flags = dirstate_flag_wc_tracked;
294 } else if (state == 'r') {
298 295 if (size == dirstate_v1_nonnormal) {
299 296 t->flags =
300 (dirstate_flag_p1_tracked |
301 dirstate_flag_p2_tracked | dirstate_flag_merged);
297 dirstate_flag_p1_tracked | dirstate_flag_p2_info;
302 298 } else if (size == dirstate_v1_from_p2) {
303 t->flags =
304 (dirstate_flag_p2_tracked | dirstate_flag_clean_p2);
299 t->flags = dirstate_flag_p2_info;
305 300 } else {
306 301 t->flags = dirstate_flag_p1_tracked;
307 302 }
308 303 } else if (state == 'n') {
309 304 if (size == dirstate_v1_from_p2) {
310 305 t->flags =
311 (dirstate_flag_wc_tracked |
312 dirstate_flag_p2_tracked | dirstate_flag_clean_p2);
313 t->mode = 0;
314 t->size = dirstate_v1_from_p2;
315 t->mtime = ambiguous_time;
306 dirstate_flag_wc_tracked | dirstate_flag_p2_info;
316 307 } else if (size == dirstate_v1_nonnormal) {
317 t->flags = (dirstate_flag_wc_tracked |
318 dirstate_flag_p1_tracked |
319 dirstate_flag_possibly_dirty);
320 t->mode = 0;
321 t->size = dirstate_v1_nonnormal;
322 t->mtime = ambiguous_time;
308 t->flags =
309 dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
323 310 } else if (mtime == ambiguous_time) {
324 311 t->flags = (dirstate_flag_wc_tracked |
325 312 dirstate_flag_p1_tracked |
326 dirstate_flag_possibly_dirty);
313 dirstate_flag_has_meaningful_data);
327 314 t->mode = mode;
328 315 t->size = size;
329 t->mtime = 0;
330 316 } else {
331 317 t->flags = (dirstate_flag_wc_tracked |
332 dirstate_flag_p1_tracked);
318 dirstate_flag_p1_tracked |
319 dirstate_flag_has_meaningful_data |
320 dirstate_flag_has_meaningful_mtime);
333 321 t->mode = mode;
334 322 t->size = size;
335 323 t->mtime = mtime;
336 324 }
337 325 } else {
338 326 PyErr_Format(PyExc_RuntimeError,
339 327 "unknown state: `%c` (%d, %d, %d)", state, mode,
340 328 size, mtime, NULL);
341 329 Py_DECREF(t);
342 330 return NULL;
343 331 }
344 332
345 333 return t;
346 334 }
347 335
348 336 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
349 337 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
350 338 PyObject *args)
351 339 {
352 340 /* We do all the initialization here and not a tp_init function because
353 341 * dirstate_item is immutable. */
354 342 char state;
355 343 int size, mode, mtime;
356 344 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
357 345 return NULL;
358 346 }
359 347 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
360 348 };
361 349
362 350 /* constructor to help legacy API to build a new "added" item
363 351
364 352 Should eventually be removed */
365 353 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
366 354 {
367 355 dirstateItemObject *t;
368 356 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
369 357 if (!t) {
370 358 return NULL;
371 359 }
372 360 t->flags = dirstate_flag_wc_tracked;
373 361 t->mode = 0;
374 t->size = dirstate_v1_nonnormal;
375 t->mtime = ambiguous_time;
362 t->size = 0;
363 t->mtime = 0;
376 364 return (PyObject *)t;
377 365 };
378 366
379 367 /* constructor to help legacy API to build a new "merged" item
380 368
381 369 Should eventually be removed */
382 370 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
383 371 {
384 372 dirstateItemObject *t;
385 373 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
386 374 if (!t) {
387 375 return NULL;
388 376 }
389 377 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
390 dirstate_flag_p2_tracked | dirstate_flag_merged);
378 dirstate_flag_p2_info);
391 379 t->mode = 0;
392 t->size = dirstate_v1_from_p2;
393 t->mtime = ambiguous_time;
380 t->size = 0;
381 t->mtime = 0;
394 382 return (PyObject *)t;
395 383 };
396 384
397 385 /* constructor to help legacy API to build a new "from_p2" item
398 386
399 387 Should eventually be removed */
400 388 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
401 389 {
402 390 /* We do all the initialization here and not a tp_init function because
403 391 * dirstate_item is immutable. */
404 392 dirstateItemObject *t;
405 393 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
406 394 if (!t) {
407 395 return NULL;
408 396 }
409 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p2_tracked |
410 dirstate_flag_clean_p2);
397 t->flags = dirstate_flag_wc_tracked | dirstate_flag_p2_info;
411 398 t->mode = 0;
412 t->size = dirstate_v1_from_p2;
413 t->mtime = ambiguous_time;
399 t->size = 0;
400 t->mtime = 0;
414 401 return (PyObject *)t;
415 402 };
416 403
417 404 /* constructor to help legacy API to build a new "possibly" item
418 405
419 406 Should eventually be removed */
420 407 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
421 408 {
422 409 /* We do all the initialization here and not a tp_init function because
423 410 * dirstate_item is immutable. */
424 411 dirstateItemObject *t;
425 412 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
426 413 if (!t) {
427 414 return NULL;
428 415 }
429 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
430 dirstate_flag_possibly_dirty);
416 t->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
431 417 t->mode = 0;
432 t->size = dirstate_v1_nonnormal;
433 t->mtime = ambiguous_time;
418 t->size = 0;
419 t->mtime = 0;
434 420 return (PyObject *)t;
435 421 };
436 422
437 423 /* constructor to help legacy API to build a new "normal" item
438 424
439 425 Should eventually be removed */
440 426 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
441 427 {
442 428 /* We do all the initialization here and not a tp_init function because
443 429 * dirstate_item is immutable. */
444 430 dirstateItemObject *t;
445 431 int size, mode, mtime;
446 432 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
447 433 return NULL;
448 434 }
449 435
450 436 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
451 437 if (!t) {
452 438 return NULL;
453 439 }
454 440 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked);
455 441 t->mode = mode;
456 442 t->size = size;
457 443 t->mtime = mtime;
458 444 return (PyObject *)t;
459 445 };
460 446
461 447 /* This means the next status call will have to actually check its content
462 448 to make sure it is correct. */
463 449 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
464 450 {
465 self->flags |= dirstate_flag_possibly_dirty;
451 self->flags &= ~dirstate_flag_has_meaningful_mtime;
466 452 Py_RETURN_NONE;
467 453 }
468 454
469 455 /* See docstring of the python implementation for details */
470 456 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
471 457 PyObject *args)
472 458 {
473 459 int size, mode, mtime;
474 460 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
475 461 return NULL;
476 462 }
477 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
463 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
464 dirstate_flag_has_meaningful_data |
465 dirstate_flag_has_meaningful_mtime;
478 466 self->mode = mode;
479 467 self->size = size;
480 468 self->mtime = mtime;
481 469 Py_RETURN_NONE;
482 470 }
483 471
484 472 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
485 473 {
486 474 self->flags |= dirstate_flag_wc_tracked;
487 self->flags |= dirstate_flag_possibly_dirty;
488 /* size = None on the python size turn into size = NON_NORMAL when
489 * accessed. So the next line is currently required, but a some future
490 * clean up would be welcome. */
491 self->size = dirstate_v1_nonnormal;
475 self->flags &= ~dirstate_flag_has_meaningful_mtime;
492 476 Py_RETURN_NONE;
493 477 }
494 478
495 479 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
496 480 {
497 481 self->flags &= ~dirstate_flag_wc_tracked;
498 482 self->mode = 0;
499 483 self->mtime = 0;
500 484 self->size = 0;
501 485 Py_RETURN_NONE;
502 486 }
503 487
504 488 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
505 489 {
506 if (dirstate_item_c_merged(self) || dirstate_item_c_from_p2(self)) {
507 if (dirstate_item_c_merged(self)) {
508 self->flags |= dirstate_flag_p1_tracked;
509 } else {
510 self->flags &= ~dirstate_flag_p1_tracked;
511 }
512 self->flags &=
513 ~(dirstate_flag_merged | dirstate_flag_clean_p1 |
514 dirstate_flag_clean_p2 | dirstate_flag_p2_tracked);
515 self->flags |= dirstate_flag_possibly_dirty;
490 if (self->flags & dirstate_flag_p2_info) {
491 self->flags &= ~(dirstate_flag_p2_info |
492 dirstate_flag_has_meaningful_data |
493 dirstate_flag_has_meaningful_mtime);
516 494 self->mode = 0;
517 495 self->mtime = 0;
518 /* size = None on the python size turn into size = NON_NORMAL
519 * when accessed. So the next line is currently required, but a
520 * some future clean up would be welcome. */
521 self->size = dirstate_v1_nonnormal;
496 self->size = 0;
522 497 }
523 498 Py_RETURN_NONE;
524 499 }
525 500 static PyMethodDef dirstate_item_methods[] = {
526 501 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
527 502 "return a \"state\" suitable for v1 serialization"},
528 503 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
529 504 "return a \"mode\" suitable for v1 serialization"},
530 505 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
531 506 "return a \"size\" suitable for v1 serialization"},
532 507 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
533 508 "return a \"mtime\" suitable for v1 serialization"},
534 509 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
535 510 "True if the stored mtime would be ambiguous with the current time"},
536 511 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
537 512 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
538 513 {"new_added", (PyCFunction)dirstate_item_new_added,
539 514 METH_NOARGS | METH_CLASS,
540 515 "constructor to help legacy API to build a new \"added\" item"},
541 516 {"new_merged", (PyCFunction)dirstate_item_new_merged,
542 517 METH_NOARGS | METH_CLASS,
543 518 "constructor to help legacy API to build a new \"merged\" item"},
544 519 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
545 520 METH_NOARGS | METH_CLASS,
546 521 "constructor to help legacy API to build a new \"from_p2\" item"},
547 522 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
548 523 METH_NOARGS | METH_CLASS,
549 524 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
550 525 {"new_normal", (PyCFunction)dirstate_item_new_normal,
551 526 METH_VARARGS | METH_CLASS,
552 527 "constructor to help legacy API to build a new \"normal\" item"},
553 528 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
554 529 METH_NOARGS, "mark a file as \"possibly dirty\""},
555 530 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
556 531 "mark a file as \"clean\""},
557 532 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
558 533 "mark a file as \"tracked\""},
559 534 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
560 535 "mark a file as \"untracked\""},
561 536 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
562 537 "remove all \"merge-only\" from a DirstateItem"},
563 538 {NULL} /* Sentinel */
564 539 };
565 540
566 541 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
567 542 {
568 543 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
569 544 };
570 545
571 546 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
572 547 {
573 548 return PyInt_FromLong(dirstate_item_c_v1_size(self));
574 549 };
575 550
576 551 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
577 552 {
578 553 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
579 554 };
580 555
581 556 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
582 557 {
583 558 char state = dirstate_item_c_v1_state(self);
584 559 return PyBytes_FromStringAndSize(&state, 1);
585 560 };
586 561
587 562 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
588 563 {
589 564 if (dirstate_item_c_tracked(self)) {
590 565 Py_RETURN_TRUE;
591 566 } else {
592 567 Py_RETURN_FALSE;
593 568 }
594 569 };
595 570
596 571 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
597 572 {
598 573 if (dirstate_item_c_added(self)) {
599 574 Py_RETURN_TRUE;
600 575 } else {
601 576 Py_RETURN_FALSE;
602 577 }
603 578 };
604 579
605 580 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
606 581 {
607 582 if (dirstate_item_c_merged(self)) {
608 583 Py_RETURN_TRUE;
609 584 } else {
610 585 Py_RETURN_FALSE;
611 586 }
612 587 };
613 588
614 589 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
615 590 {
616 591 if (dirstate_item_c_from_p2(self)) {
617 592 Py_RETURN_TRUE;
618 593 } else {
619 594 Py_RETURN_FALSE;
620 595 }
621 596 };
622 597
623 598 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
624 599 {
625 600 if (!(self->flags & dirstate_flag_wc_tracked)) {
626 601 Py_RETURN_FALSE;
627 } else if (dirstate_item_c_added(self)) {
602 } else if (!(self->flags & dirstate_flag_p1_tracked)) {
628 603 Py_RETURN_FALSE;
629 } else if (self->flags & dirstate_flag_merged) {
630 Py_RETURN_FALSE;
631 } else if (self->flags & dirstate_flag_clean_p2) {
604 } else if (self->flags & dirstate_flag_p2_info) {
632 605 Py_RETURN_FALSE;
633 606 } else {
634 607 Py_RETURN_TRUE;
635 608 }
636 609 };
637 610
638 611 static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self)
639 612 {
640 unsigned char mask = dirstate_flag_wc_tracked |
641 dirstate_flag_p1_tracked |
642 dirstate_flag_p2_tracked;
643 if ((self->flags & mask) != 0) {
613 if (dirstate_item_c_any_tracked(self)) {
644 614 Py_RETURN_TRUE;
645 615 } else {
646 616 Py_RETURN_FALSE;
647 617 }
648 618 };
649 619
650 620 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
651 621 {
652 622 if (dirstate_item_c_removed(self)) {
653 623 Py_RETURN_TRUE;
654 624 } else {
655 625 Py_RETURN_FALSE;
656 626 }
657 627 };
658 628
659 629 static PyGetSetDef dirstate_item_getset[] = {
660 630 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
661 631 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
662 632 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
663 633 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
664 634 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
665 635 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
666 636 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
667 637 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
668 638 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
669 639 NULL},
670 640 {"any_tracked", (getter)dirstate_item_get_any_tracked, NULL, "any_tracked",
671 641 NULL},
672 642 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
673 643 {NULL} /* Sentinel */
674 644 };
675 645
676 646 PyTypeObject dirstateItemType = {
677 647 PyVarObject_HEAD_INIT(NULL, 0) /* header */
678 648 "dirstate_tuple", /* tp_name */
679 649 sizeof(dirstateItemObject), /* tp_basicsize */
680 650 0, /* tp_itemsize */
681 651 (destructor)dirstate_item_dealloc, /* tp_dealloc */
682 652 0, /* tp_print */
683 653 0, /* tp_getattr */
684 654 0, /* tp_setattr */
685 655 0, /* tp_compare */
686 656 0, /* tp_repr */
687 657 0, /* tp_as_number */
688 658 0, /* tp_as_sequence */
689 659 0, /* tp_as_mapping */
690 660 0, /* tp_hash */
691 661 0, /* tp_call */
692 662 0, /* tp_str */
693 663 0, /* tp_getattro */
694 664 0, /* tp_setattro */
695 665 0, /* tp_as_buffer */
696 666 Py_TPFLAGS_DEFAULT, /* tp_flags */
697 667 "dirstate tuple", /* tp_doc */
698 668 0, /* tp_traverse */
699 669 0, /* tp_clear */
700 670 0, /* tp_richcompare */
701 671 0, /* tp_weaklistoffset */
702 672 0, /* tp_iter */
703 673 0, /* tp_iternext */
704 674 dirstate_item_methods, /* tp_methods */
705 675 0, /* tp_members */
706 676 dirstate_item_getset, /* tp_getset */
707 677 0, /* tp_base */
708 678 0, /* tp_dict */
709 679 0, /* tp_descr_get */
710 680 0, /* tp_descr_set */
711 681 0, /* tp_dictoffset */
712 682 0, /* tp_init */
713 683 0, /* tp_alloc */
714 684 dirstate_item_new, /* tp_new */
715 685 };
716 686
717 687 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
718 688 {
719 689 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
720 690 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
721 691 char state, *cur, *str, *cpos;
722 692 int mode, size, mtime;
723 693 unsigned int flen, pos = 40;
724 694 Py_ssize_t len = 40;
725 695 Py_ssize_t readlen;
726 696
727 697 if (!PyArg_ParseTuple(
728 698 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
729 699 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
730 700 goto quit;
731 701 }
732 702
733 703 len = readlen;
734 704
735 705 /* read parents */
736 706 if (len < 40) {
737 707 PyErr_SetString(PyExc_ValueError,
738 708 "too little data for parents");
739 709 goto quit;
740 710 }
741 711
742 712 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
743 713 str + 20, (Py_ssize_t)20);
744 714 if (!parents) {
745 715 goto quit;
746 716 }
747 717
748 718 /* read filenames */
749 719 while (pos >= 40 && pos < len) {
750 720 if (pos + 17 > len) {
751 721 PyErr_SetString(PyExc_ValueError,
752 722 "overflow in dirstate");
753 723 goto quit;
754 724 }
755 725 cur = str + pos;
756 726 /* unpack header */
757 727 state = *cur;
758 728 mode = getbe32(cur + 1);
759 729 size = getbe32(cur + 5);
760 730 mtime = getbe32(cur + 9);
761 731 flen = getbe32(cur + 13);
762 732 pos += 17;
763 733 cur += 17;
764 734 if (flen > len - pos) {
765 735 PyErr_SetString(PyExc_ValueError,
766 736 "overflow in dirstate");
767 737 goto quit;
768 738 }
769 739
770 740 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
771 741 size, mtime);
772 742 if (!entry)
773 743 goto quit;
774 744 cpos = memchr(cur, 0, flen);
775 745 if (cpos) {
776 746 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
777 747 cname = PyBytes_FromStringAndSize(
778 748 cpos + 1, flen - (cpos - cur) - 1);
779 749 if (!fname || !cname ||
780 750 PyDict_SetItem(cmap, fname, cname) == -1 ||
781 751 PyDict_SetItem(dmap, fname, entry) == -1) {
782 752 goto quit;
783 753 }
784 754 Py_DECREF(cname);
785 755 } else {
786 756 fname = PyBytes_FromStringAndSize(cur, flen);
787 757 if (!fname ||
788 758 PyDict_SetItem(dmap, fname, entry) == -1) {
789 759 goto quit;
790 760 }
791 761 }
792 762 Py_DECREF(fname);
793 763 Py_DECREF(entry);
794 764 fname = cname = entry = NULL;
795 765 pos += flen;
796 766 }
797 767
798 768 ret = parents;
799 769 Py_INCREF(ret);
800 770 quit:
801 771 Py_XDECREF(fname);
802 772 Py_XDECREF(cname);
803 773 Py_XDECREF(entry);
804 774 Py_XDECREF(parents);
805 775 return ret;
806 776 }
807 777
808 778 /*
809 779 * Efficiently pack a dirstate object into its on-disk format.
810 780 */
811 781 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
812 782 {
813 783 PyObject *packobj = NULL;
814 784 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
815 785 Py_ssize_t nbytes, pos, l;
816 786 PyObject *k, *v = NULL, *pn;
817 787 char *p, *s;
818 788 int now;
819 789
820 790 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
821 791 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
822 792 &now)) {
823 793 return NULL;
824 794 }
825 795
826 796 if (PyTuple_Size(pl) != 2) {
827 797 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
828 798 return NULL;
829 799 }
830 800
831 801 /* Figure out how much we need to allocate. */
832 802 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
833 803 PyObject *c;
834 804 if (!PyBytes_Check(k)) {
835 805 PyErr_SetString(PyExc_TypeError, "expected string key");
836 806 goto bail;
837 807 }
838 808 nbytes += PyBytes_GET_SIZE(k) + 17;
839 809 c = PyDict_GetItem(copymap, k);
840 810 if (c) {
841 811 if (!PyBytes_Check(c)) {
842 812 PyErr_SetString(PyExc_TypeError,
843 813 "expected string key");
844 814 goto bail;
845 815 }
846 816 nbytes += PyBytes_GET_SIZE(c) + 1;
847 817 }
848 818 }
849 819
850 820 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
851 821 if (packobj == NULL) {
852 822 goto bail;
853 823 }
854 824
855 825 p = PyBytes_AS_STRING(packobj);
856 826
857 827 pn = PyTuple_GET_ITEM(pl, 0);
858 828 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
859 829 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
860 830 goto bail;
861 831 }
862 832 memcpy(p, s, l);
863 833 p += 20;
864 834 pn = PyTuple_GET_ITEM(pl, 1);
865 835 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
866 836 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
867 837 goto bail;
868 838 }
869 839 memcpy(p, s, l);
870 840 p += 20;
871 841
872 842 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
873 843 dirstateItemObject *tuple;
874 844 char state;
875 845 int mode, size, mtime;
876 846 Py_ssize_t len, l;
877 847 PyObject *o;
878 848 char *t;
879 849
880 850 if (!dirstate_tuple_check(v)) {
881 851 PyErr_SetString(PyExc_TypeError,
882 852 "expected a dirstate tuple");
883 853 goto bail;
884 854 }
885 855 tuple = (dirstateItemObject *)v;
886 856
887 857 state = dirstate_item_c_v1_state(tuple);
888 858 mode = dirstate_item_c_v1_mode(tuple);
889 859 size = dirstate_item_c_v1_size(tuple);
890 860 mtime = dirstate_item_c_v1_mtime(tuple);
891 861 if (state == 'n' && mtime == now) {
892 862 /* See pure/parsers.py:pack_dirstate for why we do
893 863 * this. */
894 864 mtime = -1;
895 865 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
896 866 state, mode, size, mtime);
897 867 if (!mtime_unset) {
898 868 goto bail;
899 869 }
900 870 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
901 871 goto bail;
902 872 }
903 873 Py_DECREF(mtime_unset);
904 874 mtime_unset = NULL;
905 875 }
906 876 *p++ = state;
907 877 putbe32((uint32_t)mode, p);
908 878 putbe32((uint32_t)size, p + 4);
909 879 putbe32((uint32_t)mtime, p + 8);
910 880 t = p + 12;
911 881 p += 16;
912 882 len = PyBytes_GET_SIZE(k);
913 883 memcpy(p, PyBytes_AS_STRING(k), len);
914 884 p += len;
915 885 o = PyDict_GetItem(copymap, k);
916 886 if (o) {
917 887 *p++ = '\0';
918 888 l = PyBytes_GET_SIZE(o);
919 889 memcpy(p, PyBytes_AS_STRING(o), l);
920 890 p += l;
921 891 len += l + 1;
922 892 }
923 893 putbe32((uint32_t)len, t);
924 894 }
925 895
926 896 pos = p - PyBytes_AS_STRING(packobj);
927 897 if (pos != nbytes) {
928 898 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
929 899 (long)pos, (long)nbytes);
930 900 goto bail;
931 901 }
932 902
933 903 return packobj;
934 904 bail:
935 905 Py_XDECREF(mtime_unset);
936 906 Py_XDECREF(packobj);
937 907 Py_XDECREF(v);
938 908 return NULL;
939 909 }
940 910
941 911 #define BUMPED_FIX 1
942 912 #define USING_SHA_256 2
943 913 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
944 914
945 915 static PyObject *readshas(const char *source, unsigned char num,
946 916 Py_ssize_t hashwidth)
947 917 {
948 918 int i;
949 919 PyObject *list = PyTuple_New(num);
950 920 if (list == NULL) {
951 921 return NULL;
952 922 }
953 923 for (i = 0; i < num; i++) {
954 924 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
955 925 if (hash == NULL) {
956 926 Py_DECREF(list);
957 927 return NULL;
958 928 }
959 929 PyTuple_SET_ITEM(list, i, hash);
960 930 source += hashwidth;
961 931 }
962 932 return list;
963 933 }
964 934
965 935 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
966 936 uint32_t *msize)
967 937 {
968 938 const char *data = databegin;
969 939 const char *meta;
970 940
971 941 double mtime;
972 942 int16_t tz;
973 943 uint16_t flags;
974 944 unsigned char nsuccs, nparents, nmetadata;
975 945 Py_ssize_t hashwidth = 20;
976 946
977 947 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
978 948 PyObject *metadata = NULL, *ret = NULL;
979 949 int i;
980 950
981 951 if (data + FM1_HEADER_SIZE > dataend) {
982 952 goto overflow;
983 953 }
984 954
985 955 *msize = getbe32(data);
986 956 data += 4;
987 957 mtime = getbefloat64(data);
988 958 data += 8;
989 959 tz = getbeint16(data);
990 960 data += 2;
991 961 flags = getbeuint16(data);
992 962 data += 2;
993 963
994 964 if (flags & USING_SHA_256) {
995 965 hashwidth = 32;
996 966 }
997 967
998 968 nsuccs = (unsigned char)(*data++);
999 969 nparents = (unsigned char)(*data++);
1000 970 nmetadata = (unsigned char)(*data++);
1001 971
1002 972 if (databegin + *msize > dataend) {
1003 973 goto overflow;
1004 974 }
1005 975 dataend = databegin + *msize; /* narrow down to marker size */
1006 976
1007 977 if (data + hashwidth > dataend) {
1008 978 goto overflow;
1009 979 }
1010 980 prec = PyBytes_FromStringAndSize(data, hashwidth);
1011 981 data += hashwidth;
1012 982 if (prec == NULL) {
1013 983 goto bail;
1014 984 }
1015 985
1016 986 if (data + nsuccs * hashwidth > dataend) {
1017 987 goto overflow;
1018 988 }
1019 989 succs = readshas(data, nsuccs, hashwidth);
1020 990 if (succs == NULL) {
1021 991 goto bail;
1022 992 }
1023 993 data += nsuccs * hashwidth;
1024 994
1025 995 if (nparents == 1 || nparents == 2) {
1026 996 if (data + nparents * hashwidth > dataend) {
1027 997 goto overflow;
1028 998 }
1029 999 parents = readshas(data, nparents, hashwidth);
1030 1000 if (parents == NULL) {
1031 1001 goto bail;
1032 1002 }
1033 1003 data += nparents * hashwidth;
1034 1004 } else {
1035 1005 parents = Py_None;
1036 1006 Py_INCREF(parents);
1037 1007 }
1038 1008
1039 1009 if (data + 2 * nmetadata > dataend) {
1040 1010 goto overflow;
1041 1011 }
1042 1012 meta = data + (2 * nmetadata);
1043 1013 metadata = PyTuple_New(nmetadata);
1044 1014 if (metadata == NULL) {
1045 1015 goto bail;
1046 1016 }
1047 1017 for (i = 0; i < nmetadata; i++) {
1048 1018 PyObject *tmp, *left = NULL, *right = NULL;
1049 1019 Py_ssize_t leftsize = (unsigned char)(*data++);
1050 1020 Py_ssize_t rightsize = (unsigned char)(*data++);
1051 1021 if (meta + leftsize + rightsize > dataend) {
1052 1022 goto overflow;
1053 1023 }
1054 1024 left = PyBytes_FromStringAndSize(meta, leftsize);
1055 1025 meta += leftsize;
1056 1026 right = PyBytes_FromStringAndSize(meta, rightsize);
1057 1027 meta += rightsize;
1058 1028 tmp = PyTuple_New(2);
1059 1029 if (!left || !right || !tmp) {
1060 1030 Py_XDECREF(left);
1061 1031 Py_XDECREF(right);
1062 1032 Py_XDECREF(tmp);
1063 1033 goto bail;
1064 1034 }
1065 1035 PyTuple_SET_ITEM(tmp, 0, left);
1066 1036 PyTuple_SET_ITEM(tmp, 1, right);
1067 1037 PyTuple_SET_ITEM(metadata, i, tmp);
1068 1038 }
1069 1039 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1070 1040 (int)tz * 60, parents);
1071 1041 goto bail; /* return successfully */
1072 1042
1073 1043 overflow:
1074 1044 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1075 1045 bail:
1076 1046 Py_XDECREF(prec);
1077 1047 Py_XDECREF(succs);
1078 1048 Py_XDECREF(metadata);
1079 1049 Py_XDECREF(parents);
1080 1050 return ret;
1081 1051 }
1082 1052
1083 1053 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1084 1054 {
1085 1055 const char *data, *dataend;
1086 1056 Py_ssize_t datalen, offset, stop;
1087 1057 PyObject *markers = NULL;
1088 1058
1089 1059 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1090 1060 &offset, &stop)) {
1091 1061 return NULL;
1092 1062 }
1093 1063 if (offset < 0) {
1094 1064 PyErr_SetString(PyExc_ValueError,
1095 1065 "invalid negative offset in fm1readmarkers");
1096 1066 return NULL;
1097 1067 }
1098 1068 if (stop > datalen) {
1099 1069 PyErr_SetString(
1100 1070 PyExc_ValueError,
1101 1071 "stop longer than data length in fm1readmarkers");
1102 1072 return NULL;
1103 1073 }
1104 1074 dataend = data + datalen;
1105 1075 data += offset;
1106 1076 markers = PyList_New(0);
1107 1077 if (!markers) {
1108 1078 return NULL;
1109 1079 }
1110 1080 while (offset < stop) {
1111 1081 uint32_t msize;
1112 1082 int error;
1113 1083 PyObject *record = fm1readmarker(data, dataend, &msize);
1114 1084 if (!record) {
1115 1085 goto bail;
1116 1086 }
1117 1087 error = PyList_Append(markers, record);
1118 1088 Py_DECREF(record);
1119 1089 if (error) {
1120 1090 goto bail;
1121 1091 }
1122 1092 data += msize;
1123 1093 offset += msize;
1124 1094 }
1125 1095 return markers;
1126 1096 bail:
1127 1097 Py_DECREF(markers);
1128 1098 return NULL;
1129 1099 }
1130 1100
1131 1101 static char parsers_doc[] = "Efficient content parsing.";
1132 1102
1133 1103 PyObject *encodedir(PyObject *self, PyObject *args);
1134 1104 PyObject *pathencode(PyObject *self, PyObject *args);
1135 1105 PyObject *lowerencode(PyObject *self, PyObject *args);
1136 1106 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1137 1107
1138 1108 static PyMethodDef methods[] = {
1139 1109 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1140 1110 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1141 1111 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1142 1112 "parse a revlog index\n"},
1143 1113 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1144 1114 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1145 1115 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1146 1116 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1147 1117 "construct a dict with an expected size\n"},
1148 1118 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1149 1119 "make file foldmap\n"},
1150 1120 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1151 1121 "escape a UTF-8 byte string to JSON (fast path)\n"},
1152 1122 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1153 1123 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1154 1124 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1155 1125 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1156 1126 "parse v1 obsolete markers\n"},
1157 1127 {NULL, NULL}};
1158 1128
1159 1129 void dirs_module_init(PyObject *mod);
1160 1130 void manifest_module_init(PyObject *mod);
1161 1131 void revlog_module_init(PyObject *mod);
1162 1132
1163 1133 static const int version = 20;
1164 1134
1165 1135 static void module_init(PyObject *mod)
1166 1136 {
1167 1137 PyModule_AddIntConstant(mod, "version", version);
1168 1138
1169 1139 /* This module constant has two purposes. First, it lets us unit test
1170 1140 * the ImportError raised without hard-coding any error text. This
1171 1141 * means we can change the text in the future without breaking tests,
1172 1142 * even across changesets without a recompile. Second, its presence
1173 1143 * can be used to determine whether the version-checking logic is
1174 1144 * present, which also helps in testing across changesets without a
1175 1145 * recompile. Note that this means the pure-Python version of parsers
1176 1146 * should not have this module constant. */
1177 1147 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1178 1148
1179 1149 dirs_module_init(mod);
1180 1150 manifest_module_init(mod);
1181 1151 revlog_module_init(mod);
1182 1152
1183 1153 if (PyType_Ready(&dirstateItemType) < 0) {
1184 1154 return;
1185 1155 }
1186 1156 Py_INCREF(&dirstateItemType);
1187 1157 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1188 1158 }
1189 1159
1190 1160 static int check_python_version(void)
1191 1161 {
1192 1162 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1193 1163 long hexversion;
1194 1164 if (!sys) {
1195 1165 return -1;
1196 1166 }
1197 1167 ver = PyObject_GetAttrString(sys, "hexversion");
1198 1168 Py_DECREF(sys);
1199 1169 if (!ver) {
1200 1170 return -1;
1201 1171 }
1202 1172 hexversion = PyInt_AsLong(ver);
1203 1173 Py_DECREF(ver);
1204 1174 /* sys.hexversion is a 32-bit number by default, so the -1 case
1205 1175 * should only occur in unusual circumstances (e.g. if sys.hexversion
1206 1176 * is manually set to an invalid value). */
1207 1177 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1208 1178 PyErr_Format(PyExc_ImportError,
1209 1179 "%s: The Mercurial extension "
1210 1180 "modules were compiled with Python " PY_VERSION
1211 1181 ", but "
1212 1182 "Mercurial is currently using Python with "
1213 1183 "sys.hexversion=%ld: "
1214 1184 "Python %s\n at: %s",
1215 1185 versionerrortext, hexversion, Py_GetVersion(),
1216 1186 Py_GetProgramFullPath());
1217 1187 return -1;
1218 1188 }
1219 1189 return 0;
1220 1190 }
1221 1191
1222 1192 #ifdef IS_PY3K
1223 1193 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1224 1194 parsers_doc, -1, methods};
1225 1195
1226 1196 PyMODINIT_FUNC PyInit_parsers(void)
1227 1197 {
1228 1198 PyObject *mod;
1229 1199
1230 1200 if (check_python_version() == -1)
1231 1201 return NULL;
1232 1202 mod = PyModule_Create(&parsers_module);
1233 1203 module_init(mod);
1234 1204 return mod;
1235 1205 }
1236 1206 #else
1237 1207 PyMODINIT_FUNC initparsers(void)
1238 1208 {
1239 1209 PyObject *mod;
1240 1210
1241 1211 if (check_python_version() == -1) {
1242 1212 return;
1243 1213 }
1244 1214 mod = Py_InitModule3("parsers", methods, parsers_doc);
1245 1215 module_init(mod);
1246 1216 }
1247 1217 #endif
@@ -1,82 +1,80
1 1 /*
2 2 util.h - utility functions for interfacing with the various python APIs.
3 3
4 4 This software may be used and distributed according to the terms of
5 5 the GNU General Public License, incorporated herein by reference.
6 6 */
7 7
8 8 #ifndef _HG_UTIL_H_
9 9 #define _HG_UTIL_H_
10 10
11 11 #include "compat.h"
12 12
13 13 #if PY_MAJOR_VERSION >= 3
14 14 #define IS_PY3K
15 15 #endif
16 16
17 17 /* helper to switch things like string literal depending on Python version */
18 18 #ifdef IS_PY3K
19 19 #define PY23(py2, py3) py3
20 20 #else
21 21 #define PY23(py2, py3) py2
22 22 #endif
23 23
24 24 /* clang-format off */
25 25 typedef struct {
26 26 PyObject_HEAD
27 27 unsigned char flags;
28 28 int mode;
29 29 int size;
30 30 int mtime;
31 31 } dirstateItemObject;
32 32 /* clang-format on */
33 33
34 34 static const unsigned char dirstate_flag_wc_tracked = 1;
35 35 static const unsigned char dirstate_flag_p1_tracked = 1 << 1;
36 static const unsigned char dirstate_flag_p2_tracked = 1 << 2;
37 static const unsigned char dirstate_flag_possibly_dirty = 1 << 3;
38 static const unsigned char dirstate_flag_merged = 1 << 4;
39 static const unsigned char dirstate_flag_clean_p1 = 1 << 5;
40 static const unsigned char dirstate_flag_clean_p2 = 1 << 6;
36 static const unsigned char dirstate_flag_p2_info = 1 << 2;
37 static const unsigned char dirstate_flag_has_meaningful_data = 1 << 3;
38 static const unsigned char dirstate_flag_has_meaningful_mtime = 1 << 4;
41 39
42 40 extern PyTypeObject dirstateItemType;
43 41 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateItemType)
44 42
45 43 #ifndef MIN
46 44 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
47 45 #endif
48 46 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
49 47 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
50 48 #define true 1
51 49 #define false 0
52 50 typedef unsigned char bool;
53 51 #else
54 52 #include <stdbool.h>
55 53 #endif
56 54
57 55 static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
58 56 {
59 57 /* _PyDict_NewPresized expects a minused parameter, but it actually
60 58 creates a dictionary that's the nearest power of two bigger than the
61 59 parameter. For example, with the initial minused = 1000, the
62 60 dictionary created has size 1024. Of course in a lot of cases that
63 61 can be greater than the maximum load factor Python's dict object
64 62 expects (= 2/3), so as soon as we cross the threshold we'll resize
65 63 anyway. So create a dictionary that's at least 3/2 the size. */
66 64 return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
67 65 }
68 66
69 67 /* Convert a PyInt or PyLong to a long. Returns false if there is an
70 68 error, in which case an exception will already have been set. */
71 69 static inline bool pylong_to_long(PyObject *pylong, long *out)
72 70 {
73 71 *out = PyLong_AsLong(pylong);
74 72 /* Fast path to avoid hitting PyErr_Occurred if the value was obviously
75 73 * not an error. */
76 74 if (*out != -1) {
77 75 return true;
78 76 }
79 77 return PyErr_Occurred() == NULL;
80 78 }
81 79
82 80 #endif /* _HG_UTIL_H_ */
@@ -1,805 +1,781
1 1 # dirstatemap.py
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 from __future__ import absolute_import
7 7
8 8 import errno
9 9
10 10 from .i18n import _
11 11
12 12 from . import (
13 13 error,
14 14 pathutil,
15 15 policy,
16 16 pycompat,
17 17 txnutil,
18 18 util,
19 19 )
20 20
21 21 from .dirstateutils import (
22 22 docket as docketmod,
23 23 )
24 24
25 25 parsers = policy.importmod('parsers')
26 26 rustmod = policy.importrust('dirstate')
27 27
28 28 propertycache = util.propertycache
29 29
30 30 if rustmod is None:
31 31 DirstateItem = parsers.DirstateItem
32 32 else:
33 33 DirstateItem = rustmod.DirstateItem
34 34
35 35 rangemask = 0x7FFFFFFF
36 36
37 37
38 38 class _dirstatemapcommon(object):
39 39 """
40 40 Methods that are identical for both implementations of the dirstatemap
41 41 class, with and without Rust extensions enabled.
42 42 """
43 43
44 44 # please pytype
45 45
46 46 _map = None
47 47 copymap = None
48 48
49 49 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
50 50 self._use_dirstate_v2 = use_dirstate_v2
51 51 self._nodeconstants = nodeconstants
52 52 self._ui = ui
53 53 self._opener = opener
54 54 self._root = root
55 55 self._filename = b'dirstate'
56 56 self._nodelen = 20 # Also update Rust code when changing this!
57 57 self._parents = None
58 58 self._dirtyparents = False
59 59
60 60 # for consistent view between _pl() and _read() invocations
61 61 self._pendingmode = None
62 62
63 63 def preload(self):
64 64 """Loads the underlying data, if it's not already loaded"""
65 65 self._map
66 66
67 67 def get(self, key, default=None):
68 68 return self._map.get(key, default)
69 69
70 70 def __len__(self):
71 71 return len(self._map)
72 72
73 73 def __iter__(self):
74 74 return iter(self._map)
75 75
76 76 def __contains__(self, key):
77 77 return key in self._map
78 78
79 79 def __getitem__(self, item):
80 80 return self._map[item]
81 81
82 82 ### sub-class utility method
83 83 #
84 84 # Use to allow for generic implementation of some method while still coping
85 85 # with minor difference between implementation.
86 86
87 87 def _dirs_incr(self, filename, old_entry=None):
88 88 """incremente the dirstate counter if applicable
89 89
90 90 This might be a no-op for some subclass who deal with directory
91 91 tracking in a different way.
92 92 """
93 93
94 94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
95 95 """decremente the dirstate counter if applicable
96 96
97 97 This might be a no-op for some subclass who deal with directory
98 98 tracking in a different way.
99 99 """
100 100
101 101 def _refresh_entry(self, f, entry):
102 102 """record updated state of an entry"""
103 103
104 104 def _insert_entry(self, f, entry):
105 105 """add a new dirstate entry (or replace an unrelated one)
106 106
107 107 The fact it is actually new is the responsability of the caller
108 108 """
109 109
110 110 def _drop_entry(self, f):
111 111 """remove any entry for file f
112 112
113 113 This should also drop associated copy information
114 114
115 115 The fact we actually need to drop it is the responsability of the caller"""
116 116
117 117 ### method to manipulate the entries
118 118
119 119 def set_possibly_dirty(self, filename):
120 120 """record that the current state of the file on disk is unknown"""
121 121 entry = self[filename]
122 122 entry.set_possibly_dirty()
123 123 self._refresh_entry(filename, entry)
124 124
125 125 def set_clean(self, filename, mode, size, mtime):
126 126 """mark a file as back to a clean state"""
127 127 entry = self[filename]
128 128 mtime = mtime & rangemask
129 129 size = size & rangemask
130 130 entry.set_clean(mode, size, mtime)
131 131 self._refresh_entry(filename, entry)
132 132 self.copymap.pop(filename, None)
133 133
134 134 def set_tracked(self, filename):
135 135 new = False
136 136 entry = self.get(filename)
137 137 if entry is None:
138 138 self._dirs_incr(filename)
139 139 entry = DirstateItem(
140 p1_tracked=False,
141 p2_tracked=False,
142 140 wc_tracked=True,
143 merged=False,
144 clean_p1=False,
145 clean_p2=False,
146 possibly_dirty=False,
147 parentfiledata=None,
148 141 )
149 142
150 143 self._insert_entry(filename, entry)
151 144 new = True
152 145 elif not entry.tracked:
153 146 self._dirs_incr(filename, entry)
154 147 entry.set_tracked()
155 148 self._refresh_entry(filename, entry)
156 149 new = True
157 150 else:
158 151 # XXX This is probably overkill for more case, but we need this to
159 152 # fully replace the `normallookup` call with `set_tracked` one.
160 153 # Consider smoothing this in the future.
161 154 entry.set_possibly_dirty()
162 155 self._refresh_entry(filename, entry)
163 156 return new
164 157
165 158 def set_untracked(self, f):
166 159 """Mark a file as no longer tracked in the dirstate map"""
167 160 entry = self.get(f)
168 161 if entry is None:
169 162 return False
170 163 else:
171 164 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
172 165 if not entry.merged:
173 166 self.copymap.pop(f, None)
174 167 entry.set_untracked()
175 168 self._refresh_entry(f, entry)
176 169 return True
177 170
178 171 def reset_state(
179 172 self,
180 173 filename,
181 174 wc_tracked=False,
182 175 p1_tracked=False,
183 176 p2_tracked=False,
184 177 merged=False,
185 178 clean_p1=False,
186 179 clean_p2=False,
187 180 possibly_dirty=False,
188 181 parentfiledata=None,
189 182 ):
190 183 """Set a entry to a given state, diregarding all previous state
191 184
192 185 This is to be used by the part of the dirstate API dedicated to
193 186 adjusting the dirstate after a update/merge.
194 187
195 188 note: calling this might result to no entry existing at all if the
196 189 dirstate map does not see any point at having one for this file
197 190 anymore.
198 191 """
199 192 if merged and (clean_p1 or clean_p2):
200 193 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
201 194 raise error.ProgrammingError(msg)
202 195 # copy information are now outdated
203 196 # (maybe new information should be in directly passed to this function)
204 197 self.copymap.pop(filename, None)
205 198
206 199 if not (p1_tracked or p2_tracked or wc_tracked):
207 200 old_entry = self._map.get(filename)
208 201 self._drop_entry(filename)
209 202 self._dirs_decr(filename, old_entry=old_entry)
210 203 return
211 elif merged:
212 pass
213 elif not (p1_tracked or p2_tracked) and wc_tracked:
214 pass # file is added, nothing special to adjust
215 elif (p1_tracked or p2_tracked) and not wc_tracked:
216 pass
217 elif clean_p2 and wc_tracked:
218 pass
219 elif not p1_tracked and p2_tracked and wc_tracked:
220 clean_p2 = True
221 elif possibly_dirty:
222 pass
223 elif wc_tracked:
224 # this is a "normal" file
225 if parentfiledata is None:
226 msg = b'failed to pass parentfiledata for a normal file: %s'
227 msg %= filename
228 raise error.ProgrammingError(msg)
229 else:
230 assert False, 'unreachable'
204
205 p2_info = merged or clean_p2
206 if merged:
207 assert p1_tracked
208
209 has_meaningful_mtime = not possibly_dirty
231 210
232 211 old_entry = self._map.get(filename)
233 212 self._dirs_incr(filename, old_entry)
234 213 entry = DirstateItem(
235 214 wc_tracked=wc_tracked,
236 215 p1_tracked=p1_tracked,
237 p2_tracked=p2_tracked,
238 merged=merged,
239 clean_p1=clean_p1,
240 clean_p2=clean_p2,
241 possibly_dirty=possibly_dirty,
216 p2_info=p2_info,
217 has_meaningful_mtime=has_meaningful_mtime,
242 218 parentfiledata=parentfiledata,
243 219 )
244 220 self._insert_entry(filename, entry)
245 221
246 222
247 223 class dirstatemap(_dirstatemapcommon):
248 224 """Map encapsulating the dirstate's contents.
249 225
250 226 The dirstate contains the following state:
251 227
252 228 - `identity` is the identity of the dirstate file, which can be used to
253 229 detect when changes have occurred to the dirstate file.
254 230
255 231 - `parents` is a pair containing the parents of the working copy. The
256 232 parents are updated by calling `setparents`.
257 233
258 234 - the state map maps filenames to tuples of (state, mode, size, mtime),
259 235 where state is a single character representing 'normal', 'added',
260 236 'removed', or 'merged'. It is read by treating the dirstate as a
261 237 dict. File state is updated by calling various methods (see each
262 238 documentation for details):
263 239
264 240 - `reset_state`,
265 241 - `set_tracked`
266 242 - `set_untracked`
267 243 - `set_clean`
268 244 - `set_possibly_dirty`
269 245
270 246 - `copymap` maps destination filenames to their source filename.
271 247
272 248 The dirstate also provides the following views onto the state:
273 249
274 250 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
275 251 form that they appear as in the dirstate.
276 252
277 253 - `dirfoldmap` is a dict mapping normalized directory names to the
278 254 denormalized form that they appear as in the dirstate.
279 255 """
280 256
281 257 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
282 258 super(dirstatemap, self).__init__(
283 259 ui, opener, root, nodeconstants, use_dirstate_v2
284 260 )
285 261 if self._use_dirstate_v2:
286 262 msg = "Dirstate V2 not supportedi"
287 263 msg += "(should have detected unsupported requirement)"
288 264 raise error.ProgrammingError(msg)
289 265
290 266 ### Core data storage and access
291 267
292 268 @propertycache
293 269 def _map(self):
294 270 self._map = {}
295 271 self.read()
296 272 return self._map
297 273
298 274 @propertycache
299 275 def copymap(self):
300 276 self.copymap = {}
301 277 self._map
302 278 return self.copymap
303 279
304 280 def clear(self):
305 281 self._map.clear()
306 282 self.copymap.clear()
307 283 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
308 284 util.clearcachedproperty(self, b"_dirs")
309 285 util.clearcachedproperty(self, b"_alldirs")
310 286 util.clearcachedproperty(self, b"filefoldmap")
311 287 util.clearcachedproperty(self, b"dirfoldmap")
312 288
313 289 def items(self):
314 290 return pycompat.iteritems(self._map)
315 291
316 292 # forward for python2,3 compat
317 293 iteritems = items
318 294
319 295 def debug_iter(self, all):
320 296 """
321 297 Return an iterator of (filename, state, mode, size, mtime) tuples
322 298
323 299 `all` is unused when Rust is not enabled
324 300 """
325 301 for (filename, item) in self.items():
326 302 yield (filename, item.state, item.mode, item.size, item.mtime)
327 303
328 304 def keys(self):
329 305 return self._map.keys()
330 306
331 307 ### reading/setting parents
332 308
333 309 def parents(self):
334 310 if not self._parents:
335 311 try:
336 312 fp = self._opendirstatefile()
337 313 st = fp.read(2 * self._nodelen)
338 314 fp.close()
339 315 except IOError as err:
340 316 if err.errno != errno.ENOENT:
341 317 raise
342 318 # File doesn't exist, so the current state is empty
343 319 st = b''
344 320
345 321 l = len(st)
346 322 if l == self._nodelen * 2:
347 323 self._parents = (
348 324 st[: self._nodelen],
349 325 st[self._nodelen : 2 * self._nodelen],
350 326 )
351 327 elif l == 0:
352 328 self._parents = (
353 329 self._nodeconstants.nullid,
354 330 self._nodeconstants.nullid,
355 331 )
356 332 else:
357 333 raise error.Abort(
358 334 _(b'working directory state appears damaged!')
359 335 )
360 336
361 337 return self._parents
362 338
363 339 def setparents(self, p1, p2, fold_p2=False):
364 340 self._parents = (p1, p2)
365 341 self._dirtyparents = True
366 342 copies = {}
367 343 if fold_p2:
368 344 for f, s in pycompat.iteritems(self._map):
369 345 # Discard "merged" markers when moving away from a merge state
370 346 if s.merged or s.from_p2:
371 347 source = self.copymap.pop(f, None)
372 348 if source:
373 349 copies[f] = source
374 350 s.drop_merge_data()
375 351 return copies
376 352
377 353 ### disk interaction
378 354
379 355 def read(self):
380 356 # ignore HG_PENDING because identity is used only for writing
381 357 self.identity = util.filestat.frompath(
382 358 self._opener.join(self._filename)
383 359 )
384 360
385 361 try:
386 362 fp = self._opendirstatefile()
387 363 try:
388 364 st = fp.read()
389 365 finally:
390 366 fp.close()
391 367 except IOError as err:
392 368 if err.errno != errno.ENOENT:
393 369 raise
394 370 return
395 371 if not st:
396 372 return
397 373
398 374 if util.safehasattr(parsers, b'dict_new_presized'):
399 375 # Make an estimate of the number of files in the dirstate based on
400 376 # its size. This trades wasting some memory for avoiding costly
401 377 # resizes. Each entry have a prefix of 17 bytes followed by one or
402 378 # two path names. Studies on various large-scale real-world repositories
403 379 # found 54 bytes a reasonable upper limit for the average path names.
404 380 # Copy entries are ignored for the sake of this estimate.
405 381 self._map = parsers.dict_new_presized(len(st) // 71)
406 382
407 383 # Python's garbage collector triggers a GC each time a certain number
408 384 # of container objects (the number being defined by
409 385 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
410 386 # for each file in the dirstate. The C version then immediately marks
411 387 # them as not to be tracked by the collector. However, this has no
412 388 # effect on when GCs are triggered, only on what objects the GC looks
413 389 # into. This means that O(number of files) GCs are unavoidable.
414 390 # Depending on when in the process's lifetime the dirstate is parsed,
415 391 # this can get very expensive. As a workaround, disable GC while
416 392 # parsing the dirstate.
417 393 #
418 394 # (we cannot decorate the function directly since it is in a C module)
419 395 parse_dirstate = util.nogc(parsers.parse_dirstate)
420 396 p = parse_dirstate(self._map, self.copymap, st)
421 397 if not self._dirtyparents:
422 398 self.setparents(*p)
423 399
424 400 # Avoid excess attribute lookups by fast pathing certain checks
425 401 self.__contains__ = self._map.__contains__
426 402 self.__getitem__ = self._map.__getitem__
427 403 self.get = self._map.get
428 404
429 405 def write(self, _tr, st, now):
430 406 d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
431 407 st.write(d)
432 408 st.close()
433 409 self._dirtyparents = False
434 410
435 411 def _opendirstatefile(self):
436 412 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
437 413 if self._pendingmode is not None and self._pendingmode != mode:
438 414 fp.close()
439 415 raise error.Abort(
440 416 _(b'working directory state may be changed parallelly')
441 417 )
442 418 self._pendingmode = mode
443 419 return fp
444 420
445 421 @propertycache
446 422 def identity(self):
447 423 self._map
448 424 return self.identity
449 425
450 426 ### code related to maintaining and accessing "extra" property
451 427 # (e.g. "has_dir")
452 428
453 429 def _dirs_incr(self, filename, old_entry=None):
454 430 """incremente the dirstate counter if applicable"""
455 431 if (
456 432 old_entry is None or old_entry.removed
457 433 ) and "_dirs" in self.__dict__:
458 434 self._dirs.addpath(filename)
459 435 if old_entry is None and "_alldirs" in self.__dict__:
460 436 self._alldirs.addpath(filename)
461 437
462 438 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
463 439 """decremente the dirstate counter if applicable"""
464 440 if old_entry is not None:
465 441 if "_dirs" in self.__dict__ and not old_entry.removed:
466 442 self._dirs.delpath(filename)
467 443 if "_alldirs" in self.__dict__ and not remove_variant:
468 444 self._alldirs.delpath(filename)
469 445 elif remove_variant and "_alldirs" in self.__dict__:
470 446 self._alldirs.addpath(filename)
471 447 if "filefoldmap" in self.__dict__:
472 448 normed = util.normcase(filename)
473 449 self.filefoldmap.pop(normed, None)
474 450
475 451 @propertycache
476 452 def filefoldmap(self):
477 453 """Returns a dictionary mapping normalized case paths to their
478 454 non-normalized versions.
479 455 """
480 456 try:
481 457 makefilefoldmap = parsers.make_file_foldmap
482 458 except AttributeError:
483 459 pass
484 460 else:
485 461 return makefilefoldmap(
486 462 self._map, util.normcasespec, util.normcasefallback
487 463 )
488 464
489 465 f = {}
490 466 normcase = util.normcase
491 467 for name, s in pycompat.iteritems(self._map):
492 468 if not s.removed:
493 469 f[normcase(name)] = name
494 470 f[b'.'] = b'.' # prevents useless util.fspath() invocation
495 471 return f
496 472
497 473 @propertycache
498 474 def dirfoldmap(self):
499 475 f = {}
500 476 normcase = util.normcase
501 477 for name in self._dirs:
502 478 f[normcase(name)] = name
503 479 return f
504 480
505 481 def hastrackeddir(self, d):
506 482 """
507 483 Returns True if the dirstate contains a tracked (not removed) file
508 484 in this directory.
509 485 """
510 486 return d in self._dirs
511 487
512 488 def hasdir(self, d):
513 489 """
514 490 Returns True if the dirstate contains a file (tracked or removed)
515 491 in this directory.
516 492 """
517 493 return d in self._alldirs
518 494
519 495 @propertycache
520 496 def _dirs(self):
521 497 return pathutil.dirs(self._map, only_tracked=True)
522 498
523 499 @propertycache
524 500 def _alldirs(self):
525 501 return pathutil.dirs(self._map)
526 502
527 503 ### code related to manipulation of entries and copy-sources
528 504
529 505 def _refresh_entry(self, f, entry):
530 506 if not entry.any_tracked:
531 507 self._map.pop(f, None)
532 508
533 509 def _insert_entry(self, f, entry):
534 510 self._map[f] = entry
535 511
536 512 def _drop_entry(self, f):
537 513 self._map.pop(f, None)
538 514 self.copymap.pop(f, None)
539 515
540 516
541 517 if rustmod is not None:
542 518
543 519 class dirstatemap(_dirstatemapcommon):
544 520 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
545 521 super(dirstatemap, self).__init__(
546 522 ui, opener, root, nodeconstants, use_dirstate_v2
547 523 )
548 524 self._docket = None
549 525
550 526 ### Core data storage and access
551 527
552 528 @property
553 529 def docket(self):
554 530 if not self._docket:
555 531 if not self._use_dirstate_v2:
556 532 raise error.ProgrammingError(
557 533 b'dirstate only has a docket in v2 format'
558 534 )
559 535 self._docket = docketmod.DirstateDocket.parse(
560 536 self._readdirstatefile(), self._nodeconstants
561 537 )
562 538 return self._docket
563 539
564 540 @propertycache
565 541 def _map(self):
566 542 """
567 543 Fills the Dirstatemap when called.
568 544 """
569 545 # ignore HG_PENDING because identity is used only for writing
570 546 self.identity = util.filestat.frompath(
571 547 self._opener.join(self._filename)
572 548 )
573 549
574 550 if self._use_dirstate_v2:
575 551 if self.docket.uuid:
576 552 # TODO: use mmap when possible
577 553 data = self._opener.read(self.docket.data_filename())
578 554 else:
579 555 data = b''
580 556 self._map = rustmod.DirstateMap.new_v2(
581 557 data, self.docket.data_size, self.docket.tree_metadata
582 558 )
583 559 parents = self.docket.parents
584 560 else:
585 561 self._map, parents = rustmod.DirstateMap.new_v1(
586 562 self._readdirstatefile()
587 563 )
588 564
589 565 if parents and not self._dirtyparents:
590 566 self.setparents(*parents)
591 567
592 568 self.__contains__ = self._map.__contains__
593 569 self.__getitem__ = self._map.__getitem__
594 570 self.get = self._map.get
595 571 return self._map
596 572
597 573 @property
598 574 def copymap(self):
599 575 return self._map.copymap()
600 576
601 577 def debug_iter(self, all):
602 578 """
603 579 Return an iterator of (filename, state, mode, size, mtime) tuples
604 580
605 581 `all`: also include with `state == b' '` dirstate tree nodes that
606 582 don't have an associated `DirstateItem`.
607 583
608 584 """
609 585 return self._map.debug_iter(all)
610 586
611 587 def clear(self):
612 588 self._map.clear()
613 589 self.setparents(
614 590 self._nodeconstants.nullid, self._nodeconstants.nullid
615 591 )
616 592 util.clearcachedproperty(self, b"_dirs")
617 593 util.clearcachedproperty(self, b"_alldirs")
618 594 util.clearcachedproperty(self, b"dirfoldmap")
619 595
620 596 def items(self):
621 597 return self._map.items()
622 598
623 599 # forward for python2,3 compat
624 600 iteritems = items
625 601
626 602 def keys(self):
627 603 return iter(self._map)
628 604
629 605 ### reading/setting parents
630 606
631 607 def setparents(self, p1, p2, fold_p2=False):
632 608 self._parents = (p1, p2)
633 609 self._dirtyparents = True
634 610 copies = {}
635 611 if fold_p2:
636 612 # Collect into an intermediate list to avoid a `RuntimeError`
637 613 # exception due to mutation during iteration.
638 614 # TODO: move this the whole loop to Rust where `iter_mut`
639 615 # enables in-place mutation of elements of a collection while
640 616 # iterating it, without mutating the collection itself.
641 617 files_with_p2_info = [
642 618 f for f, s in self._map.items() if s.merged or s.from_p2
643 619 ]
644 620 rust_map = self._map
645 621 for f in files_with_p2_info:
646 622 e = rust_map.get(f)
647 623 source = self.copymap.pop(f, None)
648 624 if source:
649 625 copies[f] = source
650 626 e.drop_merge_data()
651 627 rust_map.set_dirstate_item(f, e)
652 628 return copies
653 629
654 630 def parents(self):
655 631 if not self._parents:
656 632 if self._use_dirstate_v2:
657 633 self._parents = self.docket.parents
658 634 else:
659 635 read_len = self._nodelen * 2
660 636 st = self._readdirstatefile(read_len)
661 637 l = len(st)
662 638 if l == read_len:
663 639 self._parents = (
664 640 st[: self._nodelen],
665 641 st[self._nodelen : 2 * self._nodelen],
666 642 )
667 643 elif l == 0:
668 644 self._parents = (
669 645 self._nodeconstants.nullid,
670 646 self._nodeconstants.nullid,
671 647 )
672 648 else:
673 649 raise error.Abort(
674 650 _(b'working directory state appears damaged!')
675 651 )
676 652
677 653 return self._parents
678 654
679 655 ### disk interaction
680 656
681 657 @propertycache
682 658 def identity(self):
683 659 self._map
684 660 return self.identity
685 661
686 662 def write(self, tr, st, now):
687 663 if not self._use_dirstate_v2:
688 664 p1, p2 = self.parents()
689 665 packed = self._map.write_v1(p1, p2, now)
690 666 st.write(packed)
691 667 st.close()
692 668 self._dirtyparents = False
693 669 return
694 670
695 671 # We can only append to an existing data file if there is one
696 672 can_append = self.docket.uuid is not None
697 673 packed, meta, append = self._map.write_v2(now, can_append)
698 674 if append:
699 675 docket = self.docket
700 676 data_filename = docket.data_filename()
701 677 if tr:
702 678 tr.add(data_filename, docket.data_size)
703 679 with self._opener(data_filename, b'r+b') as fp:
704 680 fp.seek(docket.data_size)
705 681 assert fp.tell() == docket.data_size
706 682 written = fp.write(packed)
707 683 if written is not None: # py2 may return None
708 684 assert written == len(packed), (written, len(packed))
709 685 docket.data_size += len(packed)
710 686 docket.parents = self.parents()
711 687 docket.tree_metadata = meta
712 688 st.write(docket.serialize())
713 689 st.close()
714 690 else:
715 691 old_docket = self.docket
716 692 new_docket = docketmod.DirstateDocket.with_new_uuid(
717 693 self.parents(), len(packed), meta
718 694 )
719 695 data_filename = new_docket.data_filename()
720 696 if tr:
721 697 tr.add(data_filename, 0)
722 698 self._opener.write(data_filename, packed)
723 699 # Write the new docket after the new data file has been
724 700 # written. Because `st` was opened with `atomictemp=True`,
725 701 # the actual `.hg/dirstate` file is only affected on close.
726 702 st.write(new_docket.serialize())
727 703 st.close()
728 704 # Remove the old data file after the new docket pointing to
729 705 # the new data file was written.
730 706 if old_docket.uuid:
731 707 data_filename = old_docket.data_filename()
732 708 unlink = lambda _tr=None: self._opener.unlink(data_filename)
733 709 if tr:
734 710 category = b"dirstate-v2-clean-" + old_docket.uuid
735 711 tr.addpostclose(category, unlink)
736 712 else:
737 713 unlink()
738 714 self._docket = new_docket
739 715 # Reload from the newly-written file
740 716 util.clearcachedproperty(self, b"_map")
741 717 self._dirtyparents = False
742 718
743 719 def _opendirstatefile(self):
744 720 fp, mode = txnutil.trypending(
745 721 self._root, self._opener, self._filename
746 722 )
747 723 if self._pendingmode is not None and self._pendingmode != mode:
748 724 fp.close()
749 725 raise error.Abort(
750 726 _(b'working directory state may be changed parallelly')
751 727 )
752 728 self._pendingmode = mode
753 729 return fp
754 730
755 731 def _readdirstatefile(self, size=-1):
756 732 try:
757 733 with self._opendirstatefile() as fp:
758 734 return fp.read(size)
759 735 except IOError as err:
760 736 if err.errno != errno.ENOENT:
761 737 raise
762 738 # File doesn't exist, so the current state is empty
763 739 return b''
764 740
765 741 ### code related to maintaining and accessing "extra" property
766 742 # (e.g. "has_dir")
767 743
768 744 @propertycache
769 745 def filefoldmap(self):
770 746 """Returns a dictionary mapping normalized case paths to their
771 747 non-normalized versions.
772 748 """
773 749 return self._map.filefoldmapasdict()
774 750
775 751 def hastrackeddir(self, d):
776 752 return self._map.hastrackeddir(d)
777 753
778 754 def hasdir(self, d):
779 755 return self._map.hasdir(d)
780 756
781 757 @propertycache
782 758 def dirfoldmap(self):
783 759 f = {}
784 760 normcase = util.normcase
785 761 for name in self._map.tracked_dirs():
786 762 f[normcase(name)] = name
787 763 return f
788 764
789 765 ### code related to manipulation of entries and copy-sources
790 766
791 767 def _refresh_entry(self, f, entry):
792 768 if not entry.any_tracked:
793 769 self._map.drop_item_and_copy_source(f)
794 770 else:
795 771 self._map.addfile(f, entry)
796 772
797 773 def _insert_entry(self, f, entry):
798 774 self._map.addfile(f, entry)
799 775
800 776 def _drop_entry(self, f):
801 777 self._map.drop_item_and_copy_source(f)
802 778
803 779 def __setitem__(self, key, value):
804 780 assert isinstance(value, DirstateItem)
805 781 self._map.set_dirstate_item(key, value)
@@ -1,842 +1,784
1 1 # parsers.py - Python implementation of parsers.c
2 2 #
3 3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import struct
11 11 import zlib
12 12
13 13 from ..node import (
14 14 nullrev,
15 15 sha1nodeconstants,
16 16 )
17 17 from ..thirdparty import attr
18 18 from .. import (
19 19 error,
20 20 pycompat,
21 21 revlogutils,
22 22 util,
23 23 )
24 24
25 25 from ..revlogutils import nodemap as nodemaputil
26 26 from ..revlogutils import constants as revlog_constants
27 27
28 28 stringio = pycompat.bytesio
29 29
30 30
31 31 _pack = struct.pack
32 32 _unpack = struct.unpack
33 33 _compress = zlib.compress
34 34 _decompress = zlib.decompress
35 35
36 36
37 37 # a special value used internally for `size` if the file come from the other parent
38 38 FROM_P2 = -2
39 39
40 40 # a special value used internally for `size` if the file is modified/merged/added
41 41 NONNORMAL = -1
42 42
43 43 # a special value used internally for `time` if the time is ambigeous
44 44 AMBIGUOUS_TIME = -1
45 45
46 46
47 47 @attr.s(slots=True, init=False)
48 48 class DirstateItem(object):
49 49 """represent a dirstate entry
50 50
51 51 It hold multiple attributes
52 52
53 53 # about file tracking
54 54 - wc_tracked: is the file tracked by the working copy
55 55 - p1_tracked: is the file tracked in working copy first parent
56 - p2_tracked: is the file tracked in working copy second parent
57
58 # about what possible merge action related to this file
59 - clean_p1: merge picked the file content from p1
60 - clean_p2: merge picked the file content from p2
61 - merged: file gather changes from both side.
56 - p2_info: the file has been involved in some merge operation. Either
57 because it was actually merged, or because the p2 version was
58 ahead, or because some renamed moved it there. In either case
59 `hg status` will want it displayed as modified.
62 60
63 61 # about the file state expected from p1 manifest:
64 62 - mode: the file mode in p1
65 63 - size: the file size in p1
66 64
65 These value can be set to None, which mean we don't have a meaningful value
66 to compare with. Either because we don't really care about them as there
67 `status` is known without having to look at the disk or because we don't
68 know these right now and a full comparison will be needed to find out if
69 the file is clean.
70
67 71 # about the file state on disk last time we saw it:
68 72 - mtime: the last known clean mtime for the file.
69 73
70 The last three item (mode, size and mtime) can be None if no meaningful (or
71 trusted) value exists.
72
74 This value can be set to None if no cachable state exist. Either because we
75 do not care (see previous section) or because we could not cache something
76 yet.
73 77 """
74 78
75 79 _wc_tracked = attr.ib()
76 80 _p1_tracked = attr.ib()
77 _p2_tracked = attr.ib()
78 # the three item above should probably be combined
79 #
80 # However it is unclear if they properly cover some of the most advanced
81 # merge case. So we should probably wait on this to be settled.
82 _merged = attr.ib()
83 _clean_p1 = attr.ib()
84 _clean_p2 = attr.ib()
85 _possibly_dirty = attr.ib()
81 _p2_info = attr.ib()
86 82 _mode = attr.ib()
87 83 _size = attr.ib()
88 84 _mtime = attr.ib()
89 85
90 86 def __init__(
91 87 self,
92 88 wc_tracked=False,
93 89 p1_tracked=False,
94 p2_tracked=False,
95 merged=False,
96 clean_p1=False,
97 clean_p2=False,
98 possibly_dirty=False,
90 p2_info=False,
91 has_meaningful_data=True,
92 has_meaningful_mtime=True,
99 93 parentfiledata=None,
100 94 ):
101 if merged and (clean_p1 or clean_p2):
102 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
103 raise error.ProgrammingError(msg)
104
105 assert not (merged and not p1_tracked)
106 95 self._wc_tracked = wc_tracked
107 96 self._p1_tracked = p1_tracked
108 self._p2_tracked = p2_tracked
109 self._merged = merged
110 self._clean_p1 = clean_p1
111 self._clean_p2 = clean_p2
112 self._possibly_dirty = possibly_dirty
113 if parentfiledata is None:
97 self._p2_info = p2_info
98
114 99 self._mode = None
115 100 self._size = None
116 101 self._mtime = None
117 else:
102 if parentfiledata is None:
103 has_meaningful_mtime = False
104 has_meaningful_data = False
105 if has_meaningful_data:
118 106 self._mode = parentfiledata[0]
119 107 self._size = parentfiledata[1]
108 if has_meaningful_mtime:
120 109 self._mtime = parentfiledata[2]
121 110
122 111 @classmethod
123 112 def new_added(cls):
124 113 """constructor to help legacy API to build a new "added" item
125 114
126 115 Should eventually be removed
127 116 """
128 instance = cls()
129 instance._wc_tracked = True
130 instance._p1_tracked = False
131 instance._p2_tracked = False
132 return instance
117 return cls(wc_tracked=True)
133 118
134 119 @classmethod
135 120 def new_merged(cls):
136 121 """constructor to help legacy API to build a new "merged" item
137 122
138 123 Should eventually be removed
139 124 """
140 instance = cls()
141 instance._wc_tracked = True
142 instance._p1_tracked = True # might not be True because of rename ?
143 instance._p2_tracked = True # might not be True because of rename ?
144 instance._merged = True
145 return instance
125 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
146 126
147 127 @classmethod
148 128 def new_from_p2(cls):
149 129 """constructor to help legacy API to build a new "from_p2" item
150 130
151 131 Should eventually be removed
152 132 """
153 instance = cls()
154 instance._wc_tracked = True
155 instance._p1_tracked = False # might actually be True
156 instance._p2_tracked = True
157 instance._clean_p2 = True
158 return instance
133 return cls(wc_tracked=True, p2_info=True)
159 134
160 135 @classmethod
161 136 def new_possibly_dirty(cls):
162 137 """constructor to help legacy API to build a new "possibly_dirty" item
163 138
164 139 Should eventually be removed
165 140 """
166 instance = cls()
167 instance._wc_tracked = True
168 instance._p1_tracked = True
169 instance._possibly_dirty = True
170 return instance
141 return cls(wc_tracked=True, p1_tracked=True)
171 142
172 143 @classmethod
173 144 def new_normal(cls, mode, size, mtime):
174 145 """constructor to help legacy API to build a new "normal" item
175 146
176 147 Should eventually be removed
177 148 """
178 149 assert size != FROM_P2
179 150 assert size != NONNORMAL
180 instance = cls()
181 instance._wc_tracked = True
182 instance._p1_tracked = True
183 instance._mode = mode
184 instance._size = size
185 instance._mtime = mtime
186 return instance
151 return cls(
152 wc_tracked=True,
153 p1_tracked=True,
154 parentfiledata=(mode, size, mtime),
155 )
187 156
188 157 @classmethod
189 158 def from_v1_data(cls, state, mode, size, mtime):
190 159 """Build a new DirstateItem object from V1 data
191 160
192 161 Since the dirstate-v1 format is frozen, the signature of this function
193 162 is not expected to change, unlike the __init__ one.
194 163 """
195 164 if state == b'm':
196 165 return cls.new_merged()
197 166 elif state == b'a':
198 167 return cls.new_added()
199 168 elif state == b'r':
200 instance = cls()
201 instance._wc_tracked = False
202 169 if size == NONNORMAL:
203 instance._merged = True
204 instance._p1_tracked = (
205 True # might not be True because of rename ?
206 )
207 instance._p2_tracked = (
208 True # might not be True because of rename ?
209 )
170 p1_tracked = True
171 p2_info = True
210 172 elif size == FROM_P2:
211 instance._clean_p2 = True
212 instance._p1_tracked = (
213 False # We actually don't know (file history)
214 )
215 instance._p2_tracked = True
173 p1_tracked = False
174 p2_info = True
216 175 else:
217 instance._p1_tracked = True
218 return instance
176 p1_tracked = True
177 p2_info = False
178 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
219 179 elif state == b'n':
220 180 if size == FROM_P2:
221 181 return cls.new_from_p2()
222 182 elif size == NONNORMAL:
223 183 return cls.new_possibly_dirty()
224 184 elif mtime == AMBIGUOUS_TIME:
225 185 instance = cls.new_normal(mode, size, 42)
226 186 instance._mtime = None
227 instance._possibly_dirty = True
228 187 return instance
229 188 else:
230 189 return cls.new_normal(mode, size, mtime)
231 190 else:
232 191 raise RuntimeError(b'unknown state: %s' % state)
233 192
234 193 def set_possibly_dirty(self):
235 194 """Mark a file as "possibly dirty"
236 195
237 196 This means the next status call will have to actually check its content
238 197 to make sure it is correct.
239 198 """
240 self._possibly_dirty = True
199 self._mtime = None
241 200
242 201 def set_clean(self, mode, size, mtime):
243 202 """mark a file as "clean" cancelling potential "possibly dirty call"
244 203
245 204 Note: this function is a descendant of `dirstate.normal` and is
246 205 currently expected to be call on "normal" entry only. There are not
247 206 reason for this to not change in the future as long as the ccode is
248 207 updated to preserve the proper state of the non-normal files.
249 208 """
250 209 self._wc_tracked = True
251 210 self._p1_tracked = True
252 self._p2_tracked = False # this might be wrong
253 self._merged = False
254 self._clean_p2 = False
255 self._possibly_dirty = False
256 211 self._mode = mode
257 212 self._size = size
258 213 self._mtime = mtime
259 214
260 215 def set_tracked(self):
261 216 """mark a file as tracked in the working copy
262 217
263 218 This will ultimately be called by command like `hg add`.
264 219 """
265 220 self._wc_tracked = True
266 # `set_tracked` is replacing various `normallookup` call. So we set
267 # "possibly dirty" to stay on the safe side.
221 # `set_tracked` is replacing various `normallookup` call. So we mark
222 # the files as needing lookup
268 223 #
269 224 # Consider dropping this in the future in favor of something less broad.
270 self._possibly_dirty = True
225 self._mtime = None
271 226
272 227 def set_untracked(self):
273 228 """mark a file as untracked in the working copy
274 229
275 230 This will ultimately be called by command like `hg remove`.
276 231 """
277 232 self._wc_tracked = False
278 233 self._mode = None
279 234 self._size = None
280 235 self._mtime = None
281 236
282 237 def drop_merge_data(self):
283 238 """remove all "merge-only" from a DirstateItem
284 239
285 240 This is to be call by the dirstatemap code when the second parent is dropped
286 241 """
287 if not (self.merged or self.from_p2):
288 return
289 self._p1_tracked = self.merged # why is this not already properly set ?
290
291 self._merged = False
292 self._clean_p1 = False
293 self._clean_p2 = False
294 self._p2_tracked = False
295 self._possibly_dirty = True
242 if self._p2_info:
243 self._p2_info = False
296 244 self._mode = None
297 245 self._size = None
298 246 self._mtime = None
299 247
300 248 @property
301 249 def mode(self):
302 250 return self.v1_mode()
303 251
304 252 @property
305 253 def size(self):
306 254 return self.v1_size()
307 255
308 256 @property
309 257 def mtime(self):
310 258 return self.v1_mtime()
311 259
312 260 @property
313 261 def state(self):
314 262 """
315 263 States are:
316 264 n normal
317 265 m needs merging
318 266 r marked for removal
319 267 a marked for addition
320 268
321 269 XXX This "state" is a bit obscure and mostly a direct expression of the
322 270 dirstatev1 format. It would make sense to ultimately deprecate it in
323 271 favor of the more "semantic" attributes.
324 272 """
325 273 if not self.any_tracked:
326 274 return b'?'
327 275 return self.v1_state()
328 276
329 277 @property
330 278 def tracked(self):
331 279 """True is the file is tracked in the working copy"""
332 280 return self._wc_tracked
333 281
334 282 @property
335 283 def any_tracked(self):
336 284 """True is the file is tracked anywhere (wc or parents)"""
337 return self._wc_tracked or self._p1_tracked or self._p2_tracked
285 return self._wc_tracked or self._p1_tracked or self._p2_info
338 286
339 287 @property
340 288 def added(self):
341 289 """True if the file has been added"""
342 return self._wc_tracked and not (self._p1_tracked or self._p2_tracked)
290 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
343 291
344 292 @property
345 293 def maybe_clean(self):
346 294 """True if the file has a chance to be in the "clean" state"""
347 295 if not self._wc_tracked:
348 296 return False
349 elif self.added:
297 elif not self._p1_tracked:
350 298 return False
351 elif self._merged:
352 return False
353 elif self._clean_p2:
299 elif self._p2_info:
354 300 return False
355 301 return True
356 302
357 303 @property
358 304 def merged(self):
359 305 """True if the file has been merged
360 306
361 307 Should only be set if a merge is in progress in the dirstate
362 308 """
363 return self._wc_tracked and self._merged
309 return self._wc_tracked and self._p1_tracked and self._p2_info
364 310
365 311 @property
366 312 def from_p2(self):
367 313 """True if the file have been fetched from p2 during the current merge
368 314
369 315 This is only True is the file is currently tracked.
370 316
371 317 Should only be set if a merge is in progress in the dirstate
372 318 """
373 if not self._wc_tracked:
374 return False
375 return self._clean_p2
319 return self._wc_tracked and (not self._p1_tracked) and self._p2_info
376 320
377 321 @property
378 322 def removed(self):
379 323 """True if the file has been removed"""
380 return not self._wc_tracked and (self._p1_tracked or self._p2_tracked)
324 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
381 325
382 326 def v1_state(self):
383 327 """return a "state" suitable for v1 serialization"""
384 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
328 if not self.any_tracked:
385 329 # the object has no state to record, this is -currently-
386 330 # unsupported
387 331 raise RuntimeError('untracked item')
388 332 elif self.removed:
389 333 return b'r'
390 334 elif self.merged:
391 335 return b'm'
392 336 elif self.added:
393 337 return b'a'
394 338 else:
395 339 return b'n'
396 340
397 341 def v1_mode(self):
398 342 """return a "mode" suitable for v1 serialization"""
399 343 return self._mode if self._mode is not None else 0
400 344
401 345 def v1_size(self):
402 346 """return a "size" suitable for v1 serialization"""
403 347 if not self.any_tracked:
404 348 # the object has no state to record, this is -currently-
405 349 # unsupported
406 350 raise RuntimeError('untracked item')
407 elif self.removed and self._merged:
351 elif self.removed and self._p1_tracked and self._p2_info:
408 352 return NONNORMAL
409 elif self.removed and self._clean_p2:
353 elif self.removed and self._p2_info:
410 354 return FROM_P2
411 355 elif self.removed:
412 356 return 0
413 357 elif self.merged:
414 358 return FROM_P2
415 359 elif self.added:
416 360 return NONNORMAL
417 361 elif self.from_p2:
418 362 return FROM_P2
419 elif self._possibly_dirty:
420 return self._size if self._size is not None else NONNORMAL
363 elif self._size is None:
364 return NONNORMAL
421 365 else:
422 366 return self._size
423 367
424 368 def v1_mtime(self):
425 369 """return a "mtime" suitable for v1 serialization"""
426 370 if not self.any_tracked:
427 371 # the object has no state to record, this is -currently-
428 372 # unsupported
429 373 raise RuntimeError('untracked item')
430 374 elif self.removed:
431 375 return 0
432 elif self._possibly_dirty:
433 return AMBIGUOUS_TIME
434 elif self.merged:
376 elif self._mtime is None:
435 377 return AMBIGUOUS_TIME
436 elif self.added:
378 elif self._p2_info:
437 379 return AMBIGUOUS_TIME
438 elif self.from_p2:
380 elif not self._p1_tracked:
439 381 return AMBIGUOUS_TIME
440 382 else:
441 return self._mtime if self._mtime is not None else 0
383 return self._mtime
442 384
443 385 def need_delay(self, now):
444 386 """True if the stored mtime would be ambiguous with the current time"""
445 387 return self.v1_state() == b'n' and self.v1_mtime() == now
446 388
447 389
448 390 def gettype(q):
449 391 return int(q & 0xFFFF)
450 392
451 393
452 394 class BaseIndexObject(object):
453 395 # Can I be passed to an algorithme implemented in Rust ?
454 396 rust_ext_compat = 0
455 397 # Format of an index entry according to Python's `struct` language
456 398 index_format = revlog_constants.INDEX_ENTRY_V1
457 399 # Size of a C unsigned long long int, platform independent
458 400 big_int_size = struct.calcsize(b'>Q')
459 401 # Size of a C long int, platform independent
460 402 int_size = struct.calcsize(b'>i')
461 403 # An empty index entry, used as a default value to be overridden, or nullrev
462 404 null_item = (
463 405 0,
464 406 0,
465 407 0,
466 408 -1,
467 409 -1,
468 410 -1,
469 411 -1,
470 412 sha1nodeconstants.nullid,
471 413 0,
472 414 0,
473 415 revlog_constants.COMP_MODE_INLINE,
474 416 revlog_constants.COMP_MODE_INLINE,
475 417 )
476 418
477 419 @util.propertycache
478 420 def entry_size(self):
479 421 return self.index_format.size
480 422
481 423 @property
482 424 def nodemap(self):
483 425 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
484 426 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
485 427 return self._nodemap
486 428
487 429 @util.propertycache
488 430 def _nodemap(self):
489 431 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
490 432 for r in range(0, len(self)):
491 433 n = self[r][7]
492 434 nodemap[n] = r
493 435 return nodemap
494 436
495 437 def has_node(self, node):
496 438 """return True if the node exist in the index"""
497 439 return node in self._nodemap
498 440
499 441 def rev(self, node):
500 442 """return a revision for a node
501 443
502 444 If the node is unknown, raise a RevlogError"""
503 445 return self._nodemap[node]
504 446
505 447 def get_rev(self, node):
506 448 """return a revision for a node
507 449
508 450 If the node is unknown, return None"""
509 451 return self._nodemap.get(node)
510 452
511 453 def _stripnodes(self, start):
512 454 if '_nodemap' in vars(self):
513 455 for r in range(start, len(self)):
514 456 n = self[r][7]
515 457 del self._nodemap[n]
516 458
517 459 def clearcaches(self):
518 460 self.__dict__.pop('_nodemap', None)
519 461
520 462 def __len__(self):
521 463 return self._lgt + len(self._extra)
522 464
523 465 def append(self, tup):
524 466 if '_nodemap' in vars(self):
525 467 self._nodemap[tup[7]] = len(self)
526 468 data = self._pack_entry(len(self), tup)
527 469 self._extra.append(data)
528 470
529 471 def _pack_entry(self, rev, entry):
530 472 assert entry[8] == 0
531 473 assert entry[9] == 0
532 474 return self.index_format.pack(*entry[:8])
533 475
534 476 def _check_index(self, i):
535 477 if not isinstance(i, int):
536 478 raise TypeError(b"expecting int indexes")
537 479 if i < 0 or i >= len(self):
538 480 raise IndexError
539 481
540 482 def __getitem__(self, i):
541 483 if i == -1:
542 484 return self.null_item
543 485 self._check_index(i)
544 486 if i >= self._lgt:
545 487 data = self._extra[i - self._lgt]
546 488 else:
547 489 index = self._calculate_index(i)
548 490 data = self._data[index : index + self.entry_size]
549 491 r = self._unpack_entry(i, data)
550 492 if self._lgt and i == 0:
551 493 offset = revlogutils.offset_type(0, gettype(r[0]))
552 494 r = (offset,) + r[1:]
553 495 return r
554 496
555 497 def _unpack_entry(self, rev, data):
556 498 r = self.index_format.unpack(data)
557 499 r = r + (
558 500 0,
559 501 0,
560 502 revlog_constants.COMP_MODE_INLINE,
561 503 revlog_constants.COMP_MODE_INLINE,
562 504 )
563 505 return r
564 506
565 507 def pack_header(self, header):
566 508 """pack header information as binary"""
567 509 v_fmt = revlog_constants.INDEX_HEADER
568 510 return v_fmt.pack(header)
569 511
570 512 def entry_binary(self, rev):
571 513 """return the raw binary string representing a revision"""
572 514 entry = self[rev]
573 515 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
574 516 if rev == 0:
575 517 p = p[revlog_constants.INDEX_HEADER.size :]
576 518 return p
577 519
578 520
579 521 class IndexObject(BaseIndexObject):
580 522 def __init__(self, data):
581 523 assert len(data) % self.entry_size == 0, (
582 524 len(data),
583 525 self.entry_size,
584 526 len(data) % self.entry_size,
585 527 )
586 528 self._data = data
587 529 self._lgt = len(data) // self.entry_size
588 530 self._extra = []
589 531
590 532 def _calculate_index(self, i):
591 533 return i * self.entry_size
592 534
593 535 def __delitem__(self, i):
594 536 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
595 537 raise ValueError(b"deleting slices only supports a:-1 with step 1")
596 538 i = i.start
597 539 self._check_index(i)
598 540 self._stripnodes(i)
599 541 if i < self._lgt:
600 542 self._data = self._data[: i * self.entry_size]
601 543 self._lgt = i
602 544 self._extra = []
603 545 else:
604 546 self._extra = self._extra[: i - self._lgt]
605 547
606 548
607 549 class PersistentNodeMapIndexObject(IndexObject):
608 550 """a Debug oriented class to test persistent nodemap
609 551
610 552 We need a simple python object to test API and higher level behavior. See
611 553 the Rust implementation for more serious usage. This should be used only
612 554 through the dedicated `devel.persistent-nodemap` config.
613 555 """
614 556
615 557 def nodemap_data_all(self):
616 558 """Return bytes containing a full serialization of a nodemap
617 559
618 560 The nodemap should be valid for the full set of revisions in the
619 561 index."""
620 562 return nodemaputil.persistent_data(self)
621 563
622 564 def nodemap_data_incremental(self):
623 565 """Return bytes containing a incremental update to persistent nodemap
624 566
625 567 This containst the data for an append-only update of the data provided
626 568 in the last call to `update_nodemap_data`.
627 569 """
628 570 if self._nm_root is None:
629 571 return None
630 572 docket = self._nm_docket
631 573 changed, data = nodemaputil.update_persistent_data(
632 574 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
633 575 )
634 576
635 577 self._nm_root = self._nm_max_idx = self._nm_docket = None
636 578 return docket, changed, data
637 579
638 580 def update_nodemap_data(self, docket, nm_data):
639 581 """provide full block of persisted binary data for a nodemap
640 582
641 583 The data are expected to come from disk. See `nodemap_data_all` for a
642 584 produceur of such data."""
643 585 if nm_data is not None:
644 586 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
645 587 if self._nm_root:
646 588 self._nm_docket = docket
647 589 else:
648 590 self._nm_root = self._nm_max_idx = self._nm_docket = None
649 591
650 592
651 593 class InlinedIndexObject(BaseIndexObject):
652 594 def __init__(self, data, inline=0):
653 595 self._data = data
654 596 self._lgt = self._inline_scan(None)
655 597 self._inline_scan(self._lgt)
656 598 self._extra = []
657 599
658 600 def _inline_scan(self, lgt):
659 601 off = 0
660 602 if lgt is not None:
661 603 self._offsets = [0] * lgt
662 604 count = 0
663 605 while off <= len(self._data) - self.entry_size:
664 606 start = off + self.big_int_size
665 607 (s,) = struct.unpack(
666 608 b'>i',
667 609 self._data[start : start + self.int_size],
668 610 )
669 611 if lgt is not None:
670 612 self._offsets[count] = off
671 613 count += 1
672 614 off += self.entry_size + s
673 615 if off != len(self._data):
674 616 raise ValueError(b"corrupted data")
675 617 return count
676 618
677 619 def __delitem__(self, i):
678 620 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
679 621 raise ValueError(b"deleting slices only supports a:-1 with step 1")
680 622 i = i.start
681 623 self._check_index(i)
682 624 self._stripnodes(i)
683 625 if i < self._lgt:
684 626 self._offsets = self._offsets[:i]
685 627 self._lgt = i
686 628 self._extra = []
687 629 else:
688 630 self._extra = self._extra[: i - self._lgt]
689 631
690 632 def _calculate_index(self, i):
691 633 return self._offsets[i]
692 634
693 635
694 636 def parse_index2(data, inline, revlogv2=False):
695 637 if not inline:
696 638 cls = IndexObject2 if revlogv2 else IndexObject
697 639 return cls(data), None
698 640 cls = InlinedIndexObject
699 641 return cls(data, inline), (0, data)
700 642
701 643
702 644 def parse_index_cl_v2(data):
703 645 return IndexChangelogV2(data), None
704 646
705 647
706 648 class IndexObject2(IndexObject):
707 649 index_format = revlog_constants.INDEX_ENTRY_V2
708 650
709 651 def replace_sidedata_info(
710 652 self,
711 653 rev,
712 654 sidedata_offset,
713 655 sidedata_length,
714 656 offset_flags,
715 657 compression_mode,
716 658 ):
717 659 """
718 660 Replace an existing index entry's sidedata offset and length with new
719 661 ones.
720 662 This cannot be used outside of the context of sidedata rewriting,
721 663 inside the transaction that creates the revision `rev`.
722 664 """
723 665 if rev < 0:
724 666 raise KeyError
725 667 self._check_index(rev)
726 668 if rev < self._lgt:
727 669 msg = b"cannot rewrite entries outside of this transaction"
728 670 raise KeyError(msg)
729 671 else:
730 672 entry = list(self[rev])
731 673 entry[0] = offset_flags
732 674 entry[8] = sidedata_offset
733 675 entry[9] = sidedata_length
734 676 entry[11] = compression_mode
735 677 entry = tuple(entry)
736 678 new = self._pack_entry(rev, entry)
737 679 self._extra[rev - self._lgt] = new
738 680
739 681 def _unpack_entry(self, rev, data):
740 682 data = self.index_format.unpack(data)
741 683 entry = data[:10]
742 684 data_comp = data[10] & 3
743 685 sidedata_comp = (data[10] & (3 << 2)) >> 2
744 686 return entry + (data_comp, sidedata_comp)
745 687
746 688 def _pack_entry(self, rev, entry):
747 689 data = entry[:10]
748 690 data_comp = entry[10] & 3
749 691 sidedata_comp = (entry[11] & 3) << 2
750 692 data += (data_comp | sidedata_comp,)
751 693
752 694 return self.index_format.pack(*data)
753 695
754 696 def entry_binary(self, rev):
755 697 """return the raw binary string representing a revision"""
756 698 entry = self[rev]
757 699 return self._pack_entry(rev, entry)
758 700
759 701 def pack_header(self, header):
760 702 """pack header information as binary"""
761 703 msg = 'version header should go in the docket, not the index: %d'
762 704 msg %= header
763 705 raise error.ProgrammingError(msg)
764 706
765 707
766 708 class IndexChangelogV2(IndexObject2):
767 709 index_format = revlog_constants.INDEX_ENTRY_CL_V2
768 710
769 711 def _unpack_entry(self, rev, data, r=True):
770 712 items = self.index_format.unpack(data)
771 713 entry = items[:3] + (rev, rev) + items[3:8]
772 714 data_comp = items[8] & 3
773 715 sidedata_comp = (items[8] >> 2) & 3
774 716 return entry + (data_comp, sidedata_comp)
775 717
776 718 def _pack_entry(self, rev, entry):
777 719 assert entry[3] == rev, entry[3]
778 720 assert entry[4] == rev, entry[4]
779 721 data = entry[:3] + entry[5:10]
780 722 data_comp = entry[10] & 3
781 723 sidedata_comp = (entry[11] & 3) << 2
782 724 data += (data_comp | sidedata_comp,)
783 725 return self.index_format.pack(*data)
784 726
785 727
786 728 def parse_index_devel_nodemap(data, inline):
787 729 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
788 730 return PersistentNodeMapIndexObject(data), None
789 731
790 732
791 733 def parse_dirstate(dmap, copymap, st):
792 734 parents = [st[:20], st[20:40]]
793 735 # dereference fields so they will be local in loop
794 736 format = b">cllll"
795 737 e_size = struct.calcsize(format)
796 738 pos1 = 40
797 739 l = len(st)
798 740
799 741 # the inner loop
800 742 while pos1 < l:
801 743 pos2 = pos1 + e_size
802 744 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
803 745 pos1 = pos2 + e[4]
804 746 f = st[pos2:pos1]
805 747 if b'\0' in f:
806 748 f, c = f.split(b'\0')
807 749 copymap[f] = c
808 750 dmap[f] = DirstateItem.from_v1_data(*e[:4])
809 751 return parents
810 752
811 753
812 754 def pack_dirstate(dmap, copymap, pl, now):
813 755 now = int(now)
814 756 cs = stringio()
815 757 write = cs.write
816 758 write(b"".join(pl))
817 759 for f, e in pycompat.iteritems(dmap):
818 760 if e.need_delay(now):
819 761 # The file was last modified "simultaneously" with the current
820 762 # write to dirstate (i.e. within the same second for file-
821 763 # systems with a granularity of 1 sec). This commonly happens
822 764 # for at least a couple of files on 'update'.
823 765 # The user could change the file without changing its size
824 766 # within the same second. Invalidate the file's mtime in
825 767 # dirstate, forcing future 'status' calls to compare the
826 768 # contents of the file if the size is the same. This prevents
827 769 # mistakenly treating such files as clean.
828 770 e.set_possibly_dirty()
829 771
830 772 if f in copymap:
831 773 f = b"%s\0%s" % (f, copymap[f])
832 774 e = _pack(
833 775 b">cllll",
834 776 e.v1_state(),
835 777 e.v1_mode(),
836 778 e.v1_size(),
837 779 e.v1_mtime(),
838 780 len(f),
839 781 )
840 782 write(e)
841 783 write(f)
842 784 return cs.getvalue()
@@ -1,427 +1,413
1 1 use crate::errors::HgError;
2 2 use bitflags::bitflags;
3 3 use std::convert::TryFrom;
4 4
5 5 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
6 6 pub enum EntryState {
7 7 Normal,
8 8 Added,
9 9 Removed,
10 10 Merged,
11 11 }
12 12
13 13 /// The C implementation uses all signed types. This will be an issue
14 14 /// either when 4GB+ source files are commonplace or in 2038, whichever
15 15 /// comes first.
16 16 #[derive(Debug, PartialEq, Copy, Clone)]
17 17 pub struct DirstateEntry {
18 18 flags: Flags,
19 mode: i32,
20 size: i32,
21 mtime: i32,
19 mode_size: Option<(i32, i32)>,
20 mtime: Option<i32>,
22 21 }
23 22
24 23 bitflags! {
25 pub struct Flags: u8 {
24 struct Flags: u8 {
26 25 const WDIR_TRACKED = 1 << 0;
27 26 const P1_TRACKED = 1 << 1;
28 const P2_TRACKED = 1 << 2;
29 const POSSIBLY_DIRTY = 1 << 3;
30 const MERGED = 1 << 4;
31 const CLEAN_P1 = 1 << 5;
32 const CLEAN_P2 = 1 << 6;
33 const ENTRYLESS_TREE_NODE = 1 << 7;
27 const P2_INFO = 1 << 2;
34 28 }
35 29 }
36 30
37 31 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
38 32
39 33 pub const MTIME_UNSET: i32 = -1;
40 34
41 35 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
42 36 /// other parent. This allows revert to pick the right status back during a
43 37 /// merge.
44 38 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
45 39 /// A special value used for internal representation of special case in
46 40 /// dirstate v1 format.
47 41 pub const SIZE_NON_NORMAL: i32 = -1;
48 42
49 43 impl DirstateEntry {
50 44 pub fn new(
51 flags: Flags,
52 mode_size_mtime: Option<(i32, i32, i32)>,
45 wdir_tracked: bool,
46 p1_tracked: bool,
47 p2_info: bool,
48 mode_size: Option<(i32, i32)>,
49 mtime: Option<i32>,
53 50 ) -> Self {
54 let (mode, size, mtime) =
55 mode_size_mtime.unwrap_or((0, SIZE_NON_NORMAL, MTIME_UNSET));
51 let mut flags = Flags::empty();
52 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
53 flags.set(Flags::P1_TRACKED, p1_tracked);
54 flags.set(Flags::P2_INFO, p2_info);
56 55 Self {
57 56 flags,
58 mode,
59 size,
57 mode_size,
60 58 mtime,
61 59 }
62 60 }
63 61
64 62 pub fn from_v1_data(
65 63 state: EntryState,
66 64 mode: i32,
67 65 size: i32,
68 66 mtime: i32,
69 67 ) -> Self {
70 68 match state {
71 69 EntryState::Normal => {
72 70 if size == SIZE_FROM_OTHER_PARENT {
73 71 Self::new_from_p2()
74 72 } else if size == SIZE_NON_NORMAL {
75 73 Self::new_possibly_dirty()
76 74 } else if mtime == MTIME_UNSET {
77 75 Self {
78 flags: Flags::WDIR_TRACKED
79 | Flags::P1_TRACKED
80 | Flags::POSSIBLY_DIRTY,
81 mode,
82 size,
83 mtime: 0,
76 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
77 mode_size: Some((mode, size)),
78 mtime: None,
84 79 }
85 80 } else {
86 81 Self::new_normal(mode, size, mtime)
87 82 }
88 83 }
89 84 EntryState::Added => Self::new_added(),
90 85 EntryState::Removed => Self {
91 86 flags: if size == SIZE_NON_NORMAL {
92 Flags::P1_TRACKED // might not be true because of rename ?
93 | Flags::P2_TRACKED // might not be true because of rename ?
94 | Flags::MERGED
87 Flags::P1_TRACKED | Flags::P2_INFO
95 88 } else if size == SIZE_FROM_OTHER_PARENT {
96 89 // We don’t know if P1_TRACKED should be set (file history)
97 Flags::P2_TRACKED | Flags::CLEAN_P2
90 Flags::P2_INFO
98 91 } else {
99 92 Flags::P1_TRACKED
100 93 },
101 mode: 0,
102 size: 0,
103 mtime: 0,
94 mode_size: None,
95 mtime: None,
104 96 },
105 97 EntryState::Merged => Self::new_merged(),
106 98 }
107 99 }
108 100
109 101 pub fn new_from_p2() -> Self {
110 102 Self {
111 103 // might be missing P1_TRACKED
112 flags: Flags::WDIR_TRACKED | Flags::P2_TRACKED | Flags::CLEAN_P2,
113 mode: 0,
114 size: SIZE_FROM_OTHER_PARENT,
115 mtime: MTIME_UNSET,
104 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
105 mode_size: None,
106 mtime: None,
116 107 }
117 108 }
118 109
119 110 pub fn new_possibly_dirty() -> Self {
120 111 Self {
121 flags: Flags::WDIR_TRACKED
122 | Flags::P1_TRACKED
123 | Flags::POSSIBLY_DIRTY,
124 mode: 0,
125 size: SIZE_NON_NORMAL,
126 mtime: MTIME_UNSET,
112 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
113 mode_size: None,
114 mtime: None,
127 115 }
128 116 }
129 117
130 118 pub fn new_added() -> Self {
131 119 Self {
132 120 flags: Flags::WDIR_TRACKED,
133 mode: 0,
134 size: SIZE_NON_NORMAL,
135 mtime: MTIME_UNSET,
121 mode_size: None,
122 mtime: None,
136 123 }
137 124 }
138 125
139 126 pub fn new_merged() -> Self {
140 127 Self {
141 128 flags: Flags::WDIR_TRACKED
142 129 | Flags::P1_TRACKED // might not be true because of rename ?
143 | Flags::P2_TRACKED // might not be true because of rename ?
144 | Flags::MERGED,
145 mode: 0,
146 size: SIZE_NON_NORMAL,
147 mtime: MTIME_UNSET,
130 | Flags::P2_INFO, // might not be true because of rename ?
131 mode_size: None,
132 mtime: None,
148 133 }
149 134 }
150 135
151 136 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
152 137 Self {
153 138 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
154 mode,
155 size,
156 mtime,
139 mode_size: Some((mode, size)),
140 mtime: Some(mtime),
157 141 }
158 142 }
159 143
160 144 /// Creates a new entry in "removed" state.
161 145 ///
162 146 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
163 147 /// `SIZE_FROM_OTHER_PARENT`
164 148 pub fn new_removed(size: i32) -> Self {
165 149 Self::from_v1_data(EntryState::Removed, 0, size, 0)
166 150 }
167 151
168 152 pub fn tracked(&self) -> bool {
169 153 self.flags.contains(Flags::WDIR_TRACKED)
170 154 }
171 155
172 fn tracked_in_any_parent(&self) -> bool {
173 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED)
156 fn in_either_parent(&self) -> bool {
157 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
174 158 }
175 159
176 160 pub fn removed(&self) -> bool {
177 self.tracked_in_any_parent()
178 && !self.flags.contains(Flags::WDIR_TRACKED)
161 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
179 162 }
180 163
181 164 pub fn merged(&self) -> bool {
182 self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED)
165 self.flags
166 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
183 167 }
184 168
185 169 pub fn added(&self) -> bool {
186 self.flags.contains(Flags::WDIR_TRACKED)
187 && !self.tracked_in_any_parent()
170 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
188 171 }
189 172
190 173 pub fn from_p2(&self) -> bool {
191 self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2)
174 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
175 && !self.flags.contains(Flags::P1_TRACKED)
192 176 }
193 177
194 178 pub fn maybe_clean(&self) -> bool {
195 179 if !self.flags.contains(Flags::WDIR_TRACKED) {
196 180 false
197 } else if self.added() {
181 } else if !self.flags.contains(Flags::P1_TRACKED) {
198 182 false
199 } else if self.flags.contains(Flags::MERGED) {
200 false
201 } else if self.flags.contains(Flags::CLEAN_P2) {
183 } else if self.flags.contains(Flags::P2_INFO) {
202 184 false
203 185 } else {
204 186 true
205 187 }
206 188 }
207 189
208 190 pub fn any_tracked(&self) -> bool {
209 191 self.flags.intersects(
210 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_TRACKED,
192 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
211 193 )
212 194 }
213 195
214 pub fn state(&self) -> EntryState {
196 fn v1_state(&self) -> EntryState {
197 if !self.any_tracked() {
198 // TODO: return an Option instead?
199 panic!("Accessing v1_state of an untracked DirstateEntry")
200 }
215 201 if self.removed() {
216 202 EntryState::Removed
217 203 } else if self.merged() {
218 204 EntryState::Merged
219 205 } else if self.added() {
220 206 EntryState::Added
221 207 } else {
222 208 EntryState::Normal
223 209 }
224 210 }
225 211
226 pub fn mode(&self) -> i32 {
227 self.mode
212 fn v1_mode(&self) -> i32 {
213 if let Some((mode, _size)) = self.mode_size {
214 mode
215 } else {
216 0
217 }
228 218 }
229 219
230 pub fn size(&self) -> i32 {
231 if self.removed() && self.flags.contains(Flags::MERGED) {
220 fn v1_size(&self) -> i32 {
221 if !self.any_tracked() {
222 // TODO: return an Option instead?
223 panic!("Accessing v1_size of an untracked DirstateEntry")
224 }
225 if self.removed()
226 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
227 {
232 228 SIZE_NON_NORMAL
233 } else if self.removed() && self.flags.contains(Flags::CLEAN_P2) {
229 } else if self.removed() && self.flags.contains(Flags::P2_INFO) {
234 230 SIZE_FROM_OTHER_PARENT
235 231 } else if self.removed() {
236 232 0
237 233 } else if self.merged() {
238 234 SIZE_FROM_OTHER_PARENT
239 235 } else if self.added() {
240 236 SIZE_NON_NORMAL
241 237 } else if self.from_p2() {
242 238 SIZE_FROM_OTHER_PARENT
243 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) {
244 self.size // TODO: SIZE_NON_NORMAL ?
239 } else if let Some((_mode, size)) = self.mode_size {
240 size
245 241 } else {
246 self.size
242 SIZE_NON_NORMAL
247 243 }
248 244 }
249 245
250 pub fn mtime(&self) -> i32 {
246 fn v1_mtime(&self) -> i32 {
247 if !self.any_tracked() {
248 // TODO: return an Option instead?
249 panic!("Accessing v1_mtime of an untracked DirstateEntry")
250 }
251 251 if self.removed() {
252 252 0
253 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) {
254 MTIME_UNSET
255 } else if self.merged() {
253 } else if self.flags.contains(Flags::P2_INFO) {
256 254 MTIME_UNSET
257 } else if self.added() {
258 MTIME_UNSET
259 } else if self.from_p2() {
255 } else if !self.flags.contains(Flags::P1_TRACKED) {
260 256 MTIME_UNSET
261 257 } else {
262 self.mtime
258 self.mtime.unwrap_or(MTIME_UNSET)
259 }
260 }
261
262 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
263 pub fn state(&self) -> EntryState {
264 self.v1_state()
263 265 }
266
267 // TODO: return Option?
268 pub fn mode(&self) -> i32 {
269 self.v1_mode()
270 }
271
272 // TODO: return Option?
273 pub fn size(&self) -> i32 {
274 self.v1_size()
275 }
276
277 // TODO: return Option?
278 pub fn mtime(&self) -> i32 {
279 self.v1_mtime()
264 280 }
265 281
266 282 pub fn drop_merge_data(&mut self) {
267 if self.flags.contains(Flags::CLEAN_P1)
268 || self.flags.contains(Flags::CLEAN_P2)
269 || self.flags.contains(Flags::MERGED)
270 || self.flags.contains(Flags::P2_TRACKED)
271 {
272 if self.flags.contains(Flags::MERGED) {
273 self.flags.insert(Flags::P1_TRACKED);
274 } else {
275 self.flags.remove(Flags::P1_TRACKED);
276 }
277 self.flags.remove(
278 Flags::MERGED
279 | Flags::CLEAN_P1
280 | Flags::CLEAN_P2
281 | Flags::P2_TRACKED,
282 );
283 self.flags.insert(Flags::POSSIBLY_DIRTY);
284 self.mode = 0;
285 self.mtime = 0;
286 // size = None on the python size turn into size = NON_NORMAL when
287 // accessed. So the next line is currently required, but a some
288 // future clean up would be welcome.
289 self.size = SIZE_NON_NORMAL;
283 if self.flags.contains(Flags::P2_INFO) {
284 self.flags.remove(Flags::P2_INFO);
285 self.mode_size = None;
286 self.mtime = None;
290 287 }
291 288 }
292 289
293 290 pub fn set_possibly_dirty(&mut self) {
294 self.flags.insert(Flags::POSSIBLY_DIRTY)
291 self.mtime = None
295 292 }
296 293
297 294 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
298 295 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
299 self.flags.remove(
300 Flags::P2_TRACKED // This might be wrong
301 | Flags::MERGED
302 | Flags::CLEAN_P2
303 | Flags::POSSIBLY_DIRTY,
304 );
305 self.mode = mode;
306 self.size = size;
307 self.mtime = mtime;
296 self.mode_size = Some((mode, size));
297 self.mtime = Some(mtime);
308 298 }
309 299
310 300 pub fn set_tracked(&mut self) {
311 self.flags
312 .insert(Flags::WDIR_TRACKED | Flags::POSSIBLY_DIRTY);
313 // size = None on the python size turn into size = NON_NORMAL when
314 // accessed. So the next line is currently required, but a some future
315 // clean up would be welcome.
316 self.size = SIZE_NON_NORMAL;
301 self.flags.insert(Flags::WDIR_TRACKED);
302 // `set_tracked` is replacing various `normallookup` call. So we mark
303 // the files as needing lookup
304 //
305 // Consider dropping this in the future in favor of something less
306 // broad.
307 self.mtime = None;
317 308 }
318 309
319 310 pub fn set_untracked(&mut self) {
320 311 self.flags.remove(Flags::WDIR_TRACKED);
321 self.mode = 0;
322 self.size = 0;
323 self.mtime = 0;
312 self.mode_size = None;
313 self.mtime = None;
324 314 }
325 315
326 316 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
327 317 /// in the dirstate-v1 format.
328 318 ///
329 319 /// This includes marker values such as `mtime == -1`. In the future we may
330 320 /// want to not represent these cases that way in memory, but serialization
331 321 /// will need to keep the same format.
332 322 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
333 (self.state().into(), self.mode(), self.size(), self.mtime())
323 (
324 self.v1_state().into(),
325 self.v1_mode(),
326 self.v1_size(),
327 self.v1_mtime(),
328 )
334 329 }
335 330
336 331 pub(crate) fn is_from_other_parent(&self) -> bool {
337 332 self.state() == EntryState::Normal
338 333 && self.size() == SIZE_FROM_OTHER_PARENT
339 334 }
340 335
341 336 // TODO: other platforms
342 337 #[cfg(unix)]
343 338 pub fn mode_changed(
344 339 &self,
345 340 filesystem_metadata: &std::fs::Metadata,
346 341 ) -> bool {
347 342 use std::os::unix::fs::MetadataExt;
348 343 const EXEC_BIT_MASK: u32 = 0o100;
349 344 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
350 345 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
351 346 dirstate_exec_bit != fs_exec_bit
352 347 }
353 348
354 349 /// Returns a `(state, mode, size, mtime)` tuple as for
355 350 /// `DirstateMapMethods::debug_iter`.
356 351 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
357 let state = if self.flags.contains(Flags::ENTRYLESS_TREE_NODE) {
358 b' '
359 } else {
360 self.state().into()
361 };
362 (state, self.mode(), self.size(), self.mtime())
352 (self.state().into(), self.mode(), self.size(), self.mtime())
363 353 }
364 354
365 355 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
366 356 self.state() == EntryState::Normal && self.mtime() == now
367 357 }
368 358
369 359 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
370 360 let ambiguous = self.mtime_is_ambiguous(now);
371 361 if ambiguous {
372 362 // The file was last modified "simultaneously" with the current
373 363 // write to dirstate (i.e. within the same second for file-
374 364 // systems with a granularity of 1 sec). This commonly happens
375 365 // for at least a couple of files on 'update'.
376 366 // The user could change the file without changing its size
377 367 // within the same second. Invalidate the file's mtime in
378 368 // dirstate, forcing future 'status' calls to compare the
379 369 // contents of the file if the size is the same. This prevents
380 370 // mistakenly treating such files as clean.
381 self.clear_mtime()
371 self.set_possibly_dirty()
382 372 }
383 373 ambiguous
384 374 }
385
386 pub fn clear_mtime(&mut self) {
387 self.mtime = -1;
388 }
389 375 }
390 376
391 377 impl EntryState {
392 378 pub fn is_tracked(self) -> bool {
393 379 use EntryState::*;
394 380 match self {
395 381 Normal | Added | Merged => true,
396 382 Removed => false,
397 383 }
398 384 }
399 385 }
400 386
401 387 impl TryFrom<u8> for EntryState {
402 388 type Error = HgError;
403 389
404 390 fn try_from(value: u8) -> Result<Self, Self::Error> {
405 391 match value {
406 392 b'n' => Ok(EntryState::Normal),
407 393 b'a' => Ok(EntryState::Added),
408 394 b'r' => Ok(EntryState::Removed),
409 395 b'm' => Ok(EntryState::Merged),
410 396 _ => Err(HgError::CorruptedRepository(format!(
411 397 "Incorrect dirstate entry state {}",
412 398 value
413 399 ))),
414 400 }
415 401 }
416 402 }
417 403
418 404 impl Into<u8> for EntryState {
419 405 fn into(self) -> u8 {
420 406 match self {
421 407 EntryState::Normal => b'n',
422 408 EntryState::Added => b'a',
423 409 EntryState::Removed => b'r',
424 410 EntryState::Merged => b'm',
425 411 }
426 412 }
427 413 }
@@ -1,1193 +1,1193
1 1 use bytes_cast::BytesCast;
2 2 use micro_timer::timed;
3 3 use std::borrow::Cow;
4 4 use std::convert::TryInto;
5 5 use std::path::PathBuf;
6 6
7 7 use super::on_disk;
8 8 use super::on_disk::DirstateV2ParseError;
9 9 use super::owning::OwningDirstateMap;
10 10 use super::path_with_basename::WithBasename;
11 11 use crate::dirstate::parsers::pack_entry;
12 12 use crate::dirstate::parsers::packed_entry_size;
13 13 use crate::dirstate::parsers::parse_dirstate_entries;
14 14 use crate::dirstate::parsers::Timestamp;
15 15 use crate::dirstate::CopyMapIter;
16 16 use crate::dirstate::StateMapIter;
17 17 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
18 18 use crate::dirstate::SIZE_NON_NORMAL;
19 19 use crate::matchers::Matcher;
20 20 use crate::utils::hg_path::{HgPath, HgPathBuf};
21 21 use crate::DirstateEntry;
22 22 use crate::DirstateError;
23 23 use crate::DirstateParents;
24 24 use crate::DirstateStatus;
25 25 use crate::EntryState;
26 26 use crate::FastHashMap;
27 27 use crate::PatternFileWarning;
28 28 use crate::StatusError;
29 29 use crate::StatusOptions;
30 30
31 31 /// Append to an existing data file if the amount of unreachable data (not used
32 32 /// anymore) is less than this fraction of the total amount of existing data.
33 33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34 34
35 35 pub struct DirstateMap<'on_disk> {
36 36 /// Contents of the `.hg/dirstate` file
37 37 pub(super) on_disk: &'on_disk [u8],
38 38
39 39 pub(super) root: ChildNodes<'on_disk>,
40 40
41 41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
42 42 pub(super) nodes_with_entry_count: u32,
43 43
44 44 /// Number of nodes anywhere in the tree that have
45 45 /// `.copy_source.is_some()`.
46 46 pub(super) nodes_with_copy_source_count: u32,
47 47
48 48 /// See on_disk::Header
49 49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
50 50
51 51 /// How many bytes of `on_disk` are not used anymore
52 52 pub(super) unreachable_bytes: u32,
53 53 }
54 54
55 55 /// Using a plain `HgPathBuf` of the full path from the repository root as a
56 56 /// map key would also work: all paths in a given map have the same parent
57 57 /// path, so comparing full paths gives the same result as comparing base
58 58 /// names. However `HashMap` would waste time always re-hashing the same
59 59 /// string prefix.
60 60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
61 61
62 62 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
63 63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
64 64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
65 65 InMemory(&'tree HgPathBuf),
66 66 OnDisk(&'on_disk HgPath),
67 67 }
68 68
69 69 pub(super) enum ChildNodes<'on_disk> {
70 70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
71 71 OnDisk(&'on_disk [on_disk::Node]),
72 72 }
73 73
74 74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
75 75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
76 76 OnDisk(&'on_disk [on_disk::Node]),
77 77 }
78 78
79 79 pub(super) enum NodeRef<'tree, 'on_disk> {
80 80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
81 81 OnDisk(&'on_disk on_disk::Node),
82 82 }
83 83
84 84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
85 85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
86 86 match *self {
87 87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
88 88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
89 89 }
90 90 }
91 91 }
92 92
93 93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
94 94 type Target = HgPath;
95 95
96 96 fn deref(&self) -> &HgPath {
97 97 match *self {
98 98 BorrowedPath::InMemory(in_memory) => in_memory,
99 99 BorrowedPath::OnDisk(on_disk) => on_disk,
100 100 }
101 101 }
102 102 }
103 103
104 104 impl Default for ChildNodes<'_> {
105 105 fn default() -> Self {
106 106 ChildNodes::InMemory(Default::default())
107 107 }
108 108 }
109 109
110 110 impl<'on_disk> ChildNodes<'on_disk> {
111 111 pub(super) fn as_ref<'tree>(
112 112 &'tree self,
113 113 ) -> ChildNodesRef<'tree, 'on_disk> {
114 114 match self {
115 115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
116 116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
117 117 }
118 118 }
119 119
120 120 pub(super) fn is_empty(&self) -> bool {
121 121 match self {
122 122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
123 123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
124 124 }
125 125 }
126 126
127 127 fn make_mut(
128 128 &mut self,
129 129 on_disk: &'on_disk [u8],
130 130 unreachable_bytes: &mut u32,
131 131 ) -> Result<
132 132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
133 133 DirstateV2ParseError,
134 134 > {
135 135 match self {
136 136 ChildNodes::InMemory(nodes) => Ok(nodes),
137 137 ChildNodes::OnDisk(nodes) => {
138 138 *unreachable_bytes +=
139 139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
140 140 let nodes = nodes
141 141 .iter()
142 142 .map(|node| {
143 143 Ok((
144 144 node.path(on_disk)?,
145 145 node.to_in_memory_node(on_disk)?,
146 146 ))
147 147 })
148 148 .collect::<Result<_, _>>()?;
149 149 *self = ChildNodes::InMemory(nodes);
150 150 match self {
151 151 ChildNodes::InMemory(nodes) => Ok(nodes),
152 152 ChildNodes::OnDisk(_) => unreachable!(),
153 153 }
154 154 }
155 155 }
156 156 }
157 157 }
158 158
159 159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
160 160 pub(super) fn get(
161 161 &self,
162 162 base_name: &HgPath,
163 163 on_disk: &'on_disk [u8],
164 164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
165 165 match self {
166 166 ChildNodesRef::InMemory(nodes) => Ok(nodes
167 167 .get_key_value(base_name)
168 168 .map(|(k, v)| NodeRef::InMemory(k, v))),
169 169 ChildNodesRef::OnDisk(nodes) => {
170 170 let mut parse_result = Ok(());
171 171 let search_result = nodes.binary_search_by(|node| {
172 172 match node.base_name(on_disk) {
173 173 Ok(node_base_name) => node_base_name.cmp(base_name),
174 174 Err(e) => {
175 175 parse_result = Err(e);
176 176 // Dummy comparison result, `search_result` won’t
177 177 // be used since `parse_result` is an error
178 178 std::cmp::Ordering::Equal
179 179 }
180 180 }
181 181 });
182 182 parse_result.map(|()| {
183 183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
184 184 })
185 185 }
186 186 }
187 187 }
188 188
189 189 /// Iterate in undefined order
190 190 pub(super) fn iter(
191 191 &self,
192 192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
193 193 match self {
194 194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
195 195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
196 196 ),
197 197 ChildNodesRef::OnDisk(nodes) => {
198 198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
199 199 }
200 200 }
201 201 }
202 202
203 203 /// Iterate in parallel in undefined order
204 204 pub(super) fn par_iter(
205 205 &self,
206 206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
207 207 {
208 208 use rayon::prelude::*;
209 209 match self {
210 210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
211 211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
212 212 ),
213 213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
214 214 nodes.par_iter().map(NodeRef::OnDisk),
215 215 ),
216 216 }
217 217 }
218 218
219 219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
220 220 match self {
221 221 ChildNodesRef::InMemory(nodes) => {
222 222 let mut vec: Vec<_> = nodes
223 223 .iter()
224 224 .map(|(k, v)| NodeRef::InMemory(k, v))
225 225 .collect();
226 226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
227 227 match node {
228 228 NodeRef::InMemory(path, _node) => path.base_name(),
229 229 NodeRef::OnDisk(_) => unreachable!(),
230 230 }
231 231 }
232 232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
233 233 // value: https://github.com/rust-lang/rust/issues/34162
234 234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
235 235 vec
236 236 }
237 237 ChildNodesRef::OnDisk(nodes) => {
238 238 // Nodes on disk are already sorted
239 239 nodes.iter().map(NodeRef::OnDisk).collect()
240 240 }
241 241 }
242 242 }
243 243 }
244 244
245 245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
246 246 pub(super) fn full_path(
247 247 &self,
248 248 on_disk: &'on_disk [u8],
249 249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
250 250 match self {
251 251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
252 252 NodeRef::OnDisk(node) => node.full_path(on_disk),
253 253 }
254 254 }
255 255
256 256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
257 257 /// HgPath>` detached from `'tree`
258 258 pub(super) fn full_path_borrowed(
259 259 &self,
260 260 on_disk: &'on_disk [u8],
261 261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
262 262 match self {
263 263 NodeRef::InMemory(path, _node) => match path.full_path() {
264 264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
265 265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
266 266 },
267 267 NodeRef::OnDisk(node) => {
268 268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
269 269 }
270 270 }
271 271 }
272 272
273 273 pub(super) fn base_name(
274 274 &self,
275 275 on_disk: &'on_disk [u8],
276 276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
277 277 match self {
278 278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
279 279 NodeRef::OnDisk(node) => node.base_name(on_disk),
280 280 }
281 281 }
282 282
283 283 pub(super) fn children(
284 284 &self,
285 285 on_disk: &'on_disk [u8],
286 286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
287 287 match self {
288 288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
289 289 NodeRef::OnDisk(node) => {
290 290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
291 291 }
292 292 }
293 293 }
294 294
295 295 pub(super) fn has_copy_source(&self) -> bool {
296 296 match self {
297 297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
298 298 NodeRef::OnDisk(node) => node.has_copy_source(),
299 299 }
300 300 }
301 301
302 302 pub(super) fn copy_source(
303 303 &self,
304 304 on_disk: &'on_disk [u8],
305 305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
306 306 match self {
307 307 NodeRef::InMemory(_path, node) => {
308 308 Ok(node.copy_source.as_ref().map(|s| &**s))
309 309 }
310 310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
311 311 }
312 312 }
313 313
314 314 pub(super) fn entry(
315 315 &self,
316 316 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
317 317 match self {
318 318 NodeRef::InMemory(_path, node) => {
319 319 Ok(node.data.as_entry().copied())
320 320 }
321 321 NodeRef::OnDisk(node) => node.entry(),
322 322 }
323 323 }
324 324
325 325 pub(super) fn state(
326 326 &self,
327 327 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
328 328 match self {
329 329 NodeRef::InMemory(_path, node) => {
330 330 Ok(node.data.as_entry().map(|entry| entry.state()))
331 331 }
332 332 NodeRef::OnDisk(node) => node.state(),
333 333 }
334 334 }
335 335
336 336 pub(super) fn cached_directory_mtime(
337 337 &self,
338 338 ) -> Option<&'tree on_disk::Timestamp> {
339 339 match self {
340 340 NodeRef::InMemory(_path, node) => match &node.data {
341 341 NodeData::CachedDirectory { mtime } => Some(mtime),
342 342 _ => None,
343 343 },
344 344 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
345 345 }
346 346 }
347 347
348 348 pub(super) fn descendants_with_entry_count(&self) -> u32 {
349 349 match self {
350 350 NodeRef::InMemory(_path, node) => {
351 351 node.descendants_with_entry_count
352 352 }
353 353 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
354 354 }
355 355 }
356 356
357 357 pub(super) fn tracked_descendants_count(&self) -> u32 {
358 358 match self {
359 359 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
360 360 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
361 361 }
362 362 }
363 363 }
364 364
365 365 /// Represents a file or a directory
366 366 #[derive(Default)]
367 367 pub(super) struct Node<'on_disk> {
368 368 pub(super) data: NodeData,
369 369
370 370 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
371 371
372 372 pub(super) children: ChildNodes<'on_disk>,
373 373
374 374 /// How many (non-inclusive) descendants of this node have an entry.
375 375 pub(super) descendants_with_entry_count: u32,
376 376
377 377 /// How many (non-inclusive) descendants of this node have an entry whose
378 378 /// state is "tracked".
379 379 pub(super) tracked_descendants_count: u32,
380 380 }
381 381
382 382 pub(super) enum NodeData {
383 383 Entry(DirstateEntry),
384 384 CachedDirectory { mtime: on_disk::Timestamp },
385 385 None,
386 386 }
387 387
388 388 impl Default for NodeData {
389 389 fn default() -> Self {
390 390 NodeData::None
391 391 }
392 392 }
393 393
394 394 impl NodeData {
395 395 fn has_entry(&self) -> bool {
396 396 match self {
397 397 NodeData::Entry(_) => true,
398 398 _ => false,
399 399 }
400 400 }
401 401
402 402 fn as_entry(&self) -> Option<&DirstateEntry> {
403 403 match self {
404 404 NodeData::Entry(entry) => Some(entry),
405 405 _ => None,
406 406 }
407 407 }
408 408 }
409 409
410 410 impl<'on_disk> DirstateMap<'on_disk> {
411 411 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
412 412 Self {
413 413 on_disk,
414 414 root: ChildNodes::default(),
415 415 nodes_with_entry_count: 0,
416 416 nodes_with_copy_source_count: 0,
417 417 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
418 418 unreachable_bytes: 0,
419 419 }
420 420 }
421 421
422 422 #[timed]
423 423 pub fn new_v2(
424 424 on_disk: &'on_disk [u8],
425 425 data_size: usize,
426 426 metadata: &[u8],
427 427 ) -> Result<Self, DirstateError> {
428 428 if let Some(data) = on_disk.get(..data_size) {
429 429 Ok(on_disk::read(data, metadata)?)
430 430 } else {
431 431 Err(DirstateV2ParseError.into())
432 432 }
433 433 }
434 434
435 435 #[timed]
436 436 pub fn new_v1(
437 437 on_disk: &'on_disk [u8],
438 438 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
439 439 let mut map = Self::empty(on_disk);
440 440 if map.on_disk.is_empty() {
441 441 return Ok((map, None));
442 442 }
443 443
444 444 let parents = parse_dirstate_entries(
445 445 map.on_disk,
446 446 |path, entry, copy_source| {
447 447 let tracked = entry.state().is_tracked();
448 448 let node = Self::get_or_insert_node(
449 449 map.on_disk,
450 450 &mut map.unreachable_bytes,
451 451 &mut map.root,
452 452 path,
453 453 WithBasename::to_cow_borrowed,
454 454 |ancestor| {
455 455 if tracked {
456 456 ancestor.tracked_descendants_count += 1
457 457 }
458 458 ancestor.descendants_with_entry_count += 1
459 459 },
460 460 )?;
461 461 assert!(
462 462 !node.data.has_entry(),
463 463 "duplicate dirstate entry in read"
464 464 );
465 465 assert!(
466 466 node.copy_source.is_none(),
467 467 "duplicate dirstate entry in read"
468 468 );
469 469 node.data = NodeData::Entry(*entry);
470 470 node.copy_source = copy_source.map(Cow::Borrowed);
471 471 map.nodes_with_entry_count += 1;
472 472 if copy_source.is_some() {
473 473 map.nodes_with_copy_source_count += 1
474 474 }
475 475 Ok(())
476 476 },
477 477 )?;
478 478 let parents = Some(parents.clone());
479 479
480 480 Ok((map, parents))
481 481 }
482 482
483 483 /// Assuming dirstate-v2 format, returns whether the next write should
484 484 /// append to the existing data file that contains `self.on_disk` (true),
485 485 /// or create a new data file from scratch (false).
486 486 pub(super) fn write_should_append(&self) -> bool {
487 487 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
488 488 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
489 489 }
490 490
491 491 fn get_node<'tree>(
492 492 &'tree self,
493 493 path: &HgPath,
494 494 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
495 495 let mut children = self.root.as_ref();
496 496 let mut components = path.components();
497 497 let mut component =
498 498 components.next().expect("expected at least one components");
499 499 loop {
500 500 if let Some(child) = children.get(component, self.on_disk)? {
501 501 if let Some(next_component) = components.next() {
502 502 component = next_component;
503 503 children = child.children(self.on_disk)?;
504 504 } else {
505 505 return Ok(Some(child));
506 506 }
507 507 } else {
508 508 return Ok(None);
509 509 }
510 510 }
511 511 }
512 512
513 513 /// Returns a mutable reference to the node at `path` if it exists
514 514 ///
515 515 /// This takes `root` instead of `&mut self` so that callers can mutate
516 516 /// other fields while the returned borrow is still valid
517 517 fn get_node_mut<'tree>(
518 518 on_disk: &'on_disk [u8],
519 519 unreachable_bytes: &mut u32,
520 520 root: &'tree mut ChildNodes<'on_disk>,
521 521 path: &HgPath,
522 522 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
523 523 let mut children = root;
524 524 let mut components = path.components();
525 525 let mut component =
526 526 components.next().expect("expected at least one components");
527 527 loop {
528 528 if let Some(child) = children
529 529 .make_mut(on_disk, unreachable_bytes)?
530 530 .get_mut(component)
531 531 {
532 532 if let Some(next_component) = components.next() {
533 533 component = next_component;
534 534 children = &mut child.children;
535 535 } else {
536 536 return Ok(Some(child));
537 537 }
538 538 } else {
539 539 return Ok(None);
540 540 }
541 541 }
542 542 }
543 543
544 544 pub(super) fn get_or_insert<'tree, 'path>(
545 545 &'tree mut self,
546 546 path: &HgPath,
547 547 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
548 548 Self::get_or_insert_node(
549 549 self.on_disk,
550 550 &mut self.unreachable_bytes,
551 551 &mut self.root,
552 552 path,
553 553 WithBasename::to_cow_owned,
554 554 |_| {},
555 555 )
556 556 }
557 557
558 558 fn get_or_insert_node<'tree, 'path>(
559 559 on_disk: &'on_disk [u8],
560 560 unreachable_bytes: &mut u32,
561 561 root: &'tree mut ChildNodes<'on_disk>,
562 562 path: &'path HgPath,
563 563 to_cow: impl Fn(
564 564 WithBasename<&'path HgPath>,
565 565 ) -> WithBasename<Cow<'on_disk, HgPath>>,
566 566 mut each_ancestor: impl FnMut(&mut Node),
567 567 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
568 568 let mut child_nodes = root;
569 569 let mut inclusive_ancestor_paths =
570 570 WithBasename::inclusive_ancestors_of(path);
571 571 let mut ancestor_path = inclusive_ancestor_paths
572 572 .next()
573 573 .expect("expected at least one inclusive ancestor");
574 574 loop {
575 575 // TODO: can we avoid allocating an owned key in cases where the
576 576 // map already contains that key, without introducing double
577 577 // lookup?
578 578 let child_node = child_nodes
579 579 .make_mut(on_disk, unreachable_bytes)?
580 580 .entry(to_cow(ancestor_path))
581 581 .or_default();
582 582 if let Some(next) = inclusive_ancestor_paths.next() {
583 583 each_ancestor(child_node);
584 584 ancestor_path = next;
585 585 child_nodes = &mut child_node.children;
586 586 } else {
587 587 return Ok(child_node);
588 588 }
589 589 }
590 590 }
591 591
592 592 fn add_or_remove_file(
593 593 &mut self,
594 594 path: &HgPath,
595 595 old_state: Option<EntryState>,
596 596 new_entry: DirstateEntry,
597 597 ) -> Result<(), DirstateV2ParseError> {
598 598 let had_entry = old_state.is_some();
599 599 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
600 600 let tracked_count_increment =
601 601 match (was_tracked, new_entry.state().is_tracked()) {
602 602 (false, true) => 1,
603 603 (true, false) => -1,
604 604 _ => 0,
605 605 };
606 606
607 607 let node = Self::get_or_insert_node(
608 608 self.on_disk,
609 609 &mut self.unreachable_bytes,
610 610 &mut self.root,
611 611 path,
612 612 WithBasename::to_cow_owned,
613 613 |ancestor| {
614 614 if !had_entry {
615 615 ancestor.descendants_with_entry_count += 1;
616 616 }
617 617
618 618 // We can’t use `+= increment` because the counter is unsigned,
619 619 // and we want debug builds to detect accidental underflow
620 620 // through zero
621 621 match tracked_count_increment {
622 622 1 => ancestor.tracked_descendants_count += 1,
623 623 -1 => ancestor.tracked_descendants_count -= 1,
624 624 _ => {}
625 625 }
626 626 },
627 627 )?;
628 628 if !had_entry {
629 629 self.nodes_with_entry_count += 1
630 630 }
631 631 node.data = NodeData::Entry(new_entry);
632 632 Ok(())
633 633 }
634 634
635 635 fn iter_nodes<'tree>(
636 636 &'tree self,
637 637 ) -> impl Iterator<
638 638 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
639 639 > + 'tree {
640 640 // Depth first tree traversal.
641 641 //
642 642 // If we could afford internal iteration and recursion,
643 643 // this would look like:
644 644 //
645 645 // ```
646 646 // fn traverse_children(
647 647 // children: &ChildNodes,
648 648 // each: &mut impl FnMut(&Node),
649 649 // ) {
650 650 // for child in children.values() {
651 651 // traverse_children(&child.children, each);
652 652 // each(child);
653 653 // }
654 654 // }
655 655 // ```
656 656 //
657 657 // However we want an external iterator and therefore can’t use the
658 658 // call stack. Use an explicit stack instead:
659 659 let mut stack = Vec::new();
660 660 let mut iter = self.root.as_ref().iter();
661 661 std::iter::from_fn(move || {
662 662 while let Some(child_node) = iter.next() {
663 663 let children = match child_node.children(self.on_disk) {
664 664 Ok(children) => children,
665 665 Err(error) => return Some(Err(error)),
666 666 };
667 667 // Pseudo-recursion
668 668 let new_iter = children.iter();
669 669 let old_iter = std::mem::replace(&mut iter, new_iter);
670 670 stack.push((child_node, old_iter));
671 671 }
672 672 // Found the end of a `children.iter()` iterator.
673 673 if let Some((child_node, next_iter)) = stack.pop() {
674 674 // "Return" from pseudo-recursion by restoring state from the
675 675 // explicit stack
676 676 iter = next_iter;
677 677
678 678 Some(Ok(child_node))
679 679 } else {
680 680 // Reached the bottom of the stack, we’re done
681 681 None
682 682 }
683 683 })
684 684 }
685 685
686 686 fn clear_known_ambiguous_mtimes(
687 687 &mut self,
688 688 paths: &[impl AsRef<HgPath>],
689 689 ) -> Result<(), DirstateV2ParseError> {
690 690 for path in paths {
691 691 if let Some(node) = Self::get_node_mut(
692 692 self.on_disk,
693 693 &mut self.unreachable_bytes,
694 694 &mut self.root,
695 695 path.as_ref(),
696 696 )? {
697 697 if let NodeData::Entry(entry) = &mut node.data {
698 entry.clear_mtime();
698 entry.set_possibly_dirty();
699 699 }
700 700 }
701 701 }
702 702 Ok(())
703 703 }
704 704
705 705 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
706 706 if let Cow::Borrowed(path) = path {
707 707 *unreachable_bytes += path.len() as u32
708 708 }
709 709 }
710 710 }
711 711
712 712 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
713 713 ///
714 714 /// The callback is only called for incoming `Ok` values. Errors are passed
715 715 /// through as-is. In order to let it use the `?` operator the callback is
716 716 /// expected to return a `Result` of `Option`, instead of an `Option` of
717 717 /// `Result`.
718 718 fn filter_map_results<'a, I, F, A, B, E>(
719 719 iter: I,
720 720 f: F,
721 721 ) -> impl Iterator<Item = Result<B, E>> + 'a
722 722 where
723 723 I: Iterator<Item = Result<A, E>> + 'a,
724 724 F: Fn(A) -> Result<Option<B>, E> + 'a,
725 725 {
726 726 iter.filter_map(move |result| match result {
727 727 Ok(node) => f(node).transpose(),
728 728 Err(e) => Some(Err(e)),
729 729 })
730 730 }
731 731
732 732 impl OwningDirstateMap {
733 733 pub fn clear(&mut self) {
734 734 let map = self.get_map_mut();
735 735 map.root = Default::default();
736 736 map.nodes_with_entry_count = 0;
737 737 map.nodes_with_copy_source_count = 0;
738 738 }
739 739
740 740 pub fn set_entry(
741 741 &mut self,
742 742 filename: &HgPath,
743 743 entry: DirstateEntry,
744 744 ) -> Result<(), DirstateV2ParseError> {
745 745 let map = self.get_map_mut();
746 746 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
747 747 Ok(())
748 748 }
749 749
750 750 pub fn add_file(
751 751 &mut self,
752 752 filename: &HgPath,
753 753 entry: DirstateEntry,
754 754 ) -> Result<(), DirstateError> {
755 755 let old_state = self.get(filename)?.map(|e| e.state());
756 756 let map = self.get_map_mut();
757 757 Ok(map.add_or_remove_file(filename, old_state, entry)?)
758 758 }
759 759
760 760 pub fn remove_file(
761 761 &mut self,
762 762 filename: &HgPath,
763 763 in_merge: bool,
764 764 ) -> Result<(), DirstateError> {
765 765 let old_entry_opt = self.get(filename)?;
766 766 let old_state = old_entry_opt.map(|e| e.state());
767 767 let mut size = 0;
768 768 if in_merge {
769 769 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
770 770 // during a merge. So I (marmoute) am not sure we need the
771 771 // conditionnal at all. Adding double checking this with assert
772 772 // would be nice.
773 773 if let Some(old_entry) = old_entry_opt {
774 774 // backup the previous state
775 775 if old_entry.state() == EntryState::Merged {
776 776 size = SIZE_NON_NORMAL;
777 777 } else if old_entry.state() == EntryState::Normal
778 778 && old_entry.size() == SIZE_FROM_OTHER_PARENT
779 779 {
780 780 // other parent
781 781 size = SIZE_FROM_OTHER_PARENT;
782 782 }
783 783 }
784 784 }
785 785 if size == 0 {
786 786 self.copy_map_remove(filename)?;
787 787 }
788 788 let map = self.get_map_mut();
789 789 let entry = DirstateEntry::new_removed(size);
790 790 Ok(map.add_or_remove_file(filename, old_state, entry)?)
791 791 }
792 792
793 793 pub fn drop_entry_and_copy_source(
794 794 &mut self,
795 795 filename: &HgPath,
796 796 ) -> Result<(), DirstateError> {
797 797 let was_tracked = self
798 798 .get(filename)?
799 799 .map_or(false, |e| e.state().is_tracked());
800 800 let map = self.get_map_mut();
801 801 struct Dropped {
802 802 was_tracked: bool,
803 803 had_entry: bool,
804 804 had_copy_source: bool,
805 805 }
806 806
807 807 /// If this returns `Ok(Some((dropped, removed)))`, then
808 808 ///
809 809 /// * `dropped` is about the leaf node that was at `filename`
810 810 /// * `removed` is whether this particular level of recursion just
811 811 /// removed a node in `nodes`.
812 812 fn recur<'on_disk>(
813 813 on_disk: &'on_disk [u8],
814 814 unreachable_bytes: &mut u32,
815 815 nodes: &mut ChildNodes<'on_disk>,
816 816 path: &HgPath,
817 817 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
818 818 let (first_path_component, rest_of_path) =
819 819 path.split_first_component();
820 820 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
821 821 let node = if let Some(node) = nodes.get_mut(first_path_component)
822 822 {
823 823 node
824 824 } else {
825 825 return Ok(None);
826 826 };
827 827 let dropped;
828 828 if let Some(rest) = rest_of_path {
829 829 if let Some((d, removed)) = recur(
830 830 on_disk,
831 831 unreachable_bytes,
832 832 &mut node.children,
833 833 rest,
834 834 )? {
835 835 dropped = d;
836 836 if dropped.had_entry {
837 837 node.descendants_with_entry_count -= 1;
838 838 }
839 839 if dropped.was_tracked {
840 840 node.tracked_descendants_count -= 1;
841 841 }
842 842
843 843 // Directory caches must be invalidated when removing a
844 844 // child node
845 845 if removed {
846 846 if let NodeData::CachedDirectory { .. } = &node.data {
847 847 node.data = NodeData::None
848 848 }
849 849 }
850 850 } else {
851 851 return Ok(None);
852 852 }
853 853 } else {
854 854 let had_entry = node.data.has_entry();
855 855 if had_entry {
856 856 node.data = NodeData::None
857 857 }
858 858 if let Some(source) = &node.copy_source {
859 859 DirstateMap::count_dropped_path(unreachable_bytes, source);
860 860 node.copy_source = None
861 861 }
862 862 dropped = Dropped {
863 863 was_tracked: node
864 864 .data
865 865 .as_entry()
866 866 .map_or(false, |entry| entry.state().is_tracked()),
867 867 had_entry,
868 868 had_copy_source: node.copy_source.take().is_some(),
869 869 };
870 870 }
871 871 // After recursion, for both leaf (rest_of_path is None) nodes and
872 872 // parent nodes, remove a node if it just became empty.
873 873 let remove = !node.data.has_entry()
874 874 && node.copy_source.is_none()
875 875 && node.children.is_empty();
876 876 if remove {
877 877 let (key, _) =
878 878 nodes.remove_entry(first_path_component).unwrap();
879 879 DirstateMap::count_dropped_path(
880 880 unreachable_bytes,
881 881 key.full_path(),
882 882 )
883 883 }
884 884 Ok(Some((dropped, remove)))
885 885 }
886 886
887 887 if let Some((dropped, _removed)) = recur(
888 888 map.on_disk,
889 889 &mut map.unreachable_bytes,
890 890 &mut map.root,
891 891 filename,
892 892 )? {
893 893 if dropped.had_entry {
894 894 map.nodes_with_entry_count -= 1
895 895 }
896 896 if dropped.had_copy_source {
897 897 map.nodes_with_copy_source_count -= 1
898 898 }
899 899 } else {
900 900 debug_assert!(!was_tracked);
901 901 }
902 902 Ok(())
903 903 }
904 904
905 905 pub fn has_tracked_dir(
906 906 &mut self,
907 907 directory: &HgPath,
908 908 ) -> Result<bool, DirstateError> {
909 909 let map = self.get_map_mut();
910 910 if let Some(node) = map.get_node(directory)? {
911 911 // A node without a `DirstateEntry` was created to hold child
912 912 // nodes, and is therefore a directory.
913 913 let state = node.state()?;
914 914 Ok(state.is_none() && node.tracked_descendants_count() > 0)
915 915 } else {
916 916 Ok(false)
917 917 }
918 918 }
919 919
920 920 pub fn has_dir(
921 921 &mut self,
922 922 directory: &HgPath,
923 923 ) -> Result<bool, DirstateError> {
924 924 let map = self.get_map_mut();
925 925 if let Some(node) = map.get_node(directory)? {
926 926 // A node without a `DirstateEntry` was created to hold child
927 927 // nodes, and is therefore a directory.
928 928 let state = node.state()?;
929 929 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
930 930 } else {
931 931 Ok(false)
932 932 }
933 933 }
934 934
935 935 #[timed]
936 936 pub fn pack_v1(
937 937 &mut self,
938 938 parents: DirstateParents,
939 939 now: Timestamp,
940 940 ) -> Result<Vec<u8>, DirstateError> {
941 941 let map = self.get_map_mut();
942 942 let now: i32 = now.0.try_into().expect("time overflow");
943 943 let mut ambiguous_mtimes = Vec::new();
944 944 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
945 945 // reallocations
946 946 let mut size = parents.as_bytes().len();
947 947 for node in map.iter_nodes() {
948 948 let node = node?;
949 949 if let Some(entry) = node.entry()? {
950 950 size += packed_entry_size(
951 951 node.full_path(map.on_disk)?,
952 952 node.copy_source(map.on_disk)?,
953 953 );
954 954 if entry.mtime_is_ambiguous(now) {
955 955 ambiguous_mtimes.push(
956 956 node.full_path_borrowed(map.on_disk)?
957 957 .detach_from_tree(),
958 958 )
959 959 }
960 960 }
961 961 }
962 962 map.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
963 963
964 964 let mut packed = Vec::with_capacity(size);
965 965 packed.extend(parents.as_bytes());
966 966
967 967 for node in map.iter_nodes() {
968 968 let node = node?;
969 969 if let Some(entry) = node.entry()? {
970 970 pack_entry(
971 971 node.full_path(map.on_disk)?,
972 972 &entry,
973 973 node.copy_source(map.on_disk)?,
974 974 &mut packed,
975 975 );
976 976 }
977 977 }
978 978 Ok(packed)
979 979 }
980 980
981 981 /// Returns new data and metadata together with whether that data should be
982 982 /// appended to the existing data file whose content is at
983 983 /// `map.on_disk` (true), instead of written to a new data file
984 984 /// (false).
985 985 #[timed]
986 986 pub fn pack_v2(
987 987 &mut self,
988 988 now: Timestamp,
989 989 can_append: bool,
990 990 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
991 991 let map = self.get_map_mut();
992 992 // TODO: how do we want to handle this in 2038?
993 993 let now: i32 = now.0.try_into().expect("time overflow");
994 994 let mut paths = Vec::new();
995 995 for node in map.iter_nodes() {
996 996 let node = node?;
997 997 if let Some(entry) = node.entry()? {
998 998 if entry.mtime_is_ambiguous(now) {
999 999 paths.push(
1000 1000 node.full_path_borrowed(map.on_disk)?
1001 1001 .detach_from_tree(),
1002 1002 )
1003 1003 }
1004 1004 }
1005 1005 }
1006 1006 // Borrow of `self` ends here since we collect cloned paths
1007 1007
1008 1008 map.clear_known_ambiguous_mtimes(&paths)?;
1009 1009
1010 1010 on_disk::write(map, can_append)
1011 1011 }
1012 1012
1013 1013 pub fn status<'a>(
1014 1014 &'a mut self,
1015 1015 matcher: &'a (dyn Matcher + Sync),
1016 1016 root_dir: PathBuf,
1017 1017 ignore_files: Vec<PathBuf>,
1018 1018 options: StatusOptions,
1019 1019 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1020 1020 {
1021 1021 let map = self.get_map_mut();
1022 1022 super::status::status(map, matcher, root_dir, ignore_files, options)
1023 1023 }
1024 1024
1025 1025 pub fn copy_map_len(&self) -> usize {
1026 1026 let map = self.get_map();
1027 1027 map.nodes_with_copy_source_count as usize
1028 1028 }
1029 1029
1030 1030 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1031 1031 let map = self.get_map();
1032 1032 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1033 1033 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1034 1034 Some((node.full_path(map.on_disk)?, source))
1035 1035 } else {
1036 1036 None
1037 1037 })
1038 1038 }))
1039 1039 }
1040 1040
1041 1041 pub fn copy_map_contains_key(
1042 1042 &self,
1043 1043 key: &HgPath,
1044 1044 ) -> Result<bool, DirstateV2ParseError> {
1045 1045 let map = self.get_map();
1046 1046 Ok(if let Some(node) = map.get_node(key)? {
1047 1047 node.has_copy_source()
1048 1048 } else {
1049 1049 false
1050 1050 })
1051 1051 }
1052 1052
1053 1053 pub fn copy_map_get(
1054 1054 &self,
1055 1055 key: &HgPath,
1056 1056 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1057 1057 let map = self.get_map();
1058 1058 if let Some(node) = map.get_node(key)? {
1059 1059 if let Some(source) = node.copy_source(map.on_disk)? {
1060 1060 return Ok(Some(source));
1061 1061 }
1062 1062 }
1063 1063 Ok(None)
1064 1064 }
1065 1065
1066 1066 pub fn copy_map_remove(
1067 1067 &mut self,
1068 1068 key: &HgPath,
1069 1069 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1070 1070 let map = self.get_map_mut();
1071 1071 let count = &mut map.nodes_with_copy_source_count;
1072 1072 let unreachable_bytes = &mut map.unreachable_bytes;
1073 1073 Ok(DirstateMap::get_node_mut(
1074 1074 map.on_disk,
1075 1075 unreachable_bytes,
1076 1076 &mut map.root,
1077 1077 key,
1078 1078 )?
1079 1079 .and_then(|node| {
1080 1080 if let Some(source) = &node.copy_source {
1081 1081 *count -= 1;
1082 1082 DirstateMap::count_dropped_path(unreachable_bytes, source);
1083 1083 }
1084 1084 node.copy_source.take().map(Cow::into_owned)
1085 1085 }))
1086 1086 }
1087 1087
1088 1088 pub fn copy_map_insert(
1089 1089 &mut self,
1090 1090 key: HgPathBuf,
1091 1091 value: HgPathBuf,
1092 1092 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1093 1093 let map = self.get_map_mut();
1094 1094 let node = DirstateMap::get_or_insert_node(
1095 1095 map.on_disk,
1096 1096 &mut map.unreachable_bytes,
1097 1097 &mut map.root,
1098 1098 &key,
1099 1099 WithBasename::to_cow_owned,
1100 1100 |_ancestor| {},
1101 1101 )?;
1102 1102 if node.copy_source.is_none() {
1103 1103 map.nodes_with_copy_source_count += 1
1104 1104 }
1105 1105 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1106 1106 }
1107 1107
1108 1108 pub fn len(&self) -> usize {
1109 1109 let map = self.get_map();
1110 1110 map.nodes_with_entry_count as usize
1111 1111 }
1112 1112
1113 1113 pub fn contains_key(
1114 1114 &self,
1115 1115 key: &HgPath,
1116 1116 ) -> Result<bool, DirstateV2ParseError> {
1117 1117 Ok(self.get(key)?.is_some())
1118 1118 }
1119 1119
1120 1120 pub fn get(
1121 1121 &self,
1122 1122 key: &HgPath,
1123 1123 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1124 1124 let map = self.get_map();
1125 1125 Ok(if let Some(node) = map.get_node(key)? {
1126 1126 node.entry()?
1127 1127 } else {
1128 1128 None
1129 1129 })
1130 1130 }
1131 1131
1132 1132 pub fn iter(&self) -> StateMapIter<'_> {
1133 1133 let map = self.get_map();
1134 1134 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1135 1135 Ok(if let Some(entry) = node.entry()? {
1136 1136 Some((node.full_path(map.on_disk)?, entry))
1137 1137 } else {
1138 1138 None
1139 1139 })
1140 1140 }))
1141 1141 }
1142 1142
1143 1143 pub fn iter_tracked_dirs(
1144 1144 &mut self,
1145 1145 ) -> Result<
1146 1146 Box<
1147 1147 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1148 1148 + Send
1149 1149 + '_,
1150 1150 >,
1151 1151 DirstateError,
1152 1152 > {
1153 1153 let map = self.get_map_mut();
1154 1154 let on_disk = map.on_disk;
1155 1155 Ok(Box::new(filter_map_results(
1156 1156 map.iter_nodes(),
1157 1157 move |node| {
1158 1158 Ok(if node.tracked_descendants_count() > 0 {
1159 1159 Some(node.full_path(on_disk)?)
1160 1160 } else {
1161 1161 None
1162 1162 })
1163 1163 },
1164 1164 )))
1165 1165 }
1166 1166
1167 1167 pub fn debug_iter(
1168 1168 &self,
1169 1169 all: bool,
1170 1170 ) -> Box<
1171 1171 dyn Iterator<
1172 1172 Item = Result<
1173 1173 (&HgPath, (u8, i32, i32, i32)),
1174 1174 DirstateV2ParseError,
1175 1175 >,
1176 1176 > + Send
1177 1177 + '_,
1178 1178 > {
1179 1179 let map = self.get_map();
1180 1180 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1181 1181 let debug_tuple = if let Some(entry) = node.entry()? {
1182 1182 entry.debug_tuple()
1183 1183 } else if !all {
1184 1184 return Ok(None);
1185 1185 } else if let Some(mtime) = node.cached_directory_mtime() {
1186 1186 (b' ', 0, -1, mtime.seconds() as i32)
1187 1187 } else {
1188 1188 (b' ', 0, -1, -1)
1189 1189 };
1190 1190 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1191 1191 }))
1192 1192 }
1193 1193 }
@@ -1,218 +1,219
1 1 use cpython::exc;
2 2 use cpython::PyBytes;
3 3 use cpython::PyErr;
4 4 use cpython::PyNone;
5 5 use cpython::PyObject;
6 6 use cpython::PyResult;
7 7 use cpython::Python;
8 8 use cpython::PythonObject;
9 use hg::dirstate::entry::Flags;
10 9 use hg::dirstate::DirstateEntry;
11 10 use hg::dirstate::EntryState;
12 11 use std::cell::Cell;
13 12 use std::convert::TryFrom;
14 13
15 14 py_class!(pub class DirstateItem |py| {
16 15 data entry: Cell<DirstateEntry>;
17 16
18 17 def __new__(
19 18 _cls,
20 19 wc_tracked: bool = false,
21 20 p1_tracked: bool = false,
22 p2_tracked: bool = false,
23 merged: bool = false,
24 clean_p1: bool = false,
25 clean_p2: bool = false,
26 possibly_dirty: bool = false,
21 p2_info: bool = false,
22 has_meaningful_data: bool = true,
23 has_meaningful_mtime: bool = true,
27 24 parentfiledata: Option<(i32, i32, i32)> = None,
28 25
29 26 ) -> PyResult<DirstateItem> {
30 let mut flags = Flags::empty();
31 flags.set(Flags::WDIR_TRACKED, wc_tracked);
32 flags.set(Flags::P1_TRACKED, p1_tracked);
33 flags.set(Flags::P2_TRACKED, p2_tracked);
34 flags.set(Flags::MERGED, merged);
35 flags.set(Flags::CLEAN_P1, clean_p1);
36 flags.set(Flags::CLEAN_P2, clean_p2);
37 flags.set(Flags::POSSIBLY_DIRTY, possibly_dirty);
38 let entry = DirstateEntry::new(flags, parentfiledata);
27 let mut mode_size_opt = None;
28 let mut mtime_opt = None;
29 if let Some((mode, size, mtime)) = parentfiledata {
30 if has_meaningful_data {
31 mode_size_opt = Some((mode, size))
32 }
33 if has_meaningful_mtime {
34 mtime_opt = Some(mtime)
35 }
36 }
37 let entry = DirstateEntry::new(
38 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
39 );
39 40 DirstateItem::create_instance(py, Cell::new(entry))
40 41 }
41 42
42 43 @property
43 44 def state(&self) -> PyResult<PyBytes> {
44 45 let state_byte: u8 = self.entry(py).get().state().into();
45 46 Ok(PyBytes::new(py, &[state_byte]))
46 47 }
47 48
48 49 @property
49 50 def mode(&self) -> PyResult<i32> {
50 51 Ok(self.entry(py).get().mode())
51 52 }
52 53
53 54 @property
54 55 def size(&self) -> PyResult<i32> {
55 56 Ok(self.entry(py).get().size())
56 57 }
57 58
58 59 @property
59 60 def mtime(&self) -> PyResult<i32> {
60 61 Ok(self.entry(py).get().mtime())
61 62 }
62 63
63 64 @property
64 65 def tracked(&self) -> PyResult<bool> {
65 66 Ok(self.entry(py).get().tracked())
66 67 }
67 68
68 69 @property
69 70 def added(&self) -> PyResult<bool> {
70 71 Ok(self.entry(py).get().added())
71 72 }
72 73
73 74 @property
74 75 def merged(&self) -> PyResult<bool> {
75 76 Ok(self.entry(py).get().merged())
76 77 }
77 78
78 79 @property
79 80 def removed(&self) -> PyResult<bool> {
80 81 Ok(self.entry(py).get().removed())
81 82 }
82 83
83 84 @property
84 85 def from_p2(&self) -> PyResult<bool> {
85 86 Ok(self.entry(py).get().from_p2())
86 87 }
87 88
88 89 @property
89 90 def maybe_clean(&self) -> PyResult<bool> {
90 91 Ok(self.entry(py).get().maybe_clean())
91 92 }
92 93
93 94 @property
94 95 def any_tracked(&self) -> PyResult<bool> {
95 96 Ok(self.entry(py).get().any_tracked())
96 97 }
97 98
98 99 def v1_state(&self) -> PyResult<PyBytes> {
99 100 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
100 101 let state_byte: u8 = state.into();
101 102 Ok(PyBytes::new(py, &[state_byte]))
102 103 }
103 104
104 105 def v1_mode(&self) -> PyResult<i32> {
105 106 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
106 107 Ok(mode)
107 108 }
108 109
109 110 def v1_size(&self) -> PyResult<i32> {
110 111 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
111 112 Ok(size)
112 113 }
113 114
114 115 def v1_mtime(&self) -> PyResult<i32> {
115 116 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
116 117 Ok(mtime)
117 118 }
118 119
119 120 def need_delay(&self, now: i32) -> PyResult<bool> {
120 121 Ok(self.entry(py).get().mtime_is_ambiguous(now))
121 122 }
122 123
123 124 @classmethod
124 125 def from_v1_data(
125 126 _cls,
126 127 state: PyBytes,
127 128 mode: i32,
128 129 size: i32,
129 130 mtime: i32,
130 131 ) -> PyResult<Self> {
131 132 let state = <[u8; 1]>::try_from(state.data(py))
132 133 .ok()
133 134 .and_then(|state| EntryState::try_from(state[0]).ok())
134 135 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
135 136 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
136 137 DirstateItem::create_instance(py, Cell::new(entry))
137 138 }
138 139
139 140 @classmethod
140 141 def new_added(_cls) -> PyResult<Self> {
141 142 let entry = DirstateEntry::new_added();
142 143 DirstateItem::create_instance(py, Cell::new(entry))
143 144 }
144 145
145 146 @classmethod
146 147 def new_merged(_cls) -> PyResult<Self> {
147 148 let entry = DirstateEntry::new_merged();
148 149 DirstateItem::create_instance(py, Cell::new(entry))
149 150 }
150 151
151 152 @classmethod
152 153 def new_from_p2(_cls) -> PyResult<Self> {
153 154 let entry = DirstateEntry::new_from_p2();
154 155 DirstateItem::create_instance(py, Cell::new(entry))
155 156 }
156 157
157 158 @classmethod
158 159 def new_possibly_dirty(_cls) -> PyResult<Self> {
159 160 let entry = DirstateEntry::new_possibly_dirty();
160 161 DirstateItem::create_instance(py, Cell::new(entry))
161 162 }
162 163
163 164 @classmethod
164 165 def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult<Self> {
165 166 let entry = DirstateEntry::new_normal(mode, size, mtime);
166 167 DirstateItem::create_instance(py, Cell::new(entry))
167 168 }
168 169
169 170 def drop_merge_data(&self) -> PyResult<PyNone> {
170 171 self.update(py, |entry| entry.drop_merge_data());
171 172 Ok(PyNone)
172 173 }
173 174
174 175 def set_clean(
175 176 &self,
176 177 mode: i32,
177 178 size: i32,
178 179 mtime: i32,
179 180 ) -> PyResult<PyNone> {
180 181 self.update(py, |entry| entry.set_clean(mode, size, mtime));
181 182 Ok(PyNone)
182 183 }
183 184
184 185 def set_possibly_dirty(&self) -> PyResult<PyNone> {
185 186 self.update(py, |entry| entry.set_possibly_dirty());
186 187 Ok(PyNone)
187 188 }
188 189
189 190 def set_tracked(&self) -> PyResult<PyNone> {
190 191 self.update(py, |entry| entry.set_tracked());
191 192 Ok(PyNone)
192 193 }
193 194
194 195 def set_untracked(&self) -> PyResult<PyNone> {
195 196 self.update(py, |entry| entry.set_untracked());
196 197 Ok(PyNone)
197 198 }
198 199 });
199 200
200 201 impl DirstateItem {
201 202 pub fn new_as_pyobject(
202 203 py: Python<'_>,
203 204 entry: DirstateEntry,
204 205 ) -> PyResult<PyObject> {
205 206 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
206 207 }
207 208
208 209 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
209 210 self.entry(py).get()
210 211 }
211 212
212 213 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
213 214 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
214 215 let mut entry = self.entry(py).get();
215 216 f(&mut entry);
216 217 self.entry(py).set(entry)
217 218 }
218 219 }
General Comments 0
You need to be logged in to leave comments. Login now