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