##// END OF EJS Templates
dirstate: store mtimes with nanosecond precision in memory...
Simon Sapin -
r49079:269ff897 default
parent child Browse files
Show More
@@ -0,0 +1,53
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 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 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 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 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 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 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 *value)
299 PyObject *now)
301 {
300 {
302 long now;
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 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 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 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 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 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 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 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 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 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, &map,
884 if (!PyArg_ParseTuple(args, "O!O!O!(ii):pack_dirstate", &PyDict_Type,
861 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
885 &map, &PyDict_Type, &copymap, &PyTuple_Type, &pl,
862 &now)) {
886 &now_s, &now_ns)) {
863 return NULL;
887 return NULL;
864 }
888 }
865
889
@@ -928,7 +952,7 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 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 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 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)[stat.ST_MTIME]
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 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 = 0
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 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 = 0
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 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[stat.ST_MTIME]
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 class dirstate(object):
720
724
721 def clear(self):
725 def clear(self):
722 self._map.clear()
726 self._map.clear()
723 self._lastnormaltime = 0
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 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 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 now = end # trust our estimate that the end is near now
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 = 0
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 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 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[stat.ST_MTIME] == lastnormaltime:
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 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 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 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 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 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 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[stat.ST_MTIME]
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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.v1_mtime() == now
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 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 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<u32>,
25 mtime: Option<TruncatedTimestamp>,
25 }
26 }
26
27
27 bitflags! {
28 bitflags! {
@@ -37,7 +38,7 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 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 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<u32>,
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 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 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 impl DirstateEntry {
344 bool,
350 bool,
345 bool,
351 bool,
346 Option<(u32, u32)>,
352 Option<(u32, u32)>,
347 Option<u32>,
353 Option<TruncatedTimestamp>,
348 Option<bool>,
354 Option<bool>,
349 Option<bool>,
355 Option<bool>,
350 ) {
356 ) {
@@ -429,7 +435,7 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 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 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 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: i32) -> bool {
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 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
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 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: i64,
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
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 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 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 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 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 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 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 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 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 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 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 mtime = mtime_seconds(fs_metadata);
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 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 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: i64,
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 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 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 = Timestamp(now.extract(py)?);
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 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: PyObject,
221 now: (u32, u32),
223 can_append: bool,
222 can_append: bool,
224 ) -> PyResult<PyObject> {
223 ) -> PyResult<PyObject> {
225 let now = Timestamp(now.extract(py)?);
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 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 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 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 py_class!(pub class DirstateItem |py| {
191 Ok(mtime)
192 Ok(mtime)
192 }
193 }
193
194
194 def need_delay(&self, now: i32) -> PyResult<bool> {
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 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 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
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 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: i64,
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 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 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 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 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(actualnow):
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 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