Show More
@@ -0,0 +1,53 b'' | |||
|
1 | # Copyright Mercurial Contributors | |
|
2 | # | |
|
3 | # This software may be used and distributed according to the terms of the | |
|
4 | # GNU General Public License version 2 or any later version. | |
|
5 | ||
|
6 | from __future__ import absolute_import | |
|
7 | ||
|
8 | import stat | |
|
9 | ||
|
10 | ||
|
11 | rangemask = 0x7FFFFFFF | |
|
12 | ||
|
13 | ||
|
14 | class timestamp(tuple): | |
|
15 | """ | |
|
16 | A Unix timestamp with nanoseconds precision, | |
|
17 | modulo 2**31 seconds. | |
|
18 | ||
|
19 | A 2-tuple containing: | |
|
20 | ||
|
21 | `truncated_seconds`: seconds since the Unix epoch, | |
|
22 | truncated to its lower 31 bits | |
|
23 | ||
|
24 | `subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. | |
|
25 | """ | |
|
26 | ||
|
27 | def __new__(cls, value): | |
|
28 | truncated_seconds, subsec_nanos = value | |
|
29 | value = (truncated_seconds & rangemask, subsec_nanos) | |
|
30 | return super(timestamp, cls).__new__(cls, value) | |
|
31 | ||
|
32 | ||
|
33 | def zero(): | |
|
34 | """ | |
|
35 | Returns the `timestamp` at the Unix epoch. | |
|
36 | """ | |
|
37 | return tuple.__new__(timestamp, (0, 0)) | |
|
38 | ||
|
39 | ||
|
40 | def mtime_of(stat_result): | |
|
41 | """ | |
|
42 | Takes an `os.stat_result`-like object and returns a `timestamp` object | |
|
43 | for its modification time. | |
|
44 | """ | |
|
45 | # https://docs.python.org/2/library/os.html#os.stat_float_times | |
|
46 | # "For compatibility with older Python versions, | |
|
47 | # accessing stat_result as a tuple always returns integers." | |
|
48 | secs = stat_result[stat.ST_MTIME] | |
|
49 | ||
|
50 | # For now | |
|
51 | subsec_nanos = 0 | |
|
52 | ||
|
53 | return timestamp((secs, subsec_nanos)) |
@@ -57,7 +57,8 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
57 | 57 | int has_meaningful_mtime; |
|
58 | 58 | int mode; |
|
59 | 59 | int size; |
|
60 | int mtime; | |
|
60 | int mtime_s; | |
|
61 | int mtime_ns; | |
|
61 | 62 | PyObject *parentfiledata; |
|
62 | 63 | PyObject *fallback_exec; |
|
63 | 64 | PyObject *fallback_symlink; |
@@ -111,15 +112,10 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
111 | 112 | } |
|
112 | 113 | |
|
113 | 114 | if (parentfiledata != Py_None) { |
|
114 | if (!PyTuple_CheckExact(parentfiledata)) { | |
|
115 | PyErr_SetString( | |
|
116 | PyExc_TypeError, | |
|
117 | "parentfiledata should be a Tuple or None"); | |
|
115 | if (!PyArg_ParseTuple(parentfiledata, "ii(ii)", &mode, &size, | |
|
116 | &mtime_s, &mtime_ns)) { | |
|
118 | 117 | return NULL; |
|
119 | 118 | } |
|
120 | mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0)); | |
|
121 | size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1)); | |
|
122 | mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2)); | |
|
123 | 119 | } else { |
|
124 | 120 | has_meaningful_data = 0; |
|
125 | 121 | has_meaningful_mtime = 0; |
@@ -134,9 +130,11 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
134 | 130 | } |
|
135 | 131 | if (has_meaningful_mtime) { |
|
136 | 132 | t->flags |= dirstate_flag_has_file_mtime; |
|
137 | t->mtime = mtime; | |
|
133 | t->mtime_s = mtime_s; | |
|
134 | t->mtime_ns = mtime_ns; | |
|
138 | 135 | } else { |
|
139 | t->mtime = 0; | |
|
136 | t->mtime_s = 0; | |
|
137 | t->mtime_ns = 0; | |
|
140 | 138 | } |
|
141 | 139 | return (PyObject *)t; |
|
142 | 140 | } |
@@ -254,7 +252,7 b' static inline int dirstate_item_c_v1_mti' | |||
|
254 | 252 | (self->flags & dirstate_flag_p2_info)) { |
|
255 | 253 | return ambiguous_time; |
|
256 | 254 | } else { |
|
257 | return self->mtime; | |
|
255 | return self->mtime_s; | |
|
258 | 256 | } |
|
259 | 257 | } |
|
260 | 258 | |
@@ -272,7 +270,8 b' static PyObject *dirstate_item_v2_data(d' | |||
|
272 | 270 | } else { |
|
273 | 271 | flags &= ~dirstate_flag_mode_is_symlink; |
|
274 | 272 | } |
|
275 |
return Py_BuildValue("iii", flags, self->size, self->mtime |
|
|
273 | return Py_BuildValue("iiii", flags, self->size, self->mtime_s, | |
|
274 | self->mtime_ns); | |
|
276 | 275 | }; |
|
277 | 276 | |
|
278 | 277 | static PyObject *dirstate_item_v1_state(dirstateItemObject *self) |
@@ -297,14 +296,30 b' static PyObject *dirstate_item_v1_mtime(' | |||
|
297 | 296 | }; |
|
298 | 297 | |
|
299 | 298 | static PyObject *dirstate_item_need_delay(dirstateItemObject *self, |
|
300 |
PyObject * |
|
|
299 | PyObject *now) | |
|
301 | 300 | { |
|
302 |
|
|
|
303 | if (!pylong_to_long(value, &now)) { | |
|
301 | int now_s; | |
|
302 | int now_ns; | |
|
303 | if (!PyArg_ParseTuple(now, "ii", &now_s, &now_ns)) { | |
|
304 | 304 | return NULL; |
|
305 | 305 | } |
|
306 | if (dirstate_item_c_v1_state(self) == 'n' && | |
|
307 | dirstate_item_c_v1_mtime(self) == now) { | |
|
306 | if (dirstate_item_c_v1_state(self) == 'n' && self->mtime_s == now_s) { | |
|
307 | Py_RETURN_TRUE; | |
|
308 | } else { | |
|
309 | Py_RETURN_FALSE; | |
|
310 | } | |
|
311 | }; | |
|
312 | ||
|
313 | static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self, | |
|
314 | PyObject *other) | |
|
315 | { | |
|
316 | int other_s; | |
|
317 | int other_ns; | |
|
318 | if (!PyArg_ParseTuple(other, "ii", &other_s, &other_ns)) { | |
|
319 | return NULL; | |
|
320 | } | |
|
321 | if ((self->flags & dirstate_flag_has_file_mtime) && | |
|
322 | self->mtime_s == other_s && self->mtime_ns == other_ns) { | |
|
308 | 323 | Py_RETURN_TRUE; |
|
309 | 324 | } else { |
|
310 | 325 | Py_RETURN_FALSE; |
@@ -324,7 +339,8 b' dirstate_item_from_v1_data(char state, i' | |||
|
324 | 339 | t->flags = 0; |
|
325 | 340 | t->mode = 0; |
|
326 | 341 | t->size = 0; |
|
327 | t->mtime = 0; | |
|
342 | t->mtime_s = 0; | |
|
343 | t->mtime_ns = 0; | |
|
328 | 344 | |
|
329 | 345 | if (state == 'm') { |
|
330 | 346 | t->flags = (dirstate_flag_wc_tracked | |
@@ -360,7 +376,7 b' dirstate_item_from_v1_data(char state, i' | |||
|
360 | 376 | dirstate_flag_has_file_mtime); |
|
361 | 377 | t->mode = mode; |
|
362 | 378 | t->size = size; |
|
363 | t->mtime = mtime; | |
|
379 | t->mtime_s = mtime; | |
|
364 | 380 | } |
|
365 | 381 | } else { |
|
366 | 382 | PyErr_Format(PyExc_RuntimeError, |
@@ -395,7 +411,8 b' static PyObject *dirstate_item_from_v2_m' | |||
|
395 | 411 | if (!t) { |
|
396 | 412 | return NULL; |
|
397 | 413 | } |
|
398 |
if (!PyArg_ParseTuple(args, "iii", &t->flags, &t->size, &t->mtime |
|
|
414 | if (!PyArg_ParseTuple(args, "iiii", &t->flags, &t->size, &t->mtime_s, | |
|
415 | &t->mtime_ns)) { | |
|
399 | 416 | return NULL; |
|
400 | 417 | } |
|
401 | 418 | if (t->flags & dirstate_flag_expected_state_is_modified) { |
@@ -431,8 +448,9 b' static PyObject *dirstate_item_set_possi' | |||
|
431 | 448 | static PyObject *dirstate_item_set_clean(dirstateItemObject *self, |
|
432 | 449 | PyObject *args) |
|
433 | 450 | { |
|
434 | int size, mode, mtime; | |
|
435 |
if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime |
|
|
451 | int size, mode, mtime_s, mtime_ns; | |
|
452 | if (!PyArg_ParseTuple(args, "ii(ii)", &mode, &size, &mtime_s, | |
|
453 | &mtime_ns)) { | |
|
436 | 454 | return NULL; |
|
437 | 455 | } |
|
438 | 456 | self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | |
@@ -440,7 +458,8 b' static PyObject *dirstate_item_set_clean' | |||
|
440 | 458 | dirstate_flag_has_file_mtime; |
|
441 | 459 | self->mode = mode; |
|
442 | 460 | self->size = size; |
|
443 | self->mtime = mtime; | |
|
461 | self->mtime_s = mtime_s; | |
|
462 | self->mtime_ns = mtime_ns; | |
|
444 | 463 | Py_RETURN_NONE; |
|
445 | 464 | } |
|
446 | 465 | |
@@ -455,8 +474,9 b' static PyObject *dirstate_item_set_untra' | |||
|
455 | 474 | { |
|
456 | 475 | self->flags &= ~dirstate_flag_wc_tracked; |
|
457 | 476 | self->mode = 0; |
|
458 | self->mtime = 0; | |
|
459 | 477 | self->size = 0; |
|
478 | self->mtime_s = 0; | |
|
479 | self->mtime_ns = 0; | |
|
460 | 480 | Py_RETURN_NONE; |
|
461 | 481 | } |
|
462 | 482 | |
@@ -467,8 +487,9 b' static PyObject *dirstate_item_drop_merg' | |||
|
467 | 487 | dirstate_flag_has_meaningful_data | |
|
468 | 488 | dirstate_flag_has_file_mtime); |
|
469 | 489 | self->mode = 0; |
|
470 | self->mtime = 0; | |
|
471 | 490 | self->size = 0; |
|
491 | self->mtime_s = 0; | |
|
492 | self->mtime_ns = 0; | |
|
472 | 493 | } |
|
473 | 494 | Py_RETURN_NONE; |
|
474 | 495 | } |
@@ -485,6 +506,8 b' static PyMethodDef dirstate_item_methods' | |||
|
485 | 506 | "return a \"mtime\" suitable for v1 serialization"}, |
|
486 | 507 | {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O, |
|
487 | 508 | "True if the stored mtime would be ambiguous with the current time"}, |
|
509 | {"mtime_likely_equal_to", (PyCFunction)dirstate_item_mtime_likely_equal_to, | |
|
510 | METH_O, "True if the stored mtime is likely equal to the given mtime"}, | |
|
488 | 511 | {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth, |
|
489 | 512 | METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"}, |
|
490 | 513 | {"from_v2_data", (PyCFunction)dirstate_item_from_v2_meth, |
@@ -855,11 +878,12 b' static PyObject *pack_dirstate(PyObject ' | |||
|
855 | 878 | Py_ssize_t nbytes, pos, l; |
|
856 | 879 | PyObject *k, *v = NULL, *pn; |
|
857 | 880 | char *p, *s; |
|
858 | int now; | |
|
881 | int now_s; | |
|
882 | int now_ns; | |
|
859 | 883 | |
|
860 |
if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, |
|
|
861 | &PyDict_Type, ©map, &PyTuple_Type, &pl, | |
|
862 | &now)) { | |
|
884 | if (!PyArg_ParseTuple(args, "O!O!O!(ii):pack_dirstate", &PyDict_Type, | |
|
885 | &map, &PyDict_Type, ©map, &PyTuple_Type, &pl, | |
|
886 | &now_s, &now_ns)) { | |
|
863 | 887 | return NULL; |
|
864 | 888 | } |
|
865 | 889 | |
@@ -928,7 +952,7 b' static PyObject *pack_dirstate(PyObject ' | |||
|
928 | 952 | mode = dirstate_item_c_v1_mode(tuple); |
|
929 | 953 | size = dirstate_item_c_v1_size(tuple); |
|
930 | 954 | mtime = dirstate_item_c_v1_mtime(tuple); |
|
931 | if (state == 'n' && mtime == now) { | |
|
955 | if (state == 'n' && tuple->mtime_s == now_s) { | |
|
932 | 956 | /* See pure/parsers.py:pack_dirstate for why we do |
|
933 | 957 | * this. */ |
|
934 | 958 | mtime = -1; |
@@ -27,7 +27,8 b' typedef struct {' | |||
|
27 | 27 | int flags; |
|
28 | 28 | int mode; |
|
29 | 29 | int size; |
|
30 | int mtime; | |
|
30 | int mtime_s; | |
|
31 | int mtime_ns; | |
|
31 | 32 | } dirstateItemObject; |
|
32 | 33 | /* clang-format on */ |
|
33 | 34 |
@@ -31,6 +31,10 b' from . import (' | |||
|
31 | 31 | util, |
|
32 | 32 | ) |
|
33 | 33 | |
|
34 | from .dirstateutils import ( | |
|
35 | timestamp, | |
|
36 | ) | |
|
37 | ||
|
34 | 38 | from .interfaces import ( |
|
35 | 39 | dirstate as intdirstate, |
|
36 | 40 | util as interfaceutil, |
@@ -66,7 +70,7 b' def _getfsnow(vfs):' | |||
|
66 | 70 | '''Get "now" timestamp on filesystem''' |
|
67 | 71 | tmpfd, tmpname = vfs.mkstemp() |
|
68 | 72 | try: |
|
69 |
return os.fstat(tmpfd) |
|
|
73 | return timestamp.mtime_of(os.fstat(tmpfd)) | |
|
70 | 74 | finally: |
|
71 | 75 | os.close(tmpfd) |
|
72 | 76 | vfs.unlink(tmpname) |
@@ -122,7 +126,7 b' class dirstate(object):' | |||
|
122 | 126 | # UNC path pointing to root share (issue4557) |
|
123 | 127 | self._rootdir = pathutil.normasprefix(root) |
|
124 | 128 | self._dirty = False |
|
125 |
self._lastnormaltime = |
|
|
129 | self._lastnormaltime = timestamp.zero() | |
|
126 | 130 | self._ui = ui |
|
127 | 131 | self._filecache = {} |
|
128 | 132 | self._parentwriters = 0 |
@@ -440,7 +444,7 b' class dirstate(object):' | |||
|
440 | 444 | for a in ("_map", "_branch", "_ignore"): |
|
441 | 445 | if a in self.__dict__: |
|
442 | 446 | delattr(self, a) |
|
443 |
self._lastnormaltime = |
|
|
447 | self._lastnormaltime = timestamp.zero() | |
|
444 | 448 | self._dirty = False |
|
445 | 449 | self._parentwriters = 0 |
|
446 | 450 | self._origpl = None |
@@ -639,7 +643,7 b' class dirstate(object):' | |||
|
639 | 643 | s = os.lstat(self._join(filename)) |
|
640 | 644 | mode = s.st_mode |
|
641 | 645 | size = s.st_size |
|
642 |
mtime = s |
|
|
646 | mtime = timestamp.mtime_of(s) | |
|
643 | 647 | return (mode, size, mtime) |
|
644 | 648 | |
|
645 | 649 | def _discoverpath(self, path, normed, ignoremissing, exists, storemap): |
@@ -720,7 +724,7 b' class dirstate(object):' | |||
|
720 | 724 | |
|
721 | 725 | def clear(self): |
|
722 | 726 | self._map.clear() |
|
723 |
self._lastnormaltime = |
|
|
727 | self._lastnormaltime = timestamp.zero() | |
|
724 | 728 | self._dirty = True |
|
725 | 729 | |
|
726 | 730 | def rebuild(self, parent, allfiles, changedfiles=None): |
@@ -823,7 +827,7 b' class dirstate(object):' | |||
|
823 | 827 | if now is None: |
|
824 | 828 | # use the modification time of the newly created temporary file as the |
|
825 | 829 | # filesystem's notion of 'now' |
|
826 | now = util.fstat(st)[stat.ST_MTIME] & _rangemask | |
|
830 | now = timestamp.mtime_of(util.fstat(st)) | |
|
827 | 831 | |
|
828 | 832 | # enough 'delaywrite' prevents 'pack_dirstate' from dropping |
|
829 | 833 | # timestamp of each entries in dirstate, because of 'now > mtime' |
@@ -840,11 +844,12 b' class dirstate(object):' | |||
|
840 | 844 | start = int(clock) - (int(clock) % delaywrite) |
|
841 | 845 | end = start + delaywrite |
|
842 | 846 | time.sleep(end - clock) |
|
843 |
|
|
|
847 | # trust our estimate that the end is near now | |
|
848 | now = timestamp.timestamp((end, 0)) | |
|
844 | 849 | break |
|
845 | 850 | |
|
846 | 851 | self._map.write(tr, st, now) |
|
847 |
self._lastnormaltime = |
|
|
852 | self._lastnormaltime = timestamp.zero() | |
|
848 | 853 | self._dirty = False |
|
849 | 854 | |
|
850 | 855 | def _dirignore(self, f): |
@@ -1377,17 +1382,9 b' class dirstate(object):' | |||
|
1377 | 1382 | uadd(fn) |
|
1378 | 1383 | continue |
|
1379 | 1384 | |
|
1380 | # This is equivalent to 'state, mode, size, time = dmap[fn]' but not | |
|
1381 | # written like that for performance reasons. dmap[fn] is not a | |
|
1382 | # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE | |
|
1383 | # opcode has fast paths when the value to be unpacked is a tuple or | |
|
1384 | # a list, but falls back to creating a full-fledged iterator in | |
|
1385 | # general. That is much slower than simply accessing and storing the | |
|
1386 | # tuple members one by one. | |
|
1387 | 1385 | t = dget(fn) |
|
1388 | 1386 | mode = t.mode |
|
1389 | 1387 | size = t.size |
|
1390 | time = t.mtime | |
|
1391 | 1388 | |
|
1392 | 1389 | if not st and t.tracked: |
|
1393 | 1390 | dadd(fn) |
@@ -1412,12 +1409,9 b' class dirstate(object):' | |||
|
1412 | 1409 | ladd(fn) |
|
1413 | 1410 | else: |
|
1414 | 1411 | madd(fn) |
|
1415 | elif ( | |
|
1416 | time != st[stat.ST_MTIME] | |
|
1417 | and time != st[stat.ST_MTIME] & _rangemask | |
|
1418 | ): | |
|
1412 | elif not t.mtime_likely_equal_to(timestamp.mtime_of(st)): | |
|
1419 | 1413 | ladd(fn) |
|
1420 |
elif st |
|
|
1414 | elif timestamp.mtime_of(st) == lastnormaltime: | |
|
1421 | 1415 | # fn may have just been marked as normal and it may have |
|
1422 | 1416 | # changed in the same second without changing its size. |
|
1423 | 1417 | # This can happen if we quickly do multiple commits. |
@@ -127,7 +127,6 b' class _dirstatemapcommon(object):' | |||
|
127 | 127 | def set_clean(self, filename, mode, size, mtime): |
|
128 | 128 | """mark a file as back to a clean state""" |
|
129 | 129 | entry = self[filename] |
|
130 | mtime = mtime & rangemask | |
|
131 | 130 | size = size & rangemask |
|
132 | 131 | entry.set_clean(mode, size, mtime) |
|
133 | 132 | self._refresh_entry(filename, entry) |
@@ -107,7 +107,10 b' def parse_nodes(map, copy_map, data, sta' | |||
|
107 | 107 | # Parse child nodes of this node recursively |
|
108 | 108 | parse_nodes(map, copy_map, data, children_start, children_count) |
|
109 | 109 | |
|
110 | item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s) | |
|
110 | # Don’t yet use sub-second precision if it exists in the file, | |
|
111 | # since other parts of the code still set it to zero. | |
|
112 | mtime_ns = 0 | |
|
113 | item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s, mtime_ns) | |
|
111 | 114 | if not item.any_tracked: |
|
112 | 115 | continue |
|
113 | 116 | path = slice_with_len(data, path_start, path_len) |
@@ -147,8 +150,7 b' class Node(object):' | |||
|
147 | 150 | copy_source_start = 0 |
|
148 | 151 | copy_source_len = 0 |
|
149 | 152 | if entry is not None: |
|
150 | flags, size, mtime_s = entry.v2_data() | |
|
151 | mtime_ns = 0 | |
|
153 | flags, size, mtime_s, mtime_ns = entry.v2_data() | |
|
152 | 154 | else: |
|
153 | 155 | # There are no mtime-cached directories in the Python implementation |
|
154 | 156 | flags = 0 |
@@ -249,7 +251,6 b' def pack_dirstate(map, copy_map, now):' | |||
|
249 | 251 | written to the docket. Again, see more details on the on-disk format in |
|
250 | 252 | `mercurial/helptext/internals/dirstate-v2`. |
|
251 | 253 | """ |
|
252 | now = int(now) | |
|
253 | 254 | data = bytearray() |
|
254 | 255 | root_nodes_start = 0 |
|
255 | 256 | root_nodes_len = 0 |
@@ -9,13 +9,13 b' from __future__ import absolute_import' | |||
|
9 | 9 | |
|
10 | 10 | import collections |
|
11 | 11 | import errno |
|
12 | import stat | |
|
13 | 12 | import struct |
|
14 | 13 | |
|
15 | 14 | from .i18n import _ |
|
16 | 15 | from .node import nullrev |
|
17 | 16 | from .thirdparty import attr |
|
18 | 17 | from .utils import stringutil |
|
18 | from .dirstateutils import timestamp | |
|
19 | 19 | from . import ( |
|
20 | 20 | copies, |
|
21 | 21 | encoding, |
@@ -1406,8 +1406,9 b' def batchget(repo, mctx, wctx, wantfiled' | |||
|
1406 | 1406 | if wantfiledata: |
|
1407 | 1407 | s = wfctx.lstat() |
|
1408 | 1408 | mode = s.st_mode |
|
1409 |
mtime = s |
|
|
1410 | filedata[f] = (mode, size, mtime) # for dirstate.normal | |
|
1409 | mtime = timestamp.mtime_of(s) | |
|
1410 | # for dirstate.update_file's parentfiledata argument: | |
|
1411 | filedata[f] = (mode, size, mtime) | |
|
1411 | 1412 | if i == 100: |
|
1412 | 1413 | yield False, (i, f) |
|
1413 | 1414 | i = 0 |
@@ -99,7 +99,8 b' class DirstateItem(object):' | |||
|
99 | 99 | _p2_info = attr.ib() |
|
100 | 100 | _mode = attr.ib() |
|
101 | 101 | _size = attr.ib() |
|
102 | _mtime = attr.ib() | |
|
102 | _mtime_s = attr.ib() | |
|
103 | _mtime_ns = attr.ib() | |
|
103 | 104 | _fallback_exec = attr.ib() |
|
104 | 105 | _fallback_symlink = attr.ib() |
|
105 | 106 | |
@@ -123,7 +124,8 b' class DirstateItem(object):' | |||
|
123 | 124 | |
|
124 | 125 | self._mode = None |
|
125 | 126 | self._size = None |
|
126 | self._mtime = None | |
|
127 | self._mtime_s = None | |
|
128 | self._mtime_ns = None | |
|
127 | 129 | if parentfiledata is None: |
|
128 | 130 | has_meaningful_mtime = False |
|
129 | 131 | has_meaningful_data = False |
@@ -131,10 +133,10 b' class DirstateItem(object):' | |||
|
131 | 133 | self._mode = parentfiledata[0] |
|
132 | 134 | self._size = parentfiledata[1] |
|
133 | 135 | if has_meaningful_mtime: |
|
134 | self._mtime = parentfiledata[2] | |
|
136 | self._mtime_s, self._mtime_ns = parentfiledata[2] | |
|
135 | 137 | |
|
136 | 138 | @classmethod |
|
137 | def from_v2_data(cls, flags, size, mtime): | |
|
139 | def from_v2_data(cls, flags, size, mtime_s, mtime_ns): | |
|
138 | 140 | """Build a new DirstateItem object from V2 data""" |
|
139 | 141 | has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) |
|
140 | 142 | has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_FILE_MTIME) |
@@ -170,7 +172,7 b' class DirstateItem(object):' | |||
|
170 | 172 | p2_info=bool(flags & DIRSTATE_V2_P2_INFO), |
|
171 | 173 | has_meaningful_data=has_mode_size, |
|
172 | 174 | has_meaningful_mtime=has_meaningful_mtime, |
|
173 | parentfiledata=(mode, size, mtime), | |
|
175 | parentfiledata=(mode, size, (mtime_s, mtime_ns)), | |
|
174 | 176 | fallback_exec=fallback_exec, |
|
175 | 177 | fallback_symlink=fallback_symlink, |
|
176 | 178 | ) |
@@ -207,13 +209,13 b' class DirstateItem(object):' | |||
|
207 | 209 | wc_tracked=True, |
|
208 | 210 | p1_tracked=True, |
|
209 | 211 | has_meaningful_mtime=False, |
|
210 | parentfiledata=(mode, size, 42), | |
|
212 | parentfiledata=(mode, size, (42, 0)), | |
|
211 | 213 | ) |
|
212 | 214 | else: |
|
213 | 215 | return cls( |
|
214 | 216 | wc_tracked=True, |
|
215 | 217 | p1_tracked=True, |
|
216 | parentfiledata=(mode, size, mtime), | |
|
218 | parentfiledata=(mode, size, (mtime, 0)), | |
|
217 | 219 | ) |
|
218 | 220 | else: |
|
219 | 221 | raise RuntimeError(b'unknown state: %s' % state) |
@@ -224,7 +226,8 b' class DirstateItem(object):' | |||
|
224 | 226 | This means the next status call will have to actually check its content |
|
225 | 227 | to make sure it is correct. |
|
226 | 228 | """ |
|
227 | self._mtime = None | |
|
229 | self._mtime_s = None | |
|
230 | self._mtime_ns = None | |
|
228 | 231 | |
|
229 | 232 | def set_clean(self, mode, size, mtime): |
|
230 | 233 | """mark a file as "clean" cancelling potential "possibly dirty call" |
@@ -238,7 +241,7 b' class DirstateItem(object):' | |||
|
238 | 241 | self._p1_tracked = True |
|
239 | 242 | self._mode = mode |
|
240 | 243 | self._size = size |
|
241 | self._mtime = mtime | |
|
244 | self._mtime_s, self._mtime_ns = mtime | |
|
242 | 245 | |
|
243 | 246 | def set_tracked(self): |
|
244 | 247 | """mark a file as tracked in the working copy |
@@ -250,7 +253,8 b' class DirstateItem(object):' | |||
|
250 | 253 | # the files as needing lookup |
|
251 | 254 | # |
|
252 | 255 | # Consider dropping this in the future in favor of something less broad. |
|
253 | self._mtime = None | |
|
256 | self._mtime_s = None | |
|
257 | self._mtime_ns = None | |
|
254 | 258 | |
|
255 | 259 | def set_untracked(self): |
|
256 | 260 | """mark a file as untracked in the working copy |
@@ -260,7 +264,8 b' class DirstateItem(object):' | |||
|
260 | 264 | self._wc_tracked = False |
|
261 | 265 | self._mode = None |
|
262 | 266 | self._size = None |
|
263 | self._mtime = None | |
|
267 | self._mtime_s = None | |
|
268 | self._mtime_ns = None | |
|
264 | 269 | |
|
265 | 270 | def drop_merge_data(self): |
|
266 | 271 | """remove all "merge-only" from a DirstateItem |
@@ -271,7 +276,8 b' class DirstateItem(object):' | |||
|
271 | 276 | self._p2_info = False |
|
272 | 277 | self._mode = None |
|
273 | 278 | self._size = None |
|
274 | self._mtime = None | |
|
279 | self._mtime_s = None | |
|
280 | self._mtime_ns = None | |
|
275 | 281 | |
|
276 | 282 | @property |
|
277 | 283 | def mode(self): |
@@ -285,6 +291,14 b' class DirstateItem(object):' | |||
|
285 | 291 | def mtime(self): |
|
286 | 292 | return self.v1_mtime() |
|
287 | 293 | |
|
294 | def mtime_likely_equal_to(self, other_mtime): | |
|
295 | self_sec = self._mtime_s | |
|
296 | if self_sec is None: | |
|
297 | return False | |
|
298 | self_ns = self._mtime_ns | |
|
299 | other_sec, other_ns = other_mtime | |
|
300 | return self_sec == other_sec and self_ns == other_ns | |
|
301 | ||
|
288 | 302 | @property |
|
289 | 303 | def state(self): |
|
290 | 304 | """ |
@@ -440,7 +454,7 b' class DirstateItem(object):' | |||
|
440 | 454 | flags |= DIRSTATE_V2_MODE_EXEC_PERM |
|
441 | 455 | if stat.S_ISLNK(self.mode): |
|
442 | 456 | flags |= DIRSTATE_V2_MODE_IS_SYMLINK |
|
443 | if self._mtime is not None: | |
|
457 | if self._mtime_s is not None: | |
|
444 | 458 | flags |= DIRSTATE_V2_HAS_FILE_MTIME |
|
445 | 459 | |
|
446 | 460 | if self._fallback_exec is not None: |
@@ -456,7 +470,7 b' class DirstateItem(object):' | |||
|
456 | 470 | # Note: we do not need to do anything regarding |
|
457 | 471 | # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED |
|
458 | 472 | # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME |
|
459 | return (flags, self._size or 0, self._mtime or 0) | |
|
473 | return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0) | |
|
460 | 474 | |
|
461 | 475 | def v1_state(self): |
|
462 | 476 | """return a "state" suitable for v1 serialization""" |
@@ -504,18 +518,18 b' class DirstateItem(object):' | |||
|
504 | 518 | raise RuntimeError('untracked item') |
|
505 | 519 | elif self.removed: |
|
506 | 520 | return 0 |
|
507 | elif self._mtime is None: | |
|
521 | elif self._mtime_s is None: | |
|
508 | 522 | return AMBIGUOUS_TIME |
|
509 | 523 | elif self._p2_info: |
|
510 | 524 | return AMBIGUOUS_TIME |
|
511 | 525 | elif not self._p1_tracked: |
|
512 | 526 | return AMBIGUOUS_TIME |
|
513 | 527 | else: |
|
514 | return self._mtime | |
|
528 | return self._mtime_s | |
|
515 | 529 | |
|
516 | 530 | def need_delay(self, now): |
|
517 | 531 | """True if the stored mtime would be ambiguous with the current time""" |
|
518 |
return self.v1_state() == b'n' and self. |
|
|
532 | return self.v1_state() == b'n' and self._mtime_s == now[0] | |
|
519 | 533 | |
|
520 | 534 | |
|
521 | 535 | def gettype(q): |
@@ -883,7 +897,6 b' def parse_dirstate(dmap, copymap, st):' | |||
|
883 | 897 | |
|
884 | 898 | |
|
885 | 899 | def pack_dirstate(dmap, copymap, pl, now): |
|
886 | now = int(now) | |
|
887 | 900 | cs = stringio() |
|
888 | 901 | write = cs.write |
|
889 | 902 | write(b"".join(pl)) |
@@ -14,14 +14,15 b' pub enum EntryState {' | |||
|
14 | 14 | Merged, |
|
15 | 15 | } |
|
16 | 16 | |
|
17 | /// The C implementation uses all signed types. This will be an issue | |
|
18 | /// either when 4GB+ source files are commonplace or in 2038, whichever | |
|
19 | /// comes first. | |
|
20 | #[derive(Debug, PartialEq, Copy, Clone)] | |
|
17 | /// `size` and `mtime.seconds` are truncated to 31 bits. | |
|
18 | /// | |
|
19 | /// TODO: double-check status algorithm correctness for files | |
|
20 | /// larger than 2 GiB or modified after 2038. | |
|
21 | #[derive(Debug, Copy, Clone)] | |
|
21 | 22 | pub struct DirstateEntry { |
|
22 | 23 | pub(crate) flags: Flags, |
|
23 | 24 | mode_size: Option<(u32, u32)>, |
|
24 |
mtime: Option< |
|
|
25 | mtime: Option<TruncatedTimestamp>, | |
|
25 | 26 | } |
|
26 | 27 | |
|
27 | 28 | bitflags! { |
@@ -37,7 +38,7 b' bitflags! {' | |||
|
37 | 38 | } |
|
38 | 39 | |
|
39 | 40 | /// A Unix timestamp with nanoseconds precision |
|
40 | #[derive(Copy, Clone)] | |
|
41 | #[derive(Debug, Copy, Clone)] | |
|
41 | 42 | pub struct TruncatedTimestamp { |
|
42 | 43 | truncated_seconds: u32, |
|
43 | 44 | /// Always in the `0 .. 1_000_000_000` range. |
@@ -90,6 +91,11 b' impl TruncatedTimestamp {' | |||
|
90 | 91 | } |
|
91 | 92 | } |
|
92 | 93 | |
|
94 | pub fn to_integer_second(mut self) -> Self { | |
|
95 | self.nanoseconds = 0; | |
|
96 | self | |
|
97 | } | |
|
98 | ||
|
93 | 99 | /// The lower 31 bits of the number of seconds since the epoch. |
|
94 | 100 | pub fn truncated_seconds(&self) -> u32 { |
|
95 | 101 | self.truncated_seconds |
@@ -182,7 +188,7 b' impl DirstateEntry {' | |||
|
182 | 188 | p1_tracked: bool, |
|
183 | 189 | p2_info: bool, |
|
184 | 190 | mode_size: Option<(u32, u32)>, |
|
185 |
mtime: Option< |
|
|
191 | mtime: Option<TruncatedTimestamp>, | |
|
186 | 192 | fallback_exec: Option<bool>, |
|
187 | 193 | fallback_symlink: Option<bool>, |
|
188 | 194 | ) -> Self { |
@@ -191,9 +197,6 b' impl DirstateEntry {' | |||
|
191 | 197 | assert!(mode & !RANGE_MASK_31BIT == 0); |
|
192 | 198 | assert!(size & !RANGE_MASK_31BIT == 0); |
|
193 | 199 | } |
|
194 | if let Some(mtime) = mtime { | |
|
195 | assert!(mtime & !RANGE_MASK_31BIT == 0); | |
|
196 | } | |
|
197 | 200 | let mut flags = Flags::empty(); |
|
198 | 201 | flags.set(Flags::WDIR_TRACKED, wdir_tracked); |
|
199 | 202 | flags.set(Flags::P1_TRACKED, p1_tracked); |
@@ -252,6 +255,9 b' impl DirstateEntry {' | |||
|
252 | 255 | let mode = u32::try_from(mode).unwrap(); |
|
253 | 256 | let size = u32::try_from(size).unwrap(); |
|
254 | 257 | let mtime = u32::try_from(mtime).unwrap(); |
|
258 | let mtime = | |
|
259 | TruncatedTimestamp::from_already_truncated(mtime, 0) | |
|
260 | .unwrap(); | |
|
255 | 261 | Self { |
|
256 | 262 | flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, |
|
257 | 263 | mode_size: Some((mode, size)), |
@@ -344,7 +350,7 b' impl DirstateEntry {' | |||
|
344 | 350 | bool, |
|
345 | 351 | bool, |
|
346 | 352 | Option<(u32, u32)>, |
|
347 |
Option< |
|
|
353 | Option<TruncatedTimestamp>, | |
|
348 | 354 | Option<bool>, |
|
349 | 355 | Option<bool>, |
|
350 | 356 | ) { |
@@ -429,7 +435,7 b' impl DirstateEntry {' | |||
|
429 | 435 | } else if !self.flags.contains(Flags::P1_TRACKED) { |
|
430 | 436 | MTIME_UNSET |
|
431 | 437 | } else if let Some(mtime) = self.mtime { |
|
432 | i32::try_from(mtime).unwrap() | |
|
438 | i32::try_from(mtime.truncated_seconds()).unwrap() | |
|
433 | 439 | } else { |
|
434 | 440 | MTIME_UNSET |
|
435 | 441 | } |
@@ -501,6 +507,10 b' impl DirstateEntry {' | |||
|
501 | 507 | } |
|
502 | 508 | } |
|
503 | 509 | |
|
510 | pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> { | |
|
511 | self.mtime | |
|
512 | } | |
|
513 | ||
|
504 | 514 | pub fn drop_merge_data(&mut self) { |
|
505 | 515 | if self.flags.contains(Flags::P2_INFO) { |
|
506 | 516 | self.flags.remove(Flags::P2_INFO); |
@@ -513,9 +523,13 b' impl DirstateEntry {' | |||
|
513 | 523 | self.mtime = None |
|
514 | 524 | } |
|
515 | 525 | |
|
516 | pub fn set_clean(&mut self, mode: u32, size: u32, mtime: u32) { | |
|
526 | pub fn set_clean( | |
|
527 | &mut self, | |
|
528 | mode: u32, | |
|
529 | size: u32, | |
|
530 | mtime: TruncatedTimestamp, | |
|
531 | ) { | |
|
517 | 532 | let size = size & RANGE_MASK_31BIT; |
|
518 | let mtime = mtime & RANGE_MASK_31BIT; | |
|
519 | 533 | self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); |
|
520 | 534 | self.mode_size = Some((mode, size)); |
|
521 | 535 | self.mtime = Some(mtime); |
@@ -577,8 +591,13 b' impl DirstateEntry {' | |||
|
577 | 591 | } |
|
578 | 592 | |
|
579 | 593 | /// True if the stored mtime would be ambiguous with the current time |
|
580 |
pub fn need_delay(&self, now: |
|
|
581 | self.state() == EntryState::Normal && self.mtime() == now | |
|
594 | pub fn need_delay(&self, now: TruncatedTimestamp) -> bool { | |
|
595 | if let Some(mtime) = self.mtime { | |
|
596 | self.state() == EntryState::Normal | |
|
597 | && mtime.truncated_seconds() == now.truncated_seconds() | |
|
598 | } else { | |
|
599 | false | |
|
600 | } | |
|
582 | 601 | } |
|
583 | 602 | } |
|
584 | 603 |
@@ -135,6 +135,3 b' pub fn pack_entry(' | |||
|
135 | 135 | packed.extend(source.as_bytes()); |
|
136 | 136 | } |
|
137 | 137 | } |
|
138 | ||
|
139 | /// Seconds since the Unix epoch | |
|
140 | pub struct Timestamp(pub i64); |
@@ -12,6 +12,7 b'' | |||
|
12 | 12 | use crate::dirstate_tree::on_disk::DirstateV2ParseError; |
|
13 | 13 | |
|
14 | 14 | use crate::{ |
|
15 | dirstate::TruncatedTimestamp, | |
|
15 | 16 | utils::hg_path::{HgPath, HgPathError}, |
|
16 | 17 | PatternError, |
|
17 | 18 | }; |
@@ -64,7 +65,7 b' pub struct StatusOptions {' | |||
|
64 | 65 | /// Remember the most recent modification timeslot for status, to make |
|
65 | 66 | /// sure we won't miss future size-preserving file content modifications |
|
66 | 67 | /// that happen within the same timeslot. |
|
67 |
pub last_normal_time: |
|
|
68 | pub last_normal_time: TruncatedTimestamp, | |
|
68 | 69 | /// Whether we are on a filesystem with UNIX-like exec flags |
|
69 | 70 | pub check_exec: bool, |
|
70 | 71 | pub list_clean: bool, |
@@ -1,7 +1,6 b'' | |||
|
1 | 1 | use bytes_cast::BytesCast; |
|
2 | 2 | use micro_timer::timed; |
|
3 | 3 | use std::borrow::Cow; |
|
4 | use std::convert::TryInto; | |
|
5 | 4 | use std::path::PathBuf; |
|
6 | 5 | |
|
7 | 6 | use super::on_disk; |
@@ -11,7 +10,6 b' use super::path_with_basename::WithBasen' | |||
|
11 | 10 | use crate::dirstate::parsers::pack_entry; |
|
12 | 11 | use crate::dirstate::parsers::packed_entry_size; |
|
13 | 12 | use crate::dirstate::parsers::parse_dirstate_entries; |
|
14 | use crate::dirstate::parsers::Timestamp; | |
|
15 | 13 | use crate::dirstate::CopyMapIter; |
|
16 | 14 | use crate::dirstate::StateMapIter; |
|
17 | 15 | use crate::dirstate::TruncatedTimestamp; |
@@ -932,10 +930,9 b' impl OwningDirstateMap {' | |||
|
932 | 930 | pub fn pack_v1( |
|
933 | 931 | &mut self, |
|
934 | 932 | parents: DirstateParents, |
|
935 | now: Timestamp, | |
|
933 | now: TruncatedTimestamp, | |
|
936 | 934 | ) -> Result<Vec<u8>, DirstateError> { |
|
937 | 935 | let map = self.get_map_mut(); |
|
938 | let now: i32 = now.0.try_into().expect("time overflow"); | |
|
939 | 936 | let mut ambiguous_mtimes = Vec::new(); |
|
940 | 937 | // Optizimation (to be measured?): pre-compute size to avoid `Vec` |
|
941 | 938 | // reallocations |
@@ -981,12 +978,10 b' impl OwningDirstateMap {' | |||
|
981 | 978 | #[timed] |
|
982 | 979 | pub fn pack_v2( |
|
983 | 980 | &mut self, |
|
984 | now: Timestamp, | |
|
981 | now: TruncatedTimestamp, | |
|
985 | 982 | can_append: bool, |
|
986 | 983 | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { |
|
987 | 984 | let map = self.get_map_mut(); |
|
988 | // TODO: how do we want to handle this in 2038? | |
|
989 | let now: i32 = now.0.try_into().expect("time overflow"); | |
|
990 | 985 | let mut paths = Vec::new(); |
|
991 | 986 | for node in map.iter_nodes() { |
|
992 | 987 | let node = node?; |
@@ -317,7 +317,7 b' impl Node {' | |||
|
317 | 317 | &self, |
|
318 | 318 | ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> { |
|
319 | 319 | if self.has_entry() { |
|
320 | Ok(dirstate_map::NodeData::Entry(self.assume_entry())) | |
|
320 | Ok(dirstate_map::NodeData::Entry(self.assume_entry()?)) | |
|
321 | 321 | } else if let Some(mtime) = self.cached_directory_mtime()? { |
|
322 | 322 | Ok(dirstate_map::NodeData::CachedDirectory { mtime }) |
|
323 | 323 | } else { |
@@ -357,7 +357,7 b' impl Node {' | |||
|
357 | 357 | file_type | permisions |
|
358 | 358 | } |
|
359 | 359 | |
|
360 | fn assume_entry(&self) -> DirstateEntry { | |
|
360 | fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> { | |
|
361 | 361 | // TODO: convert through raw bits instead? |
|
362 | 362 | let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); |
|
363 | 363 | let p1_tracked = self.flags().contains(Flags::P1_TRACKED); |
@@ -372,11 +372,19 b' impl Node {' | |||
|
372 | 372 | let mtime = if self.flags().contains(Flags::HAS_FILE_MTIME) |
|
373 | 373 | && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED) |
|
374 | 374 | { |
|
375 | Some(self.mtime.truncated_seconds.into()) | |
|
375 | // TODO: replace this by `self.mtime.try_into()?` to use | |
|
376 | // sub-second precision from the file. | |
|
377 | // We don’t do this yet because other parts of the code | |
|
378 | // always set it to zero. | |
|
379 | let mtime = TruncatedTimestamp::from_already_truncated( | |
|
380 | self.mtime.truncated_seconds.get(), | |
|
381 | 0, | |
|
382 | )?; | |
|
383 | Some(mtime) | |
|
376 | 384 | } else { |
|
377 | 385 | None |
|
378 | 386 | }; |
|
379 | DirstateEntry::from_v2_data( | |
|
387 | Ok(DirstateEntry::from_v2_data( | |
|
380 | 388 | wdir_tracked, |
|
381 | 389 | p1_tracked, |
|
382 | 390 | p2_info, |
@@ -384,14 +392,14 b' impl Node {' | |||
|
384 | 392 | mtime, |
|
385 | 393 | None, |
|
386 | 394 | None, |
|
387 | ) | |
|
395 | )) | |
|
388 | 396 | } |
|
389 | 397 | |
|
390 | 398 | pub(super) fn entry( |
|
391 | 399 | &self, |
|
392 | 400 | ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { |
|
393 | 401 | if self.has_entry() { |
|
394 | Ok(Some(self.assume_entry())) | |
|
402 | Ok(Some(self.assume_entry()?)) | |
|
395 | 403 | } else { |
|
396 | 404 | Ok(None) |
|
397 | 405 | } |
@@ -450,10 +458,7 b' impl Node {' | |||
|
450 | 458 | }; |
|
451 | 459 | let mtime = if let Some(m) = mtime_opt { |
|
452 | 460 | flags.insert(Flags::HAS_FILE_MTIME); |
|
453 | PackedTruncatedTimestamp { | |
|
454 | truncated_seconds: m.into(), | |
|
455 | nanoseconds: 0.into(), | |
|
456 | } | |
|
461 | m.into() | |
|
457 | 462 | } else { |
|
458 | 463 | PackedTruncatedTimestamp::null() |
|
459 | 464 | }; |
@@ -501,9 +501,6 b" impl<'a, 'tree, 'on_disk> StatusCommon<'" | |||
|
501 | 501 | fn truncate_u64(value: u64) -> i32 { |
|
502 | 502 | (value & 0x7FFF_FFFF) as i32 |
|
503 | 503 | } |
|
504 | fn truncate_i64(value: i64) -> i32 { | |
|
505 | (value & 0x7FFF_FFFF) as i32 | |
|
506 | } | |
|
507 | 504 | |
|
508 | 505 | let entry = dirstate_node |
|
509 | 506 | .entry()? |
@@ -531,10 +528,19 b" impl<'a, 'tree, 'on_disk> StatusCommon<'" | |||
|
531 | 528 | .modified |
|
532 | 529 | .push(hg_path.detach_from_tree()) |
|
533 | 530 | } else { |
|
534 |
let |
|
|
535 | if truncate_i64(mtime) != entry.mtime() | |
|
536 | || mtime == self.options.last_normal_time | |
|
537 | { | |
|
531 | let mtime_looks_clean; | |
|
532 | if let Some(dirstate_mtime) = entry.truncated_mtime() { | |
|
533 | let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata) | |
|
534 | .expect("OS/libc does not support mtime?") | |
|
535 | // For now don’t use sub-second precision for file mtimes | |
|
536 | .to_integer_second(); | |
|
537 | mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime) | |
|
538 | && !fs_mtime.likely_equal(self.options.last_normal_time) | |
|
539 | } else { | |
|
540 | // No mtime in the dirstate entry | |
|
541 | mtime_looks_clean = false | |
|
542 | }; | |
|
543 | if !mtime_looks_clean { | |
|
538 | 544 | self.outcome |
|
539 | 545 | .lock() |
|
540 | 546 | .unwrap() |
@@ -690,15 +696,6 b" impl<'a, 'tree, 'on_disk> StatusCommon<'" | |||
|
690 | 696 | } |
|
691 | 697 | } |
|
692 | 698 | |
|
693 | #[cfg(unix)] // TODO | |
|
694 | fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 { | |
|
695 | // Going through `Metadata::modified()` would be portable, but would take | |
|
696 | // care to construct a `SystemTime` value with sub-second precision just | |
|
697 | // for us to throw that away here. | |
|
698 | use std::os::unix::fs::MetadataExt; | |
|
699 | metadata.mtime() | |
|
700 | } | |
|
701 | ||
|
702 | 699 | struct DirEntry { |
|
703 | 700 | base_name: HgPathBuf, |
|
704 | 701 | full_path: PathBuf, |
@@ -54,7 +54,7 b' pub fn init_module(py: Python, package: ' | |||
|
54 | 54 | matcher: PyObject, |
|
55 | 55 | ignorefiles: PyList, |
|
56 | 56 | check_exec: bool, |
|
57 |
last_normal_time: |
|
|
57 | last_normal_time: (u32, u32), | |
|
58 | 58 | list_clean: bool, |
|
59 | 59 | list_ignored: bool, |
|
60 | 60 | list_unknown: bool, |
@@ -18,11 +18,10 b' use cpython::{' | |||
|
18 | 18 | |
|
19 | 19 | use crate::{ |
|
20 | 20 | dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, |
|
21 | dirstate::item::DirstateItem, | |
|
21 | dirstate::item::{timestamp, DirstateItem}, | |
|
22 | 22 | pybytes_deref::PyBytesDeref, |
|
23 | 23 | }; |
|
24 | 24 | use hg::{ |
|
25 | dirstate::parsers::Timestamp, | |
|
26 | 25 | dirstate::StateMapIter, |
|
27 | 26 | dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, |
|
28 | 27 | dirstate_tree::on_disk::DirstateV2ParseError, |
@@ -195,9 +194,9 b' py_class!(pub class DirstateMap |py| {' | |||
|
195 | 194 | &self, |
|
196 | 195 | p1: PyObject, |
|
197 | 196 | p2: PyObject, |
|
198 | now: PyObject | |
|
197 | now: (u32, u32) | |
|
199 | 198 | ) -> PyResult<PyBytes> { |
|
200 |
let now = |
|
|
199 | let now = timestamp(py, now)?; | |
|
201 | 200 | |
|
202 | 201 | let mut inner = self.inner(py).borrow_mut(); |
|
203 | 202 | let parents = DirstateParents { |
@@ -219,10 +218,10 b' py_class!(pub class DirstateMap |py| {' | |||
|
219 | 218 | /// instead of written to a new data file (False). |
|
220 | 219 | def write_v2( |
|
221 | 220 | &self, |
|
222 |
now: |
|
|
221 | now: (u32, u32), | |
|
223 | 222 | can_append: bool, |
|
224 | 223 | ) -> PyResult<PyObject> { |
|
225 |
let now = |
|
|
224 | let now = timestamp(py, now)?; | |
|
226 | 225 | |
|
227 | 226 | let mut inner = self.inner(py).borrow_mut(); |
|
228 | 227 | let result = inner.pack_v2(now, can_append); |
@@ -9,6 +9,7 b' use cpython::Python;' | |||
|
9 | 9 | use cpython::PythonObject; |
|
10 | 10 | use hg::dirstate::DirstateEntry; |
|
11 | 11 | use hg::dirstate::EntryState; |
|
12 | use hg::dirstate::TruncatedTimestamp; | |
|
12 | 13 | use std::cell::Cell; |
|
13 | 14 | use std::convert::TryFrom; |
|
14 | 15 | |
@@ -22,7 +23,7 b' py_class!(pub class DirstateItem |py| {' | |||
|
22 | 23 | p2_info: bool = false, |
|
23 | 24 | has_meaningful_data: bool = true, |
|
24 | 25 | has_meaningful_mtime: bool = true, |
|
25 | parentfiledata: Option<(u32, u32, u32)> = None, | |
|
26 | parentfiledata: Option<(u32, u32, (u32, u32))> = None, | |
|
26 | 27 | fallback_exec: Option<bool> = None, |
|
27 | 28 | fallback_symlink: Option<bool> = None, |
|
28 | 29 | |
@@ -34,7 +35,7 b' py_class!(pub class DirstateItem |py| {' | |||
|
34 | 35 | mode_size_opt = Some((mode, size)) |
|
35 | 36 | } |
|
36 | 37 | if has_meaningful_mtime { |
|
37 | mtime_opt = Some(mtime) | |
|
38 | mtime_opt = Some(timestamp(py, mtime)?) | |
|
38 | 39 | } |
|
39 | 40 | } |
|
40 | 41 | let entry = DirstateEntry::from_v2_data( |
@@ -191,10 +192,19 b' py_class!(pub class DirstateItem |py| {' | |||
|
191 | 192 | Ok(mtime) |
|
192 | 193 | } |
|
193 | 194 | |
|
194 |
def need_delay(&self, now: |
|
|
195 | def need_delay(&self, now: (u32, u32)) -> PyResult<bool> { | |
|
196 | let now = timestamp(py, now)?; | |
|
195 | 197 | Ok(self.entry(py).get().need_delay(now)) |
|
196 | 198 | } |
|
197 | 199 | |
|
200 | def mtime_likely_equal_to(&self, other: (u32, u32)) -> PyResult<bool> { | |
|
201 | if let Some(mtime) = self.entry(py).get().truncated_mtime() { | |
|
202 | Ok(mtime.likely_equal(timestamp(py, other)?)) | |
|
203 | } else { | |
|
204 | Ok(false) | |
|
205 | } | |
|
206 | } | |
|
207 | ||
|
198 | 208 | @classmethod |
|
199 | 209 | def from_v1_data( |
|
200 | 210 | _cls, |
@@ -220,8 +230,9 b' py_class!(pub class DirstateItem |py| {' | |||
|
220 | 230 | &self, |
|
221 | 231 | mode: u32, |
|
222 | 232 | size: u32, |
|
223 | mtime: u32, | |
|
233 | mtime: (u32, u32), | |
|
224 | 234 | ) -> PyResult<PyNone> { |
|
235 | let mtime = timestamp(py, mtime)?; | |
|
225 | 236 | self.update(py, |entry| entry.set_clean(mode, size, mtime)); |
|
226 | 237 | Ok(PyNone) |
|
227 | 238 | } |
@@ -261,3 +272,15 b' impl DirstateItem {' | |||
|
261 | 272 | self.entry(py).set(entry) |
|
262 | 273 | } |
|
263 | 274 | } |
|
275 | ||
|
276 | pub(crate) fn timestamp( | |
|
277 | py: Python<'_>, | |
|
278 | (s, ns): (u32, u32), | |
|
279 | ) -> PyResult<TruncatedTimestamp> { | |
|
280 | TruncatedTimestamp::from_already_truncated(s, ns).map_err(|_| { | |
|
281 | PyErr::new::<exc::ValueError, _>( | |
|
282 | py, | |
|
283 | "expected mtime truncated to 31 bits", | |
|
284 | ) | |
|
285 | }) | |
|
286 | } |
@@ -9,6 +9,7 b'' | |||
|
9 | 9 | //! `hg-core` crate. From Python, this will be seen as |
|
10 | 10 | //! `rustext.dirstate.status`. |
|
11 | 11 | |
|
12 | use crate::dirstate::item::timestamp; | |
|
12 | 13 | use crate::{dirstate::DirstateMap, exceptions::FallbackError}; |
|
13 | 14 | use cpython::exc::OSError; |
|
14 | 15 | use cpython::{ |
@@ -102,12 +103,13 b' pub fn status_wrapper(' | |||
|
102 | 103 | root_dir: PyObject, |
|
103 | 104 | ignore_files: PyList, |
|
104 | 105 | check_exec: bool, |
|
105 |
last_normal_time: |
|
|
106 | last_normal_time: (u32, u32), | |
|
106 | 107 | list_clean: bool, |
|
107 | 108 | list_ignored: bool, |
|
108 | 109 | list_unknown: bool, |
|
109 | 110 | collect_traversed_dirs: bool, |
|
110 | 111 | ) -> PyResult<PyTuple> { |
|
112 | let last_normal_time = timestamp(py, last_normal_time)?; | |
|
111 | 113 | let bytes = root_dir.extract::<PyBytes>(py)?; |
|
112 | 114 | let root_dir = get_path_from_bytes(bytes.data(py)); |
|
113 | 115 |
@@ -11,6 +11,7 b' use crate::utils::path_utils::relativize' | |||
|
11 | 11 | use clap::{Arg, SubCommand}; |
|
12 | 12 | use hg; |
|
13 | 13 | use hg::config::Config; |
|
14 | use hg::dirstate::TruncatedTimestamp; | |
|
14 | 15 | use hg::errors::HgError; |
|
15 | 16 | use hg::manifest::Manifest; |
|
16 | 17 | use hg::matchers::AlwaysMatcher; |
@@ -180,7 +181,7 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
180 | 181 | // hence be stored on dmap. Using a value that assumes we aren't |
|
181 | 182 | // below the time resolution granularity of the FS and the |
|
182 | 183 | // dirstate. |
|
183 | last_normal_time: 0, | |
|
184 | last_normal_time: TruncatedTimestamp::new_truncate(0, 0), | |
|
184 | 185 | // we're currently supporting file systems with exec flags only |
|
185 | 186 | // anyway |
|
186 | 187 | check_exec: true, |
@@ -15,6 +15,7 b' from mercurial import (' | |||
|
15 | 15 | policy, |
|
16 | 16 | registrar, |
|
17 | 17 | ) |
|
18 | from mercurial.dirstateutils import timestamp | |
|
18 | 19 | from mercurial.utils import dateutil |
|
19 | 20 | |
|
20 | 21 | try: |
@@ -40,9 +41,8 b" has_rust_dirstate = policy.importrust('d" | |||
|
40 | 41 | def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): |
|
41 | 42 | # execute what original parsers.pack_dirstate should do actually |
|
42 | 43 | # for consistency |
|
43 | actualnow = int(now) | |
|
44 | 44 | for f, e in dmap.items(): |
|
45 |
if e.need_delay( |
|
|
45 | if e.need_delay(now): | |
|
46 | 46 | e.set_possibly_dirty() |
|
47 | 47 | |
|
48 | 48 | return orig(dmap, copymap, pl, fakenow) |
@@ -62,6 +62,7 b' def fakewrite(ui, func):' | |||
|
62 | 62 | # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between |
|
63 | 63 | # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy |
|
64 | 64 | fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] |
|
65 | fakenow = timestamp.timestamp((fakenow, 0)) | |
|
65 | 66 | |
|
66 | 67 | if has_rust_dirstate: |
|
67 | 68 | # The Rust implementation does not use public parse/pack dirstate |
General Comments 0
You need to be logged in to leave comments.
Login now